目录
这里的ns的意思就是namespace
代码分析
BPF 程序部分
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2023 Hosein Bakhtiari */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
char LICENSE[] SEC("license") = "Dual BSD/GPL";
int my_pid = 0;
unsigned long long dev;
unsigned long long ino;
SEC("tp/syscalls/sys_enter_write")
int handle_tp(void *ctx)
{
struct bpf_pidns_info ns;
bpf_get_ns_current_pid_tgid(dev, ino, &ns, sizeof(ns));
if (ns.pid != my_pid)
return 0;
bpf_printk("BPF triggered from PID %d.\n", ns.pid);
return 0;
}
功能说明
功能上应该是与minimal 一样的,只不过是使用了bpf_pidns_info。bpf_pidns_info 是一个结构体,用于存储 bpf_get_ns_current_pid_tgid 函数的结果。
minimal程序不适用于具有命名空间的环境,例如docker或 WSL2,因为命名空间中进程的感知 pid 不是进程的实际 pid。要minimal在命名空间环境中执行,您需要使用minimal_ns
struct bpf_pidns_info {
__u32 pid;
__u32 tgid;
};
头文件引入说明
linux/bpf.h 是 BPF 的主要头文件,bpf/bpf_helpers.h 包含了许多用于写 BPF 程序的辅助函数,linux/sched.h 包含了进程调度相关的数据结构。
下面是详细说明:
- #include <linux/bpf.h>```linux/bpf.h 是 Linux 内核 BPF(Berkeley Packet Filter)库的主要头文件,提供了 BPF 程序需要的各种数据类型和宏定义。在这段代码中,struct bpf_pidns_info数据结构就是由linux/bpf.h` 头文件定义的。
- #include <bpf/bpf_helpers.h>```bpf/bpf_helpers.h 头文件提供了在 BPF 程序中使用的各种辅助函数的定义。在这段代码中,bpf_get_ns_current_pid_tgid和bpf_printk函数就是由bpf/bpf_helpers.h` 头文件定义的。```bpf_get_ns_current_pid_tgid 函数用于获取当前的 PID 和 TGID(线程组 ID)信息。bpf_printk函数类似于 C 语言的printf` 函数,用于在 BPF 调试日志中打印信息。
用户程序部分
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
/* Copyright (c) 2023 Hosein Bakhtiari */
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include "minimal_ns.skel.h"
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
int main(int argc, char **argv)
{
struct minimal_ns_bpf *skel;
int err;
struct stat sb;
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Open BPF application */
skel = minimal_ns_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}
/* ensure BPF program only handles write() syscalls from our process */
if (stat("/proc/self/ns/pid", &sb) == -1) {
fprintf(stderr, "Failed to acquire namespace information");
return 1;
}
skel->bss->dev = sb.st_dev;
skel->bss->ino = sb.st_ino;
skel->bss->my_pid = getpid();
/* Load & verify BPF programs */
err = minimal_ns_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}
/* Attach tracepoint handler */
err = minimal_ns_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}
printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");
for (;;) {
/* trigger our BPF program */
fprintf(stderr, ".");
sleep(1);
}
cleanup:
minimal_ns_bpf__destroy(skel);
return -err;
}
整体逻辑与minimal 是一致的,只不过首先使用 stat 系统调用获取 /proc/self/ns/pid 的文件状态。
在 Linux 中,/proc/self/ns/pid 是一个特殊的文件,它代表了当前进程的 PID namespace。stat 系统调用可以获取到这个文件的各种信息,包括设备号 (st_dev) 和 inode 号 (st_ino),这两个值可以唯一地标识一个文件。如果 stat 系统调用失败,程序会输出错误信息并退出。
然后,这段代码将获取到的设备号和 inode 号,以及当前进程的 PID,存储到 BPF 程序的 BSS section 中。在这个例子中,BPF 程序的 BSS section 是通过 skel->bss 访问的,skel->bss->dev、skel->bss->ino 和 skel->bss->my_pid 分别对应于 BSS section 中的 dev、ino 和 my_pid 三个变量。
这样,当 BPF 程序处理 write() 系统调用时,就可以检查系统调用是否来自于同一 PID namespace 中的当前进程,从而确保只处理来自当前进程的 write() 系统调用。
执行效果
$ cd examples/c
$ make minimal_ns
$ sudo ./minimal_ns
$ sudo cat /sys/kernel/debug/tracing/trace_pipe
<...>-3840345 [022] d...1 8804.331204: bpf_trace_printk: BPF triggered from PID 9087.
<...>-3840345 [022] d...1 8804.331215: bpf_trace_printk: BPF triggered from PID 9087.