你不知道的tcp半连接、全连接知识都在这了

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

  • 半连接队列,也称 SYN 队列;
  • 全连接队列,也称 accepet 队列;

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。

在这里插入图片描述

半连接和全连接队列都有最大长度限制,当超出最大长度时,系统将无法创建新连接。

在服务端可以使用 ss 命令,来查看 TCP 全连接队列的情况:

# 先看看listen状态的连接
[root@cloudstudy linux-kernel]# ss -lnt
State      Recv-Q Send-Q               Local Address:Port                              Peer Address:Port              
LISTEN     0      128                              *:22                                           *:*                  
LISTEN     0      128                      127.0.0.1:631                                          *:*                  
LISTEN     0      128                      127.0.0.1:6010                                         *:*                  
LISTEN     0      128                             :::111                                         :::*                  
LISTEN     0      128                             :::80                                          :::*                  
LISTEN     0      128                             :::22                                          :::*                  
LISTEN     0      128                            ::1:631                                         :::*                  
LISTEN     0      128                            ::1:6010                                        :::*                  

# 再看看其他状态的连接情况
[root@cloudstudy linux-kernel]# ss -nt
State      Recv-Q Send-Q               Local Address:Port                              Peer Address:Port              
ESTAB      0      36                      10.0.0.136:22                                    10.0.0.1:50904   

为什么ss 加不加 -l 参数send-Q的指标不一样呢?

这是因为:加了-l参数显示的才是全连接队列的长度。换句话说,当状态是LISTEN时,Recv-Q 和Send-Q 分别代表:

  • Recv-Q 当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接;

  • Send-Q 当前全连接最大队列长度,上面的输出结果说明监听 22等端口的 TCP 服务,最大全连接长度为 128。

当状态不是LISTEN时:

  • Recv-Q:已收到但未被应用进程读取的字节数;
  • Send-Q:已发送但未收到确认的字节数;
实战全连接队列

客户端:centos7.9 10.0.0.11

服务端:centos7.9 10.0.0.62 nginx 端口80

测试工具:wrk

# 从客户端执行
# -t 6     表示启动6个测试线程
# -c 30000  表示3万个连接
# -d 60s   表示持续压测60s
wrk -t 6 -c 30000 -d 60s http://10.0.0.62

在服务端用ss命令查阅TCP全连接队列的情况:

# 服务端查阅tcp全连接队列的情况
[root@rs01 ~]# ss -lnt | grep 80
LISTEN     75    128          *:80                       *:*                  
LISTEN     0      128       [::]:80                    [::]:*                  
[root@rs02 ~]# ss -lnt | grep 80
LISTEN     129    128          *:80                       *:*                  
LISTEN     0      128       [::]:80                    [::]:*      

我们共执行了两次 ss 命令,从上面的输出结果可以发现,当前 TCP 全连接队列达到了 129 个,超过了最大 TCP 全连接队列。

当超过了 TCP 最大全连接队列,服务端则会丢掉后续进来的 TCP 连接,丢掉的 TCP 连接的个数会被统计起来,我们可以使用 netstat -s 命令来查看:

[root@rs02 ~]#  netstat -s | grep overflowed
    241307 times the listen queue of a socket overflowed

上面看到的 241307times ,表示全连接队列溢出的次数,注意这个是累计值。可以隔几秒钟执行一次,如果这个数字一直在增加的话肯定全连接队列满了。

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

实际上,丢弃连接只是 Linux 的默认行为,我们还可以选择向客户端发送 RST 复位报文,告诉客户端连接已经建立失败。

[root@rs01 ~]# cat /proc/sys/net/ipv4/tcp_abort_on_overflow 
0

tcp_abort_on_overflow 共有两个值分别是 0 和 1,其分别表示:

  • 0 :如果全连接队列满了,那么 server 扔掉 client 发过来的 ack ;
  • 1 :如果全连接队列满了,server 发送一个 reset 包给 client,表示废掉这个握手过程和这个连接;

如果要想知道客户端连接不上服务端,是不是服务端 TCP 全连接队列满的原因,那么可以把 tcp_abort_on_overflow 设置为 1,这时如果在客户端异常中可以看到很多 connection reset by peer 的错误,那么就可以证明是由于服务端 TCP 全连接队列溢出的问题。就像下面这样:

[root@cloudstudy jason]# git clone https://github.com/wg/wrk.git wrk
Cloning into 'wrk'...
fatal: unable to access 'https://github.com/wg/wrk.git/': TCP connection reset by peer

当然这种错误不是一定会遇到。日常工作中如果没有特殊需求,我们建议设置为0.

如何增大TCP全连接队列

当发现 TCP 全连接队列发生溢出的时候,我们就需要增大该队列的大小,以便可以应对客户端大量的请求。

TCP 全连接队列足最大值取决于 somaxconn 和 backlog 之间的最小值,也就是min(somaxconn, backlog),实际上内核代码就是这么实现的:

// net/socket.c
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
......
		// /proc/sys/net/core/somaxconn
		somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        
        // 取到全连接队列的大小:min(somaxconn, backlog)
		if ((unsigned int)backlog > somaxconn)
			backlog = somaxconn;		//backlog和somaxconn取其中的最小值
......
}
  • somaxconn 是 Linux 内核的参数,默认值是 128,可以通过 /proc/sys/net/core/somaxconn 来设置其值;
  • backloglisten(int sockfd, int backlog) 函数中的 backlog 大小,Nginx 默认值是 511,可以通过修改nginx的配置文件设置其大小;

前面模拟测试中,我的测试环境:

  • somaxconn 是默认值 128;
  • Nginx 的 backlog 是默认值 511

所以测试环境的 TCP 全连接队列最大值为 min(128, 511),也就是 128.

现在我们重新压测,把 TCP 全连接队列搞大,把 somaxconn 设置成 5000:

[root@rs01 ~]# echo 5000 > /proc/sys/net/core/somaxconn

接着把 Nginx 的 backlog 也同样设置成 5000:

