当前位置: 首页 > news >正文

使用 Linux tracepoint、perf 和 eBPF 跟踪数据包

大家读完觉得有帮助记得关注和点赞!!!

目录

1 破局

1.1 逃离迷宫:上帝视角

1.2 网络跟踪:渴求利器

1.3 巨人肩膀:perf/eBPF

2 Perf

2.1 安装 perf

2.2 测试环境

2.3 初体验:跟踪 ping 包

2.4 进阶:选择跟踪点

3 eBPF

3.1 eBPF 和 kprobes

3.2 安装

3.3 自定义跟踪器:Hello World

3.4 自定义跟踪器:改进

3.4.1 添加网卡信息

3.4.2 添加网络命名空间 ID

3.4.3 只跟踪 ICMP echo request/reply 包

3.5 最终效果

4 结束语


一段时间以来,我一直在寻找 Linux 上的底层网络调试(debug)工具。

Linux 允许在主机上用虚拟网卡(virtual interface)和网络命名空间(network namespace)构建复杂的网络。但出现故障时,排障(troubleshooting)相当痛苦。如果是 3 层路由问题,mtr 可以排上用场;但如果是更底层的问题,通常只能手动检查每个网 卡/网桥/网络命名空间/iptables 规则,用 tcpdump 抓一些包,以确定到底是什么状况。 如果不了解故障之前的网络配置,那排障时的感觉就像在走迷宫

1 破局

1.1 逃离迷宫:上帝视角

逃离迷宫的一种方式是在迷宫内不断左右尝试,寻找通往出口的道路。 如果是在玩迷宫游戏(置身迷宫内),那确实只能如此;但如果不是在玩游戏, 那还有另一种逃离方式:转换视角,高空俯视

用 Linux 术语来说,就是转换到内核视角(the kernel point of view)。在这种视 角下,网络命名空间不再是容器(“containers”),而只是一些标签(labels)。内核、 数据包、网卡等此时都是“肉眼可见”的对象(objects)

原文注:上面的 “containers” 我加了引号,因为从技术上说,网络命名空间是 构成 Linux 容器的核心部件之一。

1.2 网络跟踪:渴求利器

所以我想要的是这样一个工具,它可以直接告诉我 “嗨,我看到你的包了:它从属于这个 网络命名空间这个网卡上发出,然后依次经过这些函数”。

本质上,我想要的是一个 2 层的 mtr。这样的工具存在吗?不存在我们就造一个!

本文结束时,我们将拥有一个简单、易于使用的底层网络包跟踪器(packet tracker )。如果 ping 本机上的一个 Docker 容器,它会显示类似如下信息:

# ping -4 172.17.0.2
[  4026531957]          docker0 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026531957]      vetha373ab6 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026532258]             eth0 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026532258]             eth0   reply #17146.001 172.17.0.2 -> 172.17.0.1
[  4026531957]      vetha373ab6   reply #17146.001 172.17.0.2 -> 172.17.0.1
[  4026531957]          docker0   reply #17146.001 172.17.0.2 -> 172.17.0.1

1.3 巨人肩膀:perf/eBPF

在本文中,我将聚焦两个跟踪工具:perf 和 eBPF

perf 是 Linux 上的最重要的性能分析工具之一。它和内核出自同一个源码树(source tree),但编译需要针对指定的内核版本。perf 可以跟踪内核,也可以跟踪用户程序, 还可用于采样或者设置跟踪点,可以把它想象成开销更低但功能更强大的 strace。 本文只会使用非常简单的 perf 命令。想了解更多,强烈建议访问 Brendan Gregg的博客。

eBPF 是 Linux 内核新近加入的,其中 e 是 extended 的缩写。从名字可以看出,它 是 BPF(Berkeley Packet Filter)字节码过滤器的增强版,后者是 BSD family 的网络包 过滤工具。在 Linux 上,eBPF 可以在运行中的内核(live kernel)中安全地执行任何平 台无关(platform independent)代码,只要这些代码满足一些安全前提。例如,在程序执 行之前必须验证内存访问合法性,而且要能证明程序会在有限时间内退出。如果内核无法验 证这些条件,那即使 eBPF 代码是安全的并且确定会退出,它也仍然会被拒绝。

eBPF 程序可用于 QoS 网络分类器(network classifier)、XDP(eXpress Data Plane) 很底层的网络功能和过滤功能组件、跟踪代理(tracing agent),以及其他很多方面。 任何在 /proc/kallsyms 导出的符号(内核函数)和 tracepoint, 都可以插入 eBPF tracing 代码

本文将主要关注 attach 到 tracepoints 的跟踪代理(tracing agents attached to tracepoints)。想看在内核函数埋点进行跟踪的例子,或者入门级介绍,建议阅读我之前的 eBPF 文章英文 ,中文翻译。

2 Perf

本文只会使用 perf 做非常简单的内核跟踪。

2.1 安装 perf

我的环境基于 Ubuntu 17.04 (Zesty):

$ sudo apt install linux-tools-generic
$ perf # test perf

2.2 测试环境

我们将使用 4 个 IP,其中 2 个为外部可路由网段(192.168):

  1. localhost,IP 127.0.0.1
  2. 一个干净的容器,IP 172.17.0.2
  3. 我的手机,通过 USB 连接,IP 192.168.42.129
  4. 我的手机,通过 WiFi 连接,IP 192.168.43.1

2.3 初体验:跟踪 ping 包

perf trace 是 perf 子命令,能够跟踪 packet 路径,默认输出类似于 strace(头 信息少很多)。

跟踪 ping 向 172.17.0.2 容器的包,这里我们只关心 net 事件,忽略系统调用信息:

