0%

开发原理|TCP可视化实验

I’m going to tell a joke | excel-memes, tcp-memes, udp-memes | ProgrammerHumor.io

TCP好复杂🤧,主要使用了Kali Linux上的一些网络工具来进行分析,希望能直观地解析TCP(备考中,更新缓慢…)

参考资料:网络基本功系列

1.环境准备

(1)Kali Linux虚拟机安装

image-20211018194604769

Kail Linux的官网下载对应的系统镜像文件:Get Kali ,这里我选择中间这个版本(2021.3-installer-amd64)

具体的虚拟机安装过程这里不赘述,我使用的是VMware Workstation,可以参照这篇文章进行安装:Vmware安装Kali Linux2020.2

之所以选择Kali Linux系统是因为其内置了NginxtcpdumpWireshark这些我们需要用到的实验工具

(2)VM与Hype-V不可共存

我使用的是VM15.5会与Windows的Hype-V发生冲突,所以需要关闭Hype-V

image-20211023154303399

1
bcdedit /set hypervisorlaunchtype off

重启电脑后,VM的虚拟机可以正常运行,但是wsl和docker就寄了

如果需要恢复的话,可以使用下面命令然后重启电脑

1
bcdedit /set hypervisorlaunchtype auto
(3)准备客户端和服务端

参考资料:linux修改ip地址

  • 克隆虚拟机

    搭建好一个Kali虚拟机后,通过VM的克隆功能生成另一台虚拟机,为了节省空间可以直接克隆为链接虚拟机

image-20211019205256464

  • 修改网络模式

    将两台虚拟机的网络模式设置为NAT模式

    image-20211019205636065

  • 虚拟网络编辑器修改

image-20211019205855124

​ 使用管理员开始修改,并取消“使用本地DHCP服务将IP地址分配给虚拟机”的勾

image-20211019210009660

  • 查看子网IP和网关IP

    image-20211019210418389

  • 修改客户端和服务端的IP地址

    分别进入客户端和服务器打开终端

    打开网络配置文件

    1
    vim /etc/network/interfaces

修改客户端配置文件,设定客户端IP为192.168.234.100

1
2
3
4
5
6
7
8
9
auto eth0
#静态设置ip
iface eth0 inet static
#设置ip地址
address 192.168.234.100
#设置子网掩码
netmask 255.255.255.0
#设置网关
gateway 192.168.234.2

修改服务端配置文件,设定服务端IP为192.168.234.200

1
2
3
4
5
6
7
8
9
auto eth0
#静态设置ip
iface eth0 inet static
#设置ip地址
address 192.168.234.200
#设置子网掩码
netmask 255.255.255.0
#设置网关
gateway 192.168.234.2

分别重启网卡

1
systemctl restart networking

分别查看IP信息

1
ip addr show

image-20211019224416761

image-20211019224510781

