接着 大历史下的 tcp:rto 和 f-rto 继续。
首先看 f-rto 的流程:
- tcp 进入 loss 状态,记录 snd_nxt 为 high_seq,重传 una;
- tcp 接收到 ack,如果该 ack 推进了 una,则发送新报文;
- tcp 继续接收 ack,并重传旧报文;
- 如果期间有没有重传过的报文被确认(无论 sacked or acked),均可 undo。
很容易误解的是上述第 4 步,容易被误解为:
- 如果此后直到 high_seq 的 ack 均不携带 sack 并逐步推进 una,即 undo;
这是典型的 “被代码牵着鼻子走” 了,其实整个 f-rto 与 cover 不 cover 那个 high_seq 无关,发送那个新报文的目的事实上就是在 probe,目的是获得一个 “没有重传过但却被确认” 的报文,这就说明大可不必执行超时逻辑,充其量就 fr 好了。
为什么发新数据而不是仅仅重传,把戏是新数据位于 enter loss 时的 high_seq 之外,在逻辑上很容易区分彼此。
体现 f-rto 核心目的代码在 tcp_process_loss 里:
if (tp->frto) {
/* F-RTO RFC5682 sec 3.1 (sack enhanced version). */
/* Step 3.b. A timeout is spurious if not all data are
* lost, i.e., never-retransmitted data are (s)acked.
*/
// 就是下面的逻辑
if ((flag & FLAG_ORIG_SACK_ACKED) &&
tcp_try_undo_loss(sk, true))
return;
至于为什么不执行超时逻辑就是好的,还不是 rfc 那一坨规定,比如 rto 之后必须进入一个很低的起点重新开始,因为 rto 意味着 “极其严重的拥塞” 等等。但是这种假设真的成立吗?还是要看这件事发生在什么时代。
tcp 的 rto 规范是前向兼容使然,无论什么时候 rto 的行为均和 1980 年代(tcp/ip v4 版本)的 rto 行为一致,但这并不意味着这样做就是正确且高效的。
现在我们知道,rto 并不一定意味着严重拥塞,更多可能是无线环境下链路信号出问题或者路径换了,也可能是链路断开了,围绕着这些新的但不是拥塞的问题,解法又是五花八门,对 tcp 加入了更多拧巴的复杂性。
无论激进还是保守,守恒律一定是对的且不会带来伤害。
在 loss 状态下,下面的措施是高尚的:
- 只要有 una 被推进 n,action 如下两步:
- 重传 una;
- 发送 n - 1 个新报文。
这些重传和新报文带回什么,带回什么就全按照标准方式处理好了,关键在于它们既是 probe 又是新发,所谓水清则濯缨,水浑则洗脚。至于拥塞不拥塞,交给拥塞控制好了,而拥塞控制只关注数量并不关注它们是谁,与重传逻辑是解耦的。
别小看了 tcp_process_loss 函数里下面这个颠倒:
if (tp->frto) {
/* F-RTO RFC5682 sec 3.1 (sack enhanced version). */
/* Step 3.b. A timeout is spurious if not all data are
* lost, i.e., never-retransmitted data are (s)acked.
*/
if ((flag & FLAG_ORIG_SACK_ACKED) &&
tcp_try_undo_loss(sk, true))
return;
if (flag & FLAG_SND_UNA_ADVANCED && !recovered) {
...
} else if (after(tp->snd_nxt, tp->high_seq)) {
...
}
}
既然 una 能被一直推进,为什么不能发送新报文呢?so? 要改掉谨慎的作风,就要把拥塞控制从重传中分出去。
最烦那些卖乖立牌坊的事,tlp 可以在 rto 还没到期前用旧数据 probe 就因为它是 google 的吗,简直扯犊子。我在 rto 期间 una 被推进时持续传输新数据就错了?
tcp 的问题在于,用 1970~1980 年代的现实考量约束 2020 年代的互联网现状,一个 1460 字节都不到的报文锱铢必报,过时了。
浙江温州皮鞋湿,下雨进水不会胖。