$ sudo perf trace --no-syscalls --event 'net:*' ping 172.17.0.2 -c1 > /dev/null0.000 net:net_dev_queue:dev=docker0 skbaddr=0xffff96d481988700 len=98)0.008 net:net_dev_start_xmit:dev=docker0 queue_mapping=0 skbaddr=0xffff96d481988700 vlan_tagged=0 vlan_proto=0x0000 vlan_tci=0x0000 protocol=0x0800 ip_summed=0 len=98 data_len=0 network_offset=14 transport_offset_valid=1 transport_offset=34 tx_flags=0 gso_size=0 gso_segs=0 gso_type=0)0.014 net:net_dev_queue:dev=veth79215ff skbaddr=0xffff96d481988700 len=98)0.016 net:net_dev_start_xmit:dev=veth79215ff queue_mapping=0 skbaddr=0xffff96d481988700 vlan_tagged=0 vlan_proto=0x0000 vlan_tci=0x0000 protocol=0x0800 ip_summed=0 len=98 data_len=0 network_offset=14 transport_offset_valid=1 transport_offset=34 tx_flags=0 gso_size=0 gso_segs=0 gso_type=0)0.020 net:netif_rx:dev=eth0 skbaddr=0xffff96d481988700 len=84)0.022 net:net_dev_xmit:dev=veth79215ff skbaddr=0xffff96d481988700 len=98 rc=0)0.024 net:net_dev_xmit:dev=docker0 skbaddr=0xffff96d481988700 len=98 rc=0)0.027 net:netif_receive_skb:dev=eth0 skbaddr=0xffff96d481988700 len=84)0.044 net:net_dev_queue:dev=eth0 skbaddr=0xffff96d481988b00 len=98)0.046 net:net_dev_start_xmit:dev=eth0 queue_mapping=0 skbaddr=0xffff96d481988b00 vlan_tagged=0 vlan_proto=0x0000 vlan_tci=0x0000 protocol=0x0800 ip_summed=0 len=98 data_len=0 network_offset=14 transport_offset_valid=1 transport_offset=34 tx_flags=0 gso_size=0 gso_segs=0 gso_type=0)0.048 net:netif_rx:dev=veth79215ff skbaddr=0xffff96d481988b00 len=84)0.050 net:net_dev_xmit:dev=eth0 skbaddr=0xffff96d481988b00 len=98 rc=0)0.053 net:netif_receive_skb:dev=veth79215ff skbaddr=0xffff96d481988b00 len=84)0.060 net:netif_receive_skb_entry:dev=docker0 napi_id=0x3 queue_mapping=0 skbaddr=0xffff96d481988b00 vlan_tagged=0 vlan_proto=0x0000 vlan_tci=0x0000 protocol=0x0800 ip_summed=2 hash=0x00000000 l4_hash=0 len=84 data_len=0 truesize=768 mac_header_valid=1 mac_header=-14 nr_frags=0 gso_size=0 gso_type=0)0.061 net:netif_receive_skb:dev=docker0 skbaddr=0xffff96d481988b00 len=84)

只保留事件名和 skbaddr,看起来清晰很多:

net_dev_queue           dev=docker0     skbaddr=0xffff96d481988700
net_dev_start_xmit      dev=docker0     skbaddr=0xffff96d481988700
net_dev_queue           dev=veth79215ff skbaddr=0xffff96d481988700
net_dev_start_xmit      dev=veth79215ff skbaddr=0xffff96d481988700
netif_rx                dev=eth0        skbaddr=0xffff96d481988700
net_dev_xmit            dev=veth79215ff skbaddr=0xffff96d481988700
net_dev_xmit            dev=docker0     skbaddr=0xffff96d481988700
netif_receive_skb       dev=eth0        skbaddr=0xffff96d481988700net_dev_queue           dev=eth0        skbaddr=0xffff96d481988b00
net_dev_start_xmit      dev=eth0        skbaddr=0xffff96d481988b00
netif_rx                dev=veth79215ff skbaddr=0xffff96d481988b00
net_dev_xmit            dev=eth0        skbaddr=0xffff96d481988b00
netif_receive_skb       dev=veth79215ff skbaddr=0xffff96d481988b00
netif_receive_skb_entry dev=docker0     skbaddr=0xffff96d481988b00
netif_receive_skb       dev=docker0     skbaddr=0xffff96d481988b00

这里面有很多信息。

首先注意,skbaddr 在中间变了0xffff96d481988700 -> 0xffff96d481988b00) 。变的这里,就是生成了 ICMP echo reply 包,并作为应答包发送的地方。接下来的 时间,这个包的 skbaddr 保持不变,说明没有 copy。copy 非常耗时。

其次,我们可以清楚地看到 packet 在内核的传输路径

  1. docker0 网桥
  2. veth pair 的宿主机端(veth79215ff)
  3. veth pair 的容器端(容器里的 eth0
  4. 接下来是相反的返回路径

至此,虽然我们还没有看到网络命名空间,但已经得到了一个不错的全局视图。

2.4 进阶:选择跟踪点

上面的信息有些杂,还有很多重复。我们可以选择几个最合适的跟踪点,使得输出看起来 更清爽。要查看所有可用的网络跟踪点,执行 perf list

$ sudo perf list 'net:*'

这个命令会列出 tracepoint 列表,格式 net:netif_rx冒号前面是事件类型 ,后面是事件名字。这里我选择 4 个:

  • net_dev_queue
  • netif_receive_skb_entry
  • netif_rx
  • napi_gro_receive_entry

效果:

$ sudo perf trace --no-syscalls           \--event 'net:net_dev_queue'           \--event 'net:netif_receive_skb_entry' \--event 'net:netif_rx'                \--event 'net:napi_gro_receive_entry'  \ping 172.17.0.2 -c1 > /dev/null0.000 net:net_dev_queue:dev=docker0 skbaddr=0xffff8e847720a900 len=98)0.010 net:net_dev_queue:dev=veth7781d5c skbaddr=0xffff8e847720a900 len=98)0.014 net:netif_rx:dev=eth0 skbaddr=0xffff8e847720a900 len=84)0.034 net:net_dev_queue:dev=eth0 skbaddr=0xffff8e849cb8cd00 len=98)0.036 net:netif_rx:dev=veth7781d5c skbaddr=0xffff8e849cb8cd00 len=84)0.045 net:netif_receive_skb_entry:dev=docker0 napi_id=0x1 queue_mapping=0

漂亮!

3 eBPF

