Files
bpf-developer-tutorial/12-bindsnoop/bindsnoop.md
2022-12-02 19:18:03 +08:00

2.4 KiB
Raw Blame History

eBPF 入门实践教程:编写 eBPF 程序 Bindsnoopn 监控 socket 端口绑定事件

背景

Bindsnoop 会跟踪操作 socket 端口绑定的内核函数,并且在可能会影响端口绑定的系统调用发生之前,打印 现有的 socket 选项。

实现原理

Bindsnoop 通过kprobe实现。其主要挂载点为 inet_bind 和 inet6_bind。inet_bind 为处理 IPV4 类型 socket 端口绑定系统调用的接口inet6_bind 为处理IPV6类型 socket 端口绑定系统调用的接口。

SEC("kprobe/inet_bind")
int BPF_KPROBE(ipv4_bind_entry, struct socket *socket)
{
	if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
		return 0;

	return probe_entry(ctx, socket);
}
SEC("kretprobe/inet_bind")

int BPF_KRETPROBE(ipv4_bind_exit)
{
	if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
		return 0;

	return probe_exit(ctx, 4);
}

SEC("kprobe/inet6_bind")
int BPF_KPROBE(ipv6_bind_entry, struct socket *socket)
{
	if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
		return 0;

	return probe_entry(ctx, socket);
}

SEC("kretprobe/inet6_bind")
int BPF_KRETPROBE(ipv6_bind_exit)
{
	if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
		return 0;

	return probe_exit(ctx, 6);
}

当系统试图进行socket端口绑定操作时, kprobe挂载的处理函数会被触发。在进入绑定函数时probe_entry会先被 调用,它会以 tid 为主键将 socket 信息存入 map 中。

static int probe_entry(struct pt_regs *ctx, struct socket *socket)
{
	__u64 pid_tgid = bpf_get_current_pid_tgid();
	__u32 pid = pid_tgid >> 32;
	__u32 tid = (__u32)pid_tgid;

	if (target_pid && target_pid != pid)
		return 0;

	bpf_map_update_elem(&sockets, &tid, &socket, BPF_ANY);
	return 0;
};

在执行完绑定函数后,probe_exit函数会被调用。该函数会读取tid对应的socket信息将其和其他信息一起 写入 event 结构体并输出到用户态。

struct bind_event {
	unsigned __int128 addr;
	__u64 ts_us;
	__u32 pid;
	__u32 bound_dev_if;
	int ret;
	__u16 port;
	__u16 proto;
	__u8 opts;
	__u8 ver;
	char task[TASK_COMM_LEN];
};

当用户停止该工具时,其用户态代码会读取存入的数据并按要求打印。

Eunomia中使用方式

result result

总结

Bindsnoop 通过 kprobe 挂载点,实现了对 socket 端口的监视,增强了 Eunomia 的应用范围。