#240120 使用 lwtunnel bpf 写隧道控制面程序¶
第一个 lwtunnel bpf 程序¶
使用 ip link
只能创建点对点的静态隧道,点多了之后,两两互联就需要创建大量的隧道,会产生一大堆的虚拟网卡设备,管理起来麻烦,解决的一个方法就是使用 lwtunnel(Light Weight Tunnel 轻量级隧道),将复杂的隧道控制/路由逻辑移到路由表中,lwtunnel 还支持挂载 bpf 程序,可以进一步将逻辑移到 bpf map 中,灵活的实现更加复杂的控制路由逻辑。
lwtunnel 支持以下 3 种 bpf 程序:
BPF_PROG_TYPE_LWT_IN
BPF_PROG_TYPE_LWT_OUT
BPF_PROG_TYPE_LWT_XMIT
在内核中的调用入口: https://elixir.bootlin.com/linux/latest/source/net/core/lwt_bpf.c
这 3 种 bpf 程序中比较有用的 BPF_PROG_TYPE_LWT_XMIT 类型的 bpf 程序,一般隧道控制面都是使用这个类型。
Programs attached to input and output are read-only. Programs attached to lwtunnel_xmit() can modify and redirect, push headers and redirect packets.
一个简单的 BPF_PROG_TYPE_LWT_XMIT 程序:
SEC("encap_gre")
int bpf_lwt_encap_gre(struct __sk_buff *skb)
{
struct encap_hdr {
struct iphdr iph;
struct grehdr greh;
} hdr;
int err;
memset(&hdr, 0, sizeof(struct encap_hdr));
hdr.iph.ihl = 5;
hdr.iph.version = 4;
hdr.iph.ttl = 0x40;
hdr.iph.protocol = 47; // IPPROTO_GRE
hdr.iph.saddr = 0xa601a8c0; // 192.168.1.165
// 现实程序中目标 IP 可以从 bpf map 中查询
hdr.iph.daddr = 0xa501a8c0; // 192.168.1.166
hdr.iph.tot_len = bpf_htons(skb->len + sizeof(struct encap_hdr));
hdr.greh.protocol = skb->protocol;
err = bpf_lwt_push_encap(skb, BPF_LWT_ENCAP_IP, &hdr,
sizeof(struct encap_hdr));
if (err)
return BPF_DROP;
return BPF_LWT_REROUTE;
}
编译为 bpf object 文件,加载使用以下命令:
ip route add <route> encap bpf xmit obj <bpf obj file.o> section <ELF section> dev <device>
最后,创建给这个隧道用的网卡以及添加隧道 IP,创建网卡的时候使用 external
关键字(要不得指定隧道的 local 和 remote 参数)。
# ip link add gre01 type gre external
# ip a add <ip/mask> dev gre01
隧道包的解包和静态隧道一样,内核处理,不用管了。
BPF_PROG_TYPE_LWT_IN 也可以调用 bpf_lwt_push_encap 函数,使用场景是?
https://elixir.bootlin.com/linux/v5.19/source/net/core/filter.c#L8061
其他示例程序:
bpf_redirect 前需要加上二层头¶
lwtunnel bpf 程序的入口参数 struct __sk_buff *skb
指向的网络包 skb->data 开始就是三层头(iphdr/ipv6hdr),不包含二层头,如果目标网卡是个物理网卡, bpf_redirect 需要网络包是个完整的网路包,所以 bpf_redirect 之前需要加上正确的二层头才行,否则 redirect 会失败。详细见:
https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/progs/test_lwt_redirect.c