该如此玩转 iptables-bpf

原始魔杖 iptables 配上新发明的药水 eBPF,这是怎么做到的呢?让我们一探 iptables-bpf 究竟。

实现原理

用法:iptables -I OUTPUT -m bpf --object-pinned $(EBPF_PINNED) -j DROP

bpf 是 iptables 的一个 match 模块,在 iptables 里执行一段指定的 BPF_PROG_TYPE_SOCKET_FILTER 类型的 bpf 程序。--object-pinned 指定包含了 SEC("socket") 的 pinned 到 /sys/fs/bpf bpf 文件系统挂载点下的 bpf 程序;--bytecode 指定一段 bpf 程序的字节码。

iptables 拿到 bpf 程序的结果,如果匹配结果是 true 则执行 -j 指定的 target,否则继续执行后面的 iptables 规则。

源代码分析

源码位置:/net/netfilter/xt_bpf.c

模块初始化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
static struct xt_match bpf_mt_reg[] __read_mostly = {
	...
	{
		.name		= "bpf",
		.revision	= 1,
		.family		= NFPROTO_UNSPEC,
		.checkentry	= bpf_mt_check_v1,  // 检查 bpf 程序的函数
		.match		= bpf_mt_v1,        // 匹配网络包的函数
		.destroy	= bpf_mt_destroy_v1,
		.matchsize	= sizeof(struct xt_bpf_info_v1),
		.usersize	= offsetof(struct xt_bpf_info_v1, filter),
		.me		= THIS_MODULE,
	},
};

static int __init bpf_mt_init(void)
{
	return xt_register_matches(bpf_mt_reg, ARRAY_SIZE(bpf_mt_reg));
}

module_init(bpf_mt_init);

检查 bpf 程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static int bpf_mt_check_v1(const struct xt_mtchk_param *par)
{
	struct xt_bpf_info_v1 *info = par->matchinfo;
	...
	else if (info->mode == XT_BPF_MODE_PATH_PINNED)
		return __bpf_mt_check_path(info->path, &info->filter);
	else
		return -EINVAL;
}

static int __bpf_mt_check_path(const char *path, struct bpf_prog **ret)
{
	if (strnlen(path, XT_BPF_PATH_MAX) == XT_BPF_PATH_MAX)
		return -EINVAL;
	*ret = bpf_prog_get_type_path(path, BPF_PROG_TYPE_SOCKET_FILTER);  // 指定了 socket 类型
	return PTR_ERR_OR_ZERO(*ret);
}

struct xt_bpf_info_v1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct xt_bpf_info_v1 {
	__u16 mode;
	__u16 bpf_program_num_elem;
	__s32 fd;
	union {
		struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR];
		char path[XT_BPF_PATH_MAX];
	};
	/* only used in the kernel */
	struct bpf_prog *filter __attribute__((aligned(8)));
};

匹配网络包:

1
2
3
4
5
static bool bpf_mt_v1(const struct sk_buff *skb, struct xt_action_param *par)
{
	const struct xt_bpf_info_v1 *info = par->matchinfo;
	return !!bpf_prog_run_save_cb(info->filter, (struct sk_buff *) skb);
}

总结

源代码非常简单。不看不知,原来在内核模块里可以这么使用 bpf。而我之前折腾的在内核模块里使用 bpf-map 的实现并不优雅,可以试试如 iptables-bpf 这样优雅的方式了。