本文主要是介绍Linux tcp timewait相处之道,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
关于Time wait 的特殊细节
熟悉tcp网络编程的同学对于timewait 状态可以说是既熟悉又陌生。在繁忙的server端,该状态经常会使得server无法bind,或者耗尽可用的port资源。此时此刻,心里往往不知所以,万般无奈。本文结合实验,spec和代码原理,解释了timewait 的几种处理方案。
参考文献
1) unix networking programming
2) Coping with the TCP TIME-WAIT state on busy Linux servers https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux
3) stackoverflow: SO_REUSEADDR vs SO_REUSEPORT
下文中使用的sock程序是从unix networking programming来的,下载地址 https://github.com/unpbook/unpv13e
SO_REUSEADDR和Timewait的关系
- 即使enable了SO_REUSEADDR,timewait 状态的(src ip, src port, dst ip, dst port) 也不能被connect复用。
证明步骤来自Richard steven Unix network programming 的例子:
-
在server上执行 ./sock -s 0.0.0.0 80
-
在client 上执行 ./sock 10.0.0.17 80
-
stop client
观察socket pair 处于tw 状态
tcp 0 0 10.0.0.4:46210 10.0.0.17:80 TIME_WAIT -
- 复用timewait 状态的source port 去连接server,结果失败。
./sock -b 46210 10.0.0.17 80bind() error: Address already in use
- 使用SO_REUSEADDR, 复用tw 状态的source port 去连接server,结果同样失败。
./sock -A -b 47480 10.0.0.17 80
bind() error: Address already in use
- Enable 这个选项,使得timewait状态的(src ip, src port)可以bind,否则bind会失败。另外client 能够connect 该地址,从而可能导致timewait 五元祖被复用。这似乎是一个bug。
实验步骤:
- 在server上监听80端口, ./sock -s 10.0.0.17 80
- 在client上连接server的80端口 ./sock -b 1234 10.0.0.17 80
- server 主动断开连接,使得socket 进入timewait状态
- server 再次监听, ./sock -s 10.0.0.17 80 结果会失败
./sock -s 0.0.0.0 80
can't bind local address: Address already in use
- server 设置 SO_REUSEADDR, 再次监听,结果成功
./sock -A -s 10.0.0.17 80
有趣的是,上面这个例子中,如果监听的地址是0.0.0.0, 则必须在第一步使用SO_REUSEADDR, 否则会导致第五步./sock -A -s 0.0.0.0 80失败了。
- client上执行./sock -b 1234 10.0.0.17 80 成功。timewait的五元组被复用了。 这似乎是一个bug。
tcp 0 0 10.0.0.17:8080 10.0.0.17:1234 TIME_WAIT -
该timewait的五元组被新的连接复用了。
关于SO_REUSEADDR 其他的行为
在BSD系统中,该option的本意是如果有bind 0.0.0.0:port1的socket, 则其他socket bind 一个具体的local ip:port1可以被允许。但是在linux中,这种行为不被允许。
也就是说wilcard address 和具体的local ip之间有互斥关系。
故这个option在不同的os之间是不兼容的.
实验一:
-
./sock -A -s 0.0.0.0 8080
-
./sock -A -s 10.0.0.17 8080
结果
can’t bind local address: Address already in use
实验二:
将实验一的步骤反过来
- ./sock -A -s 10.0.0.17 8080
- ./sock -A -s 0.0.0.0 8080
can’t bind local address: Address already in use
net.ipv4.tcp_tw_reuse
从上文中,我们知道通过设置socket option SO_REUSEADDR, 可以bind 并监听time wait状态的address,这对快速重启server 有很大的帮助,不过对于connect() 复用该地址没有什么作用。这是因为tcp_connect()实现中,对于分配到的port的检查和bind() 不完全一样。为了让connect过程可以快速复用tw port,可以设置内核参数net.ipv4.tcp_tw_reuse.
By enabling net.ipv4.tcp_tw_reuse, Linux will reuse an existing connection in the TIME-WAIT state for a new outgoing connection if the new timestamp is strictly bigger than the most recent timestamp recorded for the previous connection: an outgoing connection in the TIME-WAIT state can be reused after just one second.
更详细的解释请参考
https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux
实验步骤:
- client上
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_timestamps=1
- server上启动server
./sock -s 0.0.0.0 8080
- client上模拟connect耗尽临时端口,使得这些端口都变成timewati状态的。
再次发起connect,它会复用timewait的port
为了使得测试容易,缩小临时端口的数量。
sysctl -w net.ipv4.ip_local_port_range=“32768 32769”
在client上
a) ./sock 192.168.60.131 8080
b) 退出sock, 32768 port进入tw。
tcp 0 0 192.168.60.133:32768 192.168.60.131:8080 TIME_WAIT -
c) 再执行 ./sock 192.168.60.131 8080 发现32768 复用了。
tcp 0 0 192.168.60.133:32768 192.168.60.131:8080 ESTABLISHED 3618/./sock
通过bpftrace在内核关键函数加探针,证现tcp_tw_reuse的判断机制发挥了作用。
bpftrace -e 'kretprobe:tcp_twsk_unique{ printf("return %d",retval); }'
Attaching 1 probe...
^Creturn 1
TCP_TW_RECYCLE
该内核参数在4.12以后废弃了。原因是它不仅仅影响了出的报文,还影响了进的报文。如果进的报文是从nat 设备来的,由于不同的host的timestamp 不一致,会导致丢包。
这篇关于Linux tcp timewait相处之道的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!