当这个队列满了,不开启 syncookies 的时候,Server 会丢弃新来的 SYN 包,而 Client 端在多次重发 SYN 包得不到响应而返回(connection time out)错误。但是,当 Server 端开启了 syncookies=1,那么 SYN 半连接队列就没有逻辑上的最大值了,并且 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置的值也会被忽略。在 netstack 中开启了 syncookies。
func (e *endpoint) handleListenSegment(ctx *listenContext, s *segment) {
switch s.flags {
case flagSyn:
opts := parseSynSegmentOptions(s)
if incSynRcvdCount() { // 半连接队列长度 +1,成功返回 true,队列已满返回 false
go e.handleSynSegment(ctx, s, &opts)
} else {
// 这里采用 SYNCookies 策略,SYN 半连接队列就没有逻辑上的最大值
cookie := ctx.createCookie(s.id, s.sequenceNumber, encodeMSS(opts.MSS))
...
sendSynTCP(&s.route, s.id, flagSyn|flagAck, cookie, s.sequenceNumber+1, ctx.rcvWnd, synOpts)
}
case flagAck:
if data, ok := ctx.isCookieValid(s.id, s.ackNumber-1, s.sequenceNumber-1); ok && int(data) < len(mssTable) { // 验证 ACK 的正确性
...
n, err := ctx.createConnectedEndpoint(s, s.ackNumber-1, s.sequenceNumber-1, rcvdSynOptions)
if err == nil {
e.deliverAccepted(n)
}
}
}
}
Netsatck TCP(II) 连接的建立与三次握手
protocolListenLoop
当新建一个 endpoint 并且将其 Bind 到一个端口后,这个endpoint 进入 protocolListenLoop,负责监听与连接建立。
handleListenSegment 处理收到的数据包,优先判定是否有 SYN 标识。如果改数据包没有 SYN 而有 ACK 标识的话,那么根据三次握手,它属于第三步,验证其合法后,该数据包对应的连接已经建立,那么为该连接创建一个新的 endpoint,将其发给 Accept 队列。
只要包含 SYN标识,那么说明该连接处于三步握手的第一步。为什么不是第二步?因为这是 Listen 函数,当前处于连接的被动方。这里会有一个队列,称为 SYN_RCVD 队列或半连接队列。长度为 max(64,/proc/sys/net/ipv4/tcp_max_syn_backlog) ,在机器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog下配置。
当这个队列满了,不开启 syncookies 的时候,Server 会丢弃新来的 SYN 包,而 Client 端在多次重发 SYN 包得不到响应而返回(
connection time out
)错误。但是,当 Server 端开启了 syncookies=1,那么 SYN 半连接队列就没有逻辑上的最大值了,并且 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置的值也会被忽略。在 netstack 中开启了 syncookies。e.handleSynSegment 做的事情就是创建 handshake 结构体,执行三步握手,然后将完成握手的新的 endpoint 传入 Accept 队列。
主要看下 handshake 的 excute 方法,也就是具体执行握手的方法。在看代码之前我们要做到心中有 B 树,哦不,是连接状态机:
注意这个函数可以在被动的 Listen 函数里调用,也可以在主动的 Connect 里被调用,所以记住这个函数里,endpoint 有两种角色,区分不同角色及其状态很重要:
如上,excute 首先发送一个 SYN + ACK 报文(注意这里对应主动和被动两种情况),然后进入循环直到建立连接。同理,h.processSegments 也对应两种情况。如果自己是主动连接,那么自己目前处于 SYN_SENT 状态,等待一个 SYN + ACK 报文并执行 synSentState:
如果是被动连接,那么自己目前处于 SYN_RCVD 状态,等待一个 ACK: