在网络可观测性场景中,经常需要回答一个基础问题:当前这个 TCP 连接或数据包是哪个进程发起的?传统方式可能依赖 netstat、ss 等工具,但这些工具只能在用户态查询某一时刻的状态,无法在高频事件中做持续关联。eBPF 提供了一种更灵活的方式:在内核网络函数入口处通过 helper 直接读取当前任务的进程名,其中 bpf_get_current_comm 就是最常用的辅助函数之一。该函数在 eBPF 程序运行时返回调用进程的 comm 字段,也就是通常所说的程序名。

理解 bpf_get_current_comm 的工作时机很重要。eBPF 程序附着在 kprobe、tracepoint 或 socket filter 等不同挂载点时,其上下文中的当前进程含义可能不同。对于 kprobe/tcp_v4_connect 这类内核函数,运行上下文仍然属于发起连接的用户进程,因此能直接拿到准确的进程名。但如果换到软中断或 workqueue 上下文中,current 可能指向内核线程,此时进程名就不再代表原始用户进程了。因此,要用好这个 helper,需要选择合适的挂载点。
一、bpf_get_current_comm 的工作原理与限制
这个 helper 在内核中的实现并不复杂,它做的事情是从当前进程的 task_struct 中读取 comm 字段,复制到用户提供的缓冲区,并确保以空字符结尾。comm 字段由 TASK_COMM_LEN 宏定义,通常为 16 字节,所以最多容纳 15 个可见字符。这意味着像 python3.11 这样的进程名会被截断为 python3.1,在分析数据时需要注意这一点。截断后的名字仍然有一定参考价值,但不要把它当作完整可执行文件名。
进程名并不总等于可执行文件路径,也不具备唯一性。多个不同的进程可以共享同一个 comm,例如多个 Java 服务都显示为 java。如果只记录 comm,就无法区分具体是哪一个 Java 实例。更精细的方案需要结合 PID、命名空间、命令行参数等信息。但 bpf_get_current_comm 的优势在于速度极快,可以在性能敏感的网络收发路径中使用,适合先做粗粒度的进程归类。需要更精确信息时,再结合其他数据源做二次关联。
另一个容易忽略的点是上下文。eBPF 程序绑定到 kprobe/tcp_v4_connect 时,系统调用或网络栈的调用线程正是发起连接的进程,current 指向该用户进程,因此放在这里的 bpf_get_current_comm 结果准确。但如果把程序挂到 netif_receive_skb 等收包路径,数据包处理可能发生在软中断,此时 current 是 ksoftirqd 或其他内核线程,进程名就会变成内核线程名,不适合用于关联用户进程的网络活动。因此,选择挂载点必须优先考虑上下文是否仍属于目标用户进程。
二、在网络内核路径中关联进程名
对于 TCP 连接跟踪,最常见的挂载点是 tcp_v4_connect。这个函数在客户端发起 IPv4 连接时被调用,从参数中能拿到 struct sock *sk。通过读取 sk 的 __sk_common 结构中的地址和端口字段,可以获得源地址和目的地址以及端口。在同一个 kprobe 程序中先调用 bpf_get_current_comm,再读取这些字段写入 eBPF map,就实现了进程名到网络四元组的关联。服务端的被动连接可以在 tcp_v4_syn_recv_sock 或 inet_csk_accept 等函数处观测,但要注意此时当前进程可能是服务端监听线程。
除了连接建立,很多场景还关心流量大小。这时可以挂载 tcp_sendmsg 和 tcp_cleanup_rbuf 之类的函数,它们分别在发送和接收数据时触发。在这些函数中,current 仍然可能是用户进程,但对异步写入或零拷贝路径要小心,部分操作可能被延后到内核工作队列。更稳妥的方式是使用 tracepoint,例如 sock:inet_sock_set_state,该 tracepoint 在 TCP 状态变化时触发,上下文通常比较稳定,并且参数中直接包含 protocol、saddr、daddr、sport、dport 等信息,省去手动解析 sock 结构的麻烦。
如果目标只是回答哪个进程建立了哪些连接,kprobe/tcp_v4_connect 足够简单高效。若还要关联每个连接的关闭时间或状态变化,可以在 tcp_set_state 或 tcp_close 再挂一个程序,通过 map 共享连接信息。需要注意的是,map 的 key 设计很关键。常见做法是用源 IP、目标 IP、源端口、目标端口四元组拼接成哈希键,但端口可能复用,短时间内可能出现碰撞。更严谨的做法是加入进程 PID 或启动时间,或者使用内核分配的 socket cookie 作为 key。
三、完整 eBPF 程序实现与验证
下面这段 eBPF C 代码演示了在 tcp_v4_connect 挂载点获取进程名和目标地址并写入哈希 map。代码使用了 BTF 风格的 BPF_CORE_READ 来读取 sock 结构字段,避免了依赖特定内核头文件的完整结构定义。实际运行时需要内核开启 CONFIG_DEBUG_INFO_BTF。
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
struct conn_key {
__u32 pid;
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
};
struct conn_value {
char comm[16];
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, struct conn_key);
__type(value, struct conn_value);
} conn_map SEC(".maps");
SEC("kprobe/tcp_v4_connect")
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
{
struct conn_key key = {};
struct conn_value val = {};
bpf_get_current_comm(&val.comm, sizeof(val.comm));
key.pid = bpf_get_current_pid_tgid() >> 32;
// 从 sock 结构中读取 IPv4 地址和端口
key.saddr = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
key.daddr = BPF_CORE_READ(sk, __sk_common.skc_daddr);
key.sport = __bpf_ntohs(BPF_CORE_READ(sk, __sk_common.skc_num));
key.dport = __bpf_ntohs(BPF_CORE_READ(sk, __sk_common.skc_dport));
bpf_map_update_elem(&conn_map, &key, &val, BPF_ANY);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
这段代码首先定义了一个哈希 map,key 由 PID 和四元组组成,value 保存进程名。kprobe 程序在 tcp_v4_connect 入口执行时,先调用 bpf_get_current_comm 拿到进程名,再通过 bpf_get_current_pid_tgid 获取 PID,然后利用 BPF_CORE_READ 从 sock 结构中提取源地址、目的地址和端口。使用 CO-RE 方式读取字段的好处是,当内核结构布局发生细微变化时,eBPF 程序经过 BTF 重定位后仍然可以正确运行,不需要针对每个内核版本重新编译。
编译时可以使用 clang 生成 BPF 目标文件,命令为 clang -O2 -target bpf -g -c conn_track.bpf.c -o conn_track.bpf.o。如果内核支持 BTF,CO-RE 重定位会自动完成。加载时可以使用 bpftool prog load 或者通过 libbpf 编写用户态加载器。验证数据时用 bpftool map dump 即可查看类似 pid=1234 comm=curl dst=93.184.216.34 port=80 的记录。需要注意的是,comm 可能被截断,因此输出时最好保留 16 字节缓冲区并做右对齐处理。
实际使用中还会遇到一些常见问题。比如某些发行版默认限制非特权 eBPF,需要 root 权限或 CAP_BPF 能力;map 的 max_entries 设置过小会导致新连接无法记录;如果同一个进程短时间内建立大量连接,哈希表可能出现较多冲突,此时可以增加 map 大小或使用 LRU 类型。此外,对于 IPv6 连接,需要另写 tcp_v6_connect 挂载点,并把 key 中的地址字段扩展为 16 字节。总之,bpf_get_current_comm 为进程级网络观测提供了一个轻量入口,配合合适的挂载点和数据结构,可以稳定地完成网络活动关联任务。
bpf_get_current_commeBPF网络活动关联修改时间:2026-10-02 02:38:18