环境:centos7.4 内核版本3.10

最近看内核参数tcp_tw_recycle(该参数在内核 4.12 之后被移除),它用于快速回收处理TIME_WAIT状态的socket。搜索该参数相关的资料,发现同时启用该参数和tcp_timestamps后有可能在NAT环境下导致客户端始连接失败,抓包表现为:客户端一直发送SYN报文,但服务端不响应。但这些文章中只给出了如何解决问题,并没有给出如何复现问题。特别怪异的是,服务端是被动关闭的,并不会进入TIME_WAIT状态,到底怎么产生的呢?

先使用如下拓扑复现该场景,其中10.85.3.51机器为NAT服务器,10.85.1.2和10.85.3.52通过NAT服务器访问server 10.85.3.111:19090

+-------------+
| 10.85.1.2 +------------+
+-------------+ |
+-----+-------+ +---------------------+
| 10.85.3.51 +---------+ 10.85.3.111: +
+-----+-------+ +---------------------+
+-------------+ |
| 10.85.3.52 +------------+
+-------------+

在10.85.3.51机器上配置如下iptables表项,用于转发client和server之间的TCP报文。(10.85.3.51需要开启net.ipv4.ip_forward功能)

# iptables -t nat -I PREROUTING -d 10.85.3.51 -p tcp -m tcp --dport  -j DNAT --to 10.85.3.111:19090
# iptables -t nat -I POSTROUTING -d 10.85.3.111 -p tcp -m tcp --dport -j SNAT --to 10.85.3.51
  • 首先开启tcp_timestamps,关闭tcp_tw_recycle

在10.85.3.111上进行抓包并且启动10.85.1.2和10.85.3.52进行连接。报文如下,其中第4和第7条为两个连接的TCP SYN报文,后续server都进行了回复,两条连接正常建链

 # tcpdump -i eth0 src port  or dst port
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size bytes
::27.970358 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 3075335984 ecr ,nop,wscale ], length
::27.970417 IP 10.85.3.111. > 10.85.3.51.: Flags [S.], seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::27.970783 IP 10.85.3.51. > 10.85.3.111.: Flags [.], ack , win , options [nop,nop,TS val ecr ], length

::29.059890 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 1740811766 ecr ,nop,wscale ], length
::29.059949 IP 10.85.3.111. > 10.85.3.51.: Flags [S.], seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::29.060623 IP 10.85.3.51. > 10.85.3.111.: Flags [.], ack , win , options [nop,nop,TS val ecr ], length

启用tcp_tw_recycle,重复上面操作。发现即使后面一个连接的SYN报文的时间戳小于前面一个连接的SYN报文中的时间戳,也能够正常建链,并没有出现连接异常。

 # tcpdump -i eth0 src port  or dst port
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size bytes
::12.111152 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 3075920126 ecr ,nop,wscale ], length
::12.111221 IP 10.85.3.111. > 10.85.3.51.: Flags [S.], seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::12.111766 IP 10.85.3.51. > 10.85.3.111.: Flags [.], ack , win , options [nop,nop,TS val ecr ], length

::12.871092 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 1741395578 ecr ,nop,wscale ], length
::12.871149 IP 10.85.3.111. > 10.85.3.51.: Flags [S.], seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::12.871697 IP 10.85.3.51. > 10.85.3.111.: Flags [.], ack , win , options [nop,nop,TS val ecr ], length
  • 后来在这篇文章中找到灵感。正常TCP TIME_WATI时长为2MSL,用于挥手阶段最后一个ACK报文的重传,以及防止当前连接上滞留的报文影响到下一个连接。当启用tcp_tw_recycle后,系统会在一个RTO的极短时间内回收处于TIME_WAIT状态的socket,但仍然无法杜绝接收到上一个连接在链路上滞留的报文。为了防止这种情况的发生,在启用tcp_tw_recycle的情况下,由于已经释放了socket,系统无法使用socket来标记一条连接,只能退而求其次,通过判断对端IP发过来的报文的时间戳来判断该报文是新产生的还是老的报文,如果是老报文,则丢弃且不回复。

