本文主要讲述:
- 半/全连接队列是什么,有什么作用?
- 半/全连接队列的数据结构是什么?
- SYN攻击导致半连接队列满了怎么办?
- 如何避免SYN攻击?
- 为什么不使用syncookies取代半连接队列与ACK攻击?
一:半/全连接队列是什么,有什么作用?
在 TCP 三次握手调用了listen函数的时候,Linux 内核会创建并维护两个队列,分别是:
- 半连接队列,也称 SYN 队列;
- 全连接队列,也称 accept 队列;
服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。
所以半连接队列的作用是存放三次握手期间的半连接socket,全连接队列是用来存储建立了完整连接的连接,等待accept函数取出。
二: 半/全连接队列的数据结构是什么?
半连接队列和全连接队列虽然叫做队列,但是数据结构不是队列。
- 半连接队列:哈希表
- 全连接队列:链表
(1)半连接队列为什么是哈希表
半连接队列之所以选择哈希表作为数据结构是因为,半连接队列里面的连接都是不完整的,都在等待着第三次握手的到来,不同四元组连接第三次握手ACK到来的顺序和时间都不一定。可能后建立的连接,第三次握手先来。如果使用链表这样的线性逻辑表,每次第三次握手ACK到来,都需要从头到尾去逐个遍历半连接是不是这个ACK的连接。时间复杂度为On效率太低了,而使用哈希表来存储,那么查找时间复杂度为O1。
(2)全连接队列为什么是链表
全连接队列选着使用链表,是因为全连接队列里面存储的都是完整的连接,不同四元组的完整连接之间没有顺序而言,因为accept函数都会取出然后执行下一步。所以先取出谁后取出谁没有关系,使用链表刚刚合适,我们只需要每一次取出头结点就可以了。
三:SYN攻击导致半连接队列满了怎么办?
当服务器收到SYN请求的时候,即第一次握手时,会把连接放入半连接队列里面,同时回复SYN_ACK确认报文,在收到第三次握手的ACK后,又会将连接从半连接队列里面取出,然后创建一个新的连接放入全连接队列,等待accept函数调用返回。TCP在建立连接时系统会维护半连接队列和全连接队列,这两个队列都是有大小的。
SYN攻击就是将目标服务器的半连接队列打满,导致真正的用户连接的SYN报文无法接收而被丢弃。
四:如何避免SYN攻击?
想要避免SYN攻击,不单单只是考虑半连接队列大小的情况,即使半连接队列足够,全连接队列太小也不行。Linux内核对于全连接队列的处理动作由内核参数tcp_abort_on_overflow控制。
tcp_abort_on_overflow:
- 设置为0:如果全连接队列满了,服务器丢弃客户端ACK,(客户端可以重传)
- 设置为1:如果全连接队列满了,服务器发送RST包给客户端,表示废弃这个握手过程和这个连接。
避免SYN攻击有四种办法:
- 增大TCP的半连接队列。
- 增大netdev_max_backlog。
- 减少SYN+ACK的重传次数。
- 使用tcp_syncookies。
a.增大TCP的半连接队列
增大TCP的半连接队列需要同时增大内核的三个参数:只单单增大tcp_max_syn_backlog是无效的。
- 增大net.ipv4.tcp_max_syn_backlog。
- 增大listen()函数的backlog参数。
- 增大net.core.somaxconn。
b.增大netdev_max_backlog
网卡接收数据包也会有一个队列先把数据缓存起来, 等待内核处理,我们可以调大这个队列,使得该队列能够多存一些SYN包,等待内核慢慢处理。这个队列由内核参数netdev_max_backlog控制,默认1000。
c.减少SYN+ACK的重传次数
当服务器受到SYN攻击时,服务器有大量的半连接,服务器出现大量的SYN_RECV状态,因为SYN攻击不会给服务器回复第三次握手,所以处于SYN_RECV状态的服务器收不到ACK就会重传第二次握手即SYN+ACK包,如果我们把SYN+ACK包的最大重传次数改小一点,由内核参数tcp_synack_retries决定,默认为5。那么就能更快断开连接。
d.使用tcp_syncookies
开启了cookies可以在不使用半连接队列的情况下建立连接,也就没有半连接队列满一说。
net.ipv4.tcp_syncookies参数有三个值:
- 0:表示关闭该功能。
- 1:表示半连接队列满了,装不下时再启用它。
- 2:无条件启用它。
cookies=1时,半连接队列满了处理如下:
具体过程:
- 当SYN队列满了,服务器再收到SYN包,不会丢弃。根据哈希算法计算出一个cookie值,将这个cookie值放入第二次握手的报文的序列号里,然后发送第二次报文给客户端。
- 然后第三次握手客户端的ACK带着这个cookie值一起发送,服务器接收到了会验证cookie的合法性,如果合法就把该连接放入全连接队列,等待accept函数取走。
五: 为什么不使用syncookies取代半连接队列与ACK攻击?
前面说使用cookies就不怕SYN攻击了而且还能节约空间,不怕半连接队列满的情况,那为什么还要有半连接队列呢?直接用cookies模式不就好了。
虽然使用cookies模式可以节省一个半连接队列的空间,还可以防止SYN攻击,但是cookies的生成和验证是需要算法支持的,为了确保cookies的唯一性,不可被轻易防止,所以使用的算法都比较复杂。频繁的进行cookies验证和生成,计算量很大非常耗费CPU,而且服务器在没有解析cookie之前根本不知道该cookie是不是合法的,所以可以编造很多带上不合法cookies值的第三次握手ACK让CPU解析,如果解析完发现不合法,等于做了很多无用功消耗掉CPU资源,这就是ACK攻击。