Linux服务器收到SYN请求包没有回应ACK导致客户端无法建立TCP连接(文中的文章是转载的)

问题描述:

       最近测试同事反馈一个很奇怪的问题,电子白板客户端加入白板会议后,经常出现下载同步文件失败的问题。下载同步文件使用的是https请求,经抓包得知,在试图建立TCP连接时,客户端发送三次握手申请的SYN包,但是服务器没有回应ACK,然后客户端触发了TCP的丢包重转,重发SYN包,但服务器始终没回ACK,导致无法完成建立TCP连接,所以无法下载同步文件。SYN包从本端网卡发出去了,但是有没有到达服务器端呢?有没有可能在网络中SYN包被拦截了,或者网络出现异常没法到达对端了?

       为了确认服务器是否收到了客户端发出的SYN请求包,于是找运维人员要来了服务器后台的的SSH端口及登录帐号,在服务器上开启抓包,然后在客户端复现无法建立连接的问题。这其中遇到个小插曲,直接使用tcpdump启动抓包,没有设置抓哪个网卡的包,导致默认抓eth0网卡的包,实际上服务器是多网卡的,一个网卡设置的是内网的地址(eth0),一个网卡设置外网的地址(eth1),而我们的测试环境中网络线路使用的是服务器外网IP,即走的是eth1网卡,但是抓包抓的是eth0,张冠李戴了,白分析了一场。后来抓到正确的eth1网卡的包后,发现诡异的问题,服务器收到了客户端的SYN包,也收到了客户端重传的SYN包,但就是没给出回应。因为TCPIP协议栈一点不懂,只能请来公司的曾大神分析一下到底是怎么回事。后来找到原因了,以前曾大神有一次也遇到过类似的问题,大概的原因和转载的文章差不多了!

       具体原因是Linux服务器的脚本中将TCPIP协议栈的reuse和recycle选项打开了,将这两个选项关闭掉就可以了。

-----------------------------------------------------------------------------------------------------------

以下是转载的内容

-----------------------------------------------------------------------------------------------------------

linux系统收到SYN但不回SYN+ACK问题排查

原文地址:http://blog.51cto.com/leejia/1954628

一、背景:

今天下午发现线上的一台机器从办公网登录不上且所有tcp端口都telnet不通,但是通过同机房的其它机器却可以正常访问到出问题的机器。于是就立即在这台出问题的server端抓包分析,发现问题如下:
server端收到了本地pc发的SYN包,但是没有回syn+ack包,所以确认是server端系统问题。tcpdump抓包如下:

扫描二维码关注公众号,回复: 14566659 查看本文章

wKioL1mKiFzyAHTYAAZzy5AQiS4317.jpg

二、排查

1,发现系统没有任何负载

2,网卡也没有丢包

3,iptables策略也都没问题

4,系统的SYN_RECV连接很少,也没超限

5,系统的文件描述符等资源也都没问题

6,messages和dmesg中没有任何提示或者错误信息

7,通过netstat命令查看系统上协议统计信息,发现很多请求由于时间戳的问题被rejected

# netstat -s |grep reject
    2181 passive connections rejected because of time stamp
    34 packets rejects in established connections because of timestamp

三、通过google来协助

发现有同样的人遇见这个问题:

是通过调整sysctl -w net.ipv4.tcp_timestamps=0或者sysctl -w net.ipv4.tcp_tw_recycle=0来解决这个问题,于是我就顺藤摸瓜继续查。

而在查询这两个参数的过程中,发现问题原因如下:

发现是 Linux tcp_tw_recycle/tcp_timestamps设置导致的问题。 因为在linux kernel源码中发现tcp_tw_recycle/tcp_timestamps都开启的条件下,60s内同一源ip主机的socket connect请求中的timestamp必须是递增的。经过测试,我这边centos6系统(kernel 2.6.32)和centos7系统(kernel 3.10.0)都有这问题。

