我们关注的就是序号和确认号
这二者也是 TCP 实现可靠传输的方式。下图是一次随便抓包的截图(相对序列号)
意义
在TCP传输中,每一个字节都是有序号的,从0开始。通过序号的方式保存数据的顺序,接收端接受到之后进行重新排列成为需要的数据。
因此,我对于SEQ和ACK的了解就是:
SEQ 代表:发送的这个包中第一个字节(如果有payload的话)的序号
ACK 代表:已成功接受序列号到 ack-1 的数据,期望接收的下一个字节的序号为 ack
举例说明:
我已经发送了前100字节的数据,那么我下一个发送的包(如果发送窗口还有空间)的SEQ就是101,比如要发送10字节的数据,那么下一个包中的数据的字节编号就是 101 - 110. 之后如果继续发送的话,序号就是从111开始。
如果接收端接到了这个10字节的包的话,便会返回一个 ACK 为 111 的包,表示前面110个字节已经成功接收。
为什么SYN和FIN会消耗一个序列号
细心的同学可能会发现,为什么在建立连接的时候,发送的 SYN 包大小(payload)明明是0字节,但是接收端却返回 ACK = 1 ,还有断开连接的时候 FIN 包也被视为含有1字节的数据。
原因是 SYN 和 FIN 信号都是需要 acknowledgement 的,也就是你必须回复这个信号,如果它不占有一个字节的话,要如何判断你是回复这个信号还是回复这个信号之前的包呢?
例如:如果 FIN 信号不占用一个字节,回复 FIN 的 ack 包就可能被误认为是回复之前的数据包被重新发送了一次,第二次挥手无法完成,连接也就无法正常关闭了。
为什么SYN和ACK的初始值(ISN initialization sequence number)是一个随机值
参考TCP 的那些事儿(上)
ISN是不能hard code的,不然会出问题的——比如:如果连接建好后始终用1来做ISN,如果client发了30个segment过去,但是网络断了,于是 client重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client的Sequence Number 可能是3,而Server端认为client端的这个号是30了。全乱了。RFC793中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过Maximum Segment Lifetime(缩写为MSL – Wikipedia语条),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN。
什么是TCP segment of a reassembled PDU
如图所示,在抓包的时候,经常会看到[TCP segment of a reassembled PDU ] 字样的包,这个代表数据在传输层被分包了。也就是代表包大小大于MTU,此处放一下MTU与MSS区别:
MTU(Maximum Transmission Unit)最大传输单元,在TCP/IP协议族中,指的是IP数据报能经过一个物理网络的最大报文长度,其中包括了IP首部(从20个字节到60个字节不等),一般以太网的MTU设为1500字节,加上以太帧首部的长度14字节,也就是一个以太帧不会超过1500+14 = 1514字节。
MSS(Maximum Segment Size,最大报文段大小,指的是TCP报文(一种IP协议的上层协议)的最大数据报长度,其中不包括TCP首部长度。MSS由TCP链接的过程中由双方协商得出,其中SYN字段中的选项部分包括了这个信息。如果MSS+TCP首部+IP首部大于MTU,那么IP报文就会存在分片,如果小于,那么就可以不需要分片正常发送。
因此,出现这种现象的原因就是你调用一次send的时候,send的数据比 MSS 还要打,因此就被协议栈进行了分包。
顺便说一下,IP数据包的分片是通过flag字段和offset字段共同完成的。
从图中可以看到,第6个和第5个包是同一个TCP报文被分成了两个包。如果我们点开看的话,可以看到两个报文的ACK序号都一样,并且这些报文的Sequence Number都不一样,并且后一个Sequence Number为前一个Sequence Number加上前一个报文大小再加上1 。这也是判断reassembled 的方式。
点开第6个包,可以看到它将5和6的数据整合起来了。
本文为转载文章
ISN
三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。
如果ISN是固定的,攻击者很容易猜出后续的确认号。
ISN = M + F(localhost, localport, remotehost, remoteport)
M是一个计时器,每隔4微秒加1。 F是一个Hash算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证hash算法不能被外部轻易推算得出。
序列号回绕
因为ISN是随机的,所以序列号容易就会超过2^31-1. 而tcp对于丢包和乱序等问题的判断都是依赖于序列号大小比较的。此时就出现了所谓的tcp序列号回绕(sequence wraparound)问题。怎么解决?
/** The next routines deal with comparing 32 bit unsigned ints
* and worry about wraparound (automatic with unsigned arithmetic).*/
static inline int before(__u32 seq1, __u32 seq2){
return (__s32)(seq1-seq2) < 0;
}
#define after(seq2, seq1) before(seq1, seq2)
上述代码是内核中的解决回绕问题代码。s32是有符号整型的意思,而u32则是无符号整型。序列号发生回绕后,序列号变小,相减之后,把结果变成有符号数了,因此结果成了负数。
为了方便说明,我们以unsigned char和char为例来说明:
假设,seq1=255, seq2=1(发生了回绕)。
seq1 = 1111 1111 seq2 = 0000 0001
我们希望比较结果是
seq1 - seq2=
1111 1111
-0000 0001
-----------
1111 1110
由于我们将结果转化成了有符号数,由于最高位是1,因此结果是一个负数,
负数的绝对值为 0000 0001 + 1 = 0000 0010 = 2因此seq1 - seq2 < 0
注意:
如果seq2=128的话,我们会发现:
seq1 - seq2=
1111 1111
-1000 0000
-----------
0111 1111
此时结果尤为正了,判断的结果是seq1>seq2。因此,上述算法正确的前提是,回绕后的增量小于2^(n-1)-1。
由于tcp序列号用的32位无符号数,因此可以支持的回绕幅度是2^31-1,满足要求了。
这段ISN解决回绕的问题没看懂,以后再研究。
原文链接
https://www.cnblogs.com/JenningsMao/p/9487252.html
参考文档
TCP头时间戳选项与回绕序列号