在上一个章节中,我们学习了TCP的三次握手和四次挥手, 这个章节我们再来看看三次握手过程中产生的半连接队列(sync queue)和全连接队列(accept queue):
image.png
当服务器接收到客户端第一次SYN握手请求的时候,将创建的request_sock结构,存储在半连接队列里面(即是向客户端发送SYN + ACK, 并期待客户端响应ACK),此时的连接在服务端处于SYN_RECV状态。当服务端收到客户端最后的ACK确认时,将半连接中相应的连接删除,然后将连接放进全连接队列中。此时服务端的连接状态为ESTABLISHED。 进入全连接状态队列中连接等待accept()调用。

syns queue半连接队列

再说一下SYN flooding攻击,为了应对SYN flooding,linux实现了一种称为syncookie的机制,通过net.ipv4.tcp_synccookies控制,设置1表示开启。简单说syncookie就是将连接信息编码在ISN中返回给客户端,这时候server不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回来的ISN还原连接信息,以完成连接的建立,避免了半连接队列被攻击的SYN包堆满。也就是说,如果开启了syncookies的话,半连接队列就相当于是无限扩大了,所以我们一般都不开启这个参数。

如果我们将syncookies关闭的话,半连接队列的长度将是max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),此时对半连接填满时处理策略是:server将连接对弃掉,接到SYN, 不回复SYN + ACK, 这样就会造成client收不到client收不到握手响应,始终处于SYN_SENT状态。经过几次重试之后,客户端connect() 调用失败。

accept queue全连接队列

全连接队列的长度为min(backlog somaxconn)。 默认情况下,somaxconn的值为128(/proc/sys/net/core/somaxconn),表示最多有128的establish等待accept()。而backlog的值则是由int listen(int socket, int backlog)中第二个参数指定。listen里面的backlog可以由我们应用程序去定义。当全连接队列满了后的处理策略基于TCP参数net.ipv4.tcp_about_on_overflow, 一般我们设置为0。

  1. tcp_abort_on_over_flow关闭

当server收到最后一次ACK,希望将连接从半连接队列中取出放入全连接队列。但是此时全连接队列已满,此时的策略是将最后收到的ACK丢弃,并且根据net.ipv4_tcp_synack_retries定义的次数重新想client发送SYN + ACK, client在接收到重传的SYN + ACK后会认为ACK后,会重传ACK。 如果在服务端在下次接收到重传的ACK时,全连接队列又有空间了,连接就可以正常建立。

如果重传了规定次数之后,全连接队列中依旧没有空间,那么server会简单终止这次连接。这里的简单终止的意思:server不会向client发送RST,而是直接丢弃了。这就会导致在client中的连接一直处于established状态,并一直如此。如果client在此之后发送数据到server端,才会引起server响应RST。

  1. tcp_abort_on_overflow开启

在收到握手的最后一次ACK后,在全连接中如果没有空间,直接向client回复RST, 表示连接无法建立。

参考

TCP半连接队列和全连接 概述