那些年在 tcp_tw_recycle,tcp_tw_reuse犯下的错

那些年在 tcp_tw_recycle,tcp_tw_reuse犯下的错

正在上传…重新上传取消Linux云计算网络|Linux云计算网络知识分享2021/01/08 18:49

推广

写在之前

说明:

内核4.10 去掉了tcp_tw_recycle,详见: (https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4396e46187ca5070219b81773c4e65088dac50cc),内核4.12中彻底下掉了tcp_tw_recycle ,但tcp_tw_reuse还保留,如果你内核是4.12或以上,可以退出不看了,本文主要针对4.10之前的版本,单独讲讲这两个参数;

移出原因:针对每个数据包的 时间戳的生成机制发生了变化

( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95 a22caee396cef0bb2ca8fafdd82966a49367bb )

TIME_WAIT 状态是发起主动关闭的一方,在发送最后一个ACK之后会进入TIME_WAIT 的状态,也就说该发送方会保持 2MSL 时间之后才会回到初始状态(释放此连接为自由身)。

MSL 是什么?它是数据包在网络中的最大生存时间,这其实是相当于至少允许报文丢失一次的时间;产生这种状态后,使得此前 TCP 连接在 2MSL 连接等待期间,它先前使用的这个四元组(源IP、源端口、目的IP、目的端口)不能被使用。

为什么需要有 TIME_WAIT 状态?

如果主动关闭方发送最后一个ACK后,如果没有 TIME_WAIT 状态,此时客户端先前的 源IP、源端口 就释放掉了,可以被其它新连接使用,这个ACK如果在复杂的网络环境中,迟迟没有到达,丢了,被动方又发送了FIN,此 先前的源IP、源端口 在主动关闭方,已经被新连接使用,这样就会造成把其它人的连接给异常关闭。

为什么必须要等待2MSL?而不是4MSL?8MSL?

一个MSL就是报文在网络中的最长生存时间,大白话的话,就是如果存在丢包的话,在MSL时间内也会触发重传了,这里2MSL,就相当于两次丢包,一次丢包概率是百分之一,连续两次丢包的概率是万分之一,这个概率实在是太小了,所以2MSL是足够的。

TIME_WAIT状态是TCP的正常状态,然而当机器上面存在大量的TIME_WAIT状态时,TIME_WAIT 状态是主动关闭方存在的状态,在主动连接方(客户端)和被动连接方(服务端)的场景下对服务器有不同的影响,TIME_WAIT 状态即有可能在客户端产生,也有可能在服务器端产生。

如果TIME_WAIT过多,又应该如何解决?本文会从造成 TIME_WAIT 的原因,通过实验验证,并且给出解决方案。

ip_local_port_range 参数

主动连接方(客户端)会占用本地端口,TIME_WAIT状态的四元组会占用本地端口过多,导致本地端口不足,TCP连接不能成功建立,可以通过调整参数来增加本地端口的选择范围,但这样效果有限,因为 TIME_WAIT 需要等待 2MSL 时长,在这个时长内,最多也就能使用 ip_local_port_range  定义的端口,其实这些是远远不够的。

[root@k8s-master-1 ~]# sysctl -a |grep ip_local_port_range

net.ipv4.ip_local_port_range = 32768 60999[root@k8s-master-1 ~]#

其实修改它效果有限,但我们还可以使用tcp_tw_reuse参数来重用TIME_WAIT。

tcp_tw_reuse 参数

内核文档对于此参数描述如下:

tcp_tw_reuse - BOOLEAN

Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0. It should not be changed without advice/request of technical     experts.

注意只有当net.ipv4.tcp_timestamps = 1,net.ipv4.tcp_tw_reuse = 1 两个选项同时开启时,tcp_tw_reuse 才会有效,并且只有当 socket 距离上次收到数据包已经超过1秒时,端口才会被重用,下面来验证 tcp_tw_reuse 的效果。

实验准备

准备两台服务器,一台 nginx 服务器,注意 nginx 不要主动关闭连接,让客户端使用 curl 来访问服务器并主动关闭连接,在客户端产生 TIME_WAIT状态的socket。

服务器:100.73.18.152(nginx,keepalive_timeout 60;)

客户端:100.73.18.153

客户端测试脚本如下:

[root@vm-os-centos7 ~]# cat test.sh

for i in `seq 1 3`do  echo "第 $i 次 curl "  date  curl http://100.73.18.152/ -o /dev/null -s  echo "RETURN: " $?  ss -ant |grep TIMEdonesleep 2# 只有当 socket 距离上次收到数据包已经超过1秒时,端口才会被重用echo "第 4 次 curl "datecurl http://100.73.18.152/ -o /dev/null -secho "RETURN: " $?ss -ant|grep TIME[root@vm-os-centos7 ~]#

第一次实验

客户端 100.73.18.153 配置tcp_tw_reuse 为0 时,端口不够用时,会发生什么情况;

[root@vm-os-centos7 ~]# sysctl -w net.ipv4.ip_local_port_range="32768 32770"

