Tcp半连接队列和全连接队列

[toc]

什么是TCP半连接队列和全连接队列?

在TCP进行三次握手时,Liunx会为其维护两个队列:

  • syns queue,半连接队列;
  • accpet queue,全连接队列;

img

  1. 在client端发送SYN给server后,server端变成SYN_RCVD状态,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK;

  2. server端收到client端的ACK后,如果全连接队列没有满,内核会把连接从半连接队列移除,然后创建新的完全的 socket 连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来

为什么要存在半连接队列

因为根据TCP协议的特点,会存在半连接这样的网络攻击存在,叫做syn攻击,syn flood泛洪,

即不停的发SYN包,而从不回应SYN_ACK。

如果发一个SYN包就让Kernel建立一个消耗极大的socket,那么很容易就内存耗尽。

只需要一直对服务端发送syn包,但是不回ack回应包,这样就会使得服务端有大量请求处于syn_recv状态,这就是所谓的syn flood泛洪,syn攻击,DDos攻击.

半连接队列, 是解决 syn flood洪泛的 一个关键措施。

所以内核在三次握手成功之前,只分配一个占用内存极小的request_sock,以防止这种攻击的现象,再配合syn_cookie机制,尽量抵御这种半连接攻击的风险。

如何抵御syn攻击

开启tcp_syncookies功能

开启tcp_syncookies , 服务端根本不创建 request_sock对象,也不建立sock连接。

如果启用了syncookies,服务端将客户端SYN报文中的一些信息保存在了序号中,就不需要保留此连接的request_sock结构了,在发送完SYN+ACK报文之后,将已经申请的资源释放,降低DDos攻击时的资源消耗。

syncookies在接收到客户端的syn报文时,计算出一个cookies值,放到syn+ack报文中发出。cookies值通过调用secure_tcp_syn_cookie函数,根据报文中的源/目的IP地址,TCP源/目的端口号,TCP序号和MSS索引值,计算出来;当客户端返回ack报文时,取出该值验证,成功则建立连接。

如下proc文件tcp_syncookies默认值为1,表明在套接口的SYN backlog队列溢出时,将开启SYNCOOKIES功能,抵御SYN泛洪攻击。如果tcp_syncookies设置为2,将会无条件的开启SYNCOOKIES功能。

1
2
$ cat /proc/sys/net/ipv4/tcp_syncookies
1

增大全连接队列
由于全连接队列里面保存的是占用内存很大的普通socket,所以Kernel给其加了一个最大长度的限制。相关的三个参数

  • 1 - listen系统调用中传进去的backlog

  • 2 - /proc/sys/inet/ipv4/tcp_max_syn_backlog

  • 3 - /proc/sys/net/core/somaxconn

所以不能只增大tcp_max_syn_backlog, 还需要一同增大somaconn和backlog,也就是增大全连接队列

1
2
3
4
5
$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
256

$ cat /proc/sys/net/core/somaxconn
128

减少syn+ack报文的重传次数

因为我们在收到syn攻击时,服务端会重传syn+ack报文到最大次数,才会断开连接。

针对syn攻击的场景,我们可以减少ack+syn报文的重传次数,使处于syn_recv状态的它们更快断开连接

修改重传次数: /proc/sys/net/ipv4/tcp_synack_retries

1
2
$ cat /proc/sys/net/ipv4/tcp_synack_retries
5

Linux下默认会进行5次重发SYN-ACK包,重试的间隔时间从1s开始,下次的重试间隔时间是前一次的双倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才会把断开这个连接。

由于,SYN超时需要63秒,那么就给攻击者一个攻击服务器的机会,攻击者在短时间内发送大量的SYN包给Server(俗称 SYN flood 攻击)。

全连接队列、半连接队列、backlog 限制设置

  1. /proc/sys/inet/ipv4/tcp_max_syn_backlog
  2. /proc/sys/net/core/somaxconn
  3. backlog