因此复现场景为:服务端主动断开与客户端的一条连接,在后续的TCP_PAWS_MSL(60s)时间内,如果客户端发过来的SYN报文的TSVal时间戳小于系统保留的上一个连接的时间戳,则该SYN报文会被丢弃,实际表现为客户端连接超时或很慢(60s之后可正常连接)

  1. 首先server使用命令 telnet 10.85.3.51 22 连接NAT机器,并立即断开连接,此时server会很快回收一个TIME_WAIT的socket。在server端抓包,可以看到保存的该连接上对端发来的最后一个时间戳为
 # tcpdump -i eth0 src host 10.85.3.51 or dst host 10.85.3.51
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size bytes
::10.015335 IP 10.85.3.111. > 10.85.3.51.ssh: Flags [S], seq , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::10.016055 IP 10.85.3.51.ssh > 10.85.3.111.: Flags [S.], seq , ack , win , options [mss ,sackOK,TS val ecr ,nop,wscale ], length
::10.016074 IP 10.85.3.111. > 10.85.3.51.ssh: Flags [.], ack , win , options [nop,nop,TS val ecr ], length
::10.023482 IP 10.85.3.51.ssh > 10.85.3.111.: Flags [P.], seq :, ack , win , options [nop,nop,TS val ecr ], length
::10.023507 IP 10.85.3.111. > 10.85.3.51.ssh: Flags [.], ack , win , options [nop,nop,TS val ecr ], length ::15.648562 IP 10.85.3.111. > 10.85.3.51.ssh: Flags [F.], seq , ack , win , options [nop,nop,TS val ecr ], length
::15.649128 IP 10.85.3.51.ssh > 10.85.3.111.: Flags [.], ack , win , options [nop,nop,TS val ecr ], length
::15.651394 IP 10.85.3.51.ssh > 10.85.3.111.: Flags [F.], seq , ack , win , options [nop,nop,TS val ecr ], length
::15.651411 IP 10.85.3.111. > 10.85.3.51.ssh: Flags [.], ack , win , options [nop,nop,TS val ecr ], length

在断开连接的TCP_PAWS_MSL时间内启动10.85.1.2通过NAT连接到server,server端抓包可以看到该连接的SYN报文的时间戳远小于保存的时间戳,此时server端丢弃接收到的所有SYN报文,客户端连接超时。

 ::33.942378 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 1759176699 ecr ,nop,wscale ], length
::34.942300 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 1759177700 ecr ,nop,wscale ], length
::36.946320 IP 10.85.3.51. > 10.85.3.111.: Flags [S], seq , win , options [mss ,sackOK,TS val 1759179704 ecr ,nop,wscale ], length
  • 结合上述测试可以得出结论:同时启动tcp_timestamps和tcp_tw_recycle可能会导致客户端连接不上前提条件是server主动断开过与客户端的连接(可能是服务重启等原因),导致server处于TIME_WAIT状态的socket被快速回收,如果在TCP_PAWS_MSL时间内接收到客户端经NAT发过来的报文的时间戳小于前一个连接保存的时间戳,该报文会被认为是老链路残留的报文而丢弃。进而可以得出:
    1. 在NAT场景下一定不能启用tcp_tw_recycle;
    2. NAT场景下单独启动tcp_timestamps不会影响正常使用,连接断链后会在2MSL过后回收socket;
    3. 生产中不要使用tcp_tw_recycle,即使没有使用到NAT设备,但当前虚拟化环境下用到NAT的地方很多,如kubernetes的service等

TIPS

  • 为了复现如上问题,曾尝试过使用1.17.0版本的nginx作为NAT服务。但发现经过nginx的所有连接的SYN报文的时间戳都会被nginx修改,且后面连接SYN报文的时间戳一定大于前面连接的SYN报文中的时间戳,因此nginx下面不会出现客户端方式失败的场景

参考

