Linux bcc/eBPF tcpdrop

在调试基于内核的 TCP 数据包丢弃的生产问题时,我记得看到 Eric Dumazet (Google) 在 Linux 4.7 中添加了一个名为 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_drops for dropped rx packets

@@ -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 丢弃。我已经在今天的 netconf18 上对 Eric 提出了一些改进建议,例如在某个地方添加一个“原因”参数,以便更人性化地描述数据包被丢弃的原因。也许 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() 来帮助解决这个问题,以及一个新的 tcp bcc 库,用于以后在 Python 中进行处理。我相信随着时间的推移,这段代码会被重用(和改进)。

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