前面介绍的内容已经可以满足大部分 tracing 场景的需求了。如果你只是想学习如何在 Linux 上跟踪一个 packet 的传输路径,那到此已经足够了。但如果想跟更进一步,学习如 何写一个自定义的过滤器,跟踪网络命名空间、源 IP、目的 IP 等信息,请继续往下读。

3.1 eBPF 和 kprobes

从 Linux 内核 4.7 开始,eBPF 程序可以 attach 到内核跟踪点(kernel tracepoints) 。在此之前,要完成类似的工作,只能用 kprobes 之类的工具 attach 到导出的内核函 数(exported kernel sysbols)。后者虽然可以完成工作,但存在很多不足:

  1. 内核的内部(internal)API 不稳定
  2. 出于性能考虑,大部分网络相关的内层函数(inner functions)都是内联或者静态的( inlined or static),两者都不可探测
  3. 找出调用某个函数的所有地方是相当乏味的,有时所需的字段数据不全具备

这篇博客的早期版本使用了 kprobes,但结果并不是太好。 现在,诚实地说,通过内核 tracepoints 访问数据比通过 kprobe 要更加乏味。我尽量保 持本文简洁,如果你想了解本文稍老的版本,可以访问这里英文 ,中文翻译。

3.2 安装

我不是一个徒手汇编迷(fans of handwritten assembly),因此接下来将使用 bccbcc 是一个灵活强大的工具,允许用受限的 C 语法(restricted C)写内核探测代码,然后用 Python 在用户态做控制。这种方式对于生产环境算是重量级,但对开发来说非常完美。

注意:eBPF 需要 Linux Kernel 4.7+。

Ubuntu 17.04 安装 (GitHub) bcc:

# Install dependencies
$ sudo apt install bison build-essential cmake flex git libedit-dev python zlib1g-dev libelf-dev libllvm4.0 llvm-dev libclang-dev luajit luajit-5.1-dev# Grab the sources
$ git clone https://github.com/iovisor/bcc.git
$ mkdir bcc/build
$ cd bcc/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr
$ make
$ sudo make install

3.3 自定义跟踪器:Hello World

接下来我们从一个简单的 hello world 例子展示如何在底层打点。我们还是用上一篇 文章里选择的四个点:

  • net_dev_queue
  • netif_receive_skb_entry
  • netif_rx
  • napi_gro_receive_entry

每当网络包经过这些点,我们的处理逻辑就会触发。为保持简单,我们的处理逻辑只是将程 序的 comm 字段(16 字节)发送出来(到用户空间程序),这个字段里存的是发 送相应的网络包的程序的名字。

#include <bcc/proto.h>
#include <linux/sched.h>// Event structure
struct route_evt_t {char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(route_evt);static inline int do_trace(void* ctx, struct sk_buff* skb)
{// Built event for userlandstruct route_evt_t evt = {};bpf_get_current_comm(evt.comm, TASK_COMM_LEN);// Send event to userlandroute_evt.perf_submit(ctx, &evt, sizeof(evt));return 0;
}/*** Attach to Kernel Tracepoints*/
TRACEPOINT_PROBE(net, netif_rx) {return do_trace(args, (struct sk_buff*)args->skbaddr);
}TRACEPOINT_PROBE(net, net_dev_queue) {return do_trace(args, (struct sk_buff*)args->skbaddr);
}TRACEPOINT_PROBE(net, napi_gro_receive_entry) {return do_trace(args, (struct sk_buff*)args->skbaddr);
}TRACEPOINT_PROBE(net, netif_receive_skb_entry) {return do_trace(args, (struct sk_buff*)args->skbaddr);
}

可以看到,程序 attach 到 4 个 tracepoint,并会访问 skbaddr 字段,将其传给处理 逻辑函数,这个函数现在只是将程序名字发送出来。大家可能会有疑问:args->skbaddr 是 哪里来的?答案是,每次用 TRACEPONT_PROBE 定义一个 tracepoint,bcc 就会为其自 动生成 args 参数,由于它是动态生成的,因此要查看它的定义不太容易。

不过,有另外一种简单的方式可以查看。在 Linux 上每个 tracepoint 都对应一个 /sys/kernel/debug/tracing/events entry。例如对于 net:netif_rx

$ cat /sys/kernel/debug/tracing/events/net/netif_rx/format
name: netif_rx
ID: 1183
format:field:unsigned short common_type;         offset:0; size:2; signed:0;field:unsigned char common_flags;         offset:2; size:1; signed:0;field:unsigned char common_preempt_count; offset:3; size:1; signed:0;field:int common_pid;                     offset:4; size:4; signed:1;field:void * skbaddr;         offset:8;  size:8; signed:0;field:unsigned int len;       offset:16; size:4; signed:0;field:__data_loc char[] name; offset:20; size:4; signed:1;print fmt: "dev=%s skbaddr=%p len=%u", __get_str(name), REC->skbaddr, REC->len

注意最后一行 print fmt,这正是 perf trace 打印相应消息的格式。

在底层插入这样的探测点之后,我们再写个 Python 脚本,接收内核发出来的消息,每个 eBPF 发出的数据都打印一行:

#!/usr/bin/env python
# coding: utf-8from socket import inet_ntop
from bcc import BPF
import ctypes as ctbpf_text = '''<SEE CODE SNIPPET ABOVE>'''TASK_COMM_LEN = 16 # linux/sched.hclass RouteEvt(ct.Structure):_fields_ = [("comm",    ct.c_char * TASK_COMM_LEN),]def event_printer(cpu, data, size):# Decode eventevent = ct.cast(data, ct.POINTER(RouteEvt)).contents# Print eventprint "Just got a packet from %s" % (event.comm)if __name__ == "__main__":b = BPF(text=bpf_text)b["route_evt"].open_perf_buffer(event_printer)while True:b.kprobe_poll()

现在可以测试了,注意需要 root 权限。

注意:现在的代码没有对包做任何过滤,因此即便你的机器网络流量很小,输出也很可能刷屏!

$> sudo python ./tracepkt.py
...
Just got a packet from ping6
Just got a packet from ping6
Just got a packet from ping
Just got a packet from irq/46-iwlwifi
...

上面的输出显示,我正在使用 ping 和 ping6,另外 WiFi 驱动也收到了一些包。

3.4 自定义跟踪器:改进

接下来添加一些有用的数据/过滤条件。

3.4.1 添加网卡信息

首先,可以安全地删除前面代码中的 comm 字段,它在这里没什么用处。然后,include net/inet_sock.h 头文件,这里有我们所需要的函数声明。最后给 event 结构体添加 char ifname[IFNAMSIZ] 字段。

现在可以从 device 结构体中访问 device name 字段。这里开始展示出 eBPF 代码的 强大之处:我们可以访问任何受控范围内的字段。

// Get device pointer, we'll need it to get the name and network namespace
struct net_device *dev;
bpf_probe_read(&dev, sizeof(skb->dev), ((char*)skb) + offsetof(typeof(*skb), dev));// Load interface name
bpf_probe_read(&evt.ifname, IFNAMSIZ, dev->name);

现在你可以测试一下,这样是能工作的。注意相应地修改一下 Python 部分。那么,它是怎 么工作的呢?

我们引入了 net_device 结构体来访问网卡名字字段。第一个 bpf_probe_read 从内核 的网络包中将网卡名字拷贝到 dev,第二个将其接力复制到 evt.ifname

不要忘了,eBPF 的目标是允许安全地编写在内核运行的脚本。这意味着,随机内存访问是绝 对不允许的。所有的内存访问都要经过验证。除非要访问的内存在协议栈,否则都需要通 过 bpf_probe_read 读取数据。这会使得代码看起来很繁琐,但非常安全。bpf_probe_read 像是 memcpy 的一个更安全的版本,它定义在内核源文件 bpf_trace.c 中:

