1 问题
当服务器同时开启tcp_timestamps和tcp_tw_recycle选项时,会导致客户反馈连接成功率降低的情况。
but why ???
2 公网NAT的存在
NAT的全称是:Network Address Translation。
一个具体的例子就是家用的局域网络。
当使用一台无线路由器进行上网拨号后,其他的终端设备只要连接进入该无线路由器的WiFi
网络内就可以访问外网了。此时正是NAT在发挥作用。
每一台终端设备在接入无线路由器后,只是获得一个局域网IP地址,而当你在百度输入我的IP的时候
你看到的IP地址则是你无线路由器的公网IP地址。家用无线路由器完成的一个主要工作正是将终端
的局域网IP地址进行NAT转换为公网IP地址。
从上面这个简单的例子可以看到NAT在真实的互联网中是普遍存在的,比如你所在学校,单位都会一定程度上的使用NAT机制。
3 Per-host PAWS机制
在这篇介绍TCP timestamp
的文章中提到了一种针对per-host的PAWS机制。这种机制要求所有来个同一个host IP的TCP数据包的
timestamp值是递增的。当收到一个timestamp值,小于服务端记录的对应值后,则会认为这是一个过期的数据包,然后会将其丢弃。
4 解答问题
至此就不难解释为什么在同时开启tcp_timestamp和tcp_tw_recycle时,会遇到客户反馈连接成功率降低的情况了,基本的逻辑如下:
1. 同时开启tcp_timestamp和tcp_tw_recycle会启用TCP/IP协议栈的per-host的PAWS机制
2. 经过同一NAT转换后的来自不同真实client的数据流,在服务端看来是于同一host打交道
3. 虽然经过同一NAT转化,但由于不同真实client会携带各自的timestamp值
因而无法保证整过NAT转化后的数据包携带的timestamp值严格递增
4. 当服务器的per-host PAWS机制被触发后,会丢弃timestamp值不符合递增条件的数据包
解决办法就是不建议同时开启tcp_timestamp和tcp_tw_recycle。
那到底怎么配置?
开启tcp_timestamp,但不要开tcp_tw_recycle
开启tcp_timestamp,但不要开tcp_tw_recycle
开启tcp_timestamp,但不要开tcp_tw_recycle
因为timestamp有更多其他的作用,而tcp_tw_recycle本身就是依赖于timestamp的(因为timestat)。在不开启timestamp的情况下,单独开启tcp_tw_recycle并没有什么用
其实上述强调三遍的配置,正是目前Linux的默认配置。所以说啊,不真正搞懂内核的参数选项,就不要盲目修改。尤其是在官方文档对tcp_tw_recycle已经强调了不要盲目修改的情况下
那为什么有人推荐同时开启tcp_timestamp和tcp_tw_recycle呢?
因为同时开启后,能够更快的回收TIME-WAIT状态的socket <== 这也正是PAWS从per-conn在配置后扩展到per-host的目的
只可惜逻辑是对的,但是没有考虑到公网广泛存在的NAT机制可能带来的问题。
5 源码细节分析
这部分是linux 3.10源码部分的分析,算是对于以上理论分析提供的依据,不关系细节的话可以忽略本节
// tcp_v4_conn_request(), net/ipv4/tcp_ipv4.c line 1551
if (tmp_opt.saw_tstamp && // 是否见到过tcp_timestamp选项
tcp_death_row.sysctl_tw_recycle && // 接着判断是否开启recycle
(dst = inet_csk_route_req(sk, &fl4, req)) != NULL && // 最终判断saddr是否有相关记录在route表中
fl4.daddr == saffr) {
if (!tcp_peer_is_proven(req, dst, true)) { // 如果这个建连请求不能被proven,则会被丢弃
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}
// tcp_peer_is_proven() net/ipv4/tcp_metrics.c line 536
// 负责判断接收到的request请求的timestamp是否符合要求,最重要的一段代码如下
if (tm &&
// 判断保存tcpm_ts_stamp值是否有效,TCP_PAWS_MSL=60
(u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&
// 如果记录值大于当前收到的req中的timestamp值,则丢弃。TCP_PAWS_WINDOW=1
(u32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW) {
ret = false;
}
至此可以看到:在tcp_timestamp和tcp_tw_recycle同时开启时,会触发Linux的per-host的PAWS机制
接下来分析开启tcp_tw_recycle和tcp_timestamp时,是怎么快速回收TIME-WAIT的
// tcp_time_wait() net/ipv4/tcp_minisocks.c line 267
...
// ts_recent_stamp依赖于timestamp选项的开启,可进tcp_minisocks.c验证
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = tcp_remember_stamp(s);
...
// 如果能够recycle,则使用更短的rto作为timeout,从而更快回收TIME-WAIT
if (timeo < rto)
timeo = rto;
if (recycle_ok) {
tw->tw_timeout = rto;
} else {
tw->tw_timeout = TCP_TIMEWAIT_LEN;
if (state == TCP_TIME_WAIT)
timeo = TCP_TIMEWAIT_LEN;
}
inet_twsh_schedule(tw, &tcp_death_row, timeo, TCP_TIMEWAIT_LEN);
// tcp_timewait_state_process() net/ipv4/tcp_minisocks.c line 94
// 另一条进入time-wait的路线有类似的代码
if (tcp_death_row.sysctl_tw_recycle &&
tcptw->tw_ts_recent_stamp &&
tcp_tw_remember_stamp(tw))
inet_twsk_schedule(tw, &tcp_death_row, tw->tw_timeout,
TCP_TIMEWAIT_LEN);
else
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN);
参考资料
Documentation: ip-sysctl.txt
RFC 1323: TCP Extensions for High Performance