优质博文:IT-BLOG-CN
灵感来源
Time_Wait 产生的时机
TCP
四次挥手的流程
如上所知:客户端在收到服务端第三次FIN
挥手后,就会进入TIME_WAIT
状态,开启时长为2MSL
的定时器。
【1】MSL
是Maximum Segment Lifetime
报文最大生存时间,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为TCP
报文是基于IP
协议的,而IP
头中有一个TTL
字段,是IP
数据报可以经过的最大路由数,每经过一个处理他的路由器值就会减1
,当此值为0
则数据报将会被丢弃,同时发送ICMP
通知源主机;
MSL
与TTL
的区别:MSL
单位是时间,TTL
是经过路由跳数。所以MSL
应该大于等于TTL
消耗为0
的时间,以确保报文已被自然消亡。
【2】2MSL
的时间是从客户端接收到第三次握手的FIN
后发送ACK
开始计时的;
如果在
TIME-WAIT
时间内,因为客户端的ACK
没有传输到服务端,客户端又收到了服务端重发的FIN
报文,那么2MSL
时间将重新计时。
【3】Linux
系统停留在TIME_WAIT
的时间为固定的30
秒。这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它;
#define TCP_TIMEWAIT_LEN (60*HZ)
TTL
的值一般为64
,MSL
一般为30s
,意味着Linux
数据报文经过64
个路由器时间不会超过30s
,如果超过了则认为报文已经消息在网络中。
【4】客户端在收到服务端重传的FIN
报文时,TIME_WAIT
状态的等待时间会重置回2MSL
;
2MSL
解释:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又向对方发送响应,所以一来一回就是2
个MSL
时间。
如果被动关闭方Server
没有收到断开连接的ACK
报文,就会触发超时重发FIN
报文,另一方Client
接收到FIN
后,会重发ACK
给被动关闭方,一来一回正好2
个MSL
主动发起关闭连接的一方才会有
TIME-WAIT
状态。
为什么需要 TIME- WAIT状态
【1】确保被动关闭方已经关闭了连接: 当主动关闭方发出最后的ACK
后,如果由于某种异常导致报文丢失,被动方没有收到最后的ACK
报文会一直处于LAST-ACK
状态,无法进入CLOSED
状态。
假设主动关闭方跳过TIME_WAIT
状态或者处于TIME_WAIT
状态很短的时间后进入CLOSED
状态,此时主动关闭方如果使用相同的源端口,发起SYN
连接请求,被动关闭方由于还处于LAST_ACK
状态,收到SYN
包,此时就会回复RST
包,导致新连接无法正常建立起来。
【2】新的TCP
连接被建立起来了,延迟包可能干扰新的连接: 当使用原来的五元组来建立新的TCP
连接,如果上一次连接还有数据报文,由于网络拥塞等原因,在新连接建立后才到达(且序列号一致),此时就会干扰到新的连接了,当然出现这种问题的概率比较低。
TIME_WAIT 资源使用情况
【1】查询方式: 通常在业务中可以使用netstat
命令来查看系统的网络连接状态:
// 查看当前所有连接的状态,包括 TIME_WAIT 状态
netstat -an
// 查看指定 IP 地址和端口的连接状态
netstat -an | grep [IP]:[port]
// 查看所有状态为 TIME_WAIT 的连接
netstat -an | grep TIME_WAIT
// 查看所有 TIME_WAIT 状态的连接并统计数量
netstat -an | grep TIME_WAIT | wc -l
在TCP
协议中,客户端通常会使用一个随机的端口号来与服务器建立连接。每一个TIME_WAIT
状态,都会占用一个「本地端口」,上限为65535
。本地端会等待两倍的MSL
(Maximum Segment Lifetime
,最大报文生存时间)的时间,这个时间通常为几分钟,之后才会释放该端口。如果这个时候新连接使用了这个端口,就可能出现数据混乱或者安全问题。
【2】TIME_WAIT
占用系统资源: 整个系统建立连接时占用的资源:
☑️ 建立TCP
连接时,客户端需要指定目标服务器的IP
地址和端口号,这个过程可能需要进行DNS
查询和端口扫描等操作,这些操作可能会消耗一些资源
☑️ 短时间内频繁建立和关闭TCP
连接 ,TIME_WAIT
状态连接会占用系统的端口号和内存等资源,从而影响系统的性能
☑️ 随机端口号范围小了,引发端口号竞争, 临时端口号的竞争可能会导致TCP
连接建立失败或者连接超时
【3】TIME_WAIT
与连接池的关系: 使用连接池可以有效地减少连接的创建和关闭次数,从而减少TIME_WAIT
状态下的连接数量。同时,连接池还可以通过控制连接数量、超时时间等参数,进一步优化连接的使用效率。
但是,如果连接池中存在大量的TIME_WAIT
状态下的连接,那么连接池的效率可能会受到影响,从而导致系统性能下降。因此,需要通过调整连接池的参数,如最大连接数量、连接超时时间等,来避免TIME_WAIT
问题的影响。
【4】TIME_WAIT
与TCP_SYNC
TCP_SYNC
: 攻击者利用伪造的SYN
报文不断向受害者的服务器发送连接请求,但是连接并不能完成三次握手,最终服务器会不断创建半连接,引发系统崩溃。
攻击中,攻击者会大量发送伪造的SYN
报文,但不回应服务器的SYN+ACK
报文,从而使服务器不断等待客户端的ACK
报文,最终导致服务器的连接队列被占满,无法响应正常连接请求。
TIME_WAIT
和TCP_SYNC
本身没有联系,只是半连接在一定时间后会变成TIME_WAIT
状态,就可以观测到TIME_WAIT
数量增多。
TIME-WAIT
状态的危害:
【1】占用系统资源: 如果服务端主动发起关闭连接方的TIME-WAIT
状态过多。比如文件描述符、内存资源、CPU
资源、线程资源等;
【2】端口资源: 如果客户端主动发起关闭连接方的TIME-WAIT
状态过多。端口资源有限,一般可以开启的端口为32768~61000
,也可以通过net.ipv4.ip.local_port_range
参数指定范围。只要连接的是不同的服务器,端口还是可以重复使用的;
TIME-WAIT 优化
【1】net.ipv4.tcp_tw_reuse
和tcp_timestamps
:net.ipv4.tcp_tw_reuse = 1
表示开启重用,允许将TIME-WAIT socket
重新用于新的TCP
连接。默认为0
,表示关闭。
net.ipv4.tcp_tw_reuse = 1
Linux
内核参数开启后,则可以复用处于TIME_WAIT
的socket
为新的连接所用。
.tcp_tw_reuse
功能只能为客户端为发起方,因为开启该功能,在调用connect()
函数时,内核会随机找一个time_wait
状态超过1s
的连接给新的连接复用。
使用该选项的前提需要打开对TCP
时间戳的支持,即:默认为1
net.ipv4.tcp.timestamps=1
这个时间戳的字段是在TCP
头部的选项里,它由一共8
个字节表示时间戳,其中第一个4
字节字段用来保存发送该数据包的时间,第二个4
字节字段用来保存最近一次接收对方发送到达数据的时间。
由于引入了时间戳,前面提到的2MSL
问题就不存在了,因为重复的数据包会因为时间戳过期被自然丢弃。
【2】net.ipv4.tcp_max_tw_buckets
设置上限值,默认值为18000
,当系统中处于TIME_WAIT
的连接一旦超过这个值时,系统就会将后端的TIME_WAIT
连接状态重置,比较暴力。
【3】TCP
协议栈有个keepalive
的属性,可以主动探测socket
是否可用,不过这个属性的默认值很大。
全局设置可更改/etc/sysctl.conf
,加上:
net.ipv4.tcp_keepalive_intvl = 20 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_time = 60
在程序中设置如下:
int keepAlive = 1; // 开启keepalive属性 int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.