上周,同事甩过来一个链接,说的是一个陈年的bug:
https://www.spinics.net/lists/stable-commits/msg211905.html
问题出在在以下的代码:
if (!before(rs->prior_delivered, bbr->next_rtt_delivered)) {
bbr->next_rtt_delivered = tp->delivered;
bbr->rtt_cnt++;
bbr->round_start = 1;
bbr->packet_conservation = 0;
}
大致意思是,如果在一个TCP连接正在传输数据时,切换拥塞控制算法到BBR,其next_rtt_delivered被初始化成了0,可能会导致BBR认为一个round可以容纳多到 2 31 2^{31} 231的报文,当这么多报文被传输后,才能进入下一个round,显然这是不现实的,地球上最大的长肥管道一个round也容不下这么多报文。
问题出在before/after的判定方式上。看下面的等价代码:
if (!before(rs->prior_delivered, 0)) {
}
什么条件下会进入if?这要搞清楚before,after的含义。
我们知道,计算机计数不是在一个无限的域上的,而是在一个圆环域上的,类似钟表。直接看图:
请问,相对于a,b和b‘分别和a的关系是什么,before,还是after?
简单说,拿一个半圆的弧,一个端点放在a位置:
- 如果b在以a为起点顺时针的半圆弧内,那么before(a, b)为真。
- 如果b在以a为起点逆时针的半圆弧内,那么after(a,b)为真。
涉及到时间的before/after判断,依然有这个问题,注意,计算机计数在半圆内生效!
所以,对于这个BBR的bug,当rs->prior_delivered为 2 31 2^{31} 231多一点时,before(rs->prior_delivered, 0)一直到rs->prior_delivered越过0,将一直为真,因此在deliver大致 2 31 2^{31} 231个报文前,if语句将永远无法进入,这会误导BBR认为一个round容纳了 2 31 2^{31} 231个报文,进而影响其rate采样计算。
以一个报文1KB为例, 2 31 2^{31} 231大致2TB,好家伙!
触发这个bug不容易,需要你恰好在一个TCP连接已经传输了 2 31 2^{31} 231多个报文之后切换到BBR算法,大概就是3TB的数据量,哪个经理直播会展示这个数量级的数据呢?领带,西装,皮鞋?
这个bug映射到现实中,大概在环400米体育场跑马拉松时会频繁触发吧,所以马拉松必须拉到城市干道中跑。
如今网络带宽动辄25GBps+了,万一有了更猛的网络呢?32位序列号显然会显得不够用,如何来做呢?嗯,tcp结构体加一个cnt就够了吗?事情变得复杂起来了…
事实上,扩展协议不是更好么?协议头加一个bit和内卷SDN哪个更简单??
如果你说你不能自定义TCP协议,或者扯到运营商,我只能说,你对网络一无所知!
浙江温州皮鞋湿,下雨进水不会胖。