  1. 它和 memcpy 类似,因此注意内存拷贝的代价
  2. 如果遇到错误,它会返回一个错误和一个初始化为 0 的缓冲区,而不会造成程序崩溃或停 止运行

接下来为使代码看起来更加简洁,我将使用如下宏:

#define member_read(destination, source_struct, source_member)                 \do{                                                                          \bpf_probe_read(                                                            \destination,                                                             \sizeof(source_struct->source_member),                                    \((char*)source_struct) + offsetof(typeof(*source_struct), source_member) \);                                                                         \} while(0)

这样上面的例子就可以写成:

member_read(&dev, skb, dev);

3.4.2 添加网络命名空间 ID

采集网络命名空间信息非常有用,但是实现起来要复杂一些。原理上可以从两个地方访问:

  1. socket 结构体 sk
  2. device 结构体 dev

当我在写 solisten.py时 ,我使用的时 socket 结构体。不幸的是,不知道为什么,网络命名空间 ID 在跨命名空间的地 方消失了。这个字段全是 0,很明显是有非法内存访问时的返回值(回忆前面介绍的 bpf_probe_read 如何处理错误)。

幸好 device 结构体工作正常。想象一下,我们可以问一个 packet 它在哪个网卡,进而 问这个网卡它在哪个网络命名空间

struct net* net;// Get netns id. Equivalent to: evt.netns = dev->nd_net.net->ns.inum
possible_net_t *skc_net = &dev->nd_net;
member_read(&net, skc_net, net);
struct ns_common* ns = member_address(net, ns);
member_read(&evt.netns, ns, inum);

其中的宏定义如下:

#define member_address(source_struct, source_member) \
({                                                   \void* __ret;                                       \__ret = (void*) (((char*)source_struct) + offsetof(typeof(*source_struct), source_member)); \__ret;                                             \
})

这个宏还可以用于简化 member_read,这个就留给读者作为练习了。

好了,有了以上实现,我们再运行的效果就是:

$> sudo python ./tracepkt.py
[  4026531957]          docker0
[  4026531957]      vetha373ab6
[  4026532258]             eth0
[  4026532258]             eth0
[  4026531957]      vetha373ab6
[  4026531957]          docker0

如果 ping 一个容器,你看到的就是类似上面的输出。packet 首先经过本地的 docker0 网桥, 然后经 veth pair 跨过网络命名空间,最后到达容器的 eth0 网卡。应答包沿着相反的路径回 到宿主机。

至此,功能是实现了,不过还太粗糙,继续改进。

3.4.3 只跟踪 ICMP echo request/reply 包

这次我们将读取包的 IP 信息,这里我只展示 IPv4 的例子,IPv6 的与此类似。

不过,事情也并没有那么简单。我们是在和 kernel 的网络部分打交道。一些包可能还没被打 开,这意味着,变量的很多字段是没有初始化的。我们只能从 MAC 头开始,用 offset 的方式 计算 IP 头和 ICMP 头的位置。

首先从 MAC 头地址推导 IP 头地址。这里我们不(从 skb 的相应字段)加载 MAC 头长 度信息,而假设它就是固定的 14 字节。

// Compute MAC header address
char* head;
u16 mac_header;member_read(&head,       skb, head);
member_read(&mac_header, skb, mac_header);// Compute IP Header address
#define MAC_HEADER_SIZE 14;
char* ip_header_address = head + mac_header + MAC_HEADER_SIZE;

这假设了 IP 头从 skb->head + skb->mac_header + MAC_HEADER_SIZE 处开始。 现在可以解析 IP 头第一个字节的前 4 个 bit 了:

// Load IP protocol version
u8 ip_version;
bpf_probe_read(&ip_version, sizeof(u8), ip_header_address);
ip_version = ip_version >> 4 & 0xf;// Filter IPv4 packets
if (ip_version != 4) {return 0;
}

然后加载整个 IP 头,获取 IP 地址,以使得 Python 程序的输出看起来更有意义。另外注意,IP 包内的下一个头就是 ICMP 头。

// Load IP Header
struct iphdr iphdr;
bpf_probe_read(&iphdr, sizeof(iphdr), ip_header_address);// Load protocol and address
u8 icmp_offset_from_ip_header = iphdr.ihl * 4;
evt.saddr[0] = iphdr.saddr;
evt.daddr[0] = iphdr.daddr;// Filter ICMP packets
if (iphdr.protocol != IPPROTO_ICMP) {return 0;
}

最后,加载 ICMP 头,如果是 ICMP echo request 或 reply,就读取序列号:

// Compute ICMP header address and load ICMP header
char* icmp_header_address = ip_header_address + icmp_offset_from_ip_header;
struct icmphdr icmphdr;
bpf_probe_read(&icmphdr, sizeof(icmphdr), icmp_header_address);// Filter ICMP echo request and echo reply
if (icmphdr.type != ICMP_ECHO && icmphdr.type != ICMP_ECHOREPLY) {return 0;
}// Get ICMP info
evt.icmptype = icmphdr.type;
evt.icmpid   = icmphdr.un.echo.id;
evt.icmpseq  = icmphdr.un.echo.sequence;// Fix endian
evt.icmpid  = be16_to_cpu(evt.icmpid);
evt.icmpseq = be16_to_cpu(evt.icmpseq);

这就是全部工作了。

如果想过滤特定的 ping 进程的包,那可以认为 evt.icmpid 就是相应 ping 进程的进程号, 至少 Linux 上如此。

3.5 最终效果

再写一些比较简单的 Python 程序配合,就可以测试我们的跟踪器在多种场景下的用途。 以 root 权限启动这个程序,在不同终端发起几个 ping 进程,就会看到:

# ping -4 localhost
[  4026531957]               lo request #20212.001 127.0.0.1 -> 127.0.0.1
[  4026531957]               lo request #20212.001 127.0.0.1 -> 127.0.0.1
[  4026531957]               lo   reply #20212.001 127.0.0.1 -> 127.0.0.1
[  4026531957]               lo   reply #20212.001 127.0.0.1 -> 127.0.0.1

这个 ICMP 请求是进程 20212(Linux ping 的 ICMP ID)在 loopback 网卡发出的,最后的 reply 原路回到了这个 loopback。这个环回接口既是发送网卡又是接收网卡。

如果是我的 WiFi 网关会是什么样子内?

# ping -4 192.168.43.1
[  4026531957]           wlp2s0 request #20710.001 192.168.43.191 -> 192.168.43.1
[  4026531957]           wlp2s0   reply #20710.001 192.168.43.1 -> 192.168.43.191

可以看到,这种情况下走的是 WiFi 网卡,也没问题。

另外说点题外话:还记得刚开始只打印程序名的版本吗?如果在上面这种情况下执行,ICMP 请求打印的程序名会是 ping,而应答包打印的程序名会是 WiFi 驱动,因为是驱动发的应答包,至 少 Linux 上是如此。

最后还是拿我最喜欢的例子来做测试:ping 容器。之所以最喜欢并不是因为 Docker,而是 它展示了eBPF 的强大,就像给 ping 过程做了一次 X 射线检查

# ping -4 172.17.0.2
[  4026531957]          docker0 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026531957]      vetha373ab6 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026532258]             eth0 request #17146.001 172.17.0.1 -> 172.17.0.2
[  4026532258]             eth0   reply #17146.001 172.17.0.2 -> 172.17.0.1
[  4026531957]      vetha373ab6   reply #17146.001 172.17.0.2 -> 172.17.0.1
[  4026531957]          docker0   reply #17146.001 172.17.0.2 -> 172.17.0.1

