tcp recycle and reuse

tcp recycle and reuse

关于以下的三个选项的真正的含义, 以及相互之间的关系, 网络上真的是众说纷纭.

  • tw_recycle
  • tw_reuse
  • timestamps

tw_recycle & timestamps

一个核心的点在于, tw_recycle 依赖是对端的 TS, 与本机的 timestamps 选项之间
并没有直接的关系.

当然了, 一般的情况下, 如果本机关闭了 timestamps, 那么在协商的时候, 对端也
不会开启就是了.
就算对端总在报文里带有 TS 信息, 在本机 timestamps 选项关闭的情况下, 也不会识别.
所以 tw_recycle 也可以说与本机的 timestamps 选项有关了.

tw_reuse & timestamps

这两者之间的关系, 大体是没有什么问题的, tw_reuse 是依赖于 timestamps.

相同四元组

比如 peer 使用相同的地址端口再次发起了请求. 这个时候, 如果服务器的之前那个连接的 socket 还在 tw 状态.
这个时候是如何处理的.

	/* Out of window segment.

	   All the segments are ACKed immediately.

	   The only exception is new SYN. We accept it, if it is
	   not old duplicate and we are not in danger to be killed
	   by delayed old duplicates. RFC check is that it has
	   newer sequence number works at rates <40Mbit/sec.
	   However, if paws works, it is reliable AND even more,
	   we even may relax silly seq space cutoff.

	   RED-PEN: we violate main RFC requirement, if this SYN will appear
	   old duplicate (i.e. we receive RST in reply to SYN-ACK),
	   we must return socket to time-wait state. It is not good,
	   but not fatal yet.
	 */

	if (th->syn && !th->rst && !th->ack && !paws_reject &&
	    (after(TCP_SKB_CB(skb)->seq, tcptw->tw_rcv_nxt) ||
	     (tmp_opt.saw_tstamp &&
	      (s32)(tcptw->tw_ts_recent - tmp_opt.rcv_tsval) < 0))) {
		u32 isn = tcptw->tw_snd_nxt + 65535 + 2;
		if (isn == 0)
			isn++;
		TCP_SKB_CB(skb)->tcp_tw_isn = isn;
		return TCP_TW_SYN;
	}

可以看到如果是正常的 syn, 并且符合以下条件之一就可以了.

  • seq 增加了
  • peer 提供了 timestamps

回到 tw 的意义, 一个重要的意义就是等所有旧的包消失. 如果新的连接的 seq
比之前的大, 那么在底速网络中, 可以保证旧的所有的包并不会对于新的连接有影响.

另一个条件看起来好像和 tw_recycle 有些相似, 只要新的连接有 timestamp, 并且 timestamp
在增加. timestamp 机制会保证所有在这个连接上的旧的会都会被丢掉.

那么这里会不会引发那个 recycle 的 net 问题呢. 其实是会的, 但是只是一次连接失败,
并不影响什么. 因为只要你的 seq 是小的, 那么也一样会有问题. 实际上的 recycle 对于
timestamps 的利用就是要解决 seq 可能是小的情况. recycle 的net 问题的关键并不在这里.

recycle

recycle 机制是对于 timestamp 的利用. 一个核心的想法就是, 如果不符合 timestamp 的包
都会被丢掉, 那么我们为什么要等 2MSL 呢. 只要等一个 rto 保证, 最后 ack 传递成功, 就可以了.
不用等网络上的包都丢失了. 这样就实现了 tw 的快速加收.

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

如果是这样, 好像也是没有问题的. 那 recycle 的net 问题是如何出现的呢?

在收到 syn 的情况下, 由于 tw 状态的 socket 已经快速回收了, 那这个收到的 syn
有没有可能是一个旧的又重新出现的 syn 呢?
那在使用 recycle 的时候就要对于新出现的 syn 进行 timestamps 检查了.
由于 tw 的 socket 已经不存在了, 我们只能记录所有快速回收的 socket 的最后一次 timestamps.
并且这个值, 绑定到对端的 ip 上. 为什么不绑定到 ip:port 上, 我想, 如果这样, 纪录的东西太多了,
这样 tw 回收意义就没有了.

// 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;
    }
}

这里的核心就是 ip. 由于是绑定在 ip 上, 那么使用同一个 ip 的所有的机器都要保证 timestamps 的增加是
一致的, 这在 net 网络里, 当然是不行的了. 所以就有可能, 导致 net 后的一些机器, 总是
无法进行连接. 由于这些机器的 timestamps 不正确. 与之前那个相同四元组的情况相比,
这种情况更加严重得多. 这些也是 recycle 机制与 net 冲突的核心所在.

reuse

从名字也可以看出区别, reuse 只是重用 tw socket. recycle 是可以回收 socket, 这个价值在于, 可以影响更多的 socket.

reuse 在连接出去的时候, 可以复用 socket.

int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
{
	const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
	struct tcp_sock *tp = tcp_sk(sk);

	/* With PAWS, it is safe from the viewpoint
	   of data integrity. Even without PAWS it is safe provided sequence
	   spaces do not overlap i.e. at data rates <= 80Mbit/sec.

	   Actually, the idea is close to VJ's one, only timestamp cache is
	   held not per host, but per port pair and TW bucket is used as state
	   holder.

	   If TW bucket has been already destroyed we fall back to VJ's scheme
	   and use initial timestamp retrieved from peer table.
	 */
	if (tcptw->tw_ts_recent_stamp &&
	    (!twp || (sysctl_tcp_tw_reuse &&
			     get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {
		tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2;
		if (tp->write_seq == 0)
			tp->write_seq = 1;
		tp->rx_opt.ts_recent	   = tcptw->tw_ts_recent;
		tp->rx_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp;
		sock_hold(sktw);
		return 1;
	}

	return 0;
}

区别

作用

  • recycle: 加速了 timewait 的等时间从 2MSL(60s) 为 rto.
  • reuse: 在请求出去的时候, 利用端口.

范围

明显 recycle 的意义, 更大一些, 所有的 tw 都可以快速加收. 而 reuse 只在请求出动的时候有可能起作用.
对于服务器而言并不能解决 tw 大的问题.

依赖

  • recycle: 依赖于用户的 timestamp
  • reuse: 依赖于本地的 timestamp

可靠性

recycle 的依赖是有问题的, 用户的 timestamp 是不可靠的.
而 reuse 基本是可靠的. kernel 内对于 reuse 的态度也是”一般情况下没有什么问题”.
recycle 是不符合 tcp 协议的, 文档上都有所说明: “请在专业人士的指导下进行”.


发表评论

邮箱地址不会被公开。 必填项已用*标注