该如此玩转 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 这样优雅的方式了。