BPF関連のテストはこれまでトレーシングが主でしたが、ここでは実用的なものとして、XDP(eXpress Data Path)を使ったパケットフィルタリングを試してみました。
BPF開発のライブラリはいろいろな形がありますが、今回は、Go言語でコードを生成するタイプのものになります。
参考)
https://github.com/cilium/ebpf/tree/master/examples/xdp
環境)
Ubuntu 22.04
n@n10:~$ uname -r
5.15.0-67-genericn@n10:~$ grep BPF /boot/config-5.15.0-67-generic
CONFIG_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
# BPF subsystem
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
# CONFIG_BPF_PRELOAD is not set
CONFIG_BPF_LSM=y
# end of BPF subsystem
CONFIG_CGROUP_BPF=y
CONFIG_IPV6_SEG6_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_BPFILTER=y
CONFIG_BPFILTER_UMH=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_TEST_BPF=mn@n10:~$ grep XDP /boot/config-5.15.0-67-generic
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=m
CONFIG_SENSORS_XDPE122=mn@n10:~$ go version
go version go1.18.1 linux/amd64
xdp.c(DROPを加え変更)
| 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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | //go:build ignore #include "bpf_endian.h" #include "common.h" char __license[] SEC("license") = "Dual MIT/GPL"; #define MAX_MAP_ENTRIES 16 /* Define an LRU hash map for storing packet count by source IPv4 address */ struct {         __uint(type, BPF_MAP_TYPE_LRU_HASH);         __uint(max_entries, MAX_MAP_ENTRIES);         __type(key, __u32);   // source IPv4 address         __type(value, __u32); // packet count } xdp_stats_map SEC(".maps"); /* Attempt to parse the IPv4 source address from the packet. Returns 0 if there is no IPv4 header field; otherwise returns non-zero. */ static __always_inline int parse_ip_src_addr(struct xdp_md *ctx, __u32 *ip_src_addr) {         void *data_end = (void *)(long)ctx->data_end;         void *data     = (void *)(long)ctx->data;         // First, parse the ethernet header.         struct ethhdr *eth = data;         if ((void *)(eth + 1) > data_end) {                 return 0;         }         if (eth->h_proto != bpf_htons(ETH_P_IP)) {                 // The protocol is not IPv4, so we can't parse an IPv4 source address.                 return 0;         }         // Then parse the IP header.         struct iphdr *ip = (void *)(eth + 1);         if ((void *)(ip + 1) > data_end) {                 return 0;         }         // if (ip->protocol == 1) {         //              return 2;         //      }         // Return the source IP address in network byte order.         *ip_src_addr = (__u32)(ip->saddr);         return 1; } SEC("xdp") int xdp_prog_func(struct xdp_md *ctx) {         __u32 ip;         int ret = parse_ip_src_addr(ctx, &ip);         if (ret == 0) {                 // Not an IPv4 packet, so don't count it.                 goto done;         }         //      if (ret == 2) {         //              bpf_printk("Drop Ping");         //              return XDP_DROP;         //      }         __u32 *pkt_count = bpf_map_lookup_elem(&xdp_stats_map, &ip);         if (!pkt_count) {                 // No entry in the map for this IP address yet, so set the initial value to 1.                 __u32 init_pkt_count = 1;                 bpf_map_update_elem(&xdp_stats_map, &ip, &init_pkt_count, BPF_ANY);         } else {                 // Entry already exists for this IP address,                 // so increment it atomically using an LLVM built-in.                 __sync_fetch_and_add(pkt_count, 1);                 if (ip == 0xe401a8c0) {                         bpf_printk("host %x", ip);                         return XDP_PASS;                 } else {                         bpf_printk("others %x", ip);                 }         } done:         // Try changing this to XDP_DROP and see what happens!         // return XDP_PASS;         return XDP_DROP; } | 
ビルド
n@n10:~/ebpf/examples$ sudo make -C ../
[sudo] password for n:
make: Entering directory ‘/home/n/ebpf’
docker run –rm –user “1000:1000” \
-v “/home/n/ebpf”:/ebpf -w /ebpf –env MAKEFLAGS \
–env CFLAGS=”-fdebug-prefix-map=/ebpf=.” \
–env HOME=”/tmp” \
“ghcr.io/cilium/ebpf-builder:1666886595” \
make all
make: Entering directory ‘/ebpf’
find . -type f -name “*.c” | xargs clang-format -i
go generate ./…
go: downloading golang.org/x/sys v0.2.0
Compiled /ebpf/cmd/bpf2go/test/test_bpfel.o
Stripped /ebpf/cmd/bpf2go/test/test_bpfel.o
Wrote /ebpf/cmd/bpf2go/test/test_bpfel.go
Compiled /ebpf/cmd/bpf2go/test/test_bpfeb.o
Stripped /ebpf/cmd/bpf2go/test/test_bpfeb.o
….
Compiled /ebpf/examples/xdp/bpf_bpfel.o
Stripped /ebpf/examples/xdp/bpf_bpfel.o
Wrote /ebpf/examples/xdp/bpf_bpfel.go
Compiled /ebpf/examples/xdp/bpf_bpfeb.o
Stripped /ebpf/examples/xdp/bpf_bpfeb.o
Wrote /ebpf/examples/xdp/bpf_bpfeb.go
enum AdjRoomMode
enum AttachType
enum Cmd
enum FunctionId
….
attr ProgGetNextId
attr ProgLoad
attr ProgQuery
attr ProgRun
attr RawTracepointOpen
ln -srf testdata/loader-clang-14-el.elf testdata/loader-el.elf
ln -srf testdata/loader-clang-14-eb.elf testdata/loader-eb.elf
make: Leaving directory ‘/ebpf’
make: Leaving directory ‘/home/n/ebpf’
docker imageを使ってビルドしています。
xdp.cと同じフォルダにあるmain.goには、下記の記述があり、generatorにより、bpf_bpfeb.go, bpf_bpfel.goが生成されます。
// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf xdp.c — -I../headers
ebpf/cmd/bpf2go/
https://github.com/cilium/ebpf/tree/master/cmd/bpf2go
このような手の込んだことをして、カーネルモジュールを動かすしくみを作っています。
実行
n@n10:~/ebpf/examples$ go run -exec sudo ./xdp enp0s3
2023/03/06 06:06:13 Attached XDP program to iface “enp0s3” (index 2)
2023/03/06 06:06:13 Press Ctrl-C to exit and remove the program
2023/03/06 06:06:14 Map contents:
192.168.1.228 => 2
2023/03/06 06:06:15 Map contents:
192.168.1.228 => 3
2023/03/06 06:06:16 Map contents:
192.168.1.228 => 4
….
テスト内容
192.168.1.228 (host)
192.168.1.217
上二つからxdpプログラムを実行している下記にcurlコマンドを実行します。
192.168.1.172
上記出力からはわかりにくいですが、xdpプログラムを実行しているとき、ホスト以外からはcurlコマンドのレスポンスがないことを確認しています。コードのコメントをはずすとpingパケロットのフィルタリングも確認できます。
ただテストをホストからのSSH接続していて接続が不安定になったりしました。フィルタリングしていないとはいえ、デフォルトDropのロジックは、弊害があるかもしれません。要調査です。
カーネルプログラムでは、コードをコメントしただけでエラーが発生するなど、通常のCプログラムではないようなことが起きました。(未定義という単純なものではなく、invalid indirect read from stack offなどといったものなど)
このあたり関連する部分を注意深くコーディングする必要があるようです。