来点 ASCII 艺术,就变成:

       Host netns           | Container netns
+---------------------------+-----------------+
| docker0 ---> veth0e65931 ---> eth0          |
+---------------------------+-----------------+

4 结束语

在 eBPF/bcc 出现之前,要深入的排查和追踪很多网络问题,只能靠给内核打补丁。现在,我 们可以比较方便地用 eBPF/bcc 编写一些工具来完成这些事情。tracepoint 也很方便 ,提醒了我们可以在哪些地方进行探测,从而避免了去看繁杂的内核代码。即使是 kprobe 无法探测 的一些地方,例如一些内联函数和静态函数,eBPF/bcc 也可以探测。

本文的例子要添加对 IPv6 的支持也非常简单,就留给读者作为练习。

要使本文更加完善的话,需要对我们的程序做性能测试。但考虑到文章本身已经非常 长,这里就不做了。

对本文代码进行改进,然后用在跟踪路由和 iptables 判决,或是 ARP 包,也是很有意思的。 这将会把它变成一个完美的 X 射线跟踪器,对像我这样需要经常处理复杂网络问题的 人来说将非常有用。

完整的(包含 IPv6 支持)代码: https://github.com/yadutaf/tracepkt。

最后,我要感谢 @fcabestre帮我将这篇文章的草稿从 一个异常的硬盘上恢复出来,感谢 @bluxte的耐心审读, 以及技术上使得本文成为可能的 bcc 团队。

相关文章:

使用 Linux tracepoint、perf 和 eBPF 跟踪数据包

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 目录 1 破局 1.1 逃离迷宫&#xff1a;上帝视角 1.2 网络跟踪&#xff1a;渴求利器 1.3 巨人肩膀&#xff1a;perf/eBPF 2 Perf 2.1 安装 perf 2.2 测试环境 2.3 初体验&#xff1a;跟踪 ping …...

给DevOps加点料:融入安全性的DevSecOps

从前&#xff0c;安全防护只是特定团队的责任&#xff0c;在开发的最后阶段才会介入。当开发周期长达数月、甚至数年时&#xff0c;这样做没什么问题&#xff1b;但是现在&#xff0c;这种做法现在已经行不通了。 采用 DevOps 可以有效推进快速频繁的开发周期&#xff08;有时…...

MySQL视图笔记

视图的理解 ①视图是一种 虚拟表 &#xff0c;本身是 不具有数据 的&#xff0c;占用很少的内存空间&#xff0c;它是 SQL 中的一个重要概念。 ②视图建立在已有表的基础上, 视图赖以建立的这些表称为基表。 ③对视图中的数据进行增加删除和修改&#xff0c;对应的数据表&a…...

【Ubuntu与Linux操作系统:十、C/C++编程】

