TCP好复杂🤧,主要使用了Kali Linux上的一些网络工具来进行分析,希望能直观地解析TCP(备考中,更新缓慢…)
参考资料:网络基本功系列
1.环境准备
(1)Kali Linux虚拟机安装
在Kail Linux
的官网下载对应的系统镜像文件:Get Kali ,这里我选择中间这个版本(2021.3-installer-amd64)
具体的虚拟机安装过程这里不赘述,我使用的是VMware Workstation
,可以参照这篇文章进行安装:Vmware安装Kali Linux2020.2
之所以选择Kali Linux系统是因为其内置了Nginx,tcpdump,Wireshark这些我们需要用到的实验工具
(2)VM与Hype-V不可共存
我使用的是VM15.5会与Windows的Hype-V发生冲突,所以需要关闭Hype-V
1 | bcdedit /set hypervisorlaunchtype off |
重启电脑后,VM的虚拟机可以正常运行,但是wsl和docker就寄了
如果需要恢复的话,可以使用下面命令然后重启电脑
1 | bcdedit /set hypervisorlaunchtype auto |
(3)准备客户端和服务端
参考资料:linux修改ip地址
克隆虚拟机
搭建好一个Kali虚拟机后,通过VM的克隆功能生成另一台虚拟机,为了节省空间可以直接克隆为链接虚拟机
修改网络模式
将两台虚拟机的网络模式设置为NAT模式
- 虚拟网络编辑器修改
使用管理员开始修改,并取消“使用本地DHCP服务将IP地址分配给虚拟机”的勾
查看子网IP和网关IP
修改客户端和服务端的IP地址
分别进入客户端和服务器打开终端
打开网络配置文件
1
vim /etc/network/interfaces
修改客户端配置文件,设定客户端IP为192.168.234.100
1 | auto eth0 |
修改服务端配置文件,设定服务端IP为192.168.234.200
1 | auto eth0 |
分别重启网卡
1 | systemctl restart networking |
分别查看IP信息
1 | ip addr show |
(4)测试相关服务
测试服务端端口占用
检测80端口是否被占用
1
lsof -i:80
如果被占用了使用
1
kill -9 PID
关闭对应的进程
修改服务端nginx配置文件
1
vim /etc/nginx/nginx.conf
在http块中添加server块
1
2
3
4
5
6
7
8
9
10
11
12
13
14#server块
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
开启服务端nginx服务
1
systemctl start nginx
默认在80端口开启
查看nginx状态
1
systemctl status nginx
客户端访问服务端IP,即可看到部署在服务端的nginx页面
2.基本工具介绍
(1)tcpdump
参考材料:https://www.eet-china.com/mp/a35364.html
tcpdump是常用的网络抓包和分析工具,常用于在Linux服务器
tcp提供了以下选项
tcp提供了以下过滤表达式
在客户端终端进行测试
1
2
3
4
5
6-i eth0 表示抓取eth1网口的数据包
ip 表示抓取ip协议的数据包
host 表示主机过滤,抓取对应域名/ip的数据报
-nn 表示不解析ip地址和端口号的名称
tcpdump -i eth0 ip and host www.baidu.com -nn通过curl或者浏览器请求 www.baidu.com 即可获得数据包(百度——网络测试的神)
tcpdump只是用来抓取数据包,并不用来分析数据包,所以我们还需要Wireshark工具进行数据包分析
(2)Wireshark
先使用tcpdump抓取数据保存为pcap文件
1
tcpdump -i eth0 ip and host www.baidu.com -c 8 -w baidu.pacp
终端开启Wireshark
1
Wireshark
注意开启Wireshark后,不要关闭该终端,而是开启新的终端
Wireshark分析baidu.pacp文件
当然了Wireshark自身也有抓包功能,但是它有自己的一套过滤方法,我还是采用tcpdump的方法进行抓包操作
3.解密TCP连接
(1)建立一次连接
客户端抓取请求服务端的包
1
tcpdump -i any tcp and host 192.168.234.200 and port 80 -w tcp.pcap
客户端请求服务端
1
curl 192.168.234.200
退出tcpdump抓包
Ctrl+C退出tcpdump抓包
(2)Wireshark分析tcp
一次连接的传输流程
- 最开始的3个包就是TCP三次握手建立连接的包
- 中间是HTTP请求和响应的包
- 最后的3个包则是TCP三次挥手断开连接的包
时序图显示数据包交互
在Wireshark点击统计 -> 流量图,在流量类型选择TCP Flows
显示真实seq值
事实上上面的序列号seq是相对值,并不是真实值(关于序列号的算法下文会介绍)
在协议首选项取消Relative Seq即可看到真实值
再次查看流量图,可以看到真实的seq值
(3)TCP连接流程分析
- TCP连接完整流程
三次握手
传输数据
四次挥手
这里只有三次是因为服务器端收到客户端的
FIN
后,服务器端同时也要关闭连接,这样就可 以把ACK
和FIN
合并到一起发送,节省了一个包,变成了“三次挥手”服务器端收到客户端的
FIN
后,很可能还没发送完数据,所以就会先回复客户端一个ACK
包,稍等一会儿,完成所有数据包的发送后,才会发送FIN
包,这也就是四次挥手了
4.TCP连接异常情况分析
参考资料:
(1)TCP第一次握手SYN丢包
向一个不存在的主机地址发起连接即可模拟TCP第一次握手SYN丢包的状况
开启tcpdump监控47.48.49.50(不存在的地址)
1
tcpdump -i any tcp and host 47.48.49.50 and port 80 -w tcp_sys_timeout.pcap
发起对47.48.49.50的请求
1
date;curl 47.48.49.50;date
date显示当前时间用于记录tcp用时
开启wireshark分析tcp_sys_timeout.pcap
分析TCP重传过程
当客户端发起TCP第一次握手SYN包,在超时间没有收到ACK就会重传SYN数据包,而且时间会逐渐翻倍,
kali Liunx这里重传了4次SYN后,服务端就会发送RST复位报文给客户端表示终止这个握手过程和这个连接
相关参数调整
重传的次数与tcp_syn_retries参数有关
1
cat /proc/sys/net/ipv4/tcp_syn_retries
可以看到kali linux需要重传6次(所以为什么这里只重传了4次?🧐,我也妹开tcp_abort_on_overflow呀,不是队列满的问题吧)
我们可以调小tcp_syn_retries重新测试
1 | echo 2 > /proc/sys/net/ipv4/tcp_syn_retries |
可以看到SYN重传了2次
(2)TCP第二次握手SYN、ACK丢包
客户端添加防火墙
模拟客户端接收不到服务端的响应,可以在客户端添加防火墙设置,把
192.168.234.200
服务器ban掉1
iptables -I INPUT -s 192.168.234.200 -j DROP
可以查看清单
1
iptables -L
客户端开启tcpdump监控192.168.234.200
1
tcpdump -i any tcp and host 192.168.234.200 and port 80 -w tcp_two.pcap
发起对192.168.234.200的请求
1
curl 192.168.234.200
在这里kali linux就会卡住了,建议直接手动关闭该命令
wireshark分析数据包
客户端视角:客户端发起SYN请求后,由于防火墙屏蔽了服务器所有的数据包,所有无法接受到SYN,ACK包,所以和上一种情 况一样要重发SYN包
服务器角度:服务端收到客户的SYN包后,就会回SYN、ACK包,但是客户端一直没有回ACK,服务端在超时后,重传了 SYN、 ACK 包,接着一会,客户端超时重传的SYN包又抵达了服务端,服务端收到后,超时定时器就重新计时,然后回SYN、ACK包
当第二次握手的SYN、ACK丢包时,客户端会超时重发SYN包,服务端也会超时重传SYN、ACK包
相关参数
tcp_syn_retries
:客户端SYN重传次数1
cat /proc/sys/net/ipv4/tcp_syn_retries
tcp_synack_retries
:服务端重传ACK,SYN次数
1 | cat /proc/sys/net/ipv4/tcp_synack_retries |
更改次数
1 | 设置SYN重传次数为1 |
重新抓包分析
移除防火墙规则
1
iptables -D INPUT 1
(3)TCP第三次握手ACK丢包
服务器添加防火墙屏蔽来自客户端的ACK包
1
iptables -I INPUT -s 192.168.234.100 -p tcp --tcp-flag ACK ACK -j DROP
客户端开启tcpdump监控192.168.234.200
1
tcpdump -i any tcp and host 192.168.234.200 and port 80 -w tcp_ack_timeout.pcap
客户端向服务端发起telnet
1
telnet 192.168.234.200
等待很长一段时间客户端的telent才断开连接…..
查看客户端服务端状态
客户端已完成TCP连接建立处于处于
ESTABLISHED
状态1
netstat -napt | grep 192.168.234.100
服务器收不到第三次握手的ACK包,所以一开始处于SYN_RECV
状态
1 | netstat -napt | grep 192.168.234.200 |
过了一段时间后在查询服务端状态,tcp连接就消失了
而客户端还是处于 ESTABLISHED
状态(在不传输数据的情况下会持续大概两个小时😰)
在客户端建立的telnet会话,输入字符消息进行发送
这里要持续很长一段时间客户端telnet才断开连接
Wireshark分析数据包
- 客户端发送 SYN 包给服务端,服务端收到后,回了个 SYN、ACK 包给客户端,此时服务端的 TCP 连接处于
SYN_RECV
状态; - 客户端收到服务端的 SYN、ACK 包后,给服务端回了个 ACK 包,此时客户端的 TCP 连接处于
ESTABLISHED
状态 - 服务端配置防火墙屏蔽了客户端的ACK包,以服务端会有一段时间处于
SYN_RECV
状态,没有进入ESTABLISHED
状态 - 接着,服务端超时重传了 SYN、ACK 包,重传了 5 次后,也就是超过 tcp_synack_retries 的值,然后就没有继续重传了,此时服务端的 TCP 连接主动中止了,所以刚才处于 SYN_RECV 状态的 TCP 连接断开了,而客户端依然处于
ESTABLISHED
状态 - 客户端依然处于
ESTABLISHED
状态,于是就在客户端的 telnet 会话输入了 123456 字符 - 由于服务端已经断开连接,客户端发送的数据报文,一直在超时重传,每一次重传,RTO 的值是指数增长的,所以持续了好长一段时间,客户端的 telnet 才报错退出了,此时会重传了 15 次
相关参数
TCP 建立连接后的数据包传输,客户端发送数据报文最大超时重传次数是由
tcp_retries2
指定,默认值是 15 次1
cat /proc/sys/net/ipv4/tcp_retries2
按照时间倍增原理重传了15次后终于是结束了
如果这种情况下客户端不发送数据,客户端什么时候才会断开处于 ESTABLISHED 状态的连接这里需要提到TCP的保活机制,在一个规定的时间段(tcp_keepalive_time:保活时间)内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔(tcp_keepalive_intvl:每次检测间隔),发送一个「探测报文」,该探测报文包含的数据非常少,如果连续几个探测报文(tcp_keepalive_probes:检测次数)都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序
相关参数如下:
1 | cat /proc/sys/net/ipv4/tcp_keepalive_time |
按照系统默认的设置来计算在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接
5.解密TCP快速建立连接
参考资料:tcp fast open分析(里面有详细的实验过程)
(1)普通连接与快速连接的差别
RTT即一个数据包往返的时间,所有一个握手过程为0.5RTT(第三次握手是可以携带数据的,所以ACK和HTTP请求一起发送)
普通连接每次发起HTTP请求都要重新进行上次握手过程,经历的RTT都是一样的
快速连接第一次建立连接时,第二次握手会产生一个Cookie(其中维护着TCP相关信息)发给客户端,客户端就会缓存着这个Cookie;下次请求时,客户端在SYN包带上Cookie,服务端可以直接通过Cookie获得TCP相关信息,从而跳过三次握手的过程
(2)Fast Open相关参数
tcp_fastopn参数可以设置Fast Open的模式:
- 0 关闭
- 1 作为客户端使用 Fast Open 功能
- 2 作为服务端使用 Fast Open 功能
- 3 无论作为客户端还是服务器,都可以使用 Fast Open 功能
查看kali linux的tcp_fastopn,可以看到kali已默认开启作为客户端使用 Fast Open 功能
1 | cat /proc/sys/net/ipv4/tcp_fastopen |
(3)wireshark分析数据包
6.解密TCP重复确认和快速重传
(1)TCP乱序数据包处理方式
但接收方收到乱序数据包时,会发送重复的ACK,以告知发送方要重发该数据包,当发送方收到3个重复ACK时就会触发快速重传,立即重发丢失的数据包
(2)wireshark分析数据包
- 数据包1期望下一个返回的的数据包seq为1,但是返回的数据包2的seq为10945,说明收到了乱序数据包
- 数据包3重发seq = 1, ack=1,表明这是重复的ACK
- 数据包4,6返回的仍然是乱序的数据包,于是5,7还是重发seq = 1, ack=1的重复ACK
- 当对方收到三次重复的ACK后,快速重传seq=1,len=168的数据包8
- 当收到重传的数据包后,发现seq=1是期望的数据包,预设发送确认报文ACK
(3)相关参数
tcp_sack参数可以开启选择性SACK,一旦数据包丢失并收到重复ACK,即使在丢失数据包之后还成功接收了其他数据包,也只需要重 传丢失的数据包(简单来说,如果不开启SACK,丢失包之后的每个数据包都要进行重传)
查看kali linux的tcp_sack参数
1 | cat /proc/sys/net/ipv4/tcp_sack |
7.解密TCP流量控制
(1)TCP滑动窗口机制
TCP有两大关键功能:
可靠传输:保证数据确实到达目的地,如果未到达,能够发现并重传
数据流控:管理数据的发送速率,以使接收设备不致于过载
TCP数据流控的关键是滑动窗口机制,它利用接收方的接收窗口控制发送方要发送数据量,发送方的接收窗口可以告诉发送方自己TCP 缓冲空间区大小
在客户端与服务器的连接中,客户端告知服务器它一次希望从服务器接收多少字节数据,这是客户端的接收窗口,即服务器的发送窗口
- 理想情况下的窗口大小
假如应用层很快地从缓冲区读取了数据,那么窗口大小会一直保持不变
- 现实情况下的窗口大小
现实中服务器会出现繁忙的情况,当应用程序读取速度慢时,那么缓存空间会慢慢被占满,这时服务器会调整窗口大小的值,通过ACK 报文通知对方
(2)零窗口通知与窗口探测
- 零窗口:当接收方的缓存被占满后,会发送值为0的接收窗口,当发送方接收到零窗口通知时,就会停止发送数据
窗口探测:发送方接收到零窗口通知后,会定时发送窗口大小探测报文,以便知道接收方窗口大小变化
发送窗口大小探测报文的时间间隔与TCP的报文重传机制一样都是翻倍递增的
(3)发送窗口的分析
报文win
字段表明的是自己的接收窗口,而不是发送窗口
可以通过查看报文字段:Windos size value 和 Window size scaling factor 确认发送窗口的值,计算公式如下
「Window size value」 * 「Window size scaling factor」 = 「Caculated window size 」
发送窗口虽然是由接收窗口决定的,但是它是可以被网络因素影响的,所以实际上发送窗口的值是min(拥塞窗口, 接收窗口)
TCP 有累计确认机制,所以当收到多个数据包时,只需要应答最后一个数据包的 ACK 报文就可以了
8.TCP减少小报文传输
当TCP报文承载的数据非常小的时候,整个网络效率就会很低(例如一个报文TCP头部为20字节,IP头部也是20个字节,但数据只有2字节,就相当于用大货车运一个小包裹)
TCP使用两种策略来减少小报文的传输:
Nagle算法
延迟确认
(1)Nagle算法
Nagle算法数据发送策略:
- 没有已发送未确认报文时,立刻发送数据
- 存在未确认报文时,直到没有已发送未确认报文或数据长度达到MSS大小,再发送数据
如果不满足任意一条,发送方会一直囤积数据,直到满足发送条件
- 一开始没有已发送未确认的报文,H字符就会立即发出
- 在没有收到对H字符的确认报文时,发送方一直囤积数据,直到收到确认报文,此时就没有已发送未确认的报文,于是就把囤积后的 ELL 字符一起发给了接收方
- 待收到ELL字符的确认报文后,就把最后一个之后O发出
Nagle算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle 算法
关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭,如 Socket可以通过设置 TCP_NODELAY
选项来关闭这个算法
(2)延迟确认
延迟确认数据发送策略:
- 当有响应数据要发送时,ACK会跟着一起发送给对方
- 当没有响应数据要发送时,ACK将延迟一段时间,以等待是否有响应数据一起发送
- 如果在ACK延迟等待发送期间,对方第二个数据报文到达了,这时就立即发送ACK
延迟等待的时间在Linux内核中定义的,我们可以通过查看HZ(系统时钟频率)来确认
这里注意不同内核的配置文件名称不同,我的就是config-5.10.0-kali9-amd64
1 | cat /boot/config-5.10.0-kali9-amd64 | grep 'CONFIG_HZ=' |
我的这台机器的HZ=250,所以最大延迟确认时间为50ms,最小延迟确认时间为10ms
关闭延迟确认也没有全局参数,需要根据每个应用自己的特点来关闭,如TCP 延迟确认可以在 Socket 设置 TCP_QUICKACK
选项来关闭这个算法
9.TCP全连接队列
参考资料:TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?
(1)TCP半连接队列与全连接队列
在TCP三次握手中,我们如何分辨:哪些连接是半连接,哪些连接是全连接呢?
Linux通过维护两个队列来解决问题:
- 半连接队列(SYN队列)
- 全连接队列(accepet队列)
- 服务端收到客户端发起的SYN后,内核会将连接存储到半连接队列
- 服务端向客户端发送SYN+ACK
- 客户端收到SYN+ACK后,发送ACK到服务端
- 服务端收到客户端的ACK后,内核会把连接从半连接队列移除,将其添加到全连接队列,等待进程调用accept函数时把连接取出来
- 不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包
(2)全连接队列状态查询
在服务端(192.168.234.200)查看全连接队列状况
查看LISTEN 状态的连接:
1 | -l 显示状态为listen的socket |
- Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端
accept()
的 TCP 连接个数; - Send-Q:当前全连接最大队列长度,上面的输出结果说明监听 80 端口的 TCP 服务进程,最大全连接长度为 511
查看非 LISTEN 状态的连接:
先在客户端对服务端发起请求
1 | telnet 192.168.234.200 80 |
这时可以在服务端查看非 LISTEN 状态的连接
1 | -n 不解析服务名称 |
这时Recv-Q/Send-Q
表示的含义与LISTEN 状态的不同
- Recv-Q:已收到但未被应用进程读取的字节数;
- Send-Q:已发送但未收到确认的字节数;
(3)全连接队列溢出
本次模拟实验,客户端(192.168.234.100)使用wrk工具(HTTP 压测工具,它能够在单机多核 CPU 的条件下,使用系统自带的高性能 I/O 机制,通过多线程和事件模式,对目标机器产生大量的负载)对服务端(192.168.234.100)发起大量请求,以此模拟TCP全连接队列溢出的状态
客户端安装wrk工具
1
apt install wrk
客户端对服务端进行抗压测试
1
2
3
4-t 6 表示6个线程
-c 30000 表示3万个连接
-d 60s 表示持续压测60秒
wrk -t 6 -c 30000 -d 60s http://192.168.234.200
服务端多次执行ss命令查看全连接队列的情况
1
ss -lnt
可以看到TCP全链接队列逐渐上升到最大全连接长度为511,当全连接队列为512时即为全连接队列溢出,当超过了 TCP 最大全连接队列,服务端则会丢掉后续进来的 TCP 连接,所以下一个状态全连接队列又恢复了511
查看被丢弃的连接个数
1
netstat -s | grep overflowed
结论
当服务端并发处理大量请求时,如果 TCP 全连接队列过小,就容易溢出。发生 TCP 全连接队溢出的时候,后续的请求就会被丢弃,这样就会出现服务端请求数量上不去的现象
(4)全连接队列溢出策略
应对全连接队列溢出有以下两种应对策略:
- 队列满了以后丢弃后续连接(Liunx默认策略)
- 向客户端发送RST复位报文,告诉客户端连接已经建立失败
查看服务端
tcp_abort_on_overflow
参数1
cat /proc/sys/net/ipv4/tcp_abort_on_overflow
- tcp_abort_on_overflow为0:如果全连接队列满了,那么 server 扔掉 client 发过来的 ack
- 如果全连接队列满了,server 发送一个
reset
包给 client,表示废掉这个握手过程和这个连接
将服务端的
tcp_abort_on_overflow
设为1进行测试1
echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow
客户端再次对服务端进行抗压测试
1 | -t 6 表示6个线程 |
客户端异常中会查看到很多的connection reset by peer
错误
通常情况下,应当把 tcp_abort_on_overflow
设置为 0,因为这样更有利于应对突发流量
(5)增大全连接队列
TCP全连接队列最大值取决于somaxconn(Linux内核参数)和backlog(Nginx中配置)之间的最小值
查看服务端somaxconn的值
1
cat /proc/sys/net/core/somaxconn
修改backlog的默认值
nginx的backlog默认值为511(配置文件可以不写出),现在修改为4000
1
vim /etc/nginx/nginx.conf
在listen端口后面加上
1
backlog=4000
重启nginx服务
1 | systemctl reload nginx |
服务端再次执行ss命令,查看TCP全连接队列大小
再次进行测压
如果看到溢出的次数不再增加,说明TCP全连接队列最大值为4000时可以抗住3万连接的并发请求
10.TCP半连接队列
(1)查看TCP半连接队列长度
客户端运行wrk请求服务端
1 | wrk -t 6 -c 30000 -d 60s http://192.168.234.200 |
服务端运行以下命令查看当前TCP半连接队列长度
(ps:注意要将nginx的backlog调整回默认值,否则半连接队列会处理太快)
1 | netstat -natp | grep SYN_RECV | wc -l |
(2)半连接队列溢出
模拟TCP半连接溢出的场景,实际上就是对服务端一直发送SYN包,但是不回第三次握手ACK,这样会使服务端有大量的处于 SYN_RECV
状态的 TCP 连接(即半连接状态),这也就是所谓的 SYN 洪泛、SYN 攻击、DDos 攻击
首先要先关闭服务端的tcp_syncookies,tcp_syncookies 是Linux缓解 SYN 攻击其中一个手段
1
echo 0 > /proc/sys/net/ipv4/tcp_syncookies
客户端使用hping3工具模拟SYN攻击
kali linux已内置安装了hping3无须再次安装
1
2
3
4-S 指定TCP包的标志位SYN
-p 80 指定探测的目的端口
---flood 以泛洪的方式攻击
hping3 -S -p 80 --flood 192.168.234.200
服务端循环查看当前TCP半连接队列大小
1
2
3
4
5
6
7while true;
do
sleep 0.5;
echo "当前半连接队列数:";
netstat -natp | grep SYN_RECV | wc -l;
echo " ";
done;
服务端循环使用
netstat -s
查看半连接队列溢出情况1
2
3
4
5
6
7while true;
do
sleep 1;
echo "半连接队列溢出数";
netstat -s | grep "SYNs to LISTEN";
echo " ";
done;上面的数值是累计值,如果有上升的趋势,说明当前存在半连接队列溢出的现象
(3)增大半连接队列
半连接队列最大值(max_qlen_log)不是单单由 max_syn_backlog 决定,还跟 somaxconn 和 backlog 有关系
- 当 max_syn_backlog > min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = min(somaxconn, backlog) * 2;
- 当 max_syn_backlog < min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = max_syn_backlog * 2;
- 即 max_syn_backlog,somaxconn,backlog最小的一个数乘以2为半连接队列最大值
因为nginx的backlog默认为511,所以该系统max_qlen_log的值为256
max_qlen_log 是理论半连接队列最大值,并不一定代表服务端处于 SYN_REVC 状态的最大个数
(4)半连接队列溢出策略
半连接队列溢出有两种应对策略:
- 当syncookies=0时,TCP会丢弃连接
- 当syncookies=1时,服务端开启 syncookies 功能,其可以在不使用 SYN 半连接队列的情况下成功建立连接(默认配置)
syncookies的运行原理:服务器根据当前状态计算出一个值,放在己方发出的 SYN+ACK 报文中发出,当客户端返回 ACK 报文时,取出该值验证,如果合法,就认为连接建立成功
我们可以使用下面的命令查看系统syncookies的值
为了应对SYN攻击,其默认为1(上面的实验我们改为了0)
1 | cat /proc/sys/net/ipv4/tcp_syncookies |
syncookies 参数主要有以下三个值:
- 0 值,表示关闭该功能
- 1 值,表示仅当 SYN 半连接队列放不下时,再启用它
- 2 值,表示无条件开启功能
(5)SYN攻击应对方法
- 增大半连接队列
要注意需要同时增大tcp_max_syn_backlog ,somaxconn 和 backlog,其中最小值的两倍即为半连接队列的最大值,同时somaxconn 和 backlog的最小值为全连接队列的最大值
- 开启 tcp_syncookies 功能(一般默认开启)
1 | echo 1 > /proc/sys/net/ipv4/tcp_syncookies |
- 减少 SYN+ACK 重传次数
当服务端受到 SYN 攻击时,就会有大量处于 SYN_REVC 状态的 TCP 连接,处于这个状态的 TCP 会重传 SYN+ACK ,当重传超过次数达到上限后,就会断开连接
所以我们也可以减少SYN+ACK的重传次数,以加快SYN_REVC状态的TCP连接端口
1 | 设置ACK,SYN重传次数为1 |
,SYN重传次数为1
echo 1 > /proc/sys/net/ipv4/tcp_synack_retries