环境: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. ArcGIS随机数生成

    arcgis python 随机数 语法用法一例: //--------------------------------------------- //定义函数getnums  返回一个随机数(0,5 ...

  2. .NET 跨域问题解决

    后端处理:var callback=context.Request.QueryString["callback"].ToString(); context.Response.Wri ...

  3. vuePress自动部署到Github Page脚本踩坑

    背景 照着官网的教程来就行了,踩了个小坑,记录一下,希望对你有帮助 这是部署后的效果 小坑1 如图所示,官网推荐部署命令 然而windows 没有bash 指令, 直接运行报错 两个解决方法: 项目根 ...

  4. matlab 基础语法

    计算次幂 Trial>> 3 ^ 2 % 3 raised to the power of 2 ans = 9 MATLAB 计算正弦值 Trial>> sin(pi /2) ...

  5. DDL创建数据库,表以及约束(极客时间学习笔记)

    DDL DDL是DBMS的核心组件,是SQL的重要组成部分. DDL的正确性和稳定性是整个SQL发型的重要基础. DDL的基础语法及设计工具 DDL的英文是Data Definition Langua ...

  6. Spark(4)

    Spark Core官网学习回顾 Speed disk 10x memory 100x Easy code interactive shell Unified Stack Batch Streamin ...

  7. day02 整理

    目录 编程语言的分类 机器语言 汇编语言 高级语言 编译型语言(谷歌翻译) 解释型语言(同声传译) 执行python程序的两种方式 Jupyter的使用 jupyter的介绍 安装 基本使用 Jupy ...

  8. linux 广播和组播

    广播和组播 广播,必须使用UDP协议,是只能在局域网内使用,指定接收端的IP为*.*.*.255后,发送的信息,局域网内的所有接受端就能够接到信息了. 广播的发送端代码 #include <st ...

  9. Java八大排序之希尔(Shell)排序

    希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法.该 ...

  10. adb命令之解锁打卡

    adb devicesadb shell input keyevent 26                       按手机电源键adb shell input swipe 400 1080 40 ...