tcptrace 一般用来排查长肥管道的吞吐问题,之前一直看不懂,最近完整学习并着手做了些验证,整理如下。
建议对照着 laixintao 老师的 这篇文章 来补齐一些概念
tcptrace 图中的基本元素
上面的 tcptrace 示例中(关注其中的元素,其它的时序不真实):
- 横轴是时间,纵轴是 Sequence Number,可以理解为包的字节数
- 其中每 发送 一个数据包,就会画一条蓝色的
I型竖线段,长度表示数据量 - 上方的绿色阶梯线代表接收方的窗口大小(Receive Window),即最多能发多少数据
- 下方的棕黄色阶梯线代表已经被 ACK 的数据量,即接收方实际收到的数据
- 红色的线段代表选择确认(SACK),即接收方收到的数据中间有缺失,红线代表收到了哪些数据
- 棕黄线下方的小竖线代表重复 ACK(dup ACK),通常代表接收方收到了乱序的包
所以这个图也只是把发的包和收到包的信息以图形化的方式展现出来,方便我们分析。
tcptrace 图中的“距离”信息
除了直接标记出来的元素,还可以通过图中的一些“距离”,得知其它一些概念
- Bytes in Flight: 发送方已经发出去但还没有被 ACK 的数据量,某时刻蓝色线和棕黄色线的垂直距离
- Window Room(窗口余量): 接收方还能接收的数据量,某时刻绿色线和棕黄色线的垂直距离
- RTT(往返时间): 数据发送到确认接收所需的时间,图中蓝色线和棕黄色线的水平距离
还有就是整体的斜率,代表吞吐量(Throughput),斜率越大,吞吐量越高。
发送方与接收方抓的包有差异
发送方作为最初发包和接收 ACK 的一方,可以看到整体的 RTT 及链路信息,如下图中可以看到蓝线和 ACK 线的距离(代表 RTT):
而接收方中中的蓝线和 ACK 线的距离,则代表的是本机接收到包后本机发 ACK 的距离,并不能代表 RTT。但是接收方额外地可以看到发送方的一些乱序包,而发送方是看不到的。
结论是两方的抓包有各自无法替代的信息,如果可能尽量两方同时抓包,如果只有一方,尽量是发送方。
常见网络问题下 tcptrace 图的表现
丢包
- 看到红色的 SACK 线,说明接收方有包没收到
- 看到不断有递增的红色 SACK 线,说明后续的包都收到了,但是丢失的包依旧没收到
- 丢包期间,接收方的 buffer 不能释放,导致窗口没有增长
- 终于收到丢失的包后,综色的 ACK 线跳跃式上升,且绿色窗口线也上升,说明接收方释放了 buffer
- 由于接收方窗口增大,发送方马上利用了新增的窗口,蓝色线也跟着上升
额外注意这个图里可以看到发送方重传了没有收到数据。
吞吐受到接收方窗口限制
- 蓝线和距离绿线非常接近,说明发送方一直发送,接近了接收方窗口的上限
- 绿线一上升,蓝线也跟着上升,说明发送方马上利用了接收方新增的窗口
结合起来看,说明接收方窗口太小,限制了发送方的吞吐。
吞吐受到发送方窗口限制
发送方能发多少数据除了受接收方窗口限制外,还受两个因素影响,一个是发送窗口(代码里使用 SO_SNDBUF 指定,内核里通过 net.ipv4.tcp_wmem 指定),另一个是拥塞窗口(cwnd)。例如下面的示例(构造时把 net.ipv4.tcp_wmem 设置成了 4096 4096 4096)[1]:
上图中蓝线不断上升,但远不到绿线的位置就停止了,直到有 ACK 一定数据后才继续发送。说明是发送方做了限制。但是究竟是 send_buffer 的限制还是 cwnd 的限制?这就需要使用 “Window Scaling”:
简单理解 Windown Scaling 里计算的是每个包对应的 “Bytes In Flight” 的数据,也就是“发出但未被 ACK”的数据,通常这个信息可以认为约等于发送方的 cwnd。但是上图中这个窗口信息很“平”,而一般 cwnd 是会波动的,因此可以认为不是 cwnd 引起的,那么就只能是发送窗口了。作为对比,我们看一个不显式设置 send buffer 的示例:
可以看到上图中随着一些丢包的发生,窗口也在不断变化,这与 cwnd 随着 RTT 和丢包不断变化相匹配。
特例:零窗口
上面提到速率可能跟发送方窗口有关,还有一种特例是因为各种原因(如消费的速度太慢)导致发送方窗口为 0, 此时 wireshark 会特地标记为 x:
网络状况很差
模拟 5% 的丢包与 10% 的乱序得到如下示例,一眼看就是很“不均匀”,有红色的丢包,棕色线有坑坑挖挖的乱序,整体的上升趋势(斜率)一会高一会低:
我们再放大看一些特征:
完美的连接
完美的情况下,会是一个非常干净的图,笔直的线,没有 SACK、乱序等复杂元素,也能达到网络的带宽:
附:模拟网络条件用到的一些命令
上面的实验是在 Mac 上用 podman 的两个 container 完成的,使用以下方式模拟网络条件:
- 先
podman machine ssh进入 podman 虚拟机 - 这个虚拟机缺少
sch_netem内核模块,通过rpm-ostree install kernel-modules-extra安装 - 重启 podman 的虚拟机
- 通过
podman machine ssh进入虚拟机执行modprobe sch_netem - 视情况执行如下
tc命令来设置网络条件
tc qdisc del dev veth0 root |
其中 veth0, veth1 这两个网卡,是在虚拟机中通过如下命令定位
podman ps获取 container idpodman inspect <container id> | grep Pid获取 Pidnsenter -t <pid> -n ip link看到 container 网卡对应的 id 为eth0@if5,注意这里的5ip link找到编号为5的网卡5: veth1@if2,因此为veth1
pcap 文件
- iperf-bad-net.pcap
- iperf-client-1mb.pcap
- iperf-client-buf-4k.pcap
- iperf-client-buf-auto-with-loss.pcap
- iperf-client-buf-auto.pcap
- tcptrace-client-defaultwindow.pcapng
- tcptrace-client-loss.pcapng
- Lab1-GreerBombal_ItsNotTheNetwork.pcapng
参考
- 用 Wireshark 分析 TCP 吞吐瓶颈 laixintao 老师的博客,看了之后才第一次真正读懂了 tcptrace 图的含义
- Wireshark TCP Trace Graph Tutorial 细致地介绍了一些常见网络问题下 tcptrace 图的表现,可以作为 laixintao 老师博客的补充
- 在Wireshark的tcptrace图中看清TCP拥塞控制算法的细节(CUBIC/BBR算法为例) 逻辑性地分析不同拥塞算法下 tcptrace 图的表现,但我本身对算法不熟悉,只能浅尝则止
思考题:这张图里,为什么不是一收到 ACK 后就立马发送新的包? ↩