公网服务器或客户端为 NAT 网络的服务器不要同时开启 tcp_tw_recycle 和 tcp_timestamps

来源:互联网 发布:淘宝如何绑定银行卡 编辑:程序博客网 时间:2024/06/02 14:39

目录

    • 目录
    • 背景及现象
    • 问题原因
      • 结论
    • 实践
      • 如何配置 tcp_tw_recycle 和 tcp_timestamps
      • 为什么很多时候开启了 tcp_tw_recycle 也并没有产生问题

背景及现象

服务器:公网服务器
客户端:外网手游客户端
玩家侧现象:大批处于同一局域网内的内侧玩家连接服务器超时,但是切换成 4G 之后连接顺畅。
服务端现象:netstat -s" 显示 “passive connections rejected because of time stamp” 数量增长快速,关闭 tcp_tw_recycletcp_timestamps 恢复正常,玩家侧现象消失。

问题原因

tcp_tw_recycle 依赖于 tcp_timestamps, 前者默认关闭,后者默认开启。如果二者都开启,当服务器收到 FIN 包时,这个 FIN 包中的时间戳值会按 IP 缓存起来(缓存 TCP_PAWS_MSL, 即 60s),缓存时间内,当同一 IP 的 SYN 包时间戳小于该值,则直接丢弃。代码分析如下:

tcp_v4_conn_request()

/* 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_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 ((u32)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;        }}

由于 peer->tcp_ts_stamp 记录的是之前收到 FIN 包时服务器的时间戳,peer->tcp_ts 记录的是 FIN 包中的时间戳,req->ts_recent 表示当前 SYN 包中的时间戳,因此 (u32)get_seconds() - peer->tcp_ts_stamp < TCP_PAWS_MSL 表示缓存还未过期,(s32)(peer->tcp_ts - req->ts_recent) > TCP_PAWS_WINDOW 表示当前 SYN 包中的时间戳比之前 FIN 包中的时间戳还小 1s, 因此认为当前 SYN 包时之前重传的,直接丢弃。

req - struct request_sock
peer- struct inet_peer, inet_getpeer()
get_seconds() - 获取当前服务器时间

TCP_PAWS_MSL

#define TCP_PAWS_MSL    60              /* Per-host timestamps are invalidated                                         * after this time. It should be equal                                         * (or greater than) TCP_TIMEWAIT_LEN                                         * to provide reliability equal to one                                         * provided by timewait state.

TCP_PAWS_WINDOW

#define TCP_PAWS_WINDOW 1               /* Replay window for per-host                                         * timestamps. It must be less than                                         * minimal timewait lifetime.                                         */

tcp_v4_tw_remember_stamp()

int tcp_v4_tw_remember_stamp(struct inet_timewait_sock *tw){        struct inet_peer *peer = inet_getpeer(tw->tw_daddr, 1);        if (peer) {                const struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);                if ((s32)(peer->tcp_ts - tcptw->tw_ts_recent) <= 0 ||                    ((u32)get_seconds() - peer->tcp_ts_stamp > TCP_PAWS_MSL &&                     peer->tcp_ts_stamp <= (u32)tcptw->tw_ts_recent_stamp)) {                        peer->tcp_ts_stamp = (u32)tcptw->tw_ts_recent_stamp;                        peer->tcp_ts       = tcptw->tw_ts_recent;                }                inet_putpeer(peer);                return 1;        }        return 0;}

peer->tcp_ts_stamptcptw->tw_ts_recent_stamp, peer->tcp_tstcptw->tw_ts_recent, 见 tcp_timewait_state_process().

tcptw - struct tcp_timewait_sock, struct inet_timewait_sock

tcp_timewait_state_process()

/* FIN arrived, enter true time-wait state. */tw->tw_substate   = TCP_TIME_WAIT;tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_seq;if (tmp_opt.saw_tstamp) {        tcptw->tw_ts_recent_stamp = get_seconds();        tcptw->tw_ts_recent       = tmp_opt.rcv_tsval;}/* I am shamed, but failed to make it more elegant. * Yes, it is direct reference to IP, which is impossible * to generalize to IPv6. Taking into account that IPv6 * do not understand recycling in any case, it not * a big problem in practice. --ANK */if (tw->tw_family == AF_INET &&    tcp_death_row.sysctl_tw_recycle && tcptw->tw_ts_recent_stamp &&    tcp_v4_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);return TCP_TW_ACK;

从此处可以看出,tcptw->tw_ts_recent_stamp 记录的是收到 FIN 包时服务器当前时间,而 tcptw->tw_ts_recent 记录的是 FIN 包中对端的时间戳。

结论

由于大量内侧玩家位于同一 NAT 网络中,所有玩家的 IP 被转换成同一出口 IP, 但时间戳却保留,因此到达服务器的包中时间戳顺序跟到达顺序是不一致的,当同时开启 tcp_tw_recycletcp_timestamps 时,一个玩家断开连接可能导致其他玩家的 SYN 包被丢弃掉。

实践

如何配置 tcp_tw_recycle 和 tcp_timestamps

tcp_timestamp 建议开启。tcp_timestamp 是 RFC1323 定义的优化选项,主要用于 TCP 连接中 RTT(Round Trip Time) 的计算,开启 tcp_timestamp 有利于系统计算更加准确的 RTT,也就有利于 TCP 性能的提升。

tcp_tw_recycle 建议关闭。

多数系统的默认设置也是如此。

为什么很多时候开启了 tcp_tw_recycle 也并没有产生问题

一是现网中很少出现批量用户处于同一 NAT 网络中且包被 NAT 网关转发之后顺序错乱,即使出现数量也很少;
二是有些客户端系统并没有使用时间戳,例如 XP 和多数 Windows 7.

阅读全文
0 0
原创粉丝点击