Linux ftrace TCP 重传跟踪

为什么我的 Linux 系统会进行 TCP 重传?让我们看看数据包详细信息和内核状态:

./tcpretrans

时间 PID LADDR:LPORT – RADDR:RPORT 状态
05:16:44 3375 10.150.18.225:53874 R> 10.105.152.3:6001 已建立
05:16:44 3375 10.150.18.225:53874 R> 10.105.152.3:6001 已建立
05:16:54 4028 10.150.18.225:6002 R> 10.150.30.249:1710 已建立
05:16:54 4028 10.150.18.225:6002 R> 10.150.30.249:1710 已建立
05:16:54 4028 10.150.18.225:6002 R> 10.150.30.249:1710 已建立
05:16:55 0 10.150.18.225:47115 R> 10.71.171.158:6001 已建立
05:16:58 0 10.150.18.225:44388 R> 10.103.130.120:6001 已建立
^C
结束追踪…
惊人的!
tcpretrans 是我的perf-tools集合中的一个脚本,它使用ftrace来动态检测 tcp_retransmit_skb() 内核函数。这很棒的一个原因是开销可以忽略不计:它只会将检测添加到重传路径。它还使用现有的 Linux 内核功能 ftrace 和 kprobes,甚至不需要内核调试信息。
这不会跟踪每个数据包然后进行过滤,例如,如果我使用了任何 tcpdump/libpcap/kernel-packet-filter 方法,这对于高数据包速率可能会变得很痛苦。(我认为当您可以仅跟踪内核重传代码路径时,跟踪每个数据包也会很懒惰。)
使用 ftrace 之类的跟踪器意味着我还可以挖掘内核状态,这对于 tcpdump 之类的在线跟踪器是不可见的。我只包括了内核状态列,但任何内核端都可以包括在内。
-s 选项将包括导致 TCP 重新传输的内核堆栈跟踪:

./tcpretrans -s

时间 PID LADDR:LPORT – RADDR:RPORT 状态
06:21:10 19516 10.144.107.151:22 R> 10.13.106.251:32167 已建立
=> tcp_fastretrans_alert
=> tcp_ack
=> tcp_rcv_建立
=> tcp_v4_do_rcv
=> tcp_v4_rcv
=> ip_local_deliver_finish
=> ip_local_deliver
=> ip_rcv_finish
=> ip_rcv
=> __netif_receive_skb
=> netif_receive_skb
=> handle_incoming_queue
=> xennet_poll
=> net_rx_action
=> __do_softirq
=> call_softirq
=> do_softirq
=> irq_exit
=> xen_evtchn_do_upcall
=> xen_do_hypervisor_callback
[…]
在这种情况下,重传是在接收到数据包 (ip_rcv())、处理 TCP ACK (tcp_ack()) 之后发送的,然后通过 tcp_fastretrans_alert()。这是 TCP 快速重传。即,这些:

netstat -s | grep -in 回退

重传 242 段
46 次快速重传
2 次转发重传
3 次慢启动重传

以下是一些基于计时器的重传以供比较:
06:38:45 0 10.11.172.162:7102 R> 10.10.153.60:47538 已建立
=> tcp_write_timer_handler
=> tcp_write_timer
=> call_timer_fn
=> run_timer_softirq
=> __do_softirq
=> irq_exit
=> xen_evtchn_do_upcall
=> xen_hvm_callback_vector
=> default_idle
=> arch_cpu_idle
=> cpu_startup_entry
=> start_secondary
06:38:45 0 10.11.172.162:7102 R> 10.10.153.60:47539 已建立
=> tcp_write_timer_handler
=> tcp_write_timer
=> call_timer_fn
=> run_timer_softirq
=> __do_softirq
=> irq_exit
=> xen_evtchn_do_upcall
=> xen_hvm_callback_vector
=> default_idle
=> arch_cpu_idle
=> cpu_startup_entry
=> 休息初始化
=> start_kernel
=> x86_64_start_reservations
=> x86_64_start_kernel
这些来自回调和 tcp_write_timer_handler()。基于定时器的重传比快速重传更糟糕,因为它们为应用程序请求增加了基于定时器的延迟。在 Linux 中,这通常是 1000 毫秒。
我的基于 tcpretrans ftrace 的工具是一个 hack,如果不进行更改,可能无法在某些系统上运行(它也不支持 IPv6)。为了挖掘像 IP 地址这样的自定义细节,我真的应该使用像 SystemTap 这样的可编程跟踪器。但是,我想看看仅使用 ftrace 是否可行,因为它可以更容易地在我的生产环境(Netflix 云)中使用。
它是这样工作的:
使用 kprobes 动态检测 tcp_retransmit_skb(),并捕获 %di 寄存器。
假设 skb 指针在寄存器 %di 中(要知道肯定需要内核调试信息,我通常在生产系统上没有)。在非 x86 系统上,这很可能在另一个寄存器中,并且该脚本需要编辑。
等待一秒钟。
使用 skb 指针读取 tcp_retransmit_skb() 事件的内核缓冲区。
读取 /proc/net/tcp,并通过 skb 缓存套接字详细信息。
假设长时间会话(> 1 秒)发生重新传输,并且会话详细信息在读取时仍然在 /proc/net/tcp 中。
解析 tcp_retransmit_skb() 事件的内核缓冲区,并使用我们之前读取的 /proc/net/tcp 数据中的会话详细信息打印重传事件。
转到 3。
在早期版本中,我在重传发生时同步读取 /proc/net/tcp,但在频繁重传和数万个打开连接的生产系统上(导致 /proc/net/tcp 非常大),CPU 开销为这种做法太过分了。上面的方法每秒只读取 /proc/net/tcp 一次。
因此,在 /proc/net/tcp 中有套接字详细信息是一种恩赐,而无需使用 ftrace 挖掘它们。这是可能的,但是如果没有内核调试信息,脚本会变得更加脆弱,因为会有几个关于寄存器和结构偏移的假设,而不仅仅是 skb 在 %di 中。如果没有 /proc/net/tcp,我只会用内核 debuginfo 进行真正的尝试,这样我可以让它变得相当可靠。
到目前为止,tcpretrans 已被证明对于快速获取有关 TCP 重传的一些详细信息非常有用:不仅是源地址和目标地址和端口,还有内核状态和堆栈跟踪。

转载翻译自: https://www.brendangregg.com