linux开启tcp_timestamps和tcp_tw_recycle引发的问题研究的更多相关文章

  1. 开启tcp_timestamps和tcp_tw_recycle造成NAT转发连接不上

    文章转载自:https://segmentfault.com/a/1190000022264813

  2. tcp_timestamps和tcp_tw_recycle

    不同时开启tcp_timestamps和tcp_tw_recycle的场景描述 FULL NAT下 FULL NAT  在client请求VIP 时,不仅替换了package 的dst ip,还替换了 ...

  3. LINUX开启允许对外访问的网络端口

    LINUX开启允许对外访问的网络端口  LINUX通过下面的命令可以开启允许对外访问的网络端口: /sbin/iptables -I INPUT -p tcp --dport 8000 -j ACCE ...

  4. Linux开启路由的方法

    Linux开启路由的命令很简单,只需要一条命令即可: [root@localhost ~]# echo 1 > /proc/sys/net/ipv4/ip_forward 这个只是临时修改,如果 ...

  5. Linux开启MySQL远程连接

    Linux开启MySQL远程连接的设置步骤 . MySQL默认root用户只能本地访问,不能远程连接管理MySQL数据库,那么Linux下如何开启MySQL远程连接?设置步骤如下: 1.GRANT命令 ...

  6. kali linux 开启ssh服务

    kali linux 一般默认不开启ssh服务,可使用命令查看ssh服务是否开启 命令:service ssh status 如果显示ssh服务没有开启需要修改ssh配置文件将ssh服务开启,kali ...

  7. Linux下基于LDAP统一用户认证的研究

    Linux下基于LDAP统一用户认证的研究                   本文出自 "李晨光原创技术博客" 博客,谢绝转载!

  8. linux开启FTP以及添加用户配置权限,只允许访问自身目录,不能跳转根目录

    1.切换到root用户 2.查看是否安装vsftp,我这个是已经安装的. [root@localhost vsftpd]# rpm -qa |grep vsftpd vsftpd--.el7_2.x8 ...

  9. SuSE Linux 开启VNC服务

    一.启动VNC服务输入命令 vncserver  二.编辑启动脚步vi /root/.vnc/xstartup 把twm &注释改为#twm & 然后再最下面增加2行startgnom ...

随机推荐

  1. v-model绑定一个对象,组件内部分别负责不同字段的场景实现

    我们知道v-model对于单个property双向数据绑定非常有用,保持父子组件之间的数据传递和同步,但也有很多场景下希望一个组件能够处理多个数据字段,这时就有一些小小技巧了. https://sim ...

  2. git和小乌龟在windows下安装

    一:所需软件 (1):git 下载地址:https://git-scm.com/download (2):TortoiseGit 下载地址:https://tortoisegit.org/downlo ...

  3. tf.reduce_sum() and tf.where()的用法

    import tensorflow as tfimport numpy as npsess=tf.Session()a=np.ones((5,6))c=tf.cast(tf.reduce_sum(a, ...

  4. 使用jq操作脚本生成元素的事件

    其实这个很简单,是jq里面的一个delegate操作,具体如下: $("div").delegate("button","click",fu ...

  5. vuejs兄弟通信$emit和$on

    1   vm.$on( event, callback ) 监听当前实例上的自定义事件.事件可以由vm.$emit触发.回调函数会接收所有传入事件触发函数的额外参数. 2 vm.$emit( even ...

  6. vue定时器+弹框 跳到登陆页面

    1.做一个请求拦截,并弹框提示几秒后,跳转到登陆首页或是点击确定之后直接跳转拦截用了this.$axios.interceptors.response页面上的弹框组件用了vux的组件vux地址:htt ...

  7. Nginx内置变量以及日志格式变量

    $args #请求中的参数值$query_string #同 $args$arg_NAME #GET请求中NAME的值$is_args #如果请求中有参数,值为"?",否则为空字符 ...

  8. 免费音乐播放器-airplay(网上收集,仅供学习与研究,支持正版)

    AirPlay媒体播放器无需安装,并不会修改注册表,生成桌面快捷方式,加入启动菜单及快速启动. 因此,您只需要下载本软件,保存在任何地方即可使用.使用方便,界面酷炫. 自动下载和显示歌词.专辑封面.汇 ...

  9. day34_8_16 MySQL数据库一

    一.数据库概念 在没有数据库时,编写项目都是使用db模拟数据库.将文件以列表或字典等存入文件夹. 但是,不同的程序员存放数据的方式都是各不相同的,而且摆放的位置都是不一样的. 如何同一数据格式,和如何 ...

  10. 学习:逆向PUSH越界/INT 68/反调试导致的程序

    自己根据shark恒老师的分析,总结一下: 一般反调试自动关闭程序利用的函数有: 1.CreateToolhelp32Snapshot 2.FindWindow 3.ExitProcess 4.Pos ...