Linux下WEB100补丁对窗口扩大选项的问题
前段时间在跟踪一个RDP应用的问题,RDP应用在过设备透明代理处理后速度变得比不过要慢的多,至少一半以上。嫌RDP每次使用太麻烦了,于是使用了一简单的TCP发包服务端和收包客户端,用来模拟RDP的数据流,加快测试的效率。结果发现最简单的发包应用经过透明代理后就会慢上3s左右,一开始以为这是代理处理带来的处理时延,每个包的处理时延累加起来就达到的这个数字,后来抓包看了下发现不对,于是使用Linux做发包的服务端,Windows做客户端,连接时抓包如下:
-bash-2.05b# tcpdump -i eth0 port 8888 -nn -c 20
tcpdump: listening on eth0
15:24:16.709831 192.168.0.148.60320 > 192.168.3.121.8888: S 1286639064:1286639064(0) win 8192 <mss 1460,nop,nop,sack,nop,wscale 8> (DF)
15:24:16.713211 192.168.3.121.8888 > 192.168.0.148.60320: S 1778803221:1778803221(0) ack 1286639065 win 5840 <mss 1460,nop,nop,sackOK,nop,wscale 7>
15:24:16.709831 192.168.0.148.60320 > 192.168.3.121.8888: . ack 1 win 256 (DF)
15:24:19.740047 192.168.3.121.8888 > 192.168.0.148.60320: P 1:257(256) ack 1 win 45
15:24:19.929831 192.168.0.148.60320 > 192.168.3.121.8888: . ack 257 win 255 (DF)
15:24:19.936427 192.168.3.121.8888 > 192.168.0.148.60320: P 257:1461(1204) ack 1 win 45
15:24:19.936538 192.168.3.121.8888 > 192.168.0.148.60320: P 1461:1601(140) ack 1 win 45
15:24:19.929831 192.168.0.148.60320 > 192.168.3.121.8888: . ack 1601 win 256 (DF)
15:24:19.929831 192.168.0.148.60320 > 192.168.3.121.8888: P 1:21(20) ack 1601 win 256 (DF)
15:24:19.937526 192.168.3.121.8888 > 192.168.0.148.60320: . ack 21 win 45
15:24:19.929831 192.168.0.148.60320 > 192.168.3.121.8888: P 21:41(20) ack 1601 win 256 (DF)
15:24:19.937731 192.168.3.121.8888 > 192.168.0.148.60320: . ack 41 win 45
15:24:19.929831 192.168.0.148.60320 > 192.168.3.121.8888: P 41:61(20) ack 1601 win 256 (DF)
15:24:19.937927 192.168.3.121.8888 > 192.168.0.148.60320: . ack 61 win 45
15:24:19.966700 192.168.3.121.8888 > 192.168.0.148.60320: . 1601:3061(1460) ack 61 win 45
15:24:19.966830 192.168.3.121.8888 > 192.168.0.148.60320: P 3061:3201(140) ack 61 win 45
问题出现在三次握手过程的第三个包和服务端发送的第一个包上,中间有一个3s的间隔,上层的服务端在acceept后,就立即发送数据了,没有任何的时延。为了确定延迟在何处引发的,在上层加了些调试信息,同时在LOCAL_OUT的最高优先级上注册了钩子函数来打印一些调试信息,结果表明延迟只可能发生在TCP协议栈中。
同时还有一个奇怪的现象,上层发的包长度都是1600字节,按道理TCP根据MSS拆包发送出去的包应该是1460+140的组合方式,这个从后面的包可以看出来,但是产生延迟的包被发送出去的时候的拆分方式却是256+1204+140的方式,而256刚好是三次握手中第三个ACK包携带的窗口值,看上去好像是刚好被这个窗口限制了。内核添加调试日志证明了就是被该窗口限制的,TCP认为超过了对方的接收窗口,导致在上层调用send的时候没有发送成功,第二次超时重发,同时拆包发送出去的。
在三次握手中可以看到有wscale的选项的,256<<8=65536,而不是256的,看来这个窗口扩大选项处理出现了问题。把TCP的建立流程再详细的跟踪一遍,主要看处理snd_wnd的地方,在tcp_rcv_state_process()里面发现,处理三次握手的第三个ACK包时,赫然一句:
tp->snd_wnd = ntohs(th->window);
这个地方应该是需要考虑窗口扩大选项的,不太相信Linux有这个大的一个BUG,去lxr上翻看Linux的原始代码,发现却是:
tp->snd_wnd = ntohs(th->window) << tp->snd_wscale;
最终发现是我们打的WEB100补丁上把这句话给修改了,补丁的这部分代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
- tp->snd_wnd = ntohs(th->window) << tp->snd_wscale;
+ WEB100_VAR_SET(tp, SndUna, tp->snd_una);
+ /* RFC1323: The window in SYN & SYN/ACK segments is
+ * never scaled (PSC/CMU patch {rreddy,mathis}@psc.edu).
+ */
+
+ tp->snd_wnd = ntohs(th->window);
+ WEB100_UPDATE_FUNC(tp, web100_update_rwin_rcvd(tp));
Google了一把,发现是这个是比较老的Web100的补丁上有的代码,新的好像没有这个代码了。
修改后测试,3s的时延消除。但是RDP慢上一半,3s的时延消除影响不大,下篇继续。