源码函数:kernel 2.6.32 tcp_v4_conn_request(),该函数是tcp层三次握手syn包的处理函数(服务端);
    源码片段:
       if (tmp_opt.saw_tstamp &&
            tcp_death_row.sysctl_tw_recycle &&
            (dst = inet_csk_route_req(sk, req)) != NULL &&
            (peer = rt_get_peer((struct rtable *)dst)) != NULL &&
            peer->v4daddr == saddr) {
            if (get_seconds() < peer->tcp_ts_stamp + TCP_PAWS_MSL &&
                (s32)(peer->tcp_ts - req->ts_recent) >
                            TCP_PAWS_WINDOW) {
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
                goto drop_and_release;
            }
        }
         
        tmp_opt.saw_tstamp:该socket支持tcp_timestamp
        sysctl_tw_recycle:本机系统开启tcp_tw_recycle选项
        TCP_PAWS_MSL:60s,该条件判断表示该源ip的上次tcp通讯发生在60s内
        TCP_PAWS_WINDOW:1,该条件判断表示该源ip的上次tcp通讯的timestamp 大于 本次tcp

总结:

我这边和其它同事通过公司出口(NAT网关只有1个ip地址)访问问题server,由于timestamp时间为系统启动到当前的时间,故我和其它同事的timestamp肯定不相同;根据上述SYN包处理源码,在tcp_tw_recycle和tcp_timestamps同时开启的条件下,timestamp大的主机访问serverN成功,而timestmap小的主机访问失败。并且,我在办公网找了两台机器可100%重现这个问题。

解决:

# echo "0" > /proc/sys/net/ipv4/tcp_tw_recycle

四、扩展

1,net.ipv4.tcp_timestamps

tcp_timestamps的本质是记录数据包的发送时间。基本的步骤如下:

  1. 发送方在发送数据时,将一个timestamp(表示发送时间)放在包里面

  2. 接收方在收到数据包后,在对应的ACK包中将收到的timestamp返回给发送方(echo back)

  3. 发送发收到ACK包后,用当前时刻now - ACK包中的timestamp就能得到准确的RTT

当然实际运用中要考虑到RTT的波动,因此有了后续的(Round-Trip Time Measurement)RTTM机制。

TCP Timestamps Option (TSopt)具体设计如下:

Kind: 8             // 标记唯一的选项类型,比如window scale是3
Length: 10 bytes    // 标记Timestamps选项的字节数
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| Kind=8 | Length=10 | TS Value (TSval) | TS ECho Reply (TSecr) |
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
   1          1             4                       4

timestamps一个双向的选项,当一方不开启时,两方都将停用timestamps。比如client端发送的SYN包中带有timestamp选项,但server端并没有开启该选项。则回复的SYN-ACK将不带timestamp选项,同时client后续回复的ACK也不会带有timestamp选项。当然,如果client发送的SYN包中就不带timestamp,双向都将停用timestamp。

tcp数据包中timestamps的value是系统开机时间到现在时间的(毫秒级)时间戳。

参数:

0:停用

1:启用(系统默认值)

2,net.ipv4.tcp_tw_recycle

TCP规范中规定的处于TIME_WAIT的TCP连接必须等待2MSL时间。但在linux中,如果开启了tcp_tw_recycle,TIME_WAIT的TCP连接就不会等待2MSL时间(而是rto或者60s),从而达到快速重用(回收)处于TIME_WAIT状态的tcp连接的目的。这就可能导致连接收到之前连接的数据。为此,linux在打开tcp_tw_recycle的情况下,会记录下TIME_WAIT连接的对端(peer)信息,包括IP地址、时间戳等。这样,当内核收到同一个IP的SYN包时,就会去比较时间戳,检查SYN包的时间戳是否滞后,如果滞后,就将其丢掉(认为是旧连接的数据)。这在绝大部分情况下是没有问题的,但是对于我们实际的client-server的服务,访问我们服务的用户一般都位于NAT之后,如果NAT之后有多个用户访问同一个服务,就有可能因为时间戳滞后的连接被丢掉。

参数:

0:停用(系统默认值)

1:启用

参考:

https://serverfault.com/questions/235965/why-would-a-server-not-send-a-syn-ack-packet-in-response-to-a-syn-packet

http://hustcat.github.io/tcp_tw_recycle-and-tcp_timestamp/

猜你喜欢

转载自blog.csdn.net/chenlycly/article/details/80369408