什么是端口复用?
端口复用是一个或多个客户端,使用相同的IP和端口,访问同一台服务器的相同IP端口,这两条TCP会话的五元组(源IP、源端口、目的IP、目的端口、协议)完全一致,发生冲突,是一种TCP通信中的异常现象。
为什么会出现这样的情况?由于SNAT技术的存在,如果SNAT的策略设置为仅转换源IP的话,这可能导致两条会话的五元组发生冲突,例如:
SNAT前,来自公网的两个客户端A(IP地址1.1.1.1)和客户端B(IP地址2.2.2.2),均使用源端口1024访问服务器9.9.9.9的80端口。此时,两个客户端会话的五元组是这样的:
客户端 | 会话状态 | 源IP | 源端口 | 目标IP | 目标端口 | 协议 |
A | SNAT前 | 1.1.1.1 | 1024 | 9.9.9.9 | 80 | TCP |
B | SNAT前 | 2.2.2.2 | 1024 | 9.9.9.9 | 80 | TCP |
客户端A和B的五元组,可以通过源IP进行区分。
假设SNAT的地址池中仅有一个IP地址9.9.9.1,这样经过仅转换源IP的SNAT后,两条会话的五元组是这样的:
客户端 | 会话状态 | 源IP | 源端口 | 目标IP | 目标端口 | 协议 |
A | SNAT后 | 9.9.9.1 | 1024 | 9.9.9.9 | 80 | TCP |
B | SNAT后 | 9.9.9.1 | 1024 | 9.9.9.9 | 80 | TCP |
经过SNAT后,两个客户端的五元组将完全一致,无法区分,导致冲突。
同端口新会话与旧会话,需要间隔多少秒?
当第一条会话未结束时,第二条相同五元组的会话“闯入”服务器,才能造成冲突,因此端口复用冲突的前提为时间吻合。
那么两条会话间隔多长时间才是“安全”的?
在第一条会话结束时,主动发送FIN包关闭会话的一方会进入到TCP TIME_WAIT状态,FIN包发送可能由客户端或服务器主动发送。如果服务器主动发送FIN包,那么服务器将会持续在TIME_WAIT状态等待连接结束,这个等待的时长为2MSL。
2MSL是两倍的最大报文段生存时间(Maximum Segment Lifetime,MSL)。MSL是指一个TCP报文在网络中能够存在的最长时间,Windows和Linux系列系统中默认的MSL为2分钟。因此,TIME_WAIT状态的持续时间通常是2倍的MSL,即4分钟。
综上所述,如果服务器主动发FIN包结束一条会话,那么在未来的4分钟内,服务器一直等待会话的结束。等待期间,该会话的五元组是无法进行端口复用的,同端口的新会话需要在4分钟后才能成功建立连接,在4分钟内,新进入的会话连接将无法建立。在高并发的生产环境中,4分钟等待时间显得十分漫长。
端口复用冲突流量长啥样?
下图展示了两条相邻会话的端口复用冲突情况:
图1:冲突的两条会话信息
两条会话的时间,第一条会话时间在16:19:35(会话最后一个ACK包出现时间),第二条会话开始时间为16:20:06(会话连接建立阶段SYN包出现时间),两条会话使用相同五元组,时间相差31秒,时间差小于240秒。
先看看16:19:35这条会话的流量(由于数据包捕获设置原因,该截图中的重复流量较多,重复流量均被标记为重传和重复ACK,忽略即可,不影响分析):
图2:冲突发生前的正常TCP会话
客户端地址*.*.*.31使用57441端口访问服务器*.*.*.3的80端口。时序图显示,服务器主动发送FIN/ACK包,客户端回复ACK包并发送FIN/ACK包,最后服务器回复ACK包,服务器发送最后一个数据包后,进入了长达240秒的TIME_WAIT状态,等待会话结束。
再来看16:20:06这条会话的流量(该截图中的重复流量较多,体现为重传和重复ACK,忽略即可,不影响分析):
图3:冲突发生后的异常TCP会话
另一公网客户端使用相同的SNAT地址*.*.*.31和57441端口访问服务器*.*.*.3的80端口。这条会话的五元组和上一条明显冲突,在新会话客户端发送SYN包后,服务器没有回复SYN/ACK包,而是回复了一个ACK,客户端认为连接异常,无法正常建立。
通过上面的几张图,相信读者应该已经能能够理解端口复用故障的理论原因和实际流量现象了。
端口复用失败导致的流量有什么特征?
在遇到故障时,如何通过流量特征判断端口复用故障?笔者认为有如下特点:
特征一:复用失败会话数据包ACK号无法与SYN包Seq号对应
如果看到三次握手连接建立失败的会话,是客户端发送SYN包,服务器回复ACK包不携带SYN位的异常情况,则可以仔细观察两包的Seq和Ack号是否能对应,判断故障是否由于端口复用失败导致。如图3中的SYN包Seq号和ACK包Ack号。
特征二:会话开始前,存在同端口会话
使用科来回溯或科来全流量设备,回溯故障会话历史240秒内的流量,观察是否存在相同五元组的其它会话
特征三:复用失败会话与原始会话的ack号一致
对比两条会话的ack确认号,能够发现失败会话与原始会话的ack号实际是一致的。这是由于服务器认为上一会话仍未结束,因此对新会话的回复仍采用旧会话的ack号。如图4所示:
图4:复用失败会话的ACK号
如发现故障流量能匹配上述三条特征,则能够确认故障是由于端口复用失败导致。
如何避免或解决端口复用问题?
注意以下问题,即可避免端口复用问题出现:
1. 扩大SNAT地址池并转换源端口:
尤其是在高并发场景下,通过增加SNAT(源网络地址转换)地址池的大小,同时启用SNAT的端口转换,可以减少地址耗尽的情况,从而降低因地址不足而导致的端口复用问题。
2. 避免服务器主动结束连接:
通常只有主动结束连接的一方才会进入TIME_WAIT状态。因此,如果可能,让客户端负责关闭连接,这样服务器端就可以避免进入TIME_WAIT状态,减少端口被占用的情况。
3. 启用服务器的快速端口重用参数:
在服务器上配置快速端口重用参数,如Linux系统中的tcp_tw_recycle和tcp_tw_reuse选项,可以允许应用程序快速跳过2MSL时间,并根据TCP时间戳识别复用端口的新TCP连接。在客户端时间戳不准确的情况下,这种方式可能引发新的问题,将在后文中详细讨论
端口复用失败的典型的故障实例
本篇文章中推荐的典型故障实例,与端口复用有关。该VPN业务由于SNAT地址池原因,导致发生端口复用异常,导致部分客户端无法正常建立连接。
来自科来公众号