(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

    image-20211019230343549

  • 客户端访问服务端IP,即可看到部署在服务端的nginx页面

    image-20211019230646780

2.基本工具介绍

(1)tcpdump

参考材料:https://www.eet-china.com/mp/a35364.html

tcpdump是常用的网络抓包和分析工具,常用于在Linux服务器

  • tcp提供了以下选项

    image.png
  • tcp提供了以下过滤表达式

    image.png
  • 在客户端终端进行测试

    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 即可获得数据包(百度——网络测试的神)

    image-20211020085021073

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后,不要关闭该终端,而是开启新的终端

    image-20211020090446325

  • Wireshark分析baidu.pacp文件

    image-20211020090612028

    img

当然了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抓包

    image-20211020192429846

(2)Wireshark分析tcp

image-20211020204055955

  • 一次连接的传输流程

    1. 最开始的3个包就是TCP三次握手建立连接的包
    2. 中间是HTTP请求和响应的包
    3. 最后的3个包则是TCP三次挥手断开连接的包
  • 时序图显示数据包交互

    在Wireshark点击统计 -> 流量图,在流量类型选择TCP Flows

    image-20211020205139464

  • 显示真实seq值

    事实上上面的序列号seq是相对值,并不是真实值(关于序列号的算法下文会介绍)

    协议首选项取消Relative Seq即可看到真实值

    img

再次查看流量图,可以看到真实的seq值

image-20211020195708923

(3)TCP连接流程分析
  • TCP连接完整流程
这里写图片描述
  • 三次握手

    image-20211020210505247

  • 传输数据

    image-20211020210523657

  • 四次挥手

    image-20211020210546911

    这里只有三次是因为服务器端收到客户端的 FIN 后,服务器端同时也要关闭连接,这样就可 以把 ACKFIN 合并到一起发送,节省了一个包,变成了“三次挥手”

    服务器端收到客户端的 FIN 后,很可能还没发送完数据,所以就会先回复客户端一个 ACK 包,稍等一会儿,完成所有数据包的发送后,才会发送 FIN 包,这也就是四次挥手了

4.TCP连接异常情况分析

参考资料:

iptables 添加,删除,查看,修改

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用时

    image-20211021091606451

  • 开启wireshark分析tcp_sys_timeout.pcap

    image-20211023155134728

  • 分析TCP重传过程

    当客户端发起TCP第一次握手SYN包,在超时间没有收到ACK就会重传SYN数据包,而且时间会逐渐翻倍,

    img

kali Liunx这里重传了4次SYN后,服务端就会发送RST复位报文给客户端表示终止这个握手过程和这个连接

  • 相关参数调整

    重传的次数与tcp_syn_retries参数有关

    1
    cat /proc/sys/net/ipv4/tcp_syn_retries

    image-20211023161746849

可以看到kali linux需要重传6次(所以为什么这里只重传了4次?🧐,我也妹开tcp_abort_on_overflow呀,不是队列满的问题吧)

我们可以调小tcp_syn_retries重新测试

1
echo 2 > /proc/sys/net/ipv4/tcp_syn_retries

image-20211023162734954

image-20211023163211916

可以看到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

image-20211023165234228

  • 客户端开启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分析数据包

    image-20211023171847580

    image-20211023172006864

    ​ 客户端视角:客户端发起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
2
3
4
# 设置SYN重传次数为1
echo 1 > /proc/sys/net/ipv4/tcp_syn_retries
# 设置ACK,SYN重传次数为2
echo 2 > /proc/sys/net/ipv4/tcp_synack_retries

​重新抓包分析

image-20211023203803972

  • 移除防火墙规则

    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

    image-20211023204935552

    等待很长一段时间客户端的telent才断开连接…..

  • 查看客户端服务端状态

    客户端已完成TCP连接建立处于处于 ESTABLISHED 状态

    1
    netstat -napt | grep 192.168.234.100

    image-20211023211201535

服务器收不到第三次握手的ACK包,所以一开始处于SYN_RECV 状态

1
netstat -napt | grep 192.168.234.200

image-20211023211703628

过了一段时间后在查询服务端状态,tcp连接就消失了

image-20211023211752357

​ 而客户端还是处于 ESTABLISHED 状态(在不传输数据的情况下会持续大概两个小时😰)

  • 在客户端建立的telnet会话,输入字符消息进行发送

    image-20211023212138121

​ 这里要持续很长一段时间客户端telnet才断开连接

  • Wireshark分析数据包

    image-20211023212430427

image-20211023212818747

  • 客户端发送 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

    image-20211023213656516

    按照时间倍增原理重传了15次后终于是结束了

image-20211023213751365

如果这种情况下客户端不发送数据,客户端什么时候才会断开处于 ESTABLISHED 状态的连接这里需要提到TCP的保活机制,在一个规定的时间段(tcp_keepalive_time:保活时间)内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔(tcp_keepalive_intvl:每次检测间隔),发送一个「探测报文」,该探测报文包含的数据非常少,如果连续几个探测报文(tcp_keepalive_probes:检测次数)都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序

相关参数如下:

1
2
3
cat /proc/sys/net/ipv4/tcp_keepalive_time
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
cat /proc/sys/net/ipv4/tcp_keepalive_probes

image-20211023214647732

按照系统默认的设置来计算在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接

img

5.解密TCP快速建立连接

参考资料:tcp fast open分析(里面有详细的实验过程)

(1)普通连接与快速连接的差别
img

RTT即一个数据包往返的时间,所有一个握手过程为0.5RTT(第三次握手是可以携带数据的,所以ACK和HTTP请求一起发送)

普通连接每次发起HTTP请求都要重新进行上次握手过程,经历的RTT都是一样的

快速连接第一次建立连接时,第二次握手会产生一个Cookie(其中维护着TCP相关信息)发给客户端,客户端就会缓存着这个Cookie;下次请求时,客户端在SYN包带上Cookie,服务端可以直接通过Cookie获得TCP相关信息,从而跳过三次握手的过程

(2)Fast Open相关参数

tcp_fastopn参数可以设置Fast Open的模式:

  1. 0 关闭
  2. 1 作为客户端使用 Fast Open 功能
  3. 2 作为服务端使用 Fast Open 功能
  4. 3 无论作为客户端还是服务器,都可以使用 Fast Open 功能

查看kali linux的tcp_fastopn,可以看到kali已默认开启作为客户端使用 Fast Open 功能

1
cat /proc/sys/net/ipv4/tcp_fastopen

image-20211024204230242

(3)wireshark分析数据包

img

6.解密TCP重复确认和快速重传

(1)TCP乱序数据包处理方式

​ 但接收方收到乱序数据包时,会发送重复的ACK,以告知发送方要重发该数据包,当发送方收到3个重复ACK时就会触发快速重传,立即重发丢失的数据包

img

(2)wireshark分析数据包

img

  • 数据包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

image-20211025154717887

7.解密TCP流量控制

(1)TCP滑动窗口机制

​ TCP有两大关键功能:

可靠传输:保证数据确实到达目的地,如果未到达,能够发现并重传

数据流控:管理数据的发送速率,以使接收设备不致于过载

​ TCP数据流控的关键是滑动窗口机制,它利用接收方的接收窗口控制发送方要发送数据量,发送方的接收窗口可以告诉发送方自己TCP 缓冲空间区大小

​ 在客户端与服务器的连接中,客户端告知服务器它一次希望从服务器接收多少字节数据,这是客户端的接收窗口,即服务器的发送窗口

  • 理想情况下的窗口大小

​ 假如应用层很快地从缓冲区读取了数据,那么窗口大小会一直保持不变

img

  • 现实情况下的窗口大小

现实中服务器会出现繁忙的情况,当应用程序读取速度慢时,那么缓存空间会慢慢被占满,这时服务器会调整窗口大小的值,通过ACK 报文通知对方

img

(2)零窗口通知与窗口探测
  • 零窗口:当接收方的缓存被占满后,会发送值为0的接收窗口,当发送方接收到零窗口通知时,就会停止发送数据

img

  • 窗口探测:发送方接收到零窗口通知后,会定时发送窗口大小探测报文,以便知道接收方窗口大小变化

    img

    发送窗口大小探测报文的时间间隔与TCP的报文重传机制一样都是翻倍递增

(3)发送窗口的分析

报文win字段表明的是自己的接收窗口,而不是发送窗口

可以通过查看报文字段:Windos size value 和 Window size scaling factor 确认发送窗口的值,计算公式如下

「Window size value」 * 「Window size scaling factor」 = 「Caculated window size 」

img

发送窗口虽然是由接收窗口决定的,但是它是可以被网络因素影响的,所以实际上发送窗口的值是min(拥塞窗口, 接收窗口)

TCP 有累计确认机制,所以当收到多个数据包时,只需要应答最后一个数据包的 ACK 报文就可以了

8.TCP减少小报文传输

当TCP报文承载的数据非常小的时候,整个网络效率就会很低(例如一个报文TCP头部为20字节,IP头部也是20个字节,但数据只有2字节,就相当于用大货车运一个小包裹)

TCP使用两种策略来减少小报文的传输:

  • Nagle算法

  • 延迟确认

(1)Nagle算法

Nagle算法数据发送策略:

  • 没有已发送未确认报文时,立刻发送数据
  • 存在未确认报文时,直到没有已发送未确认报文数据长度达到MSS大小,再发送数据

如果不满足任意一条,发送方会一直囤积数据,直到满足发送条件

img

  • 一开始没有已发送未确认的报文,H字符就会立即发出
  • 在没有收到对H字符的确认报文时,发送方一直囤积数据,直到收到确认报文,此时就没有已发送未确认的报文,于是就把囤积后的 ELL 字符一起发给了接收方
  • 待收到ELL字符的确认报文后,就把最后一个之后O发出

Nagle算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle 算法

关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭,如 Socket可以通过设置 TCP_NODELAY 选项来关闭这个算法

(2)延迟确认

延迟确认数据发送策略:

  • 当有响应数据要发送时,ACK会跟着一起发送给对方
  • 当没有响应数据要发送时,ACK将延迟一段时间,以等待是否有响应数据一起发送
  • 如果在ACK延迟等待发送期间,对方第二个数据报文到达了,这时就立即发送ACK

img

延迟等待的时间在Linux内核中定义的,我们可以通过查看HZ(系统时钟频率)来确认

image-20211025175433959

这里注意不同内核的配置文件名称不同,我的就是config-5.10.0-kali9-amd64

1
cat /boot/config-5.10.0-kali9-amd64 | grep 'CONFIG_HZ=' 

image-20211025175239492

我的这台机器的HZ=250,所以最大延迟确认时间为50ms,最小延迟确认时间为10ms

关闭延迟确认也没有全局参数,需要根据每个应用自己的特点来关闭,如TCP 延迟确认可以在 Socket 设置 TCP_QUICKACK 选项来关闭这个算法

9.TCP全连接队列

参考资料:TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

(1)TCP半连接队列与全连接队列

​ 在TCP三次握手中,我们如何分辨:哪些连接是半连接,哪些连接是全连接呢?

​ Linux通过维护两个队列来解决问题:

  • 半连接队列(SYN队列)
  • 全连接队列(accepet队列)
img
  • 服务端收到客户端发起的SYN后,内核会将连接存储到半连接队列
  • 服务端向客户端发送SYN+ACK
  • 客户端收到SYN+ACK后,发送ACK到服务端
  • 服务端收到客户端的ACK后,内核会把连接从半连接队列移除,将其添加到全连接队列,等待进程调用accept函数时把连接取出来
  • 不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包
(2)全连接队列状态查询

在服务端(192.168.234.200)查看全连接队列状况

查看LISTEN 状态的连接:

1
2
3
4
# -l 显示状态为listen的socket
# -n 不解析服务名称
# -t 只显示tcp socket
ss -lnt

image-20211026194430102

  • Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接个数;
  • Send-Q:当前全连接最大队列长度,上面的输出结果说明监听 80 端口的 TCP 服务进程,最大全连接长度为 511

查看非 LISTEN 状态的连接:

先在客户端对服务端发起请求

1
telnet 192.168.234.200 80

这时可以在服务端查看非 LISTEN 状态的连接

1
2
3
# -n 不解析服务名称
# -t 只显示tcp socket
ss -nt

image-20211026195348911

这时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

    image-20211026201412323

  • 服务端多次执行ss命令查看全连接队列的情况

    1
    ss -lnt

    image-20211026202152288

    可以看到TCP全链接队列逐渐上升到最大全连接长度为511,当全连接队列为512时即为全连接队列溢出当超过了 TCP 最大全连接队列,服务端则会丢掉后续进来的 TCP 连接,所以下一个状态全连接队列又恢复了511

  • 查看被丢弃的连接个数

    1
    netstat -s | grep overflowed

    image-20211026203156762

  • 结论

    全连接队列溢出

当服务端并发处理大量请求时,如果 TCP 全连接队列过小,就容易溢出。发生 TCP 全连接队溢出的时候,后续的请求就会被丢弃,这样就会出现服务端请求数量上不去的现象

(4)全连接队列溢出策略
  • 应对全连接队列溢出有以下两种应对策略:

    1. 队列满了以后丢弃后续连接(Liunx默认策略)
    2. 向客户端发送RST复位报文,告诉客户端连接已经建立失败
  • 查看服务端tcp_abort_on_overflow参数

    1
    cat /proc/sys/net/ipv4/tcp_abort_on_overflow

    image-20211026204705056

    1. tcp_abort_on_overflow为0:如果全连接队列满了,那么 server 扔掉 client 发过来的 ack
    2. 如果全连接队列满了,server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接
  • 将服务端的tcp_abort_on_overflow设为1进行测试

    1
    echo 1 > /proc/sys/net/ipv4/tcp_abort_on_overflow

客户端再次对服务端进行抗压测试

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

​ 客户端异常中会查看到很多的connection reset by peer错误

通常情况下,应当把 tcp_abort_on_overflow 设置为 0,因为这样更有利于应对突发流量

(5)增大全连接队列

TCP全连接队列最大值取决于somaxconn(Linux内核参数)和backlog(Nginx中配置)之间的最小值

  • 查看服务端somaxconn的值

    1
    cat /proc/sys/net/core/somaxconn

    image-20211026213244511

  • 修改backlog的默认值

    nginx的backlog默认值为511(配置文件可以不写出),现在修改为4000

    1
    vim /etc/nginx/nginx.conf

    在listen端口后面加上

    1
    backlog=4000

    image-20211026230115019

重启nginx服务

1
systemctl reload  nginx

服务端再次执行ss命令,查看TCP全连接队列大小

image-20211026230418393

  • 再次进行测压

    image-20211026230850636

如果看到溢出的次数不再增加,说明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

image-20211027081537300

(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
    7
    while true; 
    do
    sleep 0.5;
    echo "当前半连接队列数:";
    netstat -natp | grep SYN_RECV | wc -l;
    echo " ";
    done;
  • 服务端循环使用 netstat -s 查看半连接队列溢出情况

    1
    2
    3
    4
    5
    6
    7
    while true; 
    do
    sleep 1;
    echo "半连接队列溢出数";
    netstat -s | grep "SYNs to LISTEN";
    echo " ";
    done;

    image-20211027090955138

    上面的数值是累计值,如果有上升的趋势,说明当前存在半连接队列溢出的现象

(3)增大半连接队列

半连接队列最大值(max_qlen_log)不是单单由 max_syn_backlog 决定,还跟 somaxconn 和 backlog 有关系

img

  • 当 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为半连接队列最大值

image-20211027132147887

因为nginx的backlog默认为511,所以该系统max_qlen_log的值为256

max_qlen_log 是理论半连接队列最大值,并不一定代表服务端处于 SYN_REVC 状态的最大个数

(4)半连接队列溢出策略

半连接队列溢出有两种应对策略:

  • 当syncookies=0时,TCP会丢弃连接
  • 当syncookies=1时,服务端开启 syncookies 功能,其可以在不使用 SYN 半连接队列的情况下成功建立连接(默认配置)

img

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_backlogsomaxconnbacklog,其中最小值的两倍即为半连接队列的最大值,同时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
2
# 设置ACK,SYN重传次数为1
echo 1 > /proc/sys/net/ipv4/tcp_synack_retries

,SYN重传次数为1
echo 1 > /proc/sys/net/ipv4/tcp_synack_retries