在这篇文章中我们看一下server端在接收到异常数据系列时的处理,主要目的是通过wireshark示例对这些异常数据系列的处理有一个直观的认识,感兴趣的自行阅读相关代码和协议,这里不再进行详细介绍
在进行下面的测试前,首先如下设置相关的参数,其中window参数指定了到127.0.0.2的tcp连接的最大接收窗口。
[email protected]:/home/******/tcp12# ip route change local 127.0.0.2 dev lo window 40
一、wireshark示例
1、一个包部分数据在rcv_nxt后
如下图所示,client在与server建立连接后,首先发出No4数据包,系列号对应[1,10],下图中text列表示这个TCP数据包中实际传输的数据,No4的len=10,应用层实际传输的数据内容为"0123456789"。接着client又在No6数据包中传输系列号为[6,15]的数据包,传输的内容同样为"0123456789",长度同样为10bytes。
可以看到这里No4和No6两个数据包部分系列号重复,No4数据包系列号[6,10]对应字符串"56789",而No6数据包系列号[6,10]对应字符串"01234"。server端对于No6数据包正常回复了一个ACK报文,Ack=16,也就是说确认了系列号16以前的数据,同时可以看到No7这个ACK确认包还带有一个DSACK块,指示发送端收到了重复报文。
最终server端应用层实际读取了15bytes数据,内容为"012345678956789",可见server端把No6报文系列号[6,10]对应的数据丢弃了,但是保留了系列号[11,15]的内容。
2、一个乱序包完全在接收窗口外
如下图所示,client在与server端建立连接后,首先正常发送No4报文,接着发送乱序报文No6。从No5这个确认包可以看到Ack+Win=51,也就是说从系列号51开始的数据都是接收窗外外部的数据(包括系列号51对应的byte)。No6报文对应的系列号为[51,60]正好完全处于接收窗口外部,因此server端会丢失No6这个数据包。server端应用层最终读取了10bytes的数据,即"0123456789"。
3、一个乱序包部分在接收窗口外
这个示例与上一个不同之处在于No6系列号范围为[46,55],也即这个数据包[46,50]系列号在接收窗口内,而[51,55]在接收窗口外部。
接着看No7,No7通过SACK完整的确认了No6数据包,把落在接收窗口外部的[51,55]系列号也完整确认了,注意这个示例与上面的对比。当乱序包部分系列号落在接收窗口外部的时候,linux会正常完整的接收这个数据包,包括落在接收窗口外部的数据。而当这个数据包完全落在接收窗口外部的时候,linux则不会接收这个数据包。
另外值得注意的是No8-No11四次挥手关闭连接的过程,No8携带了SACK信息,No9则回复了一个ACK,注意No10这个FIN包的起始系列号为11,server端正常的接收了这个数据包,这也就意味着server端会把收到乱序No6报文丢掉,应用层并不能成功读取。
4、快速路径下连续包落在接收窗口外
linux对于接收到的数据包处理流程分为快速路径和慢速路径,在接收到紧急指针、tcp头中的window size发生变化等场景下都会进入慢速处理路径,慢速路径相比快速路径会执行更多检查处理。我们先来看一下快速路径下,连续包落在接收窗口外部的情况。为了不让server端更新接收窗口,我们通过SO_RCVBUF选项固定server端的接收缓存,同时我们通过特定的数据包让server端进入delay ACK模式。执行下面的测试前取消通过路由表设置的window参数。
相关的数据交互如下,No4数据包用来触发server端快速的进入delay ACK模式,server回复完No5这个确认包后就会进入delay ACK模式。关于delay ACK相关内容可以参考前面的文章。同时注意No5报文通告的Win=2,也就是接收窗口只剩余2bytes。
接着我们看到client端发送的No6-No32报文都没有触发server端回复ACK确认包。其中No6报文部分落在接收窗口外部,而No7-No32报文都是完全落在接收窗口外部的数据包。从No34这个确认包可以看到server端对于部分落在接收窗口内部的No6和完全落在接收窗口外部的No7-No32都接收了。而对于No33则没有进行接收,随后在发送No35数据包server端也没有接收。
这里No6-No32能被正常接收的原因是,linux中TCP会预留一定的缓存,当在快速处理模式下时候并不会执行严格的检查,只要新接收的数据包所占用的缓存没有超过预留的缓存就会正常接收。No6-No32都没有超过预留的缓存,因此server端正常接收。但是server端在接收到No34的时候,预留缓存不足,就会直接丢弃这个数据包,并进入quick ACK模式立即回复一个ACK,可以看到No34是一个零窗ACK报文,因此server端也就退出数据包的快速处理模式,随后在收到No35的时候会进入数据包慢速处理流程,执行检查的时候发现此时接收窗口大小为0,No35落在了接收窗口外面,因此同样会直接丢弃这个数据包并立即回复一个ACK确认包。
5、慢速路径下连续包落在接收窗口外
实际上上一次示例No35的处理已经是在慢速路径下处理的了。我们在看另外一个类似的示例。下图示例中No1-No15报文的处理与上面的示例类似,不同的地方在于No16这个数据包的Win发生了更新。server端在收到No16这个数据包的时候,发现No16更新了window size就会退出快速路径的处理进入慢速路径,以执行更复杂的检查和处理。在慢速路径中server端发现No16落在的接收窗口外部,因此直接丢弃这个数据包并进入quick ACK模式立即回复一个No17确认包,回复No17确认包的时候发现Win=0,server端TCP则退出数据包的快速处理模式,在随后接收到新数据包的时候则直接进入慢速路径处理。
6、乱序包内容部分重叠
如下图所示,client依次发送5个数据包,相关TCP交互如下图所示
为了方便对比,我把五个数据包对应的做如下图展示,其中第一行表示系列号比特位,第二行表示server端应用层实际读取的数据流,余下的5行分别表示client依次发送的5个数据包,其中数据包的红色部分表示最终被server端丢弃,数据包的绿色部分表示最终被server端接收。从下图可以看到一旦一个新接收的报文与之前报文重叠的时候,如果是新接收报文的前端发生重叠,那么新接收报文的前端内容会被丢掉,如果是后端重叠,那么之前接收的报文的内容将会被丢掉。
补充说明:
1、linux相关函数数据包接收处理:tcp_rcv_established、tcp_data_queue、tcp_data_queue_ofo
2、慢速路径快速路径等相关函数:tcp_fast_path_check,重点是tp->pred_flags这个标志变量在不同地方的更新
TCP系列36—窗口管理&流控—10、linux下的异常报文系列接收