参考 iptables-bpf 的源代码实现,尝试在自定义的内核模块里运行指定的 bpf 程序。
使用的 bpf 程序源代码: iptables-bpf
内核模块源代码:Kernel module fun
效果
1
2
3
4
5
6
7
8
9
10
11
12
|
# make
# insmod ./run-bpf-prog.ko bpf_path=/sys/fs/bpf/iptbpf
# ping -c4 223.5.5.5
# rmmod run_bpf_prog
# dmesg | tail
[72450.380317] [+] bpf prog path: /sys/fs/bpf/iptbpf
[72450.380326] [+] Register run_bpf_prog module!
[72453.645039] [run_bpf_prog] 10.0.2.15 -> 223.5.5.5
[72454.694542] [run_bpf_prog] 10.0.2.15 -> 223.5.5.5
[72455.697278] [run_bpf_prog] 10.0.2.15 -> 223.5.5.5
[72456.699100] [run_bpf_prog] 10.0.2.15 -> 223.5.5.5
[72471.371127] [-] Cleaning up run_bpf_prog module.
|
实现原理
实现起来并不复杂:
- 初始化模块时,检查并获取 bpf 程序
- 在需要的时候,运行 bpf 程序、拿到运行结果
在本次实验中,是在 netfilter NF_INET_LOCAL_OUT 钩子中运行 bpf 程序,并打印一条日志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
static struct bpf_prog *bp = NULL; // bpf 程序
static int __bpf_check_path(struct bpf_prog **ret) {
if (strnlen(bpf_path, XT_BPF_PATH_MAX) == XT_BPF_PATH_MAX) return -EINVAL;
*ret = bpf_prog_get_type_path(bpf_path, BPF_PROG_TYPE_SOCKET_FILTER); // 获取 bpf 程序 SEC(socket)
return PTR_ERR_OR_ZERO(*ret);
}
static bool run_bpf_prog(struct sk_buff *skb) {
return !!bpf_prog_run_save_cb(bp, skb); // 运行 bpf 程序
}
unsigned int run_bpf_prog_hook(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state) {
__be32 saddr, daddr;
struct iphdr *iphdr;
if (!run_bpf_prog(skb)) return NF_ACCEPT; // 获取 bpf 程序运行结果
iphdr = (struct iphdr *)skb_network_header(skb);
saddr = iphdr->saddr;
daddr = iphdr->daddr;
printk(KERN_INFO "[run_bpf_prog] %pI4 -> %pI4\n", &saddr, &daddr); // 打印日志
return NF_ACCEPT;
}
static struct nf_hook_ops nfhook; // net filter hook option struct
static int init_run_bpf_prog_netfilter_hook(struct net *net) {
nfhook.hook = run_bpf_prog_hook;
nfhook.hooknum = NF_INET_LOCAL_OUT;
nfhook.pf = PF_INET;
nfhook.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(net, &nfhook); // 注册 netfilter 钩子
return 0;
}
static int run_bpf_prog_net_init(struct net *net) {
if (__bpf_check_path(&bp) != 0) { return -1; } // 获取 bpf 程序
init_run_bpf_prog_netfilter_hook(net);
return 0; // Non-zero return means that the module couldn't be loaded.
}
static int run_bpf_prog_init(void) { return run_bpf_prog_net_init(&init_net); }
module_init(run_bpf_prog_init);
|
实验环境
1
2
3
4
5
6
7
8
9
|
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 21.04
Release: 21.04
Codename: hirsute
# uname -a
Linux pagani 5.11.0-31-generic #33-Ubuntu SMP Wed Aug 11 13:19:04 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
|
总结
以前没想过在内核模块里运行 bpf 程序,这次将内核模块和 eBPF 关联起来,真是有趣,因为 eBPF 带给内核模块无与伦比的可编程性。
内核模块可以将经常变动的、根据业务进行定制的逻辑放到 bpf 程序里,从而在需要的时候只更新 bpf 程序即可,而无需更新内核模块,从而保障了内核模块的稳定性。