目录

代码分析

BPF 程序部分

功能说明

头文件引入说明

用户程序部分

执行效果


这里的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 包含了进程调度相关的数据结构。

下面是详细说明:

  1. #include <linux/bpf.h>```linux/bpf.h 是 Linux 内核 BPF(Berkeley Packet Filter)库的主要头文件,提供了 BPF 程序需要的各种数据类型和宏定义。在这段代码中,struct bpf_pidns_info数据结构就是由linux/bpf.h` 头文件定义的。
  2. #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.
07-16 06:46