梁越

全连接队列和半连接队列

0 人看过

内核网络基础

大家对于 TCP 的三次握手应该都比较熟悉了,对于服务端,收到 SYN 包后该怎么处理,收到 Establish 之后又该怎么处理,或者说这些连接放在哪里,其实这也是之前面试问过的问题

我们在回顾下三次握手的流程图

*基本流程

上图是常见的三次握手流程,客户端调用 connect 函数后发送 SYN 报文,服务端收到后将连接信息加入半连接队列,也就是图中的 syns queue ,然后在收到最后来自客户端的的 ACK 报文后将其从半连接队列移除,加入全连接队列,也就是 accept queue ,然后服务端调用 accept 的时候会从全连接队列拿出一个来进行连接

*半连接队列(syn队列)长度

max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)

机器的 tcp_max_syn_backlog 值在 /proc/sys/net/ipv4/tcp_max_syn_backlog 下配置,在内核2.2之后默认值位2048

*全连接队列(accept队列)长度

min(backlog, somaxconn)

默认情况下, somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则应该是由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 可以由我们的应用程序去定义的

在Linux内核2.4.25之前,是写死在代码常量 SOMAXCONN ,在Linux内核2.4.25之后,在配置文件 /proc/sys/net/core/somaxconn 中直接修改,或者在 /etc/sysctl.conf 中配置 net.core.somaxconn = 128

*当 accept q 满了,syn q 中有连接完成时,需要转移进 accept q,该做什么样的处理?

在源码 net/ipv4/tcp_minisocks.c 中,这个行为是由 tcp_check_req 函数控制的:

child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
if (child == NULL)
goto listen_overflow;

再来看一眼 listen_overflow:

listen_overflow:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}

/proc/sys/net/ipv4/tcp_abort_on_overflow的值决定了后面的操作,0表示直接丢弃该ACK,1表示发送RST通知client;相应的,client则会分别返回read timeout 或者 connection reset by peer。

这说明除非设置了 /proc/sys/net/ipv4/tcp_abort_on_overflow 为 1 ,不然的话系统什么都不做。但是这个什么都不做,并不代表系统真的什么都不做了。当系统忽略了最后的 ACK,而系统中还有一个 net.ipv4.tcp_synack_retries 设置时,Linux 会重新发送 SYN ACK 包。而客户端收到多个 SYN ACK 包,则会认为之前的 ACK 丢包了。于是促使客户端再次发送 ACK ,在 accept q 有空闲的时候最终完成连接。若 accept q 始终满员,则最终客户端收到 RST 包。

同时 accept q 满了,对 syn q 也有影响,在代码 net/ipv4/tcp_ipv4.c :

/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}

这说明如果 accept q 满了,系统会开始检查 SYN 包收到的频率,并且做出限制。如果达到了其定义的限制(我还不知道是如何定义的),变开始丢弃 syn q 中的包。我个人根据观察到的现象认为,其必须要满足 syn q 也已经被塞满这个条件,才会丢包。

*当 syn q 满了,系统还在不断的收到 SYN 包时,怎么处理?

在系统 syncookies=1 时,syn q 满了以后,系统会对 SYN 返回 syncookie 包。所返回的 SYN ACK 包会产生一个新的标志位 want_cookie。这个标志位默认为 0 ,但当要验证 cookies 时,want_cookie 会设置为 1.
发送的 cookie 数值是通过 cookie_v4_init_sequence() 函数计算出来的,在包发送出去后不维护该连接在服务器上的状态。这么做应该是为了节约服务器资源,毕竟其目的是为了判断对方是否是恶意攻击。
客户端拿到 cookie 数值后,通过计算,返回 cookies 数值,服务端获取到返回计算该数值是否合法。从而决定是否建立连接。

不开启syncookies的时候,Server会丢弃新来的SYN包,而Client端在多次重发SYN包得不到响应而返回(connection time out)错误

*参考链接:

tcp的半连接与完全连接队列

TCP全连接队列和半连接队列已满之后的连接建立过程抓包分析[转]

Linux SYN Backlog and somaxconn