如何调试Linux TCP变慢/丢包

如何调试Linux TCP变慢/丢包

我正在尝试追踪某些特定的网络路径,这些路径的速度减慢至约 200KByte/秒。我通过各种测试看到了这种性能,包括scprsynciperf3

$ iperf3 -c 157.130.91.64 -R
Connecting to host 157.130.91.64, port 5201
Reverse mode, remote host 157.130.91.64 is sending
[  5] local 172.16.1.177 port 47862 connected to 157.130.91.64 port 5201
[ ID] Interval           Transfer     Bitrate
[  5]   0.00-1.00   sec   274 KBytes  2.25 Mbits/sec
[  5]   1.00-2.00   sec   199 KBytes  1.63 Mbits/sec
[  5]   2.00-3.00   sec   202 KBytes  1.66 Mbits/sec
[  5]   3.00-4.00   sec   198 KBytes  1.62 Mbits/sec
[  5]   4.00-5.00   sec   195 KBytes  1.60 Mbits/sec
[  5]   5.00-6.00   sec   184 KBytes  1.51 Mbits/sec
[  5]   6.00-7.00   sec   195 KBytes  1.60 Mbits/sec
[  5]   7.00-8.00   sec   209 KBytes  1.71 Mbits/sec
[  5]   8.00-9.00   sec   192 KBytes  1.58 Mbits/sec
[  5]   9.00-10.00  sec   187 KBytes  1.53 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  2.31 MBytes  1.94 Mbits/sec   65             sender
[  5]   0.00-10.00  sec  1.99 MBytes  1.67 Mbits/sec                  receiver

iperf Done.

有问题的主机位于第三方托管提供商上。我正在将数据下载到同一地点的数据中心。

我还没有完全确定共同点。我这边的网络有两个路由器,然后是一个 VM 主机,然后是一个虚拟机。 VM主机和最里面的路由器正在使用VxLAN(即通过ip link add vxlan100 type vxlan...),我怀疑这是问题的一部分。不过,我可以直接通过 VxLAN 获得机架内各个位置的 1Gbit 速度(使用 iperf3 测量)。我可以提供一个例子,但它读起来就像上面的那样,速度只快了几个数量级。

此时我唯一的线索是,如果我在 200KByte/sec 传输运行时捕获流量,我确实会发现 Wireshark 中 TCP 重传、TCP 乱序和 TCP Dup ACK 消息的发生率较高。这些似乎确实与缓慢有关。以更高速度运行的捕获流量也有一些 TCP 重传,但相对于发送的流量而言要少得多。

我的问题是如何调试它以找到丢失数据包的原因?有什么具体的地方我应该检查吗?似乎存在一定程度的数据包丢失导致速度变慢,但我不知道在哪里可以找到它。数据包丢失本身不会在较慢的速度下出现,也不会在我自己的数据中心内的机器内出现。似乎没有一个确切的单一位置可以预测发生这种情况,只是它肯定发生在我的数据中心中的虚拟机和另一个数据中心中的另一台机器之间。 (而且这台其他机器确实对 AWS 等其他地方有更高的传输速率,所以它是第三方机器,我已经检查过,它仅在发送到我的网络时才会重现)。

有任何想法吗?

答案1

为了后代,我将发布一个答案作为我如何解决这个问题的后续内容。我经历了几个步骤,它们可能对其他试图追踪数据包丢失的人有帮助。

iperf3是重现该问题的绝佳工具。

服务器:iperf3 -s

客户端:(iperf3 -c 157.130.91.64 -R或者不带 -R 则走另一个方向)

然后我曾经tcpdump将数据包记录到文件中,但是我在我可以访问的每个点同时运行此数据包捕获。由于我在主机之间控制两个 Linux 路由器,再加上 VxLAN 正在使用它们自己的接口,因此最终形成了 9 个独立的点,我使用以下命令捕获了这些点:

tcpdump -w /tmp/machineX-eno1.cap

然后,我scp将这些文件编辑到我的桌面上,得到了一个墨西哥卷饼,并使用 Wireshark UI 梳理了这九个不同的样本,以找出流数据包丢失的确切位置。 (Wireshark 作为一个 UI 非常出色,它甚至可以执行诸如解开 VxLAN 流量之类的操作,以便您可以看到内部的 TCP 流。但我将在tcpdump下面的示例中使用它,因为它提供了很好的可复制和粘贴的文本输出.)

现在,当链路饱和时,数据包丢失是正常的,但这种情况下的丢失比预期要早得多。对于 TCP,“窗口”用于跟踪“在需要确认之前可以发送多少数据”。窗口开始时大小适中,只要性能提高并且不存在数据包丢失,窗口就会增加。在一切正常的情况下,第一个丢失的数据包将在流中发生许多兆字节。但在问题案例中,在数据包丢失之前,它只能将大约 260k 数据传输到流中。

从我的示例中可以看出,数据包丢失tcpdump -n -r [filename]在客户端计算机上看起来像这样:

13:40:00.344219 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 246161:253401, ack 38, win 227, options [nop,nop,TS val 4032728542 ecr 187200696], length 7240
13:40:00.344232 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 253401, win 2745, options [nop,nop,TS val 187200770 ecr 4032728542], length 0
13:40:00.345642 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 253401:260641, ack 38, win 227, options [nop,nop,TS val 4032728542 ecr 187200696], length 7240
13:40:00.345673 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 260641, win 2858, options [nop,nop,TS val 187200771 ecr 4032728542], length 0
13:40:00.345772 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 260641:269329, ack 38, win 227, options [nop,nop,TS val 4032728542 ecr 187200696], length 8688

