最近在看 eBPF 的一些材料,看到 Brendan Gregg 的博客,后面想陆续针对一些主题翻译下,这一篇主要自介绍 tcpdrop,文章比较短,原文地址

在调试基于内核的 TCP 数据包丢弃的生产问题时,我记得在Linux 4.7中 Eric Dumazet(Google) 添加了一个名为 tcp_drop() 的新功能,我可以使用 kprobes 和 bcc/eBPF 进行跟踪。

这需要获得了更多的上下文来解释为什么发生这些丢包。例如:

# tcpdrop
TIME     PID    IP SADDR:SPORT          > DADDR:DPORT          STATE (FLAGS)
05:46:07 82093  4  10.74.40.245:50010   > 10.74.40.245:58484   ESTABLISHED (ACK)
    tcp_drop+0x1
    tcp_rcv_established+0x1d5
    tcp_v4_do_rcv+0x141
    tcp_v4_rcv+0x9b8
    ip_local_deliver_finish+0x9b
    ip_local_deliver+0x6f
    ip_rcv_finish+0x124
    ip_rcv+0x291
    __netif_receive_skb_core+0x554
    __netif_receive_skb+0x18
    process_backlog+0xba
    net_rx_action+0x265
    __softirqentry_text_start+0xf2
    irq_exit+0xb6
    xen_evtchn_do_upcall+0x30
    xen_hvm_callback_vector+0x1af

05:46:07 85153  4  10.74.40.245:50010   > 10.74.40.245:58446   ESTABLISHED (ACK)
    tcp_drop+0x1
    tcp_rcv_established+0x1d5
    tcp_v4_do_rcv+0x141
    tcp_v4_rcv+0x9b8
    ip_local_deliver_finish+0x9b
    ip_local_deliver+0x6f
    ip_rcv_finish+0x124
    ip_rcv+0x291
    __netif_receive_skb_core+0x554
    __netif_receive_skb+0x18
    process_backlog+0xba
    net_rx_action+0x265
    __softirqentry_text_start+0xf2
    irq_exit+0xb6
    xen_evtchn_do_upcall+0x30
    xen_hvm_callback_vector+0x1af

[...]

这是 tcpdrop,一个我为开源 bcc 项目编写的新工具。它显示了源包和目标包的详细信息,以及 TCP 会话状态(来自内核)、TCP 标志(来自包 TCP 报头)和导致这次丢包的内核堆栈跟踪。堆栈跟踪有助于回答为什么(您需要查看这些函数背后的代码才能理解它)。这也是网上没有的信息,所以你也不会通过使用包嗅探器( libpcap , tcpdump 等)看到这些。

我不得不强调Eric补丁中的这个小而重要的变化( tcp: increment sk drop for dropped rx packages )

@@ -6054,7 +6061,7 @@  int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
 
    if (!queued) {
 discard:
-       __kfree_skb(skb);
+       tcp_drop(sk, skb);
    }
    return 0;

从许多路径调用 __kfree_skb() 来释放套接字缓冲区,包括日常的代码路径。跟踪它干扰太多: 你的丢包的代码路径在许多正常路径中很难查找。但是使用新的 tcp_drop() 函数,我可以只跟踪 TCP 丢包。今天在 netcon18 上,我已经对 Eric 提了一些增强的建议,比如在某个地方添加一个 “reason” 参数,以便对丢包的原因进行更人性化的描述。也许 tcp_drop() 也应该成为一个跟踪点。

这里还有一些值得一提的代码,我的 tcpdrop 工具中的一些 eBPF/C 代码:

[...]
    // pull in details from the packet headers and the sock struct
    u16 family = sk->__sk_common.skc_family;
    char state = sk->__sk_common.skc_state;
    u16 sport = 0, dport = 0;
    u8 tcpflags = 0;
    struct tcphdr *tcp = skb_to_tcphdr(skb);
    struct iphdr *ip = skb_to_iphdr(skb);
    bpf_probe_read(&sport, sizeof(sport), &tcp->source);
    bpf_probe_read(&dport, sizeof(dport), &tcp->dest);
    bpf_probe_read(&tcpflags, sizeof(tcpflags), &tcp_flag_byte(tcp));
    sport = ntohs(sport);
    dport = ntohs(dport);

    if (family == AF_INET) {
        struct ipv4_data_t data4 = {.pid = pid, .ip = 4};
        bpf_probe_read(&data4.saddr, sizeof(u32), &ip->saddr);
        bpf_probe_read(&data4.daddr, sizeof(u32), &ip->daddr);
        data4.dport = dport;
        data4.sport = sport;
        data4.state = state;
        data4.tcpflags = tcpflags;
        data4.stack_id = stack_traces.get_stackid(ctx, 0);
        ipv4_events.perf_submit(ctx, &data4, sizeof(data4));
[...]

我以前在 bcc 中的 tcp 工具只能使用 struct sock 成员(如 tcplife )。但是这一次我需要数据包信息来查看 TCP 标志和数据包的方向。这是我第一次在 eBPF 中访问 TCP 和 IP 报头。我在 tcpdrop 中添加 skb_to_tcphdr() 和 skb_to_iphdr() 以帮助实现这一点,并为以后的 Python 处理添加了一个新的 tcp bcc 库。我相信随着时间的推移,这些代码会被重用(并得到改进)。