我求求你们能别抄来抄去的吗?每次搜个问题全是重复的博客!
参考:
- http://m.blog.chinaunix.net/uid-24683784-id-5746959.html
- https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features
简介
这篇笔记记录了TCP如何处理序列号回绕问题。
措施
主要有两种措施用于解决序列号回绕问题:
限制TCP窗口大小
处理回绕问题的关键在于,在回绕发生时,如何判断两个序列号的先后关系。
在内核中,判断先后关系的代码如下:(代码在Linux源码的include/net/tcp.h )
266
270
271 static inline bool before(__u32 seq1, __u32 seq2)
272 {
273 return (__s32)(seq1-seq2) < 0;
274 }
275 #define after(seq2, seq1) before(seq1, seq2)
可以看到,内核中判断两个序列号的先后关系实现得非常简单,相减然后强转成有符号32位整数,然后判断是否小于0即可。
通过一个例子来看这个方法的正确性,TCP序列号是一个32位无符号整数,范围是[0, 4294967295] ,现在假设要比较两个序列号:
- seq1 = 4294967295
- seq2 = 4294967296,超过了可表示的范围,回绕为0
调用before(seq1, seq2) 时,seq1-seq2 的结果是4294967295 ,然后强制转换成有符号整数,4294967295 转换成了-1 ,小于0,于是函数输出seq1在seq2前面,这和真实情况一致。
不过这种比较是有条件的,例如,仍然取:
- seq1 = 4294967295
- seq2 = 4294967296 + 4294967294,超过了可表示的范围,回绕为4294967294
这次before(seq1,seq2) 返回的结果就与事实不符了。
上面的算法能使用的前提是:回绕的范围不能太大。
准确地说,回绕的范围不能超过2**31 ,或者说,回绕之后的值,一定要小于2**31 。
那这个前提在TCP里面能满足吗?
可以!TCP窗口的最大值是1GB = 2**30 B < 2**31 B 所以使用上面的算法在实际情况中能够解决TCP的回绕问题,正确地区分出哪个序列号在前,哪个在后。
时间戳机制
理论上来说,上面的算法可以区分两个序列号,但是实际上可能出现这样的情况:
-
客户端发送了一个包A给服务器,序列号范围在[seq1, seq2] 之间 -
由于网络原因,这个包A过了很久都没有到达服务器 -
服务器流量很大,序列号回绕了一次,序列号范围又回到了[seq1, seq2] -
包A历经千辛万苦终于到达服务器 -
问题来了:这个包A是当前的有效包?还是回绕之前的包?
这个问题可以使用时间戳来解决,通过判断报文段的时间戳,可以判断报文段是否是过时的。
|