P.S. 旧文一篇,请笑纳。
参考 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
static char *bpf_path = "";
module_param(bpf_path, charp, S_IRUGO);
static struct bpf_prog *bp = NULL;
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);
return PTR_ERR_OR_ZERO(*ret);
}
static bool run_bpf_prog(struct sk_buff *skb) {
return !!bpf_prog_run_save_cb(bp, skb);
}
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;
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);
return 0;
}
static int run_bpf_prog_net_init(struct net *net) {
if (__bpf_check_path(&bp) != 0) {
printk(KERN_ERR "[x] Register run_bpf_prog module failed\n");
return -1;
}
printk(KERN_INFO "[+] bpf prog path: %s\n", bpf_path);
init_run_bpf_prog_netfilter_hook(net);
printk(KERN_INFO "[+] Register run_bpf_prog module!\n");
return 0; // Non-zero return means that the module couldn't be loaded.
}
static void run_bpf_prog_net_exit(struct net *net) {
nf_unregister_net_hook(net, &nfhook);
if (bp != NULL) bpf_prog_destroy(bp);
printk(KERN_INFO "[-] Cleaning up run_bpf_prog module.\n");
}
static int run_bpf_prog_init(void) { return run_bpf_prog_net_init(&init_net); }
static void run_bpf_prog_exit(void) { run_bpf_prog_net_exit(&init_net); }
module_init(run_bpf_prog_init);
module_exit(run_bpf_prog_exit);
|
实验环境
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 程序即可,而无需更新内核模块,从而保障了内核模块的稳定性。