2

tcp_tw_recycle引起的TCP握手失败

 2 years ago
source link: https://wakzz.cn/2020/07/05/linux/tcp_tw_recycle%E5%BC%95%E8%B5%B7%E7%9A%84TCP%E6%8F%A1%E6%89%8B%E5%A4%B1%E8%B4%A5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

tcp_tw_recycle引起的TCP握手失败

祈雨的博客
2020-07-05

测试环境的一台Nginx服务器,最近一直被前端同事吐槽网络有问题,经常出现访问HTTP请求时超时,哪怕是静态文件也经常超时。

刚开始以为是公司网络抽风了,也就没放在心上,但持续了一个星期,而且复现率很高,这才反应过来应该不是网络的锅。于是在请求客户端与Nginx服务器上均作了抓包。

本地客户端抓包结果如下图1,请求Nginx服务器TCP握手时超时。结果似乎很明朗,客户端TCP握手的SYN请求丢包导致多次重试,直到重试超时而TCP握手失败。看上去似乎就是网络问题,但这复现率也太高了,于是在服务器上也做了一次抓包。

图2为Nginx服务器抓包图,显示服务器接收到了TCP握手请求,但是未做应答,直到客户端重试超时结束。很明显,并不是网络丢包而导致的TCP握手失败,而是某种机制导致的服务器直接抛弃了客户端的请求报文。

image

image

准备两个Client机器(192.168.50.150、192.168.50.160)以及一个Server机器(118.24.117.115),并且两个Client机器均处于同一个NAT网关下(116.239.x.x)。

image

启用两个Client机器TIME-WAIT连接快速回收,编辑文件/etc/sysctl.conf,添加或修改以下参数:

net.ipv4.tcp_tw_recycle = 1

然后执行命令使sysctl.conf的参数生效:

[root@VM_0_10_centos ~]# /sbin/sysctl -p
[root@VM_0_10_centos ~]# cat /proc/sys/net/ipv4/tcp_tw_recycle
1
  1. Client1机器请求Server,Client1抓包和Server抓包如下两张图,TCP握手成功:

image

image

  1. Client2机器请求Server,Client2抓包和Server抓包如下两张图,TCP握手成功:

image

image

  1. Client1机器再次请求Server,Client1抓包和Server抓包如下两张图,TCP握手失败,复现成功:

image

image

通过netstat -s命令查看网络统计时,可以发现每次复现出TCP握手失败后,如下的一行统计值都会对应增长。

[root@VM_0_10_centos ~]# netstat -s | grep timestamp
2143 packets rejects in established connections because of timestamp

PAWS机制

Per-host PAWS机制

PAWS uses the same TCP Timestamps option as the RTTM mechanism described earlier, and assumes that every received TCP segment (including data and ACK segments) contains a timestamp SEG.TSval whose values are monotone non-decreasing in time. The basic idea is that a segment can be discarded as an old duplicate if it is received with a timestamp SEG.TSval less than some timestamp recently received on this connection.

PAWS机制是基于客户端IP而非客户端IP+端口号的,当服务端开启了net.ipv4.tcp_tw_recycle后,服务端会对SYN报文做时间戳检查。每当快速回收TIME_WAIT连接后,会在60秒缓存该客户端IP的TSval时间戳。在这60秒内如果同一个IP再发起SYN请求时,会校验新请求的TSval是否大于缓存的TSval,保证同一个请求方IP的TSval时间戳是递增的。而对于非递增的SYN请求则直接丢弃处理。

TSval并非真正的时间戳,而是由时间戳依据一定算法算出来的一个值,与时间戳有同等的特性,即随时间单调递增;

如上复现操作时的图,由于Client1和Client2处于同一个NAT网关下,对于Server来说,Client1和Client2的IP相同均为NAT网关的IP。

  1. 当Client1请求Server时,IP(116.239.x.x)的时间戳值TSval为1853021;
  2. 当Client2请求Server时,IP(116.239.x.x)的时间戳值TSval为1927141;
  3. 当Client1再次请求Server时,IP(116.239.x.x)的时间戳值TSval为1864558;

当步骤3请求时,对于Server来说,IP(116.239.x.x)的TSval时间戳小于上次请求的时间戳,因此该SYN请求直接被丢弃了,导致TCP握手失败。

Linux内核的实现

Linux内核源码中,对于PAWS机制的实现很简单,如果客户端的TCP报文中启用了timestamp option,且服务端启用了net.ipv4.tcp_tw_recycle,则会触发PAWS机制检查。

tcp_ipv4.c

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...

/* VJ's idea. We save last timestamp seen
* from the destination in peer table, when entering
* state TIME-WAIT, and check against it before
* accepting new connection request.
*
* If "isn" is not zero, this request hit alive
* timewait bucket, so that all the necessary checks
* are made in the function processing timewait state.
*/
if (tmp_opt.saw_tstamp && // TCP报文中启用了timestamp option
tcp_death_row.sysctl_tw_recycle && // 开启了net.ipv4.tcp_tw_recycle
(dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
fl4.daddr == saddr) { // 仅判断源IP相同,不区分端口号
if (!tcp_peer_is_proven(req, dst, true)) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}

...
}

tcp_metrics.c

bool tcp_peer_is_proven(struct request_sock *req, struct dst_entry *dst, bool paws_check)
{
struct tcp_metrics_block *tm;
bool ret;

if (!dst)
return false;

rcu_read_lock();
tm = __tcp_get_metrics_req(req, dst);
if (paws_check) {
if (tm &&
// peer 信息保存的时间离现在在60秒(TCP_PAWS_MSL)之内
(u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&
// peer 信息中保存的timestamp 比当前收到的SYN报文中的timestamp大1(TCP_PAWS_WINDOW)
(s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW)
ret = false;
else
ret = true;
} else {
if (tm && tcp_metric_get(tm, TCP_METRIC_RTT) && tm->tcpm_ts_stamp)
ret = true;
else
ret = false;
}
rcu_read_unlock();

return ret;
}

关闭TCP的TIME_WAIT快速回收功能即可:编辑文件/etc/sysctl.conf,添加或修改以下参数:

net.ipv4.tcp_tw_recycle = 0

然后执行命令使sysctl.conf的参数生效:

[root@VM_0_10_centos ~]# /sbin/sysctl -p
[root@VM_0_10_centos ~]# cat /proc/sys/net/ipv4/tcp_tw_recycle
0

查看Linux内核源码后发现,触发PAWS机制检查的前提条件之一是客户端的TCP请求携带了timestamp option,如下图1。但是当使用Windows操作系统时,客户端默认关闭了timestamp option,通过抓包也显示TCP请求中未携带timestamp option,却依然触发了PAWS机制,如下图2。

image

image


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK