diff --git a/Makefile b/Makefile index 3a75e1e..139d585 100644 --- a/Makefile +++ b/Makefile @@ -41,13 +41,16 @@ EBPF_TEST_MAP_BINARY := test-data/test.map.bpf.elf EBPF_TEST_LIC_SOURCE := test-data/test_license.bpf.c EBPF_TEST_LIC_BINARY := test-data/test_license.bpf.elf EBPF_TEST_INV_MAP_SOURCE := test-data/invalid_map.bpf.c -EBPF_TEST_INV_MAP_BINARY := test-data/invalid_map.bpf.elf +EBPF_TEST_INV_MAP_BINARY := test-data/invalid_map.bpf.elf +EBPF_TEST_RECOVERY_SOURCE := test-data/recoverydata.bpf.c +EBPF_TEST_RECOVERY_BINARY := test-data/recoverydata.bpf.elf build-bpf: ## Build BPF $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_SOURCE) -o $(EBPF_BINARY) $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_SOURCE) -o $(EBPF_TEST_BINARY) $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_MAP_SOURCE) -o $(EBPF_TEST_MAP_BINARY) $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_LIC_SOURCE) -o $(EBPF_TEST_LIC_BINARY) $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_INV_MAP_SOURCE) -o $(EBPF_TEST_INV_MAP_BINARY) + $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_RECOVERY_SOURCE) -o $(EBPF_TEST_RECOVERY_BINARY) vmlinuxh: bpftool btf dump file /sys/kernel/btf/vmlinux format c > $(abspath ./test-data/vmlinux.h) diff --git a/pkg/elfparser/elf.go b/pkg/elfparser/elf.go index d0997a8..a8f5567 100644 --- a/pkg/elfparser/elf.go +++ b/pkg/elfparser/elf.go @@ -503,6 +503,11 @@ func (e *elfLoader) parseProg(loadedMaps map[string]ebpf_maps.BpfMap) (map[strin return nil, fmt.Errorf("failed to get progEntry Data") } + if len(data) == 0 { + log.Infof("Missing data in prog Section") + return nil, fmt.Errorf("missing data in prog section") + } + var linkedMaps map[int]string //Apply relocation if e.reloSectionMap[progIndex] == nil { @@ -905,7 +910,7 @@ func (b *bpfSDKClient) RecoverAllBpfProgramsAndMaps() (map[string]BpfData, error } } else { log.Infof("error checking BPF FS, might not be mounted %v", err) - return nil, fmt.Errorf("error checking BPF FS might not be mounted %v", err) + return nil, fmt.Errorf("error checking BPF FS might not be mounted") } //Return DS here return loadedPrograms, nil diff --git a/pkg/elfparser/elf_test.go b/pkg/elfparser/elf_test.go index 191d28a..2155db7 100644 --- a/pkg/elfparser/elf_test.go +++ b/pkg/elfparser/elf_test.go @@ -17,9 +17,11 @@ package elfparser import ( "debug/elf" "errors" + "fmt" "os" "sort" "strings" + "syscall" "testing" mock_ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps/mocks" @@ -30,10 +32,10 @@ import ( var ( MAP_SECTION_INDEX = 8 - MAP_TYPE_1 = 27 - MAP_KEY_SIZE_1 = 0 - MAP_VALUE_SIZE_1 = 0 - MAP_ENTRIES_1 = 262144 + MAP_TYPE_1 = 9 + MAP_KEY_SIZE_1 = 16 + MAP_VALUE_SIZE_1 = 4 + MAP_ENTRIES_1 = 65536 MAP_FLAGS_1 = 0 ) @@ -54,64 +56,66 @@ func setup(t *testing.T, testPath string) *testMocks { } } -func TestLoadelf(t *testing.T) { - m := setup(t, "../../test-data/tc.ingress.bpf.elf") - defer m.ctrl.Finish() - f, _ := os.Open(m.path) - defer f.Close() - - m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() - elfFile, err := elf.NewFile(f) - assert.NoError(t, err) - - elfLoader := newElfLoader(elfFile, m.ebpf_maps, m.ebpf_progs, "test") - _, _, err = elfLoader.doLoadELF() - assert.NoError(t, err) -} +func TestLoad(t *testing.T) { + progtests := []struct { + name string + elfFileName string + wantMap int + wantProg int + wantErr error + }{ + { + name: "Test Load ELF", + elfFileName: "../../test-data/tc.ingress.bpf.elf", + wantMap: 3, + wantProg: 3, + wantErr: nil, + }, + { + name: "Test Load ELF without reloc", + elfFileName: "../../test-data/tc.bpf.elf", + wantMap: 0, + wantProg: 1, + wantErr: nil, + }, + { + name: "Missing prog data", + elfFileName: "../../test-data/test.map.bpf.elf", + wantMap: 1, + wantProg: 0, + wantErr: nil, + }, + } -func TestLoadelfWithoutReloc(t *testing.T) { - m := setup(t, "../../test-data/tc.bpf.elf") - defer m.ctrl.Finish() - f, _ := os.Open(m.path) - defer f.Close() - - m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() - - elfFile, err := elf.NewFile(f) - assert.NoError(t, err) - elfLoader := newElfLoader(elfFile, m.ebpf_maps, m.ebpf_progs, "test") - _, _, err = elfLoader.doLoadELF() - assert.NoError(t, err) -} + for _, tt := range progtests { + t.Run(tt.name, func(t *testing.T) { + + m := setup(t, tt.elfFileName) + defer m.ctrl.Finish() + f, _ := os.Open(m.path) + defer f.Close() + + m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() + m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() + m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() + m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() + m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() + m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() + + elfFile, err := elf.NewFile(f) + assert.NoError(t, err) + elfLoader := newElfLoader(elfFile, m.ebpf_maps, m.ebpf_progs, "test") + loadedProgs, loadedMaps, err := elfLoader.doLoadELF() + assert.NoError(t, err) -func TestLoadelfWithoutProg(t *testing.T) { - m := setup(t, "../../test-data/test.map.bpf.elf") - defer m.ctrl.Finish() - f, _ := os.Open(m.path) - defer f.Close() - - m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().LoadProg(gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() - m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetProgFromPinPath(gomock.Any()).AnyTimes() - m.ebpf_progs.EXPECT().GetBPFProgAssociatedMapsIDs(gomock.Any()).AnyTimes() - - elfFile, err := elf.NewFile(f) - assert.NoError(t, err) - elfLoader := newElfLoader(elfFile, m.ebpf_maps, m.ebpf_progs, "test") - _, _, err = elfLoader.doLoadELF() - assert.NoError(t, err) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.Equal(t, tt.wantProg, len(loadedProgs)) + assert.Equal(t, tt.wantMap, len(loadedMaps)) + } + }) + } } func TestParseSection(t *testing.T) { @@ -410,3 +414,293 @@ func TestParseMap(t *testing.T) { } } + +func TestParseProg(t *testing.T) { + progtests := []struct { + name string + elfFileName string + want int + invalidate bool + invalidateRelo bool + wantErr error + }{ + { + name: "Missing prog section", + elfFileName: "../../test-data/test.map.bpf.elf", + want: 0, + wantErr: nil, + }, + { + name: "Test prog data", + elfFileName: "../../test-data/tc.ingress.bpf.elf", + want: 3, + wantErr: nil, + }, + { + name: "Missing prog data", + elfFileName: "../../test-data/tc.ingress.bpf.elf", + invalidate: true, + wantErr: errors.New("missing data in prog section"), + }, + { + name: "Missing relo data", + elfFileName: "../../test-data/tc.ingress.bpf.elf", + invalidateRelo: true, + wantErr: errors.New("failed to apply relocation: unable to parse relocation entries...."), + }, + } + + for _, tt := range progtests { + t.Run(tt.name, func(t *testing.T) { + + m := setup(t, tt.elfFileName) + defer m.ctrl.Finish() + f, _ := os.Open(m.path) + defer f.Close() + + elfFile, err := elf.NewFile(f) + assert.NoError(t, err) + elfLoader := newElfLoader(elfFile, m.ebpf_maps, m.ebpf_progs, "test") + + err = elfLoader.parseSection() + assert.NoError(t, err) + + mapData, err := elfLoader.parseMap() + assert.NoError(t, err) + + m.ebpf_maps.EXPECT().CreateBPFMap(gomock.Any()).AnyTimes() + m.ebpf_maps.EXPECT().PinMap(gomock.Any(), gomock.Any()).AnyTimes() + m.ebpf_maps.EXPECT().GetMapFromPinPath(gomock.Any()).AnyTimes() + + loadedMapData, err := elfLoader.loadMap(mapData) + assert.NoError(t, err) + + if tt.invalidate { + for progIndex, progEntry := range elfLoader.progSectionMap { + var dummySection elf.Section = elf.Section{} + copiedprogSection := *(progEntry.progSection) + copiedprogSection.SectionHeader = dummySection.SectionHeader + progEntry.progSection = &copiedprogSection + elfLoader.progSectionMap[progIndex] = progEntry + } + } + + if tt.invalidateRelo { + for progIndex, reloSection := range elfLoader.reloSectionMap { + var dummySection elf.Section = elf.Section{} + copiedreloSection := *(reloSection) + copiedreloSection.SectionHeader = dummySection.SectionHeader + reloSection = &copiedreloSection + elfLoader.reloSectionMap[progIndex] = reloSection + } + } + + parsedProgData, err := elfLoader.parseProg(loadedMapData) + + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + progCount := len(parsedProgData) + assert.Equal(t, tt.want, progCount) + } + }) + } + +} + +func mount_bpf_fs() error { + fmt.Println("Let's mount BPF FS") + err := syscall.Mount("bpf", "/sys/fs/bpf", "bpf", 0, "mode=0700") + if err != nil { + fmt.Println("error mounting bpffs") + } + return err +} + +func unmount_bpf_fs() error { + fmt.Println("Let's unmount BPF FS") + err := syscall.Unmount("/sys/fs/bpf", 0) + if err != nil { + fmt.Println("error unmounting bpffs") + } + return err +} + +func TestRecovery(t *testing.T) { + + mount_bpf_fs() + defer unmount_bpf_fs() + + progtests := []struct { + name string + elfFileName string + wantMap int + wantProg int + recoverGlobal bool + forceUnMount bool + wantErr error + }{ + { + name: "Recover Global maps", + elfFileName: "../../test-data/test.map.bpf.elf", + wantMap: 1, + recoverGlobal: true, + wantErr: nil, + }, + { + name: "Recover BPF data", + elfFileName: "../../test-data/recoverydata.bpf.elf", + wantProg: 3, + wantErr: nil, + }, + { + name: "Missing BPF mount", + elfFileName: "../../test-data/recoverydata.bpf.elf", + forceUnMount: true, + wantErr: errors.New("error checking BPF FS might not be mounted"), + }, + } + + for _, tt := range progtests { + t.Run(tt.name, func(t *testing.T) { + + m := setup(t, tt.elfFileName) + defer m.ctrl.Finish() + + bpfSDKclient := New() + + if tt.recoverGlobal { + _, _, err := bpfSDKclient.LoadBpfFile(m.path, "global") + if err != nil { + assert.NoError(t, err) + } + recoveredMaps, err := bpfSDKclient.RecoverGlobalMaps() + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.Equal(t, tt.wantMap, len(recoveredMaps)) + } + } else { + _, _, err := bpfSDKclient.LoadBpfFile(m.path, "test") + if err != nil { + assert.NoError(t, err) + } + + if tt.forceUnMount { + unmount_bpf_fs() + } + recoveredData, err := bpfSDKclient.RecoverAllBpfProgramsAndMaps() + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.Equal(t, tt.wantProg, len(recoveredData)) + } + } + }) + } +} + +func TestGetMapNameFromBPFPinPath(t *testing.T) { + type args struct { + pinPath string + } + + tests := []struct { + name string + args args + want [2]string + }{ + { + name: "Ingress Map Pinpath", + args: args{ + pinPath: "/sys/fs/bpf/globals/aws/maps/hello-udp-748dc8d996-default_ingress_map", + }, + want: [2]string{"ingress_map", "hello-udp-748dc8d996-default"}, + }, + { + name: "Egress Map Pinpath", + args: args{ + pinPath: "/sys/fs/bpf/globals/aws/maps/hello-udp-748dc8d996-default_egress_map", + }, + want: [2]string{"egress_map", "hello-udp-748dc8d996-default"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got1, got2 := GetMapNameFromBPFPinPath(tt.args.pinPath) + assert.Equal(t, tt.want[0], got1) + assert.Equal(t, tt.want[1], got2) + }) + } +} + +func TestMapGlobal(t *testing.T) { + type args struct { + pinPath string + } + + tests := []struct { + name string + args args + want bool + }{ + { + name: "Ingress Map", + args: args{ + pinPath: "/sys/fs/bpf/globals/aws/maps/hello-udp-748dc8d996-default_ingress_map", + }, + want: false, + }, + { + name: "Egress Map", + args: args{ + pinPath: "/sys/fs/bpf/globals/aws/maps/hello-udp-748dc8d996-default_egress_map", + }, + want: false, + }, + { + name: "Global", + args: args{ + pinPath: "/sys/fs/bpf/globals/aws/maps/test_global", + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IsMapGlobal(tt.args.pinPath) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestProgType(t *testing.T) { + + tests := []struct { + name string + progType string + want bool + }{ + { + name: "XDP", + progType: "xdp", + want: true, + }, + { + name: "TC", + progType: "tc_cls", + want: true, + }, + { + name: "Invalid prod", + progType: "tcc_cls", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isProgTypeSupported(tt.progType) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/test-data/recoverydata.bpf.c b/test-data/recoverydata.bpf.c new file mode 100644 index 0000000..dcc289b --- /dev/null +++ b/test-data/recoverydata.bpf.c @@ -0,0 +1,103 @@ +#include "vmlinux.h" +#include +#include +#include + +#define BPF_F_NO_PREALLOC 1 +#define PIN_GLOBAL_NS 2 + +struct bpf_map_def_pvt { + __u32 type; + __u32 key_size; + __u32 value_size; + __u32 max_entries; + __u32 map_flags; + __u32 pinning; + __u32 inner_map_fd; +}; + +struct lpm_trie_key { + __u32 prefixlen; + __u8 ip[4]; +}; + +struct lpm_trie_val { + __u32 protocol; + __u32 start_port; + __u32 end_port; +}; + +struct conntrack_key { + __u32 src_ip; + __u16 src_port; + __u32 dest_ip; + __u16 dest_port; + __u8 protocol; +}; + +struct conntrack_value { + __u8 val[4]; +}; + +struct bpf_map_def_pvt SEC("maps") ingress_map = { + .type = BPF_MAP_TYPE_LPM_TRIE, + .key_size =sizeof(struct lpm_trie_key), + .value_size = sizeof(struct lpm_trie_val[16]), + .max_entries = 100, + .map_flags = BPF_F_NO_PREALLOC, + .pinning = PIN_GLOBAL_NS, +}; + +struct bpf_map_def_pvt SEC("maps") aws_conntrack_map = { + .type = BPF_MAP_TYPE_LRU_HASH, + .key_size =sizeof(struct conntrack_key), + .value_size = sizeof(struct conntrack_value), + .max_entries = 65536, + .pinning = PIN_GLOBAL_NS, +}; + + +SEC("tc_cls") +int handle_ingress(struct __sk_buff *skb) +{ + struct lpm_trie_key trie_key; + trie_key.prefixlen = 32; + trie_key.ip[0] = 10; + trie_key.ip[1] = 1; + trie_key.ip[2] = 1; + trie_key.ip[3] = 100; + + struct lpm_trie_val *trie_val; + trie_val = bpf_map_lookup_elem(&ingress_map, &trie_key); + if (trie_val == NULL) { + return BPF_DROP; + } + return BPF_OK; +} + +SEC("kprobe/nf_ct_delete") +int conn_del(struct pt_regs *ctx) { + struct nf_conn *ct = (struct nf_conn *) PT_REGS_PARM1(ctx); + struct nf_conn new_ct = {}; + bpf_probe_read(&new_ct, sizeof(new_ct), ct); + struct conntrack_key flow_key = {}; + memset(&flow_key, 0, sizeof(flow_key)); + + struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX]; + bpf_probe_read(&tuplehash, sizeof(tuplehash), &new_ct.tuplehash); + + bpf_probe_read(&flow_key.src_ip, sizeof(flow_key.src_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip); + bpf_probe_read(&flow_key.src_port, sizeof(flow_key.src_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all); + bpf_probe_read(&flow_key.dest_ip, sizeof(flow_key.dest_ip), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.ip); + bpf_probe_read(&flow_key.dest_port, sizeof(flow_key.dest_port), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.all); + bpf_probe_read(&flow_key.protocol, sizeof(flow_key.protocol), &tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum); + + return 0; +} + +SEC("tracepoint/sched/sched_process_fork") +int sched_process_fork(struct sched_process_fork_t *ctx) { + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/test-data/test.map.bpf.c b/test-data/test.map.bpf.c index 2ffb451..aa53ecf 100644 --- a/test-data/test.map.bpf.c +++ b/test-data/test.map.bpf.c @@ -4,7 +4,19 @@ #include #define PIN_GLOBAL_NS 2 -#define BPF_MAP_TYPE_RINGBUF 27 + +struct conntrack_key { + __u32 src_ip; + __u16 src_port; + __u32 dest_ip; + __u16 dest_port; + __u8 protocol; +}; + +struct conntrack_value { + __u8 val[4]; +}; + struct bpf_map_def_pvt { __u32 type; @@ -16,9 +28,11 @@ struct bpf_map_def_pvt { __u32 inner_map_fd; }; -struct bpf_map_def_pvt SEC("maps") policy_events = { - .type = BPF_MAP_TYPE_RINGBUF, - .max_entries = 256 * 1024, +struct bpf_map_def_pvt SEC("maps") aws_conntrack_map = { + .type = BPF_MAP_TYPE_LRU_HASH, + .key_size =sizeof(struct conntrack_key), + .value_size = sizeof(struct conntrack_value), + .max_entries = 65536, .pinning = PIN_GLOBAL_NS, };