第10章 C/C编程 10.1 Linux编程基础 Linux编程基础涵盖了C/C语言在Linux环境中的特点和使用方法。Linux以其高性能和开源特性成为系统编程的重要平台。 1. C语言与Linux的关系 Linux内核主要是用C语言编写的&#xff0c;因此学习C语言是理解Linux底层机制的必要前提。C语言的…...

豆包MarsCode:可以在线用的智能AI编程助手

大家好&#xff0c;今天我想和大家分享一个我最近发现的宝藏工具——豆包MarsCode。 作为一个程序员&#xff0c;我一直在寻找能够提高工作效率、快捷、 优化代码质量的在线编程工具。豆包MarsCode IDE&#xff0c;这个由字节跳动推出的智能编程助手&#xff0c;让我眼前一亮&…...

RabbitMQ基础(简单易懂)

RabbitMQ高级篇请看&#xff1a; RabbitMQ高级篇-CSDN博客 目录 什么是RabbitMQ&#xff1f; MQ 的核心概念 1. RabbitMQ 的核心组件 2. Exchange 的类型 3. 数据流向说明 如何安装RabbitQueue&#xff1f; WorkQueue&#xff08;工作队列&#xff09;&#xff1a; Fa…...

UE5 使用内置组件进行网格切割

UE引擎非常强大&#xff0c;直接内置了网格切割功能并封装为蓝图节点&#xff0c;这项功能在UE4中就存在&#xff0c;并且无需使用Chaos等模块。那么就来学习下如何使用内置组件实现网格切割。 1.配置测试用StaticMesh 对于被切割的模型&#xff0c;需要配置一些参数。以UE5…...

【面试题】技术场景 6、Java 生产环境 bug 排查

生产环境 bug 排查思路 分析日志&#xff1a;首先通过分析日志查看是否存在错误信息&#xff0c;利用之前讲过的 elk 及查看日志的命令缩小查找错误范围&#xff0c;方便定位问题。远程 debug 适用环境&#xff1a;一般公司正式生产环境不允许远程 debug&#xff0c;多在测试环…...

macOS 安装tomcat9

macOS 安装tomcat9 URL&#xff1a;https://tomcat.apache.org/download-90.cgi 解压之后放到指定目录 /Users/lanren/install/tomcat-9 自己取个名字就行 给权限&#xff1a; ① 先进行权限修改&#xff1a;终端输入sudo chmod 755 /Users/lanren/install/tomcat-9/bin/…...

多线程之旅:属性及其基本操作

上次分享到了&#xff0c;多线程中是是如何创建的&#xff0c;那么接下来&#xff0c;小编继续分享下多线程的相关知识。 多线程中的一些基本属性。 基本属性 属性获取方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDemo()是否存活isAlive()是…...

隧道网络:为数据传输开辟安全通道

什么是隧道网络&#xff1f; 想象一下&#xff0c;你正在一个陌生的城市旅行&#xff0c;并且想要访问家里的电脑。但是&#xff0c;直接连接是不可能的&#xff0c;因为家庭网络通常受到防火墙或路由器的保护&#xff0c;不允许外部直接访问。这时候&#xff0c;隧道网络&…...

Python爬虫-汽车之家各车系周销量榜数据

前言 本文是该专栏的第43篇,后面会持续分享python爬虫干货知识,记得关注。 在本专栏之前,笔者在文章《Python爬虫-汽车之家各车系月销量榜数据》中,有详细介绍,如何爬取“各车系车型的月销量榜单数据”的方法以及完整代码教学教程。 而本文,笔者同样以汽车之家平台为例,…...

【机器学习】时序数据与序列建模:理论与实践的全面指南

云边有个稻草人-CSDN博客 目录 云边有个稻草人-CSDN博客 引言 一、时序数据的特点与挑战 1.1 时序数据的特点 1.2 序列建模的挑战 二、传统方法概览 2.1 ARIMA 模型 2.2 Prophet 三、深度学习方法 3.1 RNN 和 LSTM 3.2 Attention 和 Transformer 3.3 自监督学习 四、…...

java.net.SocketException: Connection reset 异常原因分析和解决方法

导致此异常的原因&#xff0c;总结下来有三种情况&#xff1a; 一、服务器端偶尔出现了异常&#xff0c;导致连接关闭 解决方法&#xff1a; 采用出错重试机制 二、 服务器端和客户端使用的连接方式不一致 解决方法&#xff1a; 服务器端和客户端使用相同的连接方式&#xff…...

【华为OD-E卷 - 恢复数字序列 100分(python、java、c++、js、c)】

【华为OD-E卷 - 恢复数字序列 100分&#xff08;python、java、c、js、c&#xff09;】 题目 对于一个连续正整数组成的序列&#xff0c;可以将其拼接成一个字符串&#xff0c;再将字符串里的部分字符打乱顺序。如序列8 9 10 11 12&#xff0c;拼接成的字符串为89101112&…...

05、Redis持久化

Redis是在内存中操作的&#xff0c;我们服器关闭重启机器后&#xff0c;正常是之前在redis中操作的数据都不存在了&#xff0c;但是实际上我们开机后重新启动redis服务&#xff0c;一样可以看到之前操作的数据。这是为什么呢&#xff1f; 我们在redis的安装目录下可以看到有一…...

Python爬虫基础——selenium模块进阶(模拟鼠标操作)

主要内容包括&#xff1a;模拟鼠标操作。常用的鼠标操作有单击、双击、右击、长按、拖动、移动等&#xff0c;模拟这些操作需要用到selenium模块中的ActionChains类。该类的基本使用方法是将实例化好的WebDriver对象作参数传到该类中&#xff0c;实例化成一个ActionChains对象&…...

C++ macro: The # operator

C macro: The # operator 1. The # operator2. Stringizing (字符串化)References 1. The # operator The # operator converts a parameter of a function-like macro into a character string literal. #define STR(x) #xAll subsequent invocations of the macro STR woul…...

一学就废|Python基础碎片,文件读写

文件处理是指通过编程接口对文件执行诸如创建、打开、读取、写入和关闭等操作的过程。它涉及管理程序与存储设备上的文件系统之间的数据流&#xff0c;确保数据得到安全高效的处理。 Python 中的文件模式 打开文件时&#xff0c;我们必须指定我们想要的模式&#xff0c;该模式…...

