WireShark出现的常见提示:
- Packet size limited during capture:标记了的包没抓全
- TCP Previous segment not captured:Wireshark 发现后一个包的 Seq 大于 Seq+Len,就知道中间缺失了一段。
- TCP ACKed unseen segment:发现被 Ack 的那个包没被抓到,就会提示。
- TCP Out-of-Order:后一个包的 Seq 号小于前一个包的 Seq+Len 时。
- TCP Dup ACK:当乱序或丢包发生时,接收方会收到一些 Seq 号比期望值大的包。没收到一个这种包就会 Ack 一次期望的 Seq 值,提现发送方。
- TCP Fast Retransmission:三次DUP ACK之后出发快速重传。(发送端后端观测,前端抓包看一个Dup ACK就发出了,应该是有缓存,3个DUP ACK之后才真正发出)
- TCP Retransmission:发送方只好等到超时了再重传。
- TCP zerowindow:0窗口,没法再收。
- TCP window Full:窗口耗尽。没法再发!
- Time-to-live exceeded:分片无法正常组装
案例一
vm client:172.18.21.57
vm server:172.18.42.13
在 vm client 执行 wget 从 server 下载大文件速度逐渐变为0 ,进行抓包分析:
1,
No.50 为client收到的 server发送数据,Seq=113587,Ack=660, Len=2576。
那么No.51 client回复的Ack=113587+2576=116163. 即 Seq=660,Ack=116163, Len=0
2,
同时116163也是期望收到的下一个数据的Seq. 即收到了No.52, Seq=116163,Ack=660, Len=2576;
再下一个收到了数据的Seq=116163+2576=118739. 即No.53,Seq=118739,Ack=660, Len=1288;
3,
再下一个期望收到数据的Seq=118739+1288=120027。在 No.55 client回复Ack=120027, Len=0
但No.54,提前收到了Seq=121315,Ack=660, Len=1288;报文被标识为TCP Previous segment not captured,表示此报文之前的一个或多个报文没有被捕获到(即Seq=120027的包没收到)。
4,No.56,基于No.54收到了的Seq=121315+1288=122603, Ack=660,Len=2576
5,No.57, Dup Ack=120027, Len=0
client发现预期的报文(即Seq=120027)迟迟没收到,便会发送重复的ack报文请求丢失的第一个报文,从而出现被标识为TCP dup ack的报文(重复应答),#前的表示报文到哪个序号丢失,#后面的是表示第几次丢失(可能由于乱序还没收到,也可能真的丢了)
6,
No.58 终于收到了期望的Seq=120027的报文,但是OutOf-Order
在server端确认这一次收到的Seq=120027只是由于乱序到达慢了,并不是由于前一个丢包后DUP ACK之后的重新换包,而且server端抓包发现收到DUP ACK之后并没有回重传包!:
发送Seq=113587的数据在No.37. 连续发送了两个数据:
No.37,Seq=113587,Ack=660, Len=21896;
No.38,Seq=135483,Ack=660, Len=30912;
No.39,收到了client回的Ack=116163
No.40和No.41收到了client要求重传的Ack,但是并没有重传!再往后看:
TCP Dup ACK 发送了多达47次都没有回,
而TCP Retransmission原因很明显是上面的超时引发的数据重传,并没有快速重传
虚拟机里sysctl有个配置项 net.ipv4.tcp_sack = 1, 经确认已经启用
#启用有选择的应答(Selective Acknowledgment),
#这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);
#(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用。
最终确认问题导致原因如下:
问题现象
当出现以下条件共存的情况下会出现scp速度变为0问题。
1,设置防火墙规则过滤掉sack-permitted、sack 报文头信息:
[root@EulerOS-BaseTemplate ~]# iptables -L -t mangle
…
Chain INPUT (policy ACCEPT)
target prot opt source destination
TCPOPTSTRIP tcp – anywhere anywhere TCPOPTSTRIP options mss,sack-permitted,sack,timestamp,md5
…
2,设置 tcp_sack = 1
[root@EulerOS-BaseTemplate ~]# sysctl -a | grep tcp_sack
net.ipv4.tcp_sack = 1
3,在链路上发生丢包。
原因分析
net.ipv4.tcp_sack与防火墙规则冲突导致。
tcp_sack 设置快速重传,但是防火墙规则把sack需要的信息给strip掉了,导致重传的数据失效,只能慢速传输,速度到最低。
在不发生丢包的情况下,没有触发重传场景,则不复现该问题。
解决方法
有两种修改方法,解决冲突问题:
打开sack功能,把防火墙过滤sack-permitted,sack规则去掉,在丢包场景下tcp性能较好。
关闭sack功能,设置net.ipv4.tcp_sack = 0,在丢包乱序场景下tcp性能较差。
案例二
iperf3打ipv6测试,接收端虚拟机抓包,发现每次都是最后的[PSH,ACK]报文乱序导致重传:
刚开始怀疑镜像gro有问题,于是关闭gro后测试,依然会重传:
而且可以注意到:
seq=972202的包已经收到了, 那么回包的ACK应该是972202+1124=973326.
但是回的ACK(包括Dup ACK)却是972202,应该是上一个包的ACK!
用wirshark打开分析,发现这个[PSH,ACK]报文的checksum计算正确,但ip头里的length填写错误,导致虚拟机把它作为了错包!
虚拟机内可以用 nstat -z -as 看到统计。统计说明见wiki:
https://www.kernel.org/doc/html/latest/networking/snmp_counter.html
这里正好回顾下gsogro的知识:
对于上面出问题的seq=972202数据包,在发送端抓包,属于如下tso大包:
包长15404, 对应tcp头里的 [TCP Segment Len: 15404] 字段;ipv6头 [Payload length: 15436]
接受端已被网卡切成小包, 每个ip长度为1460(ipv4为1500,跟mtu有关),每个tcp长度为1428,则15404被网卡切成:1428 x 10 + 1124, 即最后一片[PSH,ACK]的报文tcp头部长度应为1124, ipv6头部长度应为1156.
除此之外, checksum也是比较容易出现校验错误的地方。