net.ipv4.ip_local_port_range = 32768 32770[root@vm-os-centos7 ~]#[root@vm-os-centos7 ~]# sysctl -w net.ipv4.tcp_tw_reuse=0net.ipv4.tcp_tw_reuse = 0[root@vm-os-centos7 ~]# sysctl -a |grep -E "tcp_tw_reuse|timestamps"net.ipv4.tcp_timestamps = 1net.ipv4.tcp_tw_reuse = 0[root@vm-os-centos7 ~]#

客户端运行脚本结果如下:

[root@vm-os-centos7 ~]# sh test.sh

第 1 次 curl2020年 08月 09日 星期日 13:45:10 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80第 2 次 curl2020年 08月 09日 星期日 13:45:10 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80第 3 次 curl2020年 08月 09日 星期日 13:45:10 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80第 4 次 curl2020年 08月 09日 星期日 13:45:12 CSTRETURN: 7TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80[root@vm-os-centos7 ~]#

从结果可见第4次curl时的状态为7,失败,无法正常curl,说明端口已经被占用完。

第二次实验

客户端 100.73.18.153 配置tcp_tw_reuse 为1 时,端口不够用时,会发生什么情况;

[root@vm-os-centos7 ~]# sysctl -w net.ipv4.ip_local_port_range="32768 32770"

net.ipv4.ip_local_port_range = 32768 32770[root@vm-os-centos7 ~]#[root@vm-os-centos7 ~]# sysctl -w net.ipv4.tcp_tw_reuse=1net.ipv4.tcp_tw_reuse = 1[root@vm-os-centos7 ~]# sysctl -a |grep -E "tcp_tw_reuse|timestamps"net.ipv4.tcp_timestamps = 1net.ipv4.tcp_tw_reuse = 1[root@vm-os-centos7 ~]#

客户端运行脚本结果如下:

[root@vm-os-centos7 ~]# sh test.sh

第 1 次 curl2020年 08月 09日 星期日 13:57:00 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80第 2 次 curl2020年 08月 09日 星期日 13:57:00 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80第 3 次 curl2020年 08月 09日 星期日 13:57:00 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80第 4 次 curl2020年 08月 09日 星期日 13:57:02 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80[root@vm-os-centos7 ~]#

这里发现第4次已经return 为0了,代表端口已经被重用,注意测试脚本中的sleep 2,如果是sleep 1的话,会出现,因为只有当 socket 距离上次收到数据包已经超过1秒时,端口才会被重用。

开启tcp_tw_reuse选项可以使主动连接的一端在主动关闭的场景下每秒能建立6万多个连接。如果每秒要建立更多的连接呢?可以使用tcp_tw_recycle选项。

tcp_tw_recycle 选项

tcp_tw_recycle - BOOLEAN

Enable fast recycling TIME-WAIT sockets. Default value is 0.     It should not be changed without advice/request of technical     experts.

tcp_tw_reuse 只是在端口不足时,重用 TIME_WAIT 状态的socket,而 tcp_tw_recycle 处理更激进,它会快速回收 TIME_WAIT 状态的 socket 。注意 只有当 tcp_timestamps 和 tcp_tw_recycle 都开启时,才会快速回收,可以继续使用上面的实验验证,关闭tcp_tw_reuse,开启 recycle,把上面的脚本时间 修改为 sleep 1;因为recycle会在几百毫秒内回收掉TIME_WAIT状态,基于 tcp_tw_reuse 上面的实验,继续测试如下:

[root@vm-os-centos7 ~]# sysctl -a |grep -E "timest|reuse|recycle|ip_local_port_range"

net.ipv4.ip_local_port_range = 32768 32770net.ipv4.tcp_timestamps = 1net.ipv4.tcp_tw_recycle = 1net.ipv4.tcp_tw_reuse = 0[root@vm-os-centos7 ~]# cat test.shfor i in `seq 1 3`do  echo "第 $i 次 curl "  date  curl http://100.73.18.152/ -o /dev/null -s  echo "RETURN: " $?  ss -ant |grep TIMEdonesleep 1# 注意这里修改成1秒,让tcp_tw_recycle 在几百毫秒内快速回收掉。echo "第 4 次 curl "datecurl http://100.73.18.152/ -o /dev/null -secho "RETURN: " $?ss -ant|grep TIME[root@vm-os-centos7 ~]# sh test.sh第 1 次 curl2020年 08月 09日 星期日 14:11:41 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80第 2 次 curl2020年 08月 09日 星期日 14:11:41 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80第 3 次 curl2020年 08月 09日 星期日 14:11:41 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32768 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80TIME-WAIT 0 0 100.73.18.153:32770 100.73.18.152:80第 4 次 curl2020年 08月 09日 星期日 14:11:42 CSTRETURN: 0TIME-WAIT 0 0 100.73.18.153:32769 100.73.18.152:80[root@vm-os-centos7 ~]#

这里的第4次,发现了没有,已经没有了TIME_WAIT状态,在1秒内(几百毫秒内已经回收了)。

是否可以跳过TIME_WAIT状态吗?为什么