使用MATLAB正则表达式从文本文件中提取数据

使用MATLAB正则表达式从文本文件中提取数据 使用Python正则表达式从文本文件中提取数据的代码请看这篇文章使用正则表达式读取文本数据【Python】-CSDN博客 文本数据格式 需要提取 V 后面的数据, 并绘制出曲线. index 1V 0.000000W 0.000000E_theta 0.000000UINV 0.0…...

Java基于SSM框架的在线视频教育系统小程序【附源码、文档】

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…...

Git文件夹提交错了,怎么撤销?

最近提交了一些不应该提交的文件夹到git中,现在需要移除它们,现在简单记录一下操作日志: 情况一 文件夹已经被添加到 Git&#xff0c;但未提交 如果文件夹已经被 git add 添加到暂存区中&#xff0c;但尚未提交&#xff0c;你可以使用以下命令将其从暂存区中移除: git rm -r …...

Unity TextMesh Pro入门

概述 TextMesh Pro是Unity提供的一组工具&#xff0c;用于创建2D和3D文本。与Unity的UI文本和Text Mesh系统相比&#xff0c;TextMesh Pro提供了更好的文本格式控制和布局管理功能。 本文介绍了TMP_Text组件和Tmp字体资产(如何创建字体资产和如何解决缺字问题),还有一些高级功…...

大疆C++开发面试题及参考答案

虚函数的作用是什么&#xff1f;虚函数机制是如何实现的&#xff1f;虚表指针在内存中的存放位置在哪里&#xff1f; 虚函数主要用于实现多态性。多态是面向对象编程中的一个重要概念&#xff0c;它允许通过基类指针或引用调用派生类中重写的函数。这样可以在运行时根据对象的实…...

极品飞车6里的赛道简介

极品飞车里有很多赛道,赛道分为前向赛道Forward、后向赛道Backward。前向赛道Forward是从A点到B点;后向赛道Backward是前向赛道的逆过程,即从B点到A点。这里介绍极品飞车6的赛道长度、中英文名称翻译、难度等级。 序号赛道英文名赛道中文名总长(km)急弯难度等级1Alpine Trai…...

Swagger学习⑰——@Link注解

介绍 Link 是 Swagger/OpenAPI 3.0 注解库中的一个注解&#xff0c;用于在 OpenAPI 文档中定义链接&#xff08;Link&#xff09;。链接是一种在 API 响应中提供相关操作或资源引用的机制&#xff0c;通常用于描述操作之间的关系或提供额外的操作提示。 Link 注解的作用 Link…...

Cline(原Claude Dev)开源的IDE AI插件,如何搭配OpenRouter实现cursor功能,Cline怎么使用

Cline&#xff08;原Claude Dev&#xff09;是一个开源的IDE AI插件&#xff0c;可以使用你的命令行界面和编辑器的人工智能助手。 你可以直接在VS Code编辑器进行安装。如果你使用过Cursor AI IDE的话&#xff0c;可以尝试最新发布的Cline3.1版本。 在OpenRouter上&#xff0…...

WEB前端-3.1

目录 CSS部分 什么是CSS CSS的书写方式 网页引入CSS的方式 css的颜色、大小、边线 文本和字体样式 CSS选择器 属性选择器 伪类选择器 伪元素选择器 文本样式 display属性 背景样式 精灵图、雪碧图 元素定位 绝对定位 相对定位 浮动定位 浮动 CSS部分 什么是…...

灌区闸门自动化控制系统-精准渠道量测水-灌区现代化建设

项目背景 本项目聚焦于黑龙江某一灌区的现代化改造工程&#xff0c;该灌区覆盖广阔&#xff0c;灌溉面积高达7.5万亩&#xff0c;地域上跨越6个乡镇及涵盖17个村庄。项目核心在于通过全面的信息化建设&#xff0c;强力推动节水灌溉措施的实施&#xff0c;旨在显著提升农业用水的…...

QT中引入OpenCV库总结(qmake方式和cmake方式)

文章目录 前言opencv环境配置一、opencv库获取的两种方式二、qmake和cmake配置2.1、 qmake2.2、cmake2.2.1、引入opencv示例 三、qt与opencv对应关系四、问题 前言 我的软件环境&#xff0c;写在前面 Windows10QT5.12.12VS2017OpenCV4.5.4 opencv环境配置 一、opencv库获取…...

【DAPM杂谈之三】DAPM的初始化流程

本文主要分析DAPM的设计与实现 内核的版本是&#xff1a;linux-5.15.164&#xff0c;下载链接&#xff1a;Linux内核下载 主要讲解有关于DAPM相关的知识&#xff0c;会给出一些例程并分析内核如何去实现的 /**************************************************************…...

消息队列架构、选型、专有名词解释

私人博客传送门 消息队列专有名词解释 | 魔筝炼药师 MQ选型 | 魔筝炼药师 MQ架构 | 魔筝炼药师 MQ顺序消息 | 魔筝炼药师...

Scala语言的计算机基础

Scala语言的计算机基础 Scala是一种现代的编程语言&#xff0c;兼具面向对象和函数式编程的特性&#xff0c;广泛应用于大数据处理、后端开发和分布式系统等领域。本文将围绕Scala语言的基础知识&#xff0c;包括其语法特点、数据结构、函数式编程思想、与Java的关系以及在实际…...

爬虫基础之爬取歌曲宝歌曲批量下载

声明&#xff1a;本案列仅供学习交流使用 任何用于非法用途均与本作者无关 需求分析: 网站:邓紫棋-mp3在线免费下载-歌曲宝-找歌就用歌曲宝-MP3音乐高品质在线免费下载 (gequbao.com) 爬取 歌曲名 歌曲 实现歌手名称下载所有歌曲 本案列所使用的模块 requests (发送…...

书说 MySQL 的悲观锁和乐观锁

