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 ,进行抓包分析:

抓包 docker 搭建 抓包packet capture_tcp/ip


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之后并没有回重传包!:

抓包 docker 搭建 抓包packet capture_抓包 docker 搭建_02


发送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,但是并没有重传!再往后看:

抓包 docker 搭建 抓包packet capture_抓包 docker 搭建_03


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]报文乱序导致重传:

抓包 docker 搭建 抓包packet capture_TCP_04

刚开始怀疑镜像gro有问题,于是关闭gro后测试,依然会重传:

抓包 docker 搭建 抓包packet capture_TCP_05


而且可以注意到:

seq=972202的包已经收到了, 那么回包的ACK应该是972202+1124=973326.

但是回的ACK(包括Dup ACK)却是972202,应该是上一个包的ACK!

用wirshark打开分析,发现这个[PSH,ACK]报文的checksum计算正确,但ip头里的length填写错误,导致虚拟机把它作为了错包!

抓包 docker 搭建 抓包packet capture_TCP_06

虚拟机内可以用 nstat -z -as 看到统计。统计说明见wiki:
https://www.kernel.org/doc/html/latest/networking/snmp_counter.html

这里正好回顾下gsogro的知识:

对于上面出问题的seq=972202数据包,在发送端抓包,属于如下tso大包:

抓包 docker 搭建 抓包packet capture_网络协议_07

包长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也是比较容易出现校验错误的地方。