参考 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.

实现原理

实现起来并不复杂:

  1. 初始化模块时,检查并获取 bpf 程序
  2. 在需要的时候,运行 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 程序即可,而无需更新内核模块,从而保障了内核模块的稳定性。