谈Timewait和NAT环境下的TW快速回收

关于NAT后端服务端使能TW(Timewait)连接快速回收造成用户连接成功率降低的问题,来捋一捋。

TW的意义

对于主动关闭连接的一方,总是在FIN_WAIT2状态收到FIN并回复ACK(或在FIN_WAIT1收到FIN/ACK并回复ACK),因为回复的ACK无序列号无法得到对端的确认,所以主动关闭一方无法知道最后的ACK是否到达对端。所以主动关闭一端在回复ACK后进入TW状态,保留2MSL时间等待对方重传FIN,保证ACK可靠地到达对端。
另一方面,TW状态下的五元组连接无法被重新建立。如果TW状态下相同五元组连接被新建,那么网络中“迷路”的旧连接报文很可能重新到达,并将新连接FIN关闭或劫持。所以TW状态持续2MSL,保证旧连接报文在网络中消失。

TW状态的连接如僵尸般存在,即无法被重用,又占据着内存,有很多方式去控制TW状态连接数量,如设置tcp_max_tw_buckets内核选项控制系统TW状态连接最大值,或使能linger选项让连接在关闭时直接RST,但这样的方式不优雅也不安全。

Linux通过tcp_tw_reuse和tcp_tw_recycle内核选项控制TW连接的复用。期望TW连接的复用,就需要解决新旧连接报文区分的问题,防止旧连接报文对新连接的劫持。Linux的tcp_tw_reuse或tcp_tw_recycle功能使能需同时开启tcp_timestamps时间戳,在旧连接关闭时,记录旧连接最后到来报文的时间戳,以此为标准区别新旧连接报文,时间戳小于该记录值的报文被判定为旧连接报文,旧连接报文将被丢弃,而新连接报文允许TW连接的服用。

其中,tcp_tw_reuse为连接重用,即连接仍处于TW状态但在一定条件下允许复用,tcp_tw_recycle为连接回收,即TW状态连接将被快速回收清理。

TW重用

首先,TW重用只在客户端生效,即只有在主动发起连接的一侧才会判断是否使能了tcp_tw_reuse。

static int __inet_check_established(struct inet_timewait_death_row *death_row,
    struct sock *sk, __u16 lport,
    struct inet_timewait_sock **twp)
{
	...
	sk_nulls_for_each(sk2, node, &head->twchain) {
		if (sk2->sk_hash != hash)
			continue;
 
	    if (likely(INET_TW_MATCH(sk2, net, acookie,
	    				 saddr, daddr, ports, dif))) {
		    tw = inet_twsk(sk2);
		    if (twsk_unique(sk, sk2, twp))
		    	goto unique;
		    else
		    	goto not_unique;
	    }
	}
}

在connect调用过程中,如果选用四元组信息为TW状态连接,则调用twsk_unique()判断连接是否可以重用。

    if (tcptw->tw_ts_recent_stamp &&
        (twp == NULL || (sysctl_tcp_tw_reuse &&
         	get_seconds() - tcptw->tw_ts_recent_stamp > 1))) 

如果TW状态连接有最近的对端时间戳(对端最后一个到达报文时间戳)记录,同时目前时间与对端时间戳时间相距大于1秒,则连接可重用。

TW快速回收

快速回收前

快速回收前,即系统仍存在TW状态连接时的处理情况。首先看一下使能tcp_tw_recycle时主动进入TW状态的情况(tcp_time_wait)。

    if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
    	recycle_ok = tcp_remember_stamp(sk);
     
    if (recycle_ok) {
    	tw->tw_timeout = rto;
    } else {
    	tw->tw_timeout = TCP_TIMEWAIT_LEN;
    	if (state == TCP_TIME_WAIT)
    		timeo = TCP_TIMEWAIT_LEN;
    }

在使能tcp_tw_recycle情况下,首先对接收报文中的时间戳和接收报文时间(当前时间)进行记录,同时修改TW状态定时器超时时间为一个rto,使TW状态尽快超时回收。

再来看另一种TW状态的处理,即连接仍处于TW状态,但在连接上收到新的报文(tcp_timewait_state_process)。

    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);

可以看到处理基本和主动进入TW状态的处理相同,即记录相关时间戳和较少TW状态超时时间。

快速回收后

快速回收后,即TW状态连接已超时清理,同样五元组的连接新建时的处理。

    if (tmp_opt.saw_tstamp &&
        tcp_death_row.sysctl_tw_recycle &&
        (dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
        fl4.daddr == saddr) {
    	if (!tcp_peer_is_proven(req, dst, true)) {
    		NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
    	goto drop_and_release;
    	}
    }
     
    if (tm &&
        (u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&
        (s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW)
    	ret = false;

因为TW状态连接已被清理,所以已经无法查找到原有连接的五元组信息,但在TW状态连接快速回收前,系统将接收报文中的时间戳和接收报文时间记录在IP层,此时仍可以根据对端IP查询到上一次报文到达时携带的时间戳信息。所以,如果该对端IP前一次报文到达时间距现在小于TCP_PAWS_MSL(60秒),并且前一次报文时间戳大于新报文的时间戳,系统判断新到来的报文仍可能属于旧连接,则新的连接建立请求将被拒绝。
另外,TW状态快速回收在新的内核版本已经被移除。

NAT环境下的TW快速回收

在NAT环境下使能TW快速回收存在巨大隐患。多个客户端请求在SNAT后,源地址被转为统一地址,但因为客户端时间设置的差异,无法保证所有请求报文时间戳保持增序。一旦服务端某一连接被TW快速回收,将只在IP层保留转换后地址和最后到达报文的时间戳信息,那么时间戳不符合增序的客户端请求将被拒绝。所以,处于NAT后的服务器不建议使能TW快速回收。

你可能感兴趣的:(传输层)