# /etc/nginx/nginx.conf
    server {
        listen       80 default backlog=5000;
        listen       [::]:80;
......


# 重启nginx后查看:
[root@rs01 ~]# systemctl restart nginx
[root@rs01 ~]# ss -lnt | grep 80
LISTEN     0      5000         *:80                       *:*                  
LISTEN     0      511       [::]:80                    [::]:* 

从执行结果,可以发现 TCP 全连接最大值为 5000。

再次压测:

wrk -t 6 -c 30000 -d 60s http://10.0.0.62

服务端执行 ss 命令,查看 TCP 全连接队列使用情况:

[root@rs01 ~]# ss -lnt | grep 80
LISTEN     204     5000         *:80                       *:*                  
LISTEN     0      511       [::]:80                    [::]:*  

从上面的执行结果,可以发现全连接队列使用增长的很快,但是一直都没有超过最大值,所以就不会溢出:

[root@rs01 ~]# netstat -s | grep overflowed
    181404 times the listen queue of a socket overflowed
# 181404是没调整之前的丢弃情况,现在看只要数据没有变化就说明队列没有继续溢出。

说明 TCP 全连接队列最大值从 128 增大到 5000 后,服务端抗住了 3 万连接并发请求,也没有发生全连接队列溢出的现象了。

**总结:**如果持续不断地有连接因为 TCP 全连接队列溢出被丢弃,就应该调大 backlog 以及 somaxconn 参数。

实战半连接队列

继续使用上面的环境:

客户端:centos7.9 10.0.0.11

服务端:centos7.9 10.0.0.62 hostname:rs01

nginx 端口80

工具:hping3

TCP 半连接队列的长度,不能像全连接队列那样可以用 ss 命令查看。我们只能根据半连接的特点,通过处于SYN_RECV状态的TCP连接来查看:

[root@rs01 ~]# ss -antp | grep SYN-RECV | wc -l
0

模拟半连接状态,那就是客户端一直发送tcp syn包,但是不回复第三次握手的ACK。那服务端就会有大量的SYN_RECV状态的连接。这就是所谓的SYN泛洪攻击、DDos攻击。

# 先关闭tcp_syncookies
[root@rs01 ~]# echo 0 > /proc/sys/net/ipv4/tcp_syncookies

# 客户端发起SYN泛洪攻击
# -S    表示采用SYN半连接方法
# -p     指定攻击的端口号
# --flood   泛洪攻击,尽可能快的发送数据包
[root@cloudstudy ~]# hping3 -S -p 80 --flood 10.0.0.62


# 到服务端去查看
[root@rs01 ~]# ss -antp | grep SYN-RECV  | wc -l
2

我这返回的结果是2,如果你返回的结果是一个比较大的值,比如256,并且通过如下命令看到累计值在增加:

[root@rs01 ~]# netstat -s | grep "SYNs to LISTEN"
    185752 SYNs to LISTEN sockets dropped

如果上面返回的累计值在不断增加,就说明半连接队列已经满了。

如何增大半连接队列

内核对于半连接队列的限制是有三个逻辑的:

  1. 半连接队列满了且没有开启tcp_syncookies,丢弃
  2. 全连接队列满了,且有多个(大于1个)连接没有重传syn+ack,则丢弃
  3. 如果没有开启tcp_syncookies,并且max_syn_backlog减去半连接队列的长度小于某个值(sysctl_max_syn_backlog >> 2)时,则丢弃

实际上半连接队列最大值不是单单由 max_syn_backlog 决定,还跟 somaxconn 和 backlog 有关系。

实际调整半连接队列长度的时候,除了前面提到的需要增大全连接队列的值以外,还需要调整:

echo 5000 > /proc/sys/net/ipv4/tcp_max_syn_backlog	半连接连接队列大小,建议调整为1024及以上(默认128)
echo 5000 > /proc/sys/net/core/somaxconn
# backlog的调整需要在web服务的配置文件中调整。
其他应对SYN泛洪攻击的方法

除了调大半连接队列,还可以通过其他方式来防止SYN泛洪攻击。

方式1:开启tcp_syncookies功能(其实3.X内核是默认开启的。)

开启 tcp_syncookies 功能的方式也很简单,修改 Linux 内核参数:

[root@rs01 ~]# echo 1 > /proc/sys/net/ipv4/tcp_syncookies

方式2:减少SYN+ACK重传次数

当服务端受到 SYN 攻击时,就会有大量处于 SYN_REVC 状态的 TCP 连接,处于这个状态的 TCP 会重传 SYN+ACK (因为服务端迟迟收不到客户端的第三次ack),当重传超过次数达到上限后,就会断开连接。

那么针对 SYN 攻击的场景,我们可以减少 SYN+ACK 的重传次数,以加快处于 SYN_REVC 状态的 TCP 连接断开。

# 默认SYN+ACK 的重传次数为5次
[root@rs01 ~]# cat /proc/sys/net/ipv4/tcp_synack_retries 
5  

# 我们把它改为1次
[root@rs01 ~]# echo 1 > /proc/sys/net/ipv4/tcp_synack_retries 

参考连接:

https://zhuanlan.zhihu.com/p/144785626

https://www.cnblogs.com/zengkefu/p/5606696.html

https://blog.cloudflare.com/syn-packet-handling-in-the-wild/

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《TCP/IP详解(三卷全)PDF》是一本网络通信方面的著作,由美国著名计算机科学家Douglas E. Comer所著。该书以系统而全面的方式介绍了TCP/IP协议栈的设计、实现和应用。 该书一共分为三卷,包括《TCP/IP卷 1:协议》、《TCP/IP卷 2:实现》和《TCP/IP卷 3:TCP 宏观》。第一卷介绍了网络协议的基本原理和概念,如数据传输、网络层的IP协议、UDP协议和ICMP协议等。通过深入理解这些协议,读者可以更好地了解网络通信的工作原理。 第二卷主要讲解了TCP/IP协议栈的实现细节,包括网络接口、ARP协议、IP路由、以太网和无线网络等。通过该卷的学习,读者可以深入了解TCP/IP协议栈的底层实现原理,理解网络通信的细节和技术。 第三卷则将焦点放在了TCP协议上,详细探讨了TCP连接的建立、数据传输、流量控制和拥塞控制等主题。读者可以通过学习和实践,加深对TCP协议的理解,并学会如何利用TCP协议优化网络应用性能。 总的来说,这本书全面系统地介绍了TCP/IP协议栈的方方面面,无论是初学者还是有一定经验的网络工程师,都可以从中获得丰富的知识和实践经验。通过阅读《TCP/IP详解(三卷全)PDF》,读者可以更好地理解和应用TCP/IP协议,提升自身在网络通信领域的能力。 ### 回答2: 《TCP/IP详解(三卷全)》是由美国计算机科学家Douglas Comer所著的一套经典计算机网络参考书。该书主要讲解了TCP/IP协议族,是网络通信领域的权威指南。 本书的内容共分为三卷,分别是《TCP/IP协议》、《IP协议》和《TCP协议》。第一卷主要介绍了网络通信的基础知识TCP/IP协议族的结构,包括IP地址、子网掩码、路由选择以及网络层和链路层等各个方面的内容。第二卷重点讲解了IP协议,包括IP分组的格式、IP地址的分配和转发、IPv4和IPv6的特点等。第三卷则着重介绍了TCP协议,包括TCP连接的建立与终止、数据传输、拥塞控制和流量控制等细节。 《TCP/IP详解(三卷全)》以其全面、系统的讲解方式,深入浅出地阐述了TCP/IP协议族的原理和应用,对于学习计算机网络或从事网络工程师相关工作的人来说,是一本不可多得的参考书。无论是对于初学者还是有经验的网络专业人员,该书都提供了详实的案例和丰富的实践经验,使读者能够深入理解TCP/IP协议的工作原理,并能够应用于实际网络环境中。 总之,《TCP/IP详解(三卷全)》是一本经典而权威的网络技术书籍,对于学习和理解TCP/IP协议族的原理和应用具有重要意义。无论是对于网络专业人员还是普通的网络使用者,这套书都有很高的参考价值,帮助读者更好地掌握和应用TCP/IP协议,促进网络技术的发展和应用。 ### 回答3: 《TCP/IP详解(三卷全)PDF》是一本经典的计算机网络书籍,由Douglas E. Comer所著。这本书以全面、深入的方式介绍了TCP/IP协议族的各个方面。 首先,这本书分为三卷,每一卷都从不同的角度深入探讨了TCP/IP协议族的相关知识。第一卷主要介绍了TCP/IP协议族的基础知识,包括IP协议、ICMP协议、ARP协议等。第二卷则详细讲解了TCP协议和UDP协议的原理和实现。第三卷则针对IPv6协议进行了深入的解析。 《TCP/IP详解(三卷全)PDF》的一大特点是其详尽的内容。无论是网络层还是传输层,无论是IPv4还是IPv6,这本书都提供了全面的讲解。读者可以通过阅读本书,了解到TCP/IP协议族的每个细节,从而更好地理解和应用这些协议。 此外,这本书还提供了丰富的实例和案例分析,帮助读者更好地理解TCP/IP协议族在实际网络中的应用。同时,它还提供了许多问题与练习,供读者巩固所学知识。 总的来说,《TCP/IP详解(三卷全)PDF》是一本经典的计算机网络教材。无论是对于网络工程师还是对于对计算机网络感兴趣的读者来说,这本书都是一本不可多得的宝藏。阅读这本书,能够帮助读者深入理解计算机网络的基础知识,提升网络技术能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值