背景
在云函数的日常运营中,经常有用户提出要求协助排查网络问题。一般的手段就是使用 tcpdump
抓包,但是部署抓包往往是在问题发生之后,而且抓包后复现的时机也不确定,往往费时费力。本文讲述使用 BPF
记录 TCP
的重传和丢包记录,作为定位网络问题的一种辅助手段。
在 BPF 出现之前
在 BPF
出现之前,在 Linux
上我们也是可以解决这个问题的,只不过比较繁琐,需要对内核、调试器、编译器等许多基础知识有较深理解,参见这里。
以 TCP
重传为例,我们使用 perf
工具来查找跟踪的点位:
$ sudo perf list 'tcp:*'
List of pre-defined events (to be used in -e):
tcp:tcp_destroy_sock [Tracepoint event]
tcp:tcp_probe [Tracepoint event]
tcp:tcp_rcv_space_adjust [Tracepoint event]
tcp:tcp_receive_reset [Tracepoint event]
tcp:tcp_retransmit_skb [Tracepoint event]
tcp:tcp_retransmit_synack [Tracepoint event]
tcp:tcp_send_reset [Tracepoint event]
Metric Groups:
对于 TCP
重传,显然 tcp_retransmit_skb
是一个合适的跟踪点位。让我们来看看这个函数在内核代码中的签名:
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb, int segs);
struct sock
的定义在这里,它包含了我们所需要的信息。这是一个庞大且复杂的结构体,而且对于 kprobe
来说,我们只能使用寄存器以及偏移来输出其值。
要知道这个结构体中每个字段的真实偏移,我们需要内核的符号表,使用 GDB
来确定其值:
$ sudo apt install linux-image-unsigned-5.8.0-37-generic-dbgsym
$ gdb /usr/lib/debug/boot/vmlinux-5.8.0-37-generic
(gdb) ptype struct sock
...
(gdb) print (int)&((struct sock*)0)->__sk_common.skc_dport
$1 = 12
除此之外,我们还需要了解 X86_64
的调用约定,诸如函数调用的第一个参数使用 di
寄存器传递等。
有了这些背景知识后,我们可以使用 kprobe
来达成这一目标,记录 TCP
重传的例子如下:
$ echo 'p:kprobes/tcp_retransmit tcp_retransmit_skb port= 12(%di):u16 dst= 0(%di):u32 state= 18(%di):u8' >> /sys/kernel/debug/tracing/kprobe_events
$ echo 1 > /sys/kernel/debug/tracing/events/kprobes/tcp_retransmit/enable
这个例子不仅晦涩难懂,而且不易开发及调试,好在现在我们有了 BPF
。
BPF 横空出世
BPF
是一项革命性技术,它能在内核中运行沙箱程序, 而无需修改内核源码或者加载内核模块。
对于上面的例子,一个等价的 BPF
程序如下:
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
int log_tcp_retransmit(struct pt_regs *ctx, struct sock *sk) {
u16 port = sk->__sk_common.skc_dport;
u32 daddr = sk->__sk_common.skc_daddr;
u8 state = sk->__sk_common.skc_state;
bpf_trace_printk("tcp_retransmit port=%d dst=%d state=%dn", port, daddr, state);
return 0;
}
使用 C
编写,不需要理解 ABI
等细节,而且方便调试。你可以在这里找到完整的代码。对于 TCP
重传,也是一样的道理。
重传的日志记录在 /sys/kernel/debug/tracing/trace
,下面是一些真实的记录:
$ sudo cat /sys/kernel/debug/tracing/trace
# tracer: nop
#
# entries-in-buffer/entries-written: 103/103 #P:1
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
...
<idle>-0 [000] ..s. 2621728.977959: 0: tcp_retransmit port=37897 dst=831376843 state=1
<...>-3202753 [000] ..s. 2622104.077378: 0: tcp_retransmit port=37897 dst=831376843 state=1
...
转换一下格式,可以看到重传的目的地址及端口等信息:
代码语言:txt复制2621728.977959 2021-03-05 19:48:18.959403 tcp_retransmit 203.205.141.49 2452 TCP_ESTABLISHED
2622104.077378 2021-03-05 19:54:34.058822 tcp_retransmit 203.205.141.49 2452 TCP_ESTABLISHED
结论
本文讲述使用 BPF
带来的可观测性能力,获取 TCP
的重传及丢包记录,作为辅助定位网络问题的手段。与传统的 kprobe
方式相比, BPF
带来的可编程性极大地提升了开发效率,既没有增加系统的复杂度,也不会牺牲执行效率和安全性。