是可以的,我们知道当 TIME_WAIT 状态的 socket 数量超过 tcp_max_tw_buckets 选项指定的数量值时,会直接关闭 socket,进入 CLOSED 状态,会出现TCP: time wait bucket table overflow 的错误。若把 tcp_max_tw_buckets 选项设置为 0,则可以直接跳过 TIME_WAIT 状态。

tcp_tw_recycle 选项在 NAT 环境下使用有一些隐患,下面实验分析说明。

NAT 环境下开启 tcp_tw_recycle 选项问题

客户端主动访问,但服务端主动关闭链接,在服务端产生TIME_WAIT,又被tcp_tw_recycle快速回收时,在NAT环境下使用有一些问题,这是因为当开启 tcp_timestamps 和 tcp_tw_recycle 选项时,60秒内来自同一源 IP 主机的 TCP 分段的时间戳必须递增,否则该分段会被直接丢弃。

(图:NAT 环境下使用 tcp_tw_recycle 环境构造 )

实验准备

1. 在 100.66.208.11 服务器上面开启路由转发 net.ipv4.ip_forward 功能;

2. 在充当 NAT服务器100.66.208.11 服务器上面添加 NAT 规则;

说明:

(1)数据包进入100.66.208.11服务器后,在路由前阶段,判断到本机100.66.208.11:9080端口的数据包,转发到100.66.208.12:8080;

(2)数据包出去访问时,修改源IP为本地地址100.66.208.11,作SNAT转换,在 100.66.208.12 上面抓包时,看到的源IP均为100.66.208.11。

3.  构造 100.66.208.10 与 100.66.212.25 时间戳不一样,时间戳与服务器启动时间相关,可以把其中一台服务器重启造成两台服务器时间戳不一致;

4. 所有 服务器开启 tcp_timestamps = 1;

5. 服务器 100.66.208.12 开启tcp_tw_recycle = 1;

6. 最重要的是100.66.208.12 上面要模拟出来TIME_WAIT状态(与100.66.208.11服务器之间的),可以在 100.66.208.12 部署nginx,开启8080端口,然后nginx 开启主动关闭即keepalive_timeout  0; 参数。

[root@calico-node-2 conf]# /data/nginx/sbin/nginx -V

nginx version: nginx/0.8.55configure arguments: --prefix=/data/nginx[root@calico-node-2 conf]#

将服务器端的NGINX配置keepalive_timeout设为0,令服务器端主动关闭连接,重新加载NGINX配置生效 。

7. 在100.66.208.10与100.66.212.25上面 curl http://100.66.208.11:9080/;

实验过程

1. 在100.66.208.12上面使用tcpdump进行抓包;

[root@calico-node-2 ~]# tcpdump -i ens192 -nn port 8080 -w recycle.pcap

2. 在时间戳小的服务器上面100.66.212.25上面先curl,很快就返回了。

[root@calico-master-1 ~]# curl http://100.66.208.11:9080

<html><head><title>Welcome to nginx!</title></head><body bgcolor="white" text="black"><center><h1>Welcome to nginx!</h1></center></body></html>[root@calico-master-1 ~]#

3. 在时间戳大的服务器上面100.66.208.10上面再curl,很快也返回了。

[root@calico-node-4 ~]# curl http://100.66.208.11:9080

<html><head><title>Welcome to nginx!</title></head><body bgcolor="white" text="black"><center><h1>Welcome to nginx!</h1></center></body></html>[root@calico-node-4 ~]#

4. 再去时间戳小的服务器上面 100.66.212.25 上面先curl,发现阻塞了,一直没有返回;

[root@calico-master-1 ~]# curl http://100.66.208.11:9080

    # 阻塞中,一直未返回

5. 分析数据包

(1)1-10号包是第一次时间戳小的服务器100.66.212.25上面curl的结果,很快就返回了,时时间戳如图;

(2)11-20号包是第二次时间戳大的服务器100.66.208.10上面curl时,数据包的情况;

(3)再去时间戳小的服务器上面curl时,发现无法curl通,一直在重传SYN包,直到1分钟后才成功响应; 这是因为 当开启 tcp_timestamps 和 tcp_tw_recycle 选项时,60秒内来自同一源 IP 主机的 TCP 分段的时间戳必须递增,否则该分段会被直接丢弃。 而这里第三次的时间戳,小于第二次的,在服务器端看来,是不合法的,所以丢弃了。

再啰嗦总结下,当客户端通过NAT环境出访服务器端时,服务器端主动关闭后会产生了 TIME_WAIT 状态,如果服务器端同时开启了 tcp_timestamps 和 tcp_tw_recycle 选项时 ,那么在 60秒内来自同一源 IP 主机的 TCP 分段的时间戳必须递增 ,否则会丢弃。

总结

之前内核中 tcp_tw_recycle 仅仅不适用于nat和lvs环境,那么从 4.10 内核开始,官方修改了时间戳的生成机制,在这种情况下,无论任何时候,tcp_tw_recycle 都不应该开启,故被抛弃也是理所应当的了。tcp_tw_reuse 选项仍然可用。在服务器上面,启用该选项对于连入的TCP连接来说不起作用,但是对于客户端,比如服务器上面某个服务以客户端形式运行,再比如nginx反向代理等是一个可以考虑的方案。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值