直到上面最后一个数据包为止,一切看起来都很好 - 您可以看到它传输到流中的偏移量 269329,如 所示seq 260641:269329。麻烦等待着下一个数据包:

13:40:00.345772 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 301185:304081, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 2896

我的天啊,你说,269329到301184的数据到哪里去了?确切地。下一个数据包显示 的数据seq 301185:304081。有时数据包可能会无序到达,但通过流查看却找不到有问题的数据。

我们通过查看以下内容进一步确认这ack一点:

13:40:00.345805 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 2994, options [nop,nop,TS val 187200771 ecr 4032728542], length 0
13:40:00.345857 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 3039, options [nop,nop,TS val 187200771 ecr 4032728542,nop,nop,sack 1 {301185:304081}], length 0
13:40:00.345934 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 304081:306977, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 2896
13:40:00.345934 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 306977:309873, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 2896
13:40:00.345934 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 309873:312769, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 2896
13:40:00.345935 IP 157.130.91.64.5201 > 172.16.1.177.50298: Flags [.], seq 312769:314217, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 1448
13:40:00.345971 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 3084, options [nop,nop,TS val 187200771 ecr 4032728542,nop,nop,sack 1 {301185:306977}], length 0
13:40:00.345995 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 3129, options [nop,nop,TS val 187200772 ecr 4032728542,nop,nop,sack 1 {301185:309873}], length 0
13:40:00.346005 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 3175, options [nop,nop,TS val 187200772 ecr 4032728542,nop,nop,sack 1 {301185:312769}], length 0
13:40:00.346011 IP 172.16.1.177.50298 > 157.130.91.64.5201: Flags [.], ack 269329, win 3197, options [nop,nop,TS val 187200772 ecr 4032728542,nop,nop,sack 1 {301185:314217}], length 0

您可以看到ack 269329从客户端重复发送回服务器,这表明客户端表示它只收到了流中该点之前的完整数据包集,并且丢失了后面的数据。

现在,如果我们检查来自路由器(“下一跳”)的 tcpdump,我们发现它实际上已收到(并转发 - 这是来自入口接口,这就是为什么目标 IP 不同,因为NAT,但出口显示相同的数据包):

13:40:00.356167 IP 157.130.91.64.5201 > 32.133.287.15.50298: Flags [.], seq 253401:273673, ack 38, win 227, options [nop,nop,TS val 4032728542 ecr 187200696], length 20272
13:40:00.356235 IP 157.130.91.64.5201 > 32.133.287.15.50298: Flags [.], seq 273673:293945, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200696], length 20272
13:40:00.356291 IP 157.130.91.64.5201 > 32.133.287.15.50298: Flags [.], seq 293945:301185, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 7240
13:40:00.356367 IP 157.130.91.64.5201 > 32.133.287.15.50298: Flags [.], seq 301185:304081, ack 38, win 227, options [nop,nop,TS val 4032728543 ecr 187200697], length 2896

因此很明显,路由器认为它发送了数据包,而主机从未收到过它们。

通过进一步调查,我观察到了这个难题的其他一些非常有趣的部分:

  • 这些数据包大于 1500 字节的 MTU。 现在 1500 MTU 不计入链路层,因此以太网标头不计入其中。但这些数据包仍然有 20k 左右,这是什么..
  • 这是由“大量发送卸载”/LSO(反之亦然的“大量接收卸载”/LRO)引起的这本质上是让操作系统向网卡发送和接收这些大数据包,并且网卡将重写必要的标头(包括 TCP 标头)以形成语义上等效的与 MTU 匹配的数据包集。所以操作系统发送了一个20k的数据包,网卡打了十几个数据包并发出去,每个数据包不到1500字节。接收时则相反。
  • 您可以禁用 LSO、LRO 和其他硬件优化如果您想从等式中删除它并更好地了解正在发送的内容 - 使用ethtool例如ethtool -K enp12s0 rx off tx off sg off tso off gso off gro off lro off rxvlan off txvlan off rxhash off
  • 就我而言,这实际上确实有所改进,但还不够。我在 1Gbit 网卡上获得了 10Mbit,而不是 1.5Mbit。
  • 还可以用来ethtool查找错误,例如ethtool -S enp12s0 | grep err- 这可以产生一些有趣的东西。查看您发现的各个错误(如果有),但这可能是问题的根源。
  • 在某些情况下,在其中一台路由器上使用 tcpdump 进行捕获时,运行 tcpdump 可以提高性能(这对我来说非常奇怪)。显然,这可能是由于某些缓冲区被提前清空等引起的,但是到了这个时候,问题出现在哪个设备上就已经很清楚了——如果你可以做一些数据包日志记录并在性能上有一个重大的改进,那就太好了指示您正在监控的设备是问题的根源。
  • 另外,可以肯定的是,您还需要检查电缆、SFP 适配器、交换机端口等(在我的案例中,这些都不是问题)

底线:转储数据包并缩小网络中问题所在的范围,然后使用上面的想法或您能想到的任何其他方法来查看它是否是根本原因,使用 iperf3 轻松模拟条件以确定您刚刚更改了什么达到了预期的(或进一步不预期的)效果。

就我而言,它是我用作 Linux 路由器的机器,是从不可靠的来源购买的(我不会直接命名,但与“blaamazon”押韵),并且在 ethtool 中显示错误。但值得注意的是,通过该路由器的大多数流量都很好,只有特定的流量模式才会出现上面所示的数据包丢失情况。因此,临时连接测试都会通过,但在特定负载下,数据包会丢失并且连接会很慢。但最终,使用上述程序发现并消除了这个不守规矩的设备。

相关内容