说件好事,趣事。上周的短文 TCP RTO 与丢包检测 下出现了一条评论:
RFC3517/6675 已从 Linux 的 net-next 分支中移除。
很惊讶,但紧接着我回复道 “6.16 还在呢。不过早就该去掉了”…
这里是 commit:tcp: remove obsolete and unused RFC3517/RFC6675 loss recovery code,注意一个时间线,早在 2018 年,BBR 的作者之一 Yuchung Cheng 就提了一个相关的派池,tcp: disable RFC6675 loss detection。
当时没有将 RFC6675 完全移除,只因 “怕出问题”,“怕它还真有点用”,因此所做的只是 (“试着偷偷地”) 缺省关闭了 RFC6675,然后等着有人报 bug 或者抱怨。
这么多年过去了,没人报 bug,也没人抱怨,说明移除 RFC6675 没有任何问题,RACK 兜住了一切,于是 net-next 就将 RFC6675 相关代码彻底移除了。
派池小但意义大,TCP 瘦身了,它移除了 RFC 标准的实现,删掉了很多代码,同时也封闭了一段八股文,当面试官再拿 “3 次重复 ACK 触发快速重传” 来筛选你时,你就可以吐他了。
我也没少为 RACK or RFC6675 折腾,业务私下将 tcp_recovery 关闭,理由是升级后比对配置,以前没见过的全关,怕新特性引入问题,结果以 “频繁超时” 提 oncall,查了半天查到这个配置被改,但我必须解释 “收到 3 次 dupack 就会重传,但这种重传是 oneshot,重传丢了就只能 RTO…”,”我抓了包,貌似不是 3 次 dupack,而是 7 次…”,“哦,对,不一定是 3 次,而是 reordering 次,用 bpftrace 查当前 sock meta_data 吧”,浪费大量时间,我写了那么多相关的细节却根本没人看。
“没事你改它干啥啊,你知道这都啥意思吗?”,我相信随着 TCP 还会进一步瘦身,这种事会越来越少,与此同时,网上充斥的那些 TCP 八股文也会逐步一个个过期,不再算数。
TCP 代码少了,精炼了,这是源码分析作者的福音。我从 2009 年开始关注涉入 Linux 内核网络,读过非常多讲网络的书,但除了《TCP/IP 详解(第二版 卷 1)》和 樊东东版《Linux 内核源码剖析:TCP/IP 实现》等个位数的版本,只要讲到 TCP 就戛然而止,我是觉得 TCP 由一大堆细节错综复杂组织而成,很少有人能完全搞清楚,书虽不如论文权威,但论文专于一个点,书则要面面俱到,有这能力和精力的反而不多。
我属于少数有这能力的人之一,但我偏偏不喜欢源码分析,更别提写书,我曾经拒绝了很多这方面的写作需求。现在虽然移除 RFC 仅此 6675 一例,但很明显,这是个趋势,TCP 早年的带有妥协,追加性质的 workaround 在资源性质趋于稳定后都将不再重要,TCP 代码的瘦身一定会更进一步,代码简单了,分析它的人就多了,学习它就更容易了。
昔日的 tcp_time_to_recover 只剩下 1 行:
// 仅剩下 1 行!
static bool tcp_time_to_recover(const struct tcp_sock *tp)
{
/* Has loss detection marked at least one packet lost? */
return tp->lost_out != 0;
}
// 作为对比,看看我初识 TCP 传输优化时的 3.10 版本的对应函数:
static bool tcp_time_to_recover(struct sock *sk, int flag)
{
struct tcp_sock *tp = tcp_sk(sk);
__u32 packets_out;
/* Trick#1: The loss is proven. */
if (tp->lost_out)
return true;
/* Not-A-Trick#2 : Classic rule... */
if (tcp_dupack_heuristics(tp) > tp->reordering)
return true;
/* Trick#3 : when we use RFC2988 timer restart, fast
* retransmit can be triggered by timeout of queue head.
*/
if (tcp_is_fack(tp) && tcp_head_timedout(sk))
return true;
/* Trick#4: It is still not OK... But will it be useful to delay
* recovery more?
*/
packets_out = tp->packets_out;
if (packets_out <= tp->reordering &&
tp->sacked_out >= max_t(__u32, packets_out/2, sysctl_tcp_reordering) &&
!tcp_may_send_now(sk)) {
/* We have nothing to send. This connection is limited
* either by receiver window or by application.
*/
return true;
}
/* If a thin stream is detected, retransmit after first
* received dupack. Employ only if SACK is supported in order
* to avoid possible corner-case series of spurious retransmissions
* Use only if there are no unsent data.
*/
if ((tp->thin_dupack || sysctl_tcp_thin_dupack) &&
tcp_stream_is_thin(tp) && tcp_dupack_heuristics(tp) > 1 &&
tcp_is_sack(tp) && !tcp_send_head(sk))
return true;
/* Trick#6: TCP early retransmit, per RFC5827. To avoid spurious
* retransmissions due to small network reorderings, we implement
* Mitigation A.3 in the RFC and delay the retransmission for a short
* interval if appropriate.
*/
if (tp->do_early_retrans && !tp->retrans_out && tp->sacked_out &&
(tp->packets_out >= (tp->sacked_out + 1) && tp->packets_out < 4) &&
!tcp_may_send_now(sk))
return !tcp_pause_early_retransmit(sk, flag);
return false;
}
浙江温州皮鞋湿,下雨进水不会胖。