From 13f775e8f3dcc9ba4a20fac87bf9bb1aa1f1cc7c Mon Sep 17 00:00:00 2001 From: Jayanth Varavani <1111446+jayanthvn@users.noreply.github.com> Date: Mon, 21 Aug 2023 01:32:23 +0000 Subject: [PATCH] XDP UTs --- Makefile | 8 +- pkg/elfparser/elf_test.go | 295 ++++++++++++++++++++++++++++++++++++++ pkg/xdp/xdp.go | 34 +++-- pkg/xdp/xdp_test.go | 239 ++++++++++++++++++++++++++++++ test-data/xdp.bpf.c | 11 ++ 5 files changed, 578 insertions(+), 9 deletions(-) create mode 100644 pkg/xdp/xdp_test.go create mode 100644 test-data/xdp.bpf.c diff --git a/Makefile b/Makefile index 3a75e1e..0d1dbd1 100644 --- a/Makefile +++ b/Makefile @@ -41,13 +41,19 @@ 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 +EBPF_TEST_XDP_SOURCE := test-data/xdp.bpf.c +EBPF_TEST_XDP_BINARY := test-data/xdp.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) + $(CLANG) $(CLANG_INCLUDE) -g -O2 -Wall -fpie -target bpf -DCORE -D__BPF_TRACING__ -march=bpf -D__TARGET_ARCH_$(ARCH) -c $(EBPF_TEST_XDP_SOURCE) -o $(EBPF_TEST_XDP_BINARY) vmlinuxh: bpftool btf dump file /sys/kernel/btf/vmlinux format c > $(abspath ./test-data/vmlinux.h) diff --git a/pkg/elfparser/elf_test.go b/pkg/elfparser/elf_test.go index 191d28a..ede97c5 100644 --- a/pkg/elfparser/elf_test.go +++ b/pkg/elfparser/elf_test.go @@ -410,3 +410,298 @@ 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 TestRecoveryWithoutMount(t *testing.T) { + m := setup(t, "../../test-data/recoverydata.bpf.elf") + defer m.ctrl.Finish() + + bpfSDKclient := New() + + unmount_bpf_fs() + + _, err := bpfSDKclient.RecoverAllBpfProgramsAndMaps() + + assert.EqualError(t, err, errors.New("error checking BPF FS might not be mounted").Error()) + +} + +func TestRecovery(t *testing.T) { + + mount_bpf_fs() + defer unmount_bpf_fs() + + progtests := []struct { + name string + elfFileName string + wantMap int + wantProg int + recoverGlobal 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, + }, + } + + 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) + } + + 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) + }) + } +} +>>>>>>> 7da4278 (XDP UTs) diff --git a/pkg/xdp/xdp.go b/pkg/xdp/xdp.go index 1d4274b..b0a9c78 100644 --- a/pkg/xdp/xdp.go +++ b/pkg/xdp/xdp.go @@ -22,30 +22,48 @@ import ( var log = logger.Get() -func XDPAttach(interfaceName string, progFD int) error { +type BpfXdp interface { + XDPAttach(progFD int) error + XDPDetach() error +} + +var _ BpfXdp = &bpfXdp{} + +type bpfXdp struct { + interfaceName string +} + +func New(ifName string) BpfXdp { + return &bpfXdp{ + interfaceName: ifName, + } + +} + +func (b *bpfXdp) XDPAttach(progFD int) error { - link, err := netlink.LinkByName(interfaceName) + link, err := netlink.LinkByName(b.interfaceName) if err != nil { - log.Errorf("failed to obtain link info for %s : %v", interfaceName, err) + log.Errorf("failed to obtain link info for %s : %v", b.interfaceName, err) return err } - log.Infof("Attaching xdp prog %d to interface %s", progFD, interfaceName) + log.Infof("Attaching xdp prog %d to interface %s", progFD, b.interfaceName) if err := netlink.LinkSetXdpFdWithFlags(link, progFD, constdef.XDP_ATTACH_MODE_SKB); err != nil { log.Errorf("failed to setup xdp: %v", err) return err } - log.Infof("Attached XDP to interface %s", interfaceName) + log.Infof("Attached XDP to interface %s", b.interfaceName) return nil } -func XDPDetach(interfaceName string) error { +func (b *bpfXdp) XDPDetach() error { - link, err := netlink.LinkByName(interfaceName) + link, err := netlink.LinkByName(b.interfaceName) if err != nil { - log.Errorf("failed to obtain link info for %s : %v", interfaceName, err) + log.Errorf("failed to obtain link info for %s : %v", b.interfaceName, err) return err } diff --git a/pkg/xdp/xdp_test.go b/pkg/xdp/xdp_test.go new file mode 100644 index 0000000..03c44db --- /dev/null +++ b/pkg/xdp/xdp_test.go @@ -0,0 +1,239 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +//limitations under the License. + +package xdp + +import ( + "errors" + "fmt" + "os" + "syscall" + "testing" + + constdef "github.com/aws/aws-ebpf-sdk-go/pkg/constants" + "github.com/aws/aws-ebpf-sdk-go/pkg/elfparser" + mock_ebpf_maps "github.com/aws/aws-ebpf-sdk-go/pkg/maps/mocks" + mock_ebpf_progs "github.com/aws/aws-ebpf-sdk-go/pkg/progs/mocks" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/vishvananda/netlink" +) + +const ( + DUMMY_PROG_PREFIX = "test" + DUMMY_PROG_NAME = "xdp_test_prog" +) + +type testMocks struct { + path string + ctrl *gomock.Controller + ebpf_progs *mock_ebpf_progs.MockBpfProgAPIs + ebpf_maps *mock_ebpf_maps.MockBpfMapAPIs + xdpClient BpfXdp +} + +func setup(t *testing.T, testPath string, interfaceName string) *testMocks { + ctrl := gomock.NewController(t) + return &testMocks{ + path: testPath, + ctrl: ctrl, + ebpf_progs: mock_ebpf_progs.NewMockBpfProgAPIs(ctrl), + ebpf_maps: mock_ebpf_maps.NewMockBpfMapAPIs(ctrl), + xdpClient: New(interfaceName), + } +} + +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 setupTest(interfaceNames []string, t *testing.T) { + mount_bpf_fs() + for _, interfaceName := range interfaceNames { + linkAttr := netlink.LinkAttrs{Name: interfaceName} + linkIFB := netlink.Ifb{} + linkIFB.LinkAttrs = linkAttr + if err := netlink.LinkAdd(&linkIFB); err != nil { + assert.NoError(t, err) + } + } +} + +func teardownTest(interfaceNames []string, t *testing.T, ignoreDelErr bool) { + unmount_bpf_fs() + //Cleanup link + for _, interfaceName := range interfaceNames { + linkAttr := netlink.LinkAttrs{Name: interfaceName} + linkIFB := netlink.Ifb{} + linkIFB.LinkAttrs = linkAttr + if err := netlink.LinkDel(&linkIFB); err != nil && !ignoreDelErr { + assert.NoError(t, err) + } + } +} + +func deleteLinks(interfaceNames []string, t *testing.T) { + //Cleanup link + for _, interfaceName := range interfaceNames { + linkAttr := netlink.LinkAttrs{Name: interfaceName} + linkIFB := netlink.Ifb{} + linkIFB.LinkAttrs = linkAttr + if err := netlink.LinkDel(&linkIFB); err != nil { + assert.NoError(t, err) + } + } +} + +func TestTCXdpAttachDetach(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("Test requires root privileges.") + } + + interfaceName := "foo" + + m := setup(t, "../../test-data/xdp.bpf.elf", interfaceName) + defer m.ctrl.Finish() + + var interfaceNames []string + interfaceNames = append(interfaceNames, interfaceName) + setupTest(interfaceNames, t) + defer teardownTest(interfaceNames, t, false) + + 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() + + bpfSDKclient := elfparser.New() + progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_PREFIX) + if err != nil { + assert.NoError(t, err) + } + pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_PREFIX + "_" + DUMMY_PROG_NAME + + progFD := progInfo[pinPath].Program.ProgFD + if err := m.xdpClient.XDPAttach(progFD); err != nil { + assert.NoError(t, err) + } + + if err := m.xdpClient.XDPDetach(); err != nil { + assert.NoError(t, err) + } +} + +func TestNetLinkAPIs(t *testing.T) { + + netLinktests := []struct { + name string + interfaceName string + overrideName bool + overrideProg bool + skipAttach bool + want []int + wantErr error + }{ + { + name: "Failed Link By Name", + interfaceName: "foo", + want: nil, + overrideName: true, + wantErr: errors.New("Link not found"), + }, + { + name: "Invalid Program", + interfaceName: "foo", + want: nil, + overrideProg: true, + wantErr: errors.New("invalid argument"), + }, + { + name: "Detach without attach Program", + interfaceName: "foo", + want: nil, + skipAttach: true, + wantErr: nil, + }, + } + + for _, tt := range netLinktests { + t.Run(tt.name, func(t *testing.T) { + m := setup(t, "../../test-data/xdp.bpf.elf", tt.interfaceName) + defer m.ctrl.Finish() + + var interfaceNames []string + interfaceNames = append(interfaceNames, tt.interfaceName) + + setupTest(interfaceNames, t) + defer teardownTest(interfaceNames, t, tt.overrideName) + + 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() + + bpfSDKclient := elfparser.New() + progInfo, _, err := bpfSDKclient.LoadBpfFile(m.path, DUMMY_PROG_PREFIX) + if err != nil { + assert.NoError(t, err) + } + + if tt.overrideName { + deleteLinks(interfaceNames, t) + } + + pinPath := constdef.PROG_BPF_FS + DUMMY_PROG_PREFIX + "_" + DUMMY_PROG_NAME + + progFD := progInfo[pinPath].Program.ProgFD + + if tt.overrideProg { + progFD = 0 + } + if !tt.skipAttach { + err = m.xdpClient.XDPAttach(progFD) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + } + } + + if tt.skipAttach { + err = m.xdpClient.XDPDetach() + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + } + } + }) + } +} diff --git a/test-data/xdp.bpf.c b/test-data/xdp.bpf.c new file mode 100644 index 0000000..aba377f --- /dev/null +++ b/test-data/xdp.bpf.c @@ -0,0 +1,11 @@ +#include "vmlinux.h" +#include +#include +#include + +SEC("xdp") +int xdp_test_prog(struct xdp_md *ctx) +{ + return XDP_DROP; +} +char _license[] SEC("license") = "GPL"; \ No newline at end of file