From c7e86f51747b70b86496fdbe11d0c50b0e04d4fa Mon Sep 17 00:00:00 2001 From: gray Date: Wed, 25 Sep 2024 02:06:23 +0800 Subject: [PATCH 1/5] bpf: Init bpf test framework --- Makefile | 15 ++++ control/kern/tests/bpf_test.c | 86 +++++++++++++++++++ control/kern/tests/bpf_test.go | 151 +++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 control/kern/tests/bpf_test.c create mode 100644 control/kern/tests/bpf_test.go diff --git a/Makefile b/Makefile index 4e6afefeda..57567865f6 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,8 @@ clean-ebpf: rm -f control/bpf_bpf*.o @rm -f trace/bpf_bpf*.go && \ rm -f trace/bpf_bpf*.o + @rm -f control/kern/tests/bpftest_bpf*.go && \ + rm -f control/kern/tests/bpftest_bpf*.o fmt: go fmt ./... @@ -99,4 +101,17 @@ ebpf: submodule clean-ebpf ebpf-lint: ./scripts/checkpatch.pl --no-tree --strict --no-summary --show-types --color=always control/kern/tproxy.c --ignore COMMIT_COMMENT_SYMBOL,NOT_UNIFIED_DIFF,COMMIT_LOG_LONG_LINE,LONG_LINE_COMMENT,VOLATILE,ASSIGN_IN_IF,PREFER_DEFINED_ATTRIBUTE_MACRO,CAMELCASE,LEADING_SPACE,OPEN_ENDED_LINE,SPACING,BLOCK_COMMENT_STYLE +ebpf-test: export BPF_CLANG := $(CLANG) +ebpf-test: export BPF_STRIP_FLAG := $(STRIP_FLAG) +ebpf-test: export BPF_CFLAGS := $(CFLAGS) +ebpf-test: export BPF_TARGET := $(TARGET) +ebpf-test: export BPF_TRACE_TARGET := $(GOARCH) +ebpf-test: submodule clean-ebpf + @unset GOOS && \ + unset GOARCH && \ + unset GOARM && \ + echo $(STRIP_FLAG) && \ + go generate ./control/kern/tests/bpf_test.go && \ + go test -v ./control/kern/tests/... + ## End Ebpf diff --git a/control/kern/tests/bpf_test.c b/control/kern/tests/bpf_test.c new file mode 100644 index 0000000000..9d5ec4da34 --- /dev/null +++ b/control/kern/tests/bpf_test.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (c) 2022-2024, daeuniverse Organization + +//go:build exclude + +#define __DEBUG + +#include "../tproxy.c" + +struct { + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(max_entries, 1); + __array(values, int()); +} entry_call_map SEC(".maps") = { + .values = { + [0] = &tproxy_dae0peer_ingress, + }, +}; + +SEC("tc/pktgen/0") +int testpktgen_0(struct __sk_buff *skb) +{ + // set l2 header + bpf_skb_change_tail(skb, 14, 0); + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + struct ethhdr *eth = data; + if ((void *)(eth + 1) > data_end) { + bpf_printk("data + sizeof(*eth) > data_end\n"); + return TC_ACT_SHOT; + } + eth->h_proto = 0x0800; + eth->h_source[0] = 0x0a; + eth->h_dest[5] = 0x0b; + return TC_ACT_OK; +} + +SEC("tc/setup/0") +int testsetup_0(struct __sk_buff *skb) +{ + skb->cb[0] = TPROXY_MARK; + skb->cb[1] = IPPROTO_TCP; + bpf_tail_call(skb, &entry_call_map, 0); + return TC_ACT_OK; +} + +SEC("tc/check/0") +int testcheck_0(struct __sk_buff *skb) +{ + __u32 *status_code; + + void *data = (void *)(long)skb->data; + void *data_end = (void *)(long)skb->data_end; + + if (data + sizeof(*status_code) > data_end) { + bpf_printk("data + sizeof(*status_code) > data_end\n"); + return TC_ACT_SHOT; + } + + status_code = data; + if (*status_code != TC_ACT_OK) { + bpf_printk("status_code != TC_ACT_OK\n"); + return TC_ACT_SHOT; + } + + struct ethhdr *eth = data + sizeof(*status_code); + if ((void *)(eth + 1) > data_end) { + bpf_printk("data + sizeof(*eth) > data_end\n"); + return TC_ACT_SHOT; + } + if (eth->h_proto != 0x0800) { + bpf_printk("eth->h_proto != 0x0800\n"); + return TC_ACT_SHOT; + } + if (eth->h_source[0] != 0x0a) { + bpf_printk("eth->h_source[0] != 0x0a\n"); + return TC_ACT_SHOT; + } + if (eth->h_dest[5] != 0x0b) { + bpf_printk("eth->h_dest[5] != 0x0b\n"); + return TC_ACT_SHOT; + } + + return TC_ACT_OK; +} diff --git a/control/kern/tests/bpf_test.go b/control/kern/tests/bpf_test.go new file mode 100644 index 0000000000..eacf31fa99 --- /dev/null +++ b/control/kern/tests/bpf_test.go @@ -0,0 +1,151 @@ +/* + * SPDX-License-Identifier: AGPL-3.0-only + * Copyright (c) 2022-2024, daeuniverse Organization + */ + +package tests + +import ( + "errors" + "fmt" + "os" + "reflect" + "strings" + "testing" + + "github.com/cilium/ebpf" + "github.com/vishvananda/netlink/nl" +) + +//go:generate go run -mod=mod github.com/cilium/ebpf/cmd/bpf2go -cc "$BPF_CLANG" "$BPF_STRIP_FLAG" -cflags "$BPF_CFLAGS" -target "$BPF_TARGET" bpftest ./bpf_test.c -- -I../headers -I. + +type programSet struct { + id string + pktgen *ebpf.Program + setup *ebpf.Program + check *ebpf.Program +} + +func runBpfProgram(prog *ebpf.Program, data, ctx []byte) (statusCode uint32, dataOut, ctxOut []byte, err error) { + dataOut = make([]byte, len(data)) + if len(dataOut) > 0 { + // See comments at https://github.com/cilium/ebpf/blob/20c4d8896bdde990ce6b80d59a4262aa3ccb891d/prog.go#L563-L567 + dataOut = make([]byte, len(data)+256+2) + } + ctxOut = make([]byte, len(ctx)) + opts := &ebpf.RunOptions{ + Data: data, + DataOut: dataOut, + Context: ctx, + ContextOut: ctxOut, + Repeat: 1, + } + ret, err := prog.Run(opts) + return ret, opts.DataOut, ctxOut, err +} + +func collectPrograms(t *testing.T) (progset []programSet, err error) { + obj := &bpftestObjects{} + pinPath := "/sys/fs/bpf/dae" + if err = os.MkdirAll(pinPath, 0755); err != nil && !os.IsExist(err) { + return + } + + if err = loadBpftestObjects(obj, + &ebpf.CollectionOptions{ + Maps: ebpf.MapOptions{ + PinPath: pinPath, + }, + Programs: ebpf.ProgramOptions{ + LogSize: ebpf.DefaultVerifierLogSize * 10, + }, + }, + ); err != nil { + var ( + ve *ebpf.VerifierError + verifierLog string + ) + if errors.As(err, &ve) { + verifierLog = fmt.Sprintf("Verifier error: %+v\n", ve) + } + + t.Fatalf("Failed to load objects: %s\n%+v", verifierLog, err) + + return nil, err + } + + v := reflect.ValueOf(obj.bpftestPrograms) + typeOfV := v.Type() + for i := 0; i < v.NumField(); i++ { + progname := typeOfV.Field(i).Name + if strings.HasPrefix(progname, "Testsetup") { + progid := strings.TrimPrefix(progname, "Testsetup") + progset = append(progset, programSet{ + id: progid, + pktgen: v.FieldByName("Testpktgen" + progid).Interface().(*ebpf.Program), + setup: v.FieldByName("Testsetup" + progid).Interface().(*ebpf.Program), + check: v.FieldByName("Testcheck" + progid).Interface().(*ebpf.Program), + }) + } + } + return +} + +func printBpfDebugLog(t *testing.T) { + file, err := os.Open("/sys/kernel/tracing/trace_pipe") + if err != nil { + t.Fatalf("Failed to open trace_pipe: %v", err) + } + defer file.Close() + + buffer := make([]byte, 1024*64) + n, err := file.Read(buffer) + if err != nil { + t.Fatalf("Failed to read from trace_pipe: %v", err) + } + + fmt.Print(string(buffer[:n])) +} + +func Test(t *testing.T) { + progsets, err := collectPrograms(t) + if err != nil { + t.Fatalf("error while collecting programs: %s", err) + } + + for _, progset := range progsets { + // create ctx with the max allowed size(4k - head room - tailroom) + data := make([]byte, 4096-256-320) + + // sizeof(struct __sk_buff) < 256, let's make it 256 + ctx := make([]byte, 256) + + statusCode, data, ctx, err := runBpfProgram(progset.pktgen, data, ctx) + if err != nil { + t.Fatalf("error while running pktgen prog: %s", err) + } + if statusCode != 0 { + printBpfDebugLog(t) + t.Fatalf("error while running pktgen program: unexpected status code: %d", statusCode) + } + + statusCode, data, ctx, err = runBpfProgram(progset.setup, data, ctx) + if err != nil { + printBpfDebugLog(t) + t.Fatalf("error while running setup prog: %s", err) + } + + status := make([]byte, 4) + nl.NativeEndian().PutUint32(status, statusCode) + data = append(status, data...) + + statusCode, data, ctx, err = runBpfProgram(progset.check, data, ctx) + if err != nil { + t.Fatalf("error while running check program: %+v", err) + } + if statusCode != 0 { + printBpfDebugLog(t) + t.Fatalf("error while running check program: unexpected status code: %d", statusCode) + } + } +} From 5c32f50dd81de63e747c0ec5aa939fbf15fd7b29 Mon Sep 17 00:00:00 2001 From: gray Date: Fri, 27 Sep 2024 21:11:00 +0800 Subject: [PATCH 2/5] bpf: test dport(80) -> proxy --- control/kern/tests/bpf_test.c | 91 +++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/control/kern/tests/bpf_test.c b/control/kern/tests/bpf_test.c index 9d5ec4da34..00c5032ab5 100644 --- a/control/kern/tests/bpf_test.c +++ b/control/kern/tests/bpf_test.c @@ -7,6 +7,9 @@ #include "../tproxy.c" +#define IP4_HLEN sizeof(struct iphdr) +#define TCP_HLEN sizeof(struct tcphdr) + struct { __uint(type, BPF_MAP_TYPE_PROG_ARRAY); __uint(key_size, sizeof(__u32)); @@ -14,39 +17,70 @@ struct { __array(values, int()); } entry_call_map SEC(".maps") = { .values = { - [0] = &tproxy_dae0peer_ingress, + [0] = &tproxy_wan_egress, }, }; -SEC("tc/pktgen/0") -int testpktgen_0(struct __sk_buff *skb) +SEC("tc/pktgen/hijack_by_port_80") +int testpktgen_hijack_by_port_80(struct __sk_buff *skb) { - // set l2 header - bpf_skb_change_tail(skb, 14, 0); + bpf_skb_change_tail(skb, ETH_HLEN + IP4_HLEN + TCP_HLEN, 0); + void *data = (void *)(long)skb->data; void *data_end = (void *)(long)skb->data_end; + struct ethhdr *eth = data; if ((void *)(eth + 1) > data_end) { bpf_printk("data + sizeof(*eth) > data_end\n"); return TC_ACT_SHOT; } - eth->h_proto = 0x0800; - eth->h_source[0] = 0x0a; - eth->h_dest[5] = 0x0b; + eth->h_proto = bpf_htons(ETH_P_IP); + + struct iphdr *ip = data + ETH_HLEN; + if ((void *)(ip + 1) > data_end) { + bpf_printk("data + sizeof(*ip) > data_end\n"); + return TC_ACT_SHOT; + } + ip->ihl = 5; + ip->version = 4; + ip->protocol = IPPROTO_TCP; + ip->saddr = bpf_htonl(0xc0a80001); // 192.168.0.1 + ip->daddr = bpf_htonl(0x01010101); // 1.1.1.1 + + struct tcphdr *tcp = data + ETH_HLEN + IP4_HLEN; + if ((void *)(tcp + 1) > data_end) { + bpf_printk("data + sizeof(*tcp) > data_end\n"); + return TC_ACT_SHOT; + } + tcp->source = bpf_htons(19233); + tcp->dest = bpf_htons(80); + tcp->syn = 1; + return TC_ACT_OK; } -SEC("tc/setup/0") -int testsetup_0(struct __sk_buff *skb) +SEC("tc/setup/hijack_by_port_80") +int testsetup_hijack_by_port_80(struct __sk_buff *skb) { - skb->cb[0] = TPROXY_MARK; - skb->cb[1] = IPPROTO_TCP; + __u32 linklen = ETH_HLEN; + bpf_map_update_elem(&linklen_map, &one_key, &linklen, BPF_ANY); + + struct match_set ms = {}; + struct port_range pr = {80, 80}; + ms.port_range = pr; + ms.not = false; + ms.type = MatchType_Port; + ms.outbound = 2; + ms.must = false; + ms.mark = 0; + + bpf_map_update_elem(&routing_map, &zero_key, &ms, BPF_ANY); bpf_tail_call(skb, &entry_call_map, 0); return TC_ACT_OK; } -SEC("tc/check/0") -int testcheck_0(struct __sk_buff *skb) +SEC("tc/check/hijack_by_port_80") +int testcheck_hijack_by_port_80(struct __sk_buff *skb) { __u32 *status_code; @@ -59,8 +93,8 @@ int testcheck_0(struct __sk_buff *skb) } status_code = data; - if (*status_code != TC_ACT_OK) { - bpf_printk("status_code != TC_ACT_OK\n"); + if (*status_code != TC_ACT_REDIRECT) { + bpf_printk("status_code(%d) != TC_ACT_REDIRECT\n", *status_code); return TC_ACT_SHOT; } @@ -69,16 +103,31 @@ int testcheck_0(struct __sk_buff *skb) bpf_printk("data + sizeof(*eth) > data_end\n"); return TC_ACT_SHOT; } - if (eth->h_proto != 0x0800) { + if (eth->h_proto != bpf_htons(ETH_P_IP)) { bpf_printk("eth->h_proto != 0x0800\n"); return TC_ACT_SHOT; } - if (eth->h_source[0] != 0x0a) { - bpf_printk("eth->h_source[0] != 0x0a\n"); + + struct iphdr *ip = (void *)eth + ETH_HLEN; + if ((void *)(ip + 1) > data_end) { + bpf_printk("data + sizeof(*ip) > data_end\n"); + return TC_ACT_SHOT; + } + if (ip->protocol != IPPROTO_TCP) { + bpf_printk("ip->protocol != IPPROTO_TCP\n"); + return TC_ACT_SHOT; + } + if (ip->daddr != bpf_htonl(0x01010101)) { + bpf_printk("ip->daddr != 1.1.1.1\n"); + } + + struct tcphdr *tcp = (void *)ip + IP4_HLEN; + if ((void *)(tcp + 1) > data_end) { + bpf_printk("data + sizeof(*tcp) > data_end\n"); return TC_ACT_SHOT; } - if (eth->h_dest[5] != 0x0b) { - bpf_printk("eth->h_dest[5] != 0x0b\n"); + if (tcp->dest != bpf_htons(80)) { + bpf_printk("tcp->dest != 80\n"); return TC_ACT_SHOT; } From de56f229751154e4ea538233bd6aa4b71d3062f0 Mon Sep 17 00:00:00 2001 From: gray Date: Fri, 27 Sep 2024 21:17:27 +0800 Subject: [PATCH 3/5] bpf: benchmark mode --- control/kern/tests/bpf_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/control/kern/tests/bpf_test.go b/control/kern/tests/bpf_test.go index eacf31fa99..cff3ef9344 100644 --- a/control/kern/tests/bpf_test.go +++ b/control/kern/tests/bpf_test.go @@ -12,6 +12,7 @@ import ( "reflect" "strings" "testing" + "time" "github.com/cilium/ebpf" "github.com/vishvananda/netlink/nl" @@ -26,7 +27,7 @@ type programSet struct { check *ebpf.Program } -func runBpfProgram(prog *ebpf.Program, data, ctx []byte) (statusCode uint32, dataOut, ctxOut []byte, err error) { +func runBpfProgram(prog *ebpf.Program, data, ctx []byte, benchmark bool) (statusCode uint32, dataOut, ctxOut []byte, err error) { dataOut = make([]byte, len(data)) if len(dataOut) > 0 { // See comments at https://github.com/cilium/ebpf/blob/20c4d8896bdde990ce6b80d59a4262aa3ccb891d/prog.go#L563-L567 @@ -40,6 +41,9 @@ func runBpfProgram(prog *ebpf.Program, data, ctx []byte) (statusCode uint32, dat ContextOut: ctxOut, Repeat: 1, } + if benchmark { + opts.Repeat = 5000000 + } ret, err := prog.Run(opts) return ret, opts.DataOut, ctxOut, err } @@ -120,7 +124,7 @@ func Test(t *testing.T) { // sizeof(struct __sk_buff) < 256, let's make it 256 ctx := make([]byte, 256) - statusCode, data, ctx, err := runBpfProgram(progset.pktgen, data, ctx) + statusCode, data, ctx, err := runBpfProgram(progset.pktgen, data, ctx, false) if err != nil { t.Fatalf("error while running pktgen prog: %s", err) } @@ -129,17 +133,19 @@ func Test(t *testing.T) { t.Fatalf("error while running pktgen program: unexpected status code: %d", statusCode) } - statusCode, data, ctx, err = runBpfProgram(progset.setup, data, ctx) + currentTime := time.Now() + statusCode, data, ctx, err = runBpfProgram(progset.setup, data, ctx, true) if err != nil { printBpfDebugLog(t) t.Fatalf("error while running setup prog: %s", err) } + println(time.Since(currentTime).String()) status := make([]byte, 4) nl.NativeEndian().PutUint32(status, statusCode) data = append(status, data...) - statusCode, data, ctx, err = runBpfProgram(progset.check, data, ctx) + statusCode, data, ctx, err = runBpfProgram(progset.check, data, ctx, false) if err != nil { t.Fatalf("error while running check program: %+v", err) } From e0004c08d3e6ac97656729cbf142504d36995127 Mon Sep 17 00:00:00 2001 From: gray Date: Sat, 28 Sep 2024 12:14:12 +0800 Subject: [PATCH 4/5] bpf: benchmark fallback after 50 rule checks --- control/kern/tests/bpf_test.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/control/kern/tests/bpf_test.c b/control/kern/tests/bpf_test.c index 00c5032ab5..7c4eb8b29c 100644 --- a/control/kern/tests/bpf_test.c +++ b/control/kern/tests/bpf_test.c @@ -3,7 +3,10 @@ //go:build exclude +/* #define __DEBUG +#define __DEBUG_ROUTING +*/ #include "../tproxy.c" @@ -53,7 +56,7 @@ int testpktgen_hijack_by_port_80(struct __sk_buff *skb) return TC_ACT_SHOT; } tcp->source = bpf_htons(19233); - tcp->dest = bpf_htons(80); + tcp->dest = bpf_htons(79); tcp->syn = 1; return TC_ACT_OK; @@ -74,7 +77,23 @@ int testsetup_hijack_by_port_80(struct __sk_buff *skb) ms.must = false; ms.mark = 0; - bpf_map_update_elem(&routing_map, &zero_key, &ms, BPF_ANY); + int i = 0; + for (i = 0; i < 50; i++) { + pr.port_start = 80 + i; + pr.port_end = 80 + i; + ms.port_range = pr; + int key = i; + bpf_map_update_elem(&routing_map, &key, &ms, BPF_ANY); + } + + __builtin_memset(&ms.__value, 0, sizeof(ms.__value)); + ms.not = false; + ms.type = MatchType_Fallback; + ms.outbound = 2; + ms.must = false; + ms.mark = 0; + bpf_map_update_elem(&routing_map, &i, &ms, BPF_ANY); + bpf_tail_call(skb, &entry_call_map, 0); return TC_ACT_OK; } @@ -126,8 +145,8 @@ int testcheck_hijack_by_port_80(struct __sk_buff *skb) bpf_printk("data + sizeof(*tcp) > data_end\n"); return TC_ACT_SHOT; } - if (tcp->dest != bpf_htons(80)) { - bpf_printk("tcp->dest != 80\n"); + if (tcp->dest != bpf_htons(79)) { + bpf_printk("tcp->dest != 79\n"); return TC_ACT_SHOT; } From 3f6b7169d52d02790e40ac9621ab0096fbb3325e Mon Sep 17 00:00:00 2001 From: gray Date: Sat, 28 Sep 2024 12:23:25 +0800 Subject: [PATCH 5/5] make: disable go test cache --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 57567865f6..354f95acd1 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,7 @@ ebpf-test: submodule clean-ebpf unset GOARM && \ echo $(STRIP_FLAG) && \ go generate ./control/kern/tests/bpf_test.go && \ + go clean -testcache && \ go test -v ./control/kern/tests/... ## End Ebpf