什么是乐观锁&#xff1f;什么是悲观锁&#xff1f; 悲观锁&#xff1a; 悲观锁是一种基于悲观态度的控制机制&#xff08;最坏的程度想&#xff0c;每次并发一定会造成阻塞&#xff09;&#xff0c;用于防止数据冲突。它采取预防性措施&#xff0c;在修改数据之前将其锁定&a…...

Linux WEB漏洞

定义&#xff1a;Linux Web 漏洞是指在基于 Linux 操作系统的 Web 应用程序、Web 服务器软件或者相关的网络服务配置中存在的安全弱点。这些漏洞可能导致攻击者未经授权访问敏感信息、篡改网页内容、执行恶意代码&#xff0c;甚至完全控制服务器。 常见类型及原理 SQL 注入漏…...

AIDD - 人工智能药物设计 -深度学习赋能脂质纳米颗粒设计,实现高效肺部基因递送

Nat. Biotechnol. | 深度学习赋能脂质纳米颗粒设计&#xff0c;实现高效肺部基因递送 今天为大家介绍的是来自美国麻省理工和爱荷华大学卡弗医学院团队的一篇论文。可离子化脂质&#xff08;ionizable lipids&#xff09;是脂质纳米颗粒&#xff08;lipid nanoparticles&#…...

Selenium 进行网页自动化操作的一个示例,绕过一些网站的自动化检测。python编程

初级教程 selenium 教程和视频教程s原理与安装 - 白月黑羽 https://www.byhy.net/auto/selenium/01/#chrome%201 Selenium 自动化环境安装_哔哩哔哩_bilibili Selenium 自动化环境安装是Python Selenium Web自动化 2024版 - 自动化测试 爬虫的第2集视频&#xff0c;该合集共…...

力扣 岛屿数量

从某个点找&#xff0c;不断找相邻位置。 题目 岛屿中被“0”隔开后 &#xff0c;是每一小块状的“1”&#xff0c;本题在问有多少块。可以用dfs进行搜索&#xff0c;遍历每一个点&#xff0c;把每一个点的上下左右做搜索检测&#xff0c;当检测到就标记为“0”表示已访问过&a…...

【前端动效】HTML + CSS 实现打字机效果

目录 1. 效果展示 2. 思路分析 2.1 难点 2.2 实现思路 3. 代码实现 3.1 html部分 3.2 css部分 3.3 完整代码 4. 总结 1. 效果展示 如图所示&#xff0c;这次带来的是一个有趣的“擦除”效果&#xff0c;也可以叫做打字机效果&#xff0c;其中一段文本从左到右逐渐从…...

期刊(中英),期刊分区,期刊所在数据库(中英),出版商区别和联系

目录 对期刊、分区、数据库、出版商整体了解期刊&#xff08;中英&#xff09;期刊分区期刊所在数据库总结 出版商 对期刊、分区、数据库、出版商整体了解 下图是我对这四部分的一个理解&#xff0c;其中期刊根据论文使用语言分为中英两种&#xff0c;期刊分区是用来评判论文质…...

LLM中temperature参数设置为0

LLM中 temperature参数设置为0 当模型的temperature参数设置为0时&#xff0c;通常有以下含义&#xff1a; 解码策略角度 意味着采用贪婪解码&#xff08;greedy decoding&#xff09;策略。在每一步生成文本时&#xff0c;模型会选择概率最高的词元&#xff0c;从而使输出具…...

Javase 基础复习 函数式接口 lambda表达式 方法应用

目录 案例1 案例2 1. 函数式接口 (Functional Interface) 特点&#xff1a; 示例&#xff1a; Java 8 引入了几个常用的函数式接口&#xff1a; 2. Lambda 表达式 语法解析&#xff1a; 示例&#xff1a; 3. 方法引用 (Method References) 示例&#xff1a; 4. 方法…...

【39. 组合总和 中等】

题目&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限…...

UE小白学习日记

Level UE中的Level(关卡)和Unity中的Scene(场景)在概念和用途上非常相似,都是用来组织和管理3D环境的基本单位。让我为您详细对比一下: 相似之处: 它们都是游戏世界的容器,可以包含游戏对象、光照、地形等元素都支持场景/关卡的切换和加载都可以用来划分游戏内容,比如不同关…...

补充之前的一篇 MySQL 的索引为什么能加快查询速度

在之前的一篇文章中写了 MySQL 的索引为什么能加快查询速度&#xff0c;结合这两篇文章&#xff0c;相信你会对 MySQL 的索引有更深一步的了解 ​首先我们要理解一件事&#xff0c;无论什么数据库&#xff0c;它的数据一定都是存储在硬盘中的&#xff0c;而硬盘和内存之间的读…...

GoLand下载安装教程

一、goland环境配置 1.下载地址 https://golang.google.cn/dl/ 2.下载安装 3.添加环境变量 4.测试环境变量 输出Hello,World! 说明环境配置成功 二、goland安装 1.下载安装 https://www.jetbrains.com/go/download/download-thanks.html 2.激活使用 SFXUSA86FM-eyJsaWNlbnNl…...

GAN的应用

5、GAN的应用 ​ GANs是一个强大的生成模型&#xff0c;它可以使用随机向量生成逼真的样本。我们既不需要知道明确的真实数据分布&#xff0c;也不需要任何数学假设。这些优点使得GANs被广泛应用于图像处理、计算机视觉、序列数据等领域。上图是基于GANs的实际应用场景对不同G…...

[石榴翻译] 维吾尔语音识别 + TTS语音合成

API网址 丝路AI平台 获取 Access token 接口地址&#xff1a;https://open.xjguoyu.cn/api/auth/oauth/token&#xff0c;请求方式&#xff1a;GET&#xff0c;POST Access token是调用服务API的凭证&#xff0c;调用服务API之前需要获取 token。每次成功获取 token 以后只有…...

PHP Filesystem:深入解析与实战应用

PHP Filesystem:深入解析与实战应用 引言 PHP作为一种流行的服务器端编程语言,提供了强大的文件系统操作功能。本文将深入探讨PHP的Filesystem函数,这些函数允许开发者访问和操作服务器上的文件系统。无论是进行基本的文件操作,还是实现复杂的文件管理系统,PHP的Filesys…...