backlog
listen 的 backlog 参数。netty里边,默认设置了 200

全连接队列的最大值
全连接队列的最大值是 min(somaxconn, backlog);默认情况下, min(128, 200)=128

somaxconn
somaxconn它用于限制 tcp 的全连接队列和半连接队列的长度。

1
2
$ cat /proc/sys/net/core/somaxconn
128

该内核参数默认值一般是128(定义了系统中每一个端口最大的监听队列的长度),

如何增大全连接队列呢?

当全连接队列溢出后,我们需要增大全连接队列的长度,以提高请求容量;

TCP 全连接队列的最大值取决于 somaxconn 和 backlog 之间的最小值,也就是 min(somaxconn, backlog)

所以我们需要提高这两个参数的大小才能拿增大全连接队列。

如何增大半连接队列呢?

半连接队列的最大值 与 tcp_max_syn_backlog 配置项 关系最为紧密的。但要想增大半连接队列,不能只单纯增大 tcp_max_syn_backlog 的值,还需一同增大 somaxconn 和 backlog,也就是增大全连接队列。否则,只单纯增大 tcp_max_syn_backlog 是无效的。

在 Linux 2.6.32 内核版本,它们之间的关系,总体可以概况为:

当 tcp_max_syn_backlog > accept_queue大小时, 半连接队列最大值 max_qlen_log = accept_queue大小 * 2;
当 tcp_max_syn_backlog< accept_queue大小 时, 半连接队列最大值 max_qlen_log = tcp_max_syn_backlog * 2;

其中,accept_queue大小 = min(somaxconn, backlog)

全连接队列溢出

当服务端的全连接队列过小时,容易发生全连接队列溢出。发生全连接队列溢出,后续的请求就会被丢弃。

Linux有个参数,可以指定TCP全连接队列满了,会使用什么策略来回应客户端。

  • 可以丢弃客户端的ack报文,当然, 只是liunx的默认行为,

  • 可以向客户端发送RST报文,终止连接,告诉客户端连接失败

可以查看/proc/sys/net/ipv4/tcp_abort_on_overflow了解配置,tcp_abort_on_overflow共有两个值分别是0和1

0:如果全连接队列满了,那么 server 扔掉 client 发过来的 ack ;

1:如果全连接队列满了,server 发送一个 RST 包给 client,表示废掉这个握手过程和这个连接,终止这个握手连接;

如果设置tcp_abort_on_overflow为0的话,此时服务端全连接队列满了,客户端发送过来的ack报文,服务端丢弃。而此时客户端还会继续重传,如果此时服务端的全连接队列有空闲,那么就会接受重传的ack包,这样就能直接建立连接了。通常情况下设置为0更好,可以提高效率。

而设置tcp_abort_on_overflow为1的话,还需要重新连接;但 设置为 1排查问题比较方便;如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1,这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,那么就可以证明是由于服务端 TCP 全连接队列溢出的问题。

连接队列溢出会导致无法与服务器建立新连接 或者 客户端出现大量 connection reset by peer 错误。

如果TCP连接队列溢出,有哪些指标可以看呢?

netstat –s

1
2
3
[root@server ~]#  netstat -s | egrep "listen|LISTEN" 
667399 times the listen queue of a socket overflowed
667399 SYNs to LISTEN sockets ignored

​ 比如上面看到的 667399 times ,表示全连接队列溢出的次数,隔几秒钟执行下,如果这个数字一直在增加的话肯定全连接队列偶尔满了。

ss

1
2
3
[root@server ~]# ss -lnt
Recv-Q Send-Q Local Address:Port Peer Address:Port
0 50 *:3306 *:*

​ 上面看到的第二列Send-Q 表示第三列的listen端口上的全连接队列最大为50,第一列Recv-Q为全连接队列当前使用了多少。
​ 全连接队列的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数/proc/sys/net/core/somaxconn。
​ 半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。 不同版本的os会有些差异。