├── examples ├── kprobe │ └── exec_dump │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf_prog │ │ └── kprobe.c │ │ └── main.go ├── xdp │ ├── xdp_dump │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf_prog │ │ │ └── xdp_dump.c │ │ └── main.go │ ├── basic_firewall │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf_prog │ │ │ └── xdp_fw.c │ │ └── main.go │ ├── packet_counter │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf_prog │ │ │ └── xdp.c │ │ └── main.go │ └── bpf_redirect_map │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── main.go │ │ └── ebpf_prog │ │ └── xdp.c ├── socket_filter │ └── packet_counter │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf_prog │ │ └── sock_filter.c │ │ └── main.go ├── tc │ └── packet_counter │ │ ├── Makefile │ │ ├── ebpf_prog │ │ ├── tc.c │ │ └── tc.h │ │ └── main.go └── README.md ├── itest ├── all_test.go ├── utils_test.go ├── ebpf_prog │ ├── tc1.c │ ├── kprobe1.c │ └── xdp1.c ├── path.go ├── Makefile ├── perf_events_test.go ├── tc_test.go ├── kprobe_test.go └── xdp_test.go ├── .gitignore ├── go.mod ├── goebpf_mock ├── mock_prog_test.go ├── wrapper │ └── wrapper.go ├── mock_ebpf.go ├── mock_prog.go └── mock_map_test.go ├── .github ├── auto_assign.yml └── workflows │ └── go.yml ├── perf_events_handler_test.go ├── LICENSE ├── loader_test.go ├── mmap_ring_buffer_test.go ├── map_test.go ├── program_socket_filter.go ├── go.sum ├── perf_events_poller.go ├── mmap_ring_buffer.go ├── ebpf.go ├── program_xdp.go ├── README.md ├── bpf.h ├── perf_events_handler.go ├── program_base.go ├── doc.go ├── perf_events.go ├── utils_test.go ├── program_kprobe.go ├── program_tc.go ├── utils.go └── loader.go /examples/kprobe/exec_dump/.gitignore: -------------------------------------------------------------------------------- 1 | exec_dump -------------------------------------------------------------------------------- /examples/xdp/xdp_dump/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.elf 3 | -------------------------------------------------------------------------------- /examples/xdp/basic_firewall/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.elf 3 | -------------------------------------------------------------------------------- /examples/xdp/packet_counter/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.elf 3 | -------------------------------------------------------------------------------- /examples/xdp/bpf_redirect_map/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.elf 3 | -------------------------------------------------------------------------------- /examples/socket_filter/packet_counter/.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | *.elf 3 | -------------------------------------------------------------------------------- /itest/all_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | const ( 7 | bpfPath = "/sys/fs/bpf" 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.elf 8 | *.swp 9 | itest_test 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dropbox/goebpf 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.1 7 | github.com/vishvananda/netlink v1.1.1-0.20200218174631-5f2fc868c2d0 8 | github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe // indirect 9 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 10 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /itest/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/dropbox/goebpf" 12 | ) 13 | 14 | func TestGetNumOfPossibleCpus(t *testing.T) { 15 | cpus, err := goebpf.GetNumOfPossibleCpus() 16 | assert.NoError(t, err) 17 | assert.True(t, cpus > 0) 18 | } 19 | -------------------------------------------------------------------------------- /goebpf_mock/mock_prog_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf_mock 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/dropbox/goebpf" 10 | ) 11 | 12 | // Just to ensure that MockProgram implements goebpf.Program interface 13 | func TestMockProgram(t *testing.T) { 14 | mockBpf := NewMockSystem() 15 | mockBpf.Programs["test"] = NewMockProgram("test", goebpf.ProgramTypeXdp) 16 | } 17 | -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | # Set to true to add reviewers to pull requests 2 | addReviewers: true 3 | 4 | # A list of reviewers to be added to pull requests (GitHub user name) 5 | reviewers: 6 | - fuhry 7 | - lopter-dbx 8 | - ravenblackx 9 | 10 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 11 | skipKeywords: 12 | - wip 13 | 14 | # A number of reviewers added to the pull request 15 | # Set 0 to add all the reviewers (default: 0) 16 | numberOfReviewers: 0 17 | -------------------------------------------------------------------------------- /itest/ebpf_prog/tc1.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Set of simple TC programs for eBPF library integration tests 5 | 6 | #include "bpf_helpers.h" 7 | 8 | 9 | SEC("tc_cls") 10 | int tc1(struct __sk_buff *skb) 11 | { 12 | return BPF_OK; 13 | } 14 | 15 | SEC("tc_act") 16 | int tc2(struct __sk_buff *skb) 17 | { 18 | return BPF_DROP; 19 | } 20 | 21 | SEC("tc_act") 22 | int tc3(struct __sk_buff *skb) 23 | { 24 | return bpf_redirect(1, 0); 25 | } 26 | 27 | char _license[] SEC("license") = "GPL"; 28 | -------------------------------------------------------------------------------- /perf_events_handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | const pageSize = 4096 13 | 14 | func TestMmapMemorySize(t *testing.T) { 15 | runs := map[int]int{ 16 | 1: pageSize * 2, 17 | pageSize / 2: pageSize * 2, 18 | pageSize - 1: pageSize * 2, 19 | pageSize: pageSize * 3, 20 | } 21 | 22 | for left, right := range runs { 23 | assert.Equal(t, right, calculateMmapSize(left)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Dropbox, Inc. 2 | 3 | Licensed under GPL version 2 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /examples/xdp/bpf_redirect_map/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/xdp.c 14 | EBPF_BINARY := ebpf_prog/xdp.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/xdp/packet_counter/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/xdp.c 14 | EBPF_BINARY := ebpf_prog/xdp.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/xdp/xdp_dump/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/xdp_dump.c 14 | EBPF_BINARY := ebpf_prog/xdp_dump.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/kprobe/exec_dump/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/kprobe.c 14 | EBPF_BINARY := ebpf_prog/kprobe.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/xdp/basic_firewall/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/xdp_fw.c 14 | EBPF_BINARY := ebpf_prog/xdp_fw.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/socket_filter/packet_counter/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_SOURCE := ebpf_prog/sock_filter.c 14 | EBPF_BINARY := ebpf_prog/sock_filter.elf 15 | 16 | all: build_bpf build_go 17 | 18 | build_bpf: $(EBPF_BINARY) 19 | 20 | build_go: $(GO_BINARY) 21 | 22 | clean: 23 | $(GOCLEAN) 24 | rm -f $(GO_BINARY) 25 | rm -f $(EBPF_BINARY) 26 | 27 | $(EBPF_BINARY): $(EBPF_SOURCE) 28 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -c $^ -o $@ 29 | 30 | $(GO_BINARY): $(GO_SOURCE) 31 | $(GOBUILD) -v -o $@ 32 | -------------------------------------------------------------------------------- /examples/tc/packet_counter/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | CLANG := clang 8 | CLANG_INCLUDE := -I../../.. 9 | 10 | GO_SOURCE := main.go 11 | GO_BINARY := main 12 | 13 | EBPF_HEADERS := $(wildcard ebpf_prog/*.h) 14 | EBPF_SOURCE := $(wildcard ebpf_prog/*.c) 15 | EBPF_BINARY := ebpf_prog/tc.elf 16 | 17 | all: build_bpf build_go 18 | 19 | build_bpf: $(EBPF_BINARY) 20 | 21 | build_go: $(GO_BINARY) 22 | 23 | clean: 24 | $(GOCLEAN) 25 | rm -f $(GO_BINARY) 26 | rm -f $(EBPF_BINARY) 27 | 28 | $(EBPF_SOURCE): $(EBPF_HEADERS) 29 | 30 | $(EBPF_BINARY): $(EBPF_SOURCE) 31 | $(CLANG) $(CLANG_INCLUDE) -O2 -target bpf -DGO_EBPF -c $^ -o $@ 32 | 33 | $(GO_BINARY): $(GO_SOURCE) 34 | $(GOBUILD) -v -o $@ 35 | -------------------------------------------------------------------------------- /examples/socket_filter/packet_counter/ebpf_prog/sock_filter.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Very simple SocketFilter program to count packets. 5 | 6 | #include "bpf_helpers.h" 7 | 8 | // eBPF map to packet counter 9 | BPF_MAP_DEF(counter) = { 10 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 11 | .key_size = sizeof(__u32), 12 | .value_size = sizeof(__u64), 13 | .max_entries = 1, 14 | }; 15 | BPF_MAP_ADD(counter); 16 | 17 | 18 | // Socket Filter program // 19 | SEC("socket_filter") 20 | int packet_counter(struct __sk_buff *skb) { 21 | // Simply increase counter 22 | __u32 idx = 0; 23 | __u64 *value = bpf_map_lookup_elem(&counter, &idx); 24 | if (value) { 25 | *value += 1; 26 | } 27 | 28 | return SOCKET_FILTER_ALLOW; 29 | } 30 | 31 | char _license[] SEC("license") = "GPLv2"; 32 | -------------------------------------------------------------------------------- /loader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBpfInstruction(t *testing.T) { 13 | exp1 := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} 14 | exp2 := []byte{0x1, 0xab, 0xdd, 0xcc, 0x04, 0x03, 0x02, 0x01} 15 | 16 | // Test save() 17 | b := &bpfInstruction{} 18 | assert.Equal(t, exp1, b.save()) 19 | b.code = 1 20 | b.srcReg = 0xa 21 | b.dstReg = 0xb 22 | b.offset = 0xccdd 23 | b.imm = 0x01020304 24 | assert.Equal(t, exp2, b.save()) 25 | 26 | // Test load() 27 | b = &bpfInstruction{} 28 | b.load(exp2) 29 | assert.Equal(t, uint8(1), b.code) 30 | assert.Equal(t, uint8(0xa), b.srcReg) 31 | assert.Equal(t, uint8(0xb), b.dstReg) 32 | assert.Equal(t, uint16(0xccdd), b.offset) 33 | assert.Equal(t, uint32(0x01020304), b.imm) 34 | } 35 | -------------------------------------------------------------------------------- /itest/ebpf_prog/kprobe1.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Set of simple kprobe programs for eBPF library integration tests 5 | 6 | #include "bpf_helpers.h" 7 | 8 | BPF_MAP_DEF(perf_map) = { 9 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 10 | .max_entries = 128, 11 | }; 12 | BPF_MAP_ADD(perf_map); 13 | 14 | SEC("kprobe/guess_execve") 15 | int kprobe0(struct pt_regs *ctx) { 16 | char comm[32]; 17 | bpf_get_current_comm(comm, sizeof(comm)); 18 | bpf_perf_event_output(ctx, &perf_map, BPF_F_CURRENT_CPU, comm, sizeof(comm)); 19 | return 0; 20 | } 21 | 22 | SEC("kretprobe/guess_execve") 23 | int kprobe1(struct pt_regs *ctx) { 24 | char comm[32]; 25 | bpf_get_current_comm(comm, sizeof(comm)); 26 | bpf_perf_event_output(ctx, &perf_map, BPF_F_CURRENT_CPU, comm, sizeof(comm)); 27 | return 0; 28 | } 29 | 30 | char _license[] SEC("license") = "GPL"; 31 | -------------------------------------------------------------------------------- /itest/path.go: -------------------------------------------------------------------------------- 1 | package itest 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | ) 8 | 9 | const progSubdir = "ebpf_prog" 10 | 11 | // Helper to get the absolute path of a test program. 12 | func progPath(imageName string) string { 13 | // within `go test`, the test executable lives in a temporary directory, so 14 | // we check the WD first. 15 | exePath, err := os.Executable() 16 | if err != nil { 17 | panic(err) 18 | } 19 | wd, err := os.Getwd() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | relPath := path.Join(progSubdir, imageName) 25 | wdPath := path.Join(wd, relPath) 26 | absPath := path.Join(path.Dir(exePath), relPath) 27 | 28 | if _, err = os.Stat(wdPath); err == nil { 29 | return wdPath 30 | } 31 | 32 | if _, err = os.Stat(absPath); err == nil { 33 | return absPath 34 | } 35 | 36 | panic( 37 | fmt.Sprintf( 38 | "eBPF executable not found, checked paths:\n %q\n %q", 39 | wdPath, absPath)) 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "master", "main" ] 6 | pull_request: 7 | branches: [ "master", "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go-version: [ '1.x', '1.18', '1.16' ] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up Go version ${{ matrix.go-version }} 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Test formatting 26 | run: if gofmt -s -l . | grep -Eqx '.+'; then echo "Please fix source formatting by running 'go fmt ./...'"; exit 1; fi 27 | 28 | - name: Build 29 | run: go build -v ./... 30 | 31 | - name: Test goebpf 32 | run: go test -v -coverprofile=coverage.txt -covermode=atomic 33 | 34 | - name: Test goebpf_mock 35 | run: cd goebpf_mock && go test -v -coverprofile=coverage.txt -covermode=atomic 36 | 37 | - name: Integration test build-only - they cannot be run in CI without root access 38 | run: cd itest && make 39 | 40 | -------------------------------------------------------------------------------- /goebpf_mock/wrapper/wrapper.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Package wrapper is wrapper for "cross compiled" XDP in order to call it right from GO 5 | // P.S. this should be as part of *_test.go files, however, GO does not support 6 | // using import "C" from tests... :-( 7 | package wrapper 8 | 9 | // #cgo CFLAGS: -I../.. 10 | /* 11 | #include "bpf_helpers.h" 12 | 13 | // Since eBPF mock package is optional and have definition of "__maps_head" symbol 14 | // it may cause link error, so defining weak symbol here as well 15 | __attribute__((weak)) struct __create_map_def maps_head; 16 | __attribute__((weak)) struct __maps_head_def *__maps_head = (struct __maps_head_def*) &maps_head; 17 | 18 | BPF_MAP_DEF(map_hash) = { 19 | .map_type = BPF_MAP_TYPE_HASH, 20 | .key_size = sizeof(__u64), 21 | .value_size = sizeof(__u32), 22 | .max_entries = 50, 23 | }; 24 | BPF_MAP_ADD(map_hash); 25 | 26 | BPF_MAP_DEF(map_array) = { 27 | .map_type = BPF_MAP_TYPE_ARRAY, 28 | .key_size = sizeof(__u32), 29 | .value_size = sizeof(__u64), 30 | .max_entries = 10, 31 | }; 32 | BPF_MAP_ADD(map_array); 33 | */ 34 | import "C" 35 | 36 | // Dummy is simply nothing - just to force golang to include empty package 37 | const Dummy = 0 38 | -------------------------------------------------------------------------------- /mmap_ring_buffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import ( 7 | "testing" 8 | "unsafe" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestRingBuffer(t *testing.T) { 14 | // Create golang array with test data 15 | buffer := make([]byte, 8) 16 | for i := 0; i < len(buffer); i++ { 17 | buffer[i] = byte(i) 18 | } 19 | 20 | // Create ring buffer based on filled array 21 | rb := ringBufferFromArray(buffer) 22 | 23 | // Read 2 bytes 24 | assert.Equal(t, []byte{0, 1}, rb.Read(2)) 25 | assert.Equal(t, 2, rb.tail) 26 | 27 | // Read 6 bytes (no rollover, right at the edge) 28 | assert.Equal(t, []byte{2, 3, 4, 5, 6, 7}, rb.Read(6)) 29 | assert.Equal(t, 8, rb.tail) 30 | 31 | // Read entire 8 bytes 32 | assert.Equal(t, []byte{0, 1, 2, 3, 4, 5, 6, 7}, rb.Read(8)) 33 | assert.Equal(t, 16, rb.tail) 34 | 35 | // Read with rollover (1st read 6 bytes, then 8) 36 | assert.Equal(t, []byte{0, 1, 2, 3, 4, 5}, rb.Read(6)) 37 | assert.Equal(t, 22, rb.tail) 38 | assert.Equal(t, []byte{6, 7, 0, 1, 2, 3, 4, 5}, rb.Read(8)) 39 | assert.Equal(t, 30, rb.tail) 40 | } 41 | 42 | // Helper to construct ringBuffer from go array 43 | func ringBufferFromArray(array []byte) *mmapRingBuffer { 44 | ptr := unsafe.Pointer(&array[0]) 45 | size := len(array) 46 | 47 | return &mmapRingBuffer{ 48 | ptr: ptr, 49 | start: ptr, 50 | size: size, 51 | end: uintptr(ptr) + uintptr(size), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMapFromElf(t *testing.T) { 13 | payload1 := []byte{ 14 | 1, 0, 0, 0, // map type hash 15 | 0xa, 0, 0, 0, // key size is 10 16 | 0, 0x10, 0, 0, // value size is 4096 17 | 0, 0, 0x1, 0, // max items is 65536 18 | 0, 0, 0, 0, // flags 19 | 0, 0, 0, 0, // padding 20 | // next fields are not used by newMapFromElfSection 21 | 0, 0, 0, 0, 0, 0, 0, 0, // inner map ptr 22 | 0, 0, 0, 0, 0, 0, 0, 0, // persistent path 23 | } 24 | 25 | m, err := newMapFromElfSection(payload1) 26 | assert.NoError(t, err) 27 | assert.Equal(t, MapTypeHash, m.Type) 28 | assert.Equal(t, 10, m.KeySize) 29 | assert.Equal(t, 4096, m.ValueSize) 30 | assert.Equal(t, 65536, m.MaxEntries) 31 | 32 | // Negative 33 | m, err = newMapFromElfSection([]byte("123")) 34 | assert.Error(t, err) 35 | assert.Nil(t, m) 36 | } 37 | 38 | func TestMapCloneTemplate(t *testing.T) { 39 | m := &EbpfMap{ 40 | fd: 10, 41 | Name: "map1", 42 | Type: MapTypeHash, 43 | KeySize: 4, 44 | ValueSize: 4, 45 | MaxEntries: 100, 46 | InnerMapName: "inner", 47 | InnerMapFd: 5, 48 | } 49 | 50 | cloned := m.CloneTemplate() 51 | // Ensure that fd hasn't copied 52 | assert.Equal(t, 0, cloned.GetFd()) 53 | // Compare the rest 54 | cloned.(*EbpfMap).fd = 10 55 | assert.Equal(t, m, cloned) 56 | } 57 | -------------------------------------------------------------------------------- /itest/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Dropbox, Inc. 2 | # Full license can be found in the LICENSE file. 3 | 4 | GOCMD := go 5 | GOBUILD := $(GOCMD) build 6 | GOCLEAN := $(GOCMD) clean 7 | GOTEST := $(GOCMD) test 8 | GOGET := $(GOCMD) get 9 | 10 | TEST_SOURCE := $(wildcard *.go) 11 | TEST_BINARY := ./itest_test 12 | 13 | EBPF_XDP_SOURCE := ebpf_prog/xdp1.c 14 | EBPF_XDP_BINARY := ebpf_prog/xdp1.elf 15 | EBPF_KPROBE_SOURCE := ebpf_prog/kprobe1.c 16 | EBPF_KPROBE_BINARY := ebpf_prog/kprobe1.elf 17 | EBPF_TC_SOURCE := ebpf_prog/tc1.c 18 | EBPF_TC_BINARY := ebpf_prog/tc1.elf 19 | 20 | EUID := $(shell id -u -r) 21 | 22 | all: build_test build_bpf 23 | 24 | $(EBPF_XDP_BINARY): $(EBPF_XDP_SOURCE) 25 | clang -I.. -O2 -target bpf -c $^ -o $@ 26 | 27 | $(EBPF_KPROBE_BINARY): $(EBPF_KPROBE_SOURCE) 28 | clang -I.. -O2 -target bpf -c $^ -o $@ 29 | 30 | $(EBPF_TC_BINARY): $(EBPF_TC_SOURCE) 31 | clang -I.. -O2 -target bpf -c $^ -o $@ 32 | 33 | $(TEST_BINARY): $(TEST_SOURCE) 34 | $(GOTEST) -c -v -o $@ 35 | 36 | build_test: $(TEST_BINARY) 37 | build_bpf: $(EBPF_XDP_BINARY) $(EBPF_KPROBE_BINARY) $(EBPF_TC_BINARY) 38 | 39 | check_root: 40 | ifneq ($(EUID),0) 41 | @echo "\nPlease run as root user in order to work with eBPF maps / programs.\n" 42 | @exit 1 43 | endif 44 | 45 | clean: 46 | $(GOCLEAN) 47 | rm -f $(TEST_BINARY) 48 | rm -f $(EBPF_XDP_BINARY) 49 | rm -f $(EBPF_KPROBE_BINARY) 50 | rm -f $(EBPF_TC_BINARY) 51 | 52 | test: check_root build_bpf build_test 53 | @ulimit -l unlimited 54 | @mount bpf -t bpf /sys/fs/bpf 55 | ./$(TEST_BINARY) -test.v 56 | @umount /sys/fs/bpf 57 | 58 | run: test 59 | -------------------------------------------------------------------------------- /goebpf_mock/mock_ebpf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf_mock 5 | 6 | import ( 7 | "io" 8 | 9 | "github.com/dropbox/goebpf" 10 | ) 11 | 12 | // MockSystem is mock implementation of eBPF system 13 | type MockSystem struct { 14 | Programs map[string]goebpf.Program 15 | Maps map[string]goebpf.Map 16 | 17 | // ErrorLoadElf specifies return value for LoadElf() method. 18 | ErrorLoadElf error 19 | } 20 | 21 | // NewMockSystem creates mocked eBPF system with: 22 | // - empty program map 23 | // - all linked mock maps 24 | func NewMockSystem() *MockSystem { 25 | return &MockSystem{ 26 | Programs: make(map[string]goebpf.Program), 27 | Maps: MockMaps, 28 | } 29 | } 30 | 31 | // LoadElf does nothing, just a mock for original LoadElf 32 | func (m *MockSystem) LoadElf(path string) error { 33 | return m.ErrorLoadElf 34 | } 35 | 36 | // Load does nothing, just a mock for original Load 37 | func (m *MockSystem) Load(r io.ReaderAt) error { 38 | return m.ErrorLoadElf 39 | } 40 | 41 | // GetMaps returns all linked eBPF maps 42 | func (m *MockSystem) GetMaps() map[string]goebpf.Map { 43 | return m.Maps 44 | } 45 | 46 | // GetPrograms returns map of added eBPF programs 47 | func (m *MockSystem) GetPrograms() map[string]goebpf.Program { 48 | return m.Programs 49 | } 50 | 51 | // GetMapByName returns eBPF map by name or nil if not found 52 | func (m *MockSystem) GetMapByName(name string) goebpf.Map { 53 | if result, ok := m.Maps[name]; ok { 54 | return result 55 | } 56 | return nil 57 | } 58 | 59 | // GetProgramByName returns eBPF program by name or nil if not found 60 | func (m *MockSystem) GetProgramByName(name string) goebpf.Program { 61 | if result, ok := m.Programs[name]; ok { 62 | return result 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /examples/xdp/packet_counter/ebpf_prog/xdp.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Very simple XDP program for eBPF library integration tests 5 | 6 | #include "bpf_helpers.h" 7 | 8 | // Ethernet header 9 | struct ethhdr { 10 | __u8 h_dest[6]; 11 | __u8 h_source[6]; 12 | __u16 h_proto; 13 | } __attribute__((packed)); 14 | 15 | // IPv4 header 16 | struct iphdr { 17 | __u8 ihl : 4; 18 | __u8 version : 4; 19 | __u8 tos; 20 | __u16 tot_len; 21 | __u16 id; 22 | __u16 frag_off; 23 | __u8 ttl; 24 | __u8 protocol; 25 | __u16 check; 26 | __u32 saddr; 27 | __u32 daddr; 28 | } __attribute__((packed)); 29 | 30 | 31 | // eBPF map to store IP proto counters (tcp, udp, etc) 32 | BPF_MAP_DEF(protocols) = { 33 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 34 | .key_size = sizeof(__u32), 35 | .value_size = sizeof(__u64), 36 | .max_entries = 255, 37 | }; 38 | BPF_MAP_ADD(protocols); 39 | 40 | 41 | // XDP program // 42 | SEC("xdp") 43 | int packet_count(struct xdp_md *ctx) { 44 | void *data_end = (void *)(long)ctx->data_end; 45 | void *data = (void *)(long)ctx->data; 46 | 47 | // Only IPv4 supported for this example 48 | struct ethhdr *ether = data; 49 | if (data + sizeof(*ether) > data_end) { 50 | return XDP_ABORTED; 51 | } 52 | if (ether->h_proto == 0x08U) { // htons(ETH_P_IP) -> 0x08U 53 | data += sizeof(*ether); 54 | struct iphdr *ip = data; 55 | if (data + sizeof(*ip) > data_end) { 56 | return XDP_ABORTED; 57 | } 58 | // Increase counter in "protocols" eBPF map 59 | __u32 proto_index = ip->protocol; 60 | __u64 *counter = bpf_map_lookup_elem(&protocols, &proto_index); 61 | if (counter) { 62 | (*counter)++; 63 | } 64 | } 65 | 66 | return XDP_PASS; 67 | } 68 | 69 | char _license[] SEC("license") = "GPLv2"; 70 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Please note that `eBPF` is supported only by Linux, it will not work on `MacOS`! 4 | 5 | ## List of examples 6 | - *SocketFilter*: [Simple Packet Counter](https://github.com/dropbox/goebpf/tree/master/examples/socket_filter/packet_counter) 7 | - *XDP*: [Simple packets protocol counter](https://github.com/dropbox/goebpf/tree/master/examples/xdp/packet_counter) 8 | - *XDP*: [Basic Firewall](https://github.com/dropbox/goebpf/tree/master/examples/xdp/basic_firewall) 9 | - *XDP*: [FIB lookup and bpf_redirect example](https://github.com/dropbox/goebpf/tree/master/examples/xdp/bpf_redirect_map) 10 | - *PerfEvents*: [XDP Dump](https://github.com/dropbox/goebpf/tree/master/examples/xdp/xdp_dump) 11 | - *Kprobes*: [Exec Dump](https://github.com/dropbox/goebpf/tree/master/examples/kprobe/exec_dump) 12 | 13 | ## How to run 14 | All examples actually contain 2 parts: 15 | - The `eBPF` program written in `C` 16 | - `go` application which acts as a control plane 17 | 18 | You need to build both to make example work. 19 | 20 | ### Install prerequisites 21 | ```bash 22 | # Install clang/llvm to be able to compile C files into bpf arch 23 | $ apt-get install clang llvm make 24 | 25 | # Install goebpf package 26 | $ go get github.com/dropbox/goebpf 27 | 28 | ``` 29 | 30 | ### Run 31 | Compile both parts 32 | ```bash 33 | $ make 34 | clang -I../../.. -O2 -target bpf -c ebpf_prog/xdp.c -o ebpf_prog/xdp.elf 35 | go build -v -o main 36 | ``` 37 | Run it! 38 | 39 | ```bash 40 | $ sudo ./main [optional args] 41 | ``` 42 | You must use `sudo` or `CAP_SYS_ADMIN` / `CAP_NET_ADMIN` capabilities because of it creates kernel objects. 43 | 44 | ### How to compile only `eBPF` program 45 | ```bash 46 | $ cd [path_to_example] 47 | $ make build_bpf 48 | ``` 49 | Compiled binary will be under `ebpf_prog` folder, e.g.: 50 | ```bash 51 | $ ls -l ebpf_prog 52 | total 8 53 | -rw-r--r-- 1 root root 1524 May 15 21:20 xdp.c 54 | -rw-r--r-- 1 root root 1104 May 15 21:20 xdp.elf 55 | ``` 56 | -------------------------------------------------------------------------------- /examples/tc/packet_counter/ebpf_prog/tc.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Dropbox, Inc. 2 | // Based on the example by Konstantin Belyalov: 3 | // https://github.com/dropbox/goebpf/blob/master/examples/xdp/packet_counter/ebpf_prog/xdp.c 4 | #include "tc.h" 5 | 6 | // eBPF map to store IP metric counters 7 | BPF_MAP_DEF(metrics, BPF_MAP_TYPE_PERCPU_ARRAY, sizeof(__u32), sizeof(__u64), 255); 8 | 9 | static __always_inline int account_data(struct __sk_buff *skb, __u32 hashidx) 10 | { 11 | void *data_end = (void *)(long)skb->data_end; 12 | void *data = (void *)(long)skb->data; 13 | // Only IPv4 supported for now 14 | struct ethhdr *ether = data; 15 | if (data + sizeof(*ether) > data_end) { 16 | return TC_ACT_OK; 17 | } 18 | if (ether->h_proto == 0x08U) { // htons(ETH_P_IP) -> 0x08U 19 | data += sizeof(*ether); 20 | struct iphdr *ip = data; 21 | if (data + sizeof(*ip) > data_end) { 22 | return TC_ACT_OK; 23 | } 24 | if (ip->version != 4) { 25 | return TC_ACT_OK; 26 | } 27 | // Increment packet count 28 | __u64 *counter = bpf_map_lookup_elem(&metrics, &hashidx); 29 | if (counter) { 30 | __sync_fetch_and_add(counter, 1); 31 | } 32 | // Increment total bytes 33 | hashidx |= HASH_FLAG_UNIT_BYTES; 34 | counter = bpf_map_lookup_elem(&metrics, &hashidx); 35 | if (counter) { 36 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 37 | // byte swap tot_len to LE before adding 38 | __sync_fetch_and_add(counter, 39 | ((ip->tot_len << 8) & 0xFF00) | (ip->tot_len >> 8)); 40 | #else 41 | __sync_fetch_and_add(counter, ip->tot_len); 42 | #endif 43 | } 44 | } 45 | return TC_ACT_OK; 46 | } 47 | // TC program // 48 | SEC("tc_cls") 49 | int tc_ingress(struct __sk_buff *skb) { 50 | return account_data(skb, HASH_FLAG_DIR_INGRESS); 51 | } 52 | SEC("tc_cls") 53 | int tc_egress(struct __sk_buff *skb) { 54 | return account_data(skb, HASH_FLAG_DIR_EGRESS); 55 | } 56 | char _license[] SEC("license") = "GPLv2"; 57 | -------------------------------------------------------------------------------- /itest/perf_events_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | import ( 7 | "encoding/binary" 8 | "net" 9 | "testing" 10 | "time" 11 | 12 | "github.com/dropbox/goebpf" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | // Runs XDP program and listen for perf events generated by program 17 | func TestPerfEvents(t *testing.T) { 18 | // Read ELF, find map, load/attach program 19 | eb := goebpf.NewDefaultEbpfSystem() 20 | err := eb.LoadElf(progPath(xdpProgramFilename)) 21 | require.NoError(t, err) 22 | perfMap := eb.GetMapByName("perf_map") 23 | require.NotNil(t, perfMap) 24 | 25 | program := eb.GetProgramByName("xdp_perf") 26 | require.NotNil(t, program) 27 | 28 | // Load / Attach program to "lo" interface 29 | err = program.Load() 30 | require.NoError(t, err) 31 | err = program.Attach("lo") 32 | require.NoError(t, err) 33 | defer program.Detach() 34 | 35 | // Setup/Start perf events 36 | perfEvents, err := goebpf.NewPerfEvents(perfMap) 37 | require.NoError(t, err) 38 | perfCh, err := perfEvents.StartForAllProcessesAndCPUs(4096) 39 | require.NoError(t, err) 40 | 41 | // Send dummy UDP packet to localhost so XDP program 42 | // will catch it and emit perf event 43 | conn, err := net.Dial("udp", "127.0.0.1:4444") 44 | require.NoError(t, err) 45 | conn.Write([]byte("test")) 46 | 47 | // Read Perf Events in a separated goroutine 48 | var firstEventData []byte 49 | doneChan := make(chan struct{}) 50 | go func() { 51 | for { 52 | data, ok := <-perfCh 53 | if doneChan != nil { 54 | firstEventData = data 55 | close(doneChan) 56 | doneChan = nil 57 | } 58 | if !ok { 59 | break 60 | } 61 | } 62 | }() 63 | 64 | // Wait until first perf event (up to 1 sec) 65 | select { 66 | case <-doneChan: 67 | break 68 | case <-time.After(1 * time.Second): 69 | require.Fail(t, "timeout while waiting for perf event") 70 | } 71 | perfEvents.Stop() 72 | 73 | // Verify first received event 74 | packetSize := binary.LittleEndian.Uint32(firstEventData) 75 | require.True(t, packetSize > 20) 76 | } 77 | -------------------------------------------------------------------------------- /goebpf_mock/mock_prog.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf_mock 5 | 6 | import ( 7 | "github.com/dropbox/goebpf" 8 | ) 9 | 10 | // MockProgram is mock implementation for eBPF program 11 | type MockProgram struct { 12 | Attached bool 13 | Fd int 14 | License string 15 | Name string 16 | Section string 17 | Size int 18 | ProgType goebpf.ProgramType 19 | } 20 | 21 | // NewMockProgram creates new mock program of tp type. 22 | func NewMockProgram(name string, tp goebpf.ProgramType) *MockProgram { 23 | return &MockProgram{ 24 | Name: name, 25 | ProgType: tp, 26 | } 27 | } 28 | 29 | // Load does nothing, only to implement Program interface 30 | func (m *MockProgram) Load() error { 31 | m.Fd = 1 32 | return nil 33 | } 34 | 35 | // Close does nothing, only to implement Program interface 36 | func (m *MockProgram) Close() error { 37 | m.Fd = 0 38 | return nil 39 | } 40 | 41 | // Attach does nothing, only to implement Program interface 42 | func (m *MockProgram) Attach(meta interface{}) error { 43 | m.Attached = true 44 | return nil 45 | } 46 | 47 | // Detach does nothing, only to implement Program interface 48 | func (m *MockProgram) Detach() error { 49 | m.Attached = false 50 | return nil 51 | } 52 | 53 | // Pin does nothing, just returns nil 54 | func (m *MockProgram) Pin(path string) error { 55 | return nil 56 | } 57 | 58 | // GetFd returns mock program fd 59 | func (m *MockProgram) GetFd() int { 60 | return m.Fd 61 | } 62 | 63 | // GetType returns program type 64 | func (m *MockProgram) GetType() goebpf.ProgramType { 65 | return m.ProgType 66 | } 67 | 68 | // GetLicense return program's license 69 | func (m *MockProgram) GetLicense() string { 70 | return m.License 71 | } 72 | 73 | // GetName return program name 74 | func (m *MockProgram) GetName() string { 75 | return m.Name 76 | } 77 | 78 | // GetSection returns the section name used by the program 79 | func (m *MockProgram) GetSection() string { 80 | return m.Section 81 | } 82 | 83 | // GetSize returns program size set by user 84 | func (m *MockProgram) GetSize() int { 85 | return m.Size 86 | } 87 | -------------------------------------------------------------------------------- /examples/xdp/bpf_redirect_map/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "strings" 10 | 11 | "github.com/dropbox/goebpf" 12 | "github.com/vishvananda/netlink" 13 | ) 14 | 15 | func AttachXdp(x goebpf.Program, iList []string, devMap goebpf.Map) error { 16 | for _, intf := range iList { 17 | ifData, err := netlink.LinkByName(intf) 18 | if err != nil { 19 | fmt.Printf("Could not find link %s!\n", intf) 20 | return err 21 | } 22 | ifIndex := ifData.Attrs().Index 23 | if err := x.Attach(&goebpf.XdpAttachParams{Interface: intf, Mode: goebpf.XdpAttachModeSkb}); err != nil { 24 | fmt.Printf("Failed attaching xdp prog to %s!\n", intf) 25 | return err 26 | } 27 | if err := devMap.Upsert(ifIndex, ifIndex); err != nil { 28 | return err 29 | } 30 | fmt.Printf("XDP program attached to %s. to detach, use `ip -f link set dev %s xdp off`\n", intf, intf) 31 | } 32 | return nil 33 | } 34 | 35 | func FetchMaps(prog goebpf.System) (devMap goebpf.Map, err error) { 36 | devMap = prog.GetMapByName("if_redirect") 37 | if devMap == nil { 38 | err = fmt.Errorf("failed fetching map if_redirect from program.\n") 39 | } 40 | return 41 | } 42 | 43 | func main() { 44 | var xdpProgFile, xdpIntf string 45 | 46 | flag.StringVar(&xdpProgFile, "file", "", "xdp binary to attach") 47 | flag.StringVar(&xdpIntf, "i", "", "interfaces to attach xdp code to. comma separated") 48 | flag.Parse() 49 | 50 | if xdpProgFile == "" { 51 | panic("Please enter a valid filename.") 52 | } 53 | if xdpIntf == "" { 54 | panic("Please enter a valid interface name.") 55 | } 56 | intfList := strings.Split(strings.Replace(xdpIntf, " ", "", 0), ",") 57 | 58 | bpf := goebpf.NewDefaultEbpfSystem() 59 | if err := bpf.LoadElf(xdpProgFile); err != nil { 60 | panic(err) 61 | } 62 | xdpProg := bpf.GetProgramByName("xdp_test") 63 | if xdpProg == nil { 64 | panic(fmt.Sprintf("Could not find xdp_test program in %s", xdpProgFile)) 65 | } 66 | devMap, err := FetchMaps(bpf) 67 | if err != nil { 68 | panic(err) 69 | } 70 | if err := xdpProg.Load(); err != nil { 71 | panic(err) 72 | } 73 | fmt.Printf("Attaching program %s\n", xdpProg.GetName()) 74 | if err := AttachXdp(xdpProg, intfList, devMap); err != nil { 75 | panic(err) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/xdp/basic_firewall/ebpf_prog/xdp_fw.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Basic XDP firewall (IPv4 blacklisting) 5 | 6 | #include "bpf_helpers.h" 7 | 8 | #define MAX_RULES 16 9 | 10 | // Ethernet header 11 | struct ethhdr { 12 | __u8 h_dest[6]; 13 | __u8 h_source[6]; 14 | __u16 h_proto; 15 | } __attribute__((packed)); 16 | 17 | // IPv4 header 18 | struct iphdr { 19 | __u8 ihl : 4; 20 | __u8 version : 4; 21 | __u8 tos; 22 | __u16 tot_len; 23 | __u16 id; 24 | __u16 frag_off; 25 | __u8 ttl; 26 | __u8 protocol; 27 | __u16 check; 28 | __u32 saddr; 29 | __u32 daddr; 30 | } __attribute__((packed)); 31 | 32 | 33 | BPF_MAP_DEF(matches) = { 34 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 35 | .key_size = sizeof(__u32), 36 | .value_size = sizeof(__u64), 37 | .max_entries = MAX_RULES, 38 | }; 39 | BPF_MAP_ADD(matches); 40 | 41 | 42 | BPF_MAP_DEF(blacklist) = { 43 | .map_type = BPF_MAP_TYPE_LPM_TRIE, 44 | .key_size = sizeof(__u64), 45 | .value_size = sizeof(__u32), 46 | .max_entries = MAX_RULES, 47 | }; 48 | BPF_MAP_ADD(blacklist); 49 | 50 | // XDP program // 51 | SEC("xdp") 52 | int firewall(struct xdp_md *ctx) { 53 | void *data_end = (void *)(long)ctx->data_end; 54 | void *data = (void *)(long)ctx->data; 55 | 56 | // Only IPv4 supported for this example 57 | struct ethhdr *ether = data; 58 | if (data + sizeof(*ether) > data_end) { 59 | // Malformed Ethernet header 60 | return XDP_ABORTED; 61 | } 62 | 63 | if (ether->h_proto != 0x08U) { // htons(ETH_P_IP) -> 0x08U 64 | // Non IPv4 traffic 65 | return XDP_PASS; 66 | } 67 | 68 | data += sizeof(*ether); 69 | struct iphdr *ip = data; 70 | if (data + sizeof(*ip) > data_end) { 71 | // Malformed IPv4 header 72 | return XDP_ABORTED; 73 | } 74 | 75 | struct { 76 | __u32 prefixlen; 77 | __u32 saddr; 78 | } key; 79 | 80 | key.prefixlen = 32; 81 | key.saddr = ip->saddr; 82 | 83 | // Lookup SRC IP in blacklisted IPs 84 | __u64 *rule_idx = bpf_map_lookup_elem(&blacklist, &key); 85 | if (rule_idx) { 86 | // Matched, increase match counter for matched "rule" 87 | __u32 index = *(__u32*)rule_idx; // make verifier happy 88 | __u64 *counter = bpf_map_lookup_elem(&matches, &index); 89 | if (counter) { 90 | (*counter)++; 91 | } 92 | return XDP_DROP; 93 | } 94 | 95 | return XDP_PASS; 96 | } 97 | 98 | char _license[] SEC("license") = "GPLv2"; 99 | -------------------------------------------------------------------------------- /examples/tc/packet_counter/ebpf_prog/tc.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Dropbox, Inc. 2 | // Based on the example by Konstantin Belyalov: 3 | // https://github.com/dropbox/goebpf/blob/master/examples/xdp/packet_counter/ebpf_prog/xdp.c 4 | 5 | #ifndef GO_EBPF 6 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 7 | #include 8 | #else 9 | #include 10 | #endif 11 | #endif 12 | 13 | #ifdef GO_EBPF 14 | // if compiling for goebpf, use our local bpf_helpers.h 15 | #include "bpf_helpers.h" 16 | #else 17 | // if compiling to be attached with iproute2 18 | // (`tc filter add ... bpf obj tc.o ...`), pull these headers 19 | // in from the system 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #endif 26 | 27 | #include 28 | 29 | #define HASH_FLAG_DIR_INGRESS 0 30 | #define HASH_FLAG_DIR_EGRESS (1 << 0) 31 | #define HASH_FLAG_UNIT_PACKETS 0 32 | #define HASH_FLAG_UNIT_BYTES (1 << 1) 33 | 34 | // Ethernet header 35 | struct ethhdr { 36 | __u8 h_dest[6]; 37 | __u8 h_source[6]; 38 | __u16 h_proto; 39 | } __attribute__((packed)); 40 | 41 | // IPv4 header 42 | struct iphdr { 43 | __u8 ihl : 4; 44 | __u8 version : 4; 45 | __u8 tos; 46 | __u16 tot_len; 47 | __u16 id; 48 | __u16 frag_off; 49 | __u8 ttl; 50 | __u8 protocol; 51 | __u16 check; 52 | __u32 saddr; 53 | __u32 daddr; 54 | } __attribute__((packed)); 55 | 56 | // BPF_MAP_DEF is outdated in currently shipping linux-api-headers as of 57 | // 08/2022 :/ 58 | // Latest libbpf requires maps defined in BTF, while goebpf uses the old 59 | // maps section format, so we support both here. 60 | #undef BPF_MAP_DEF 61 | 62 | #ifdef GO_EBPF 63 | #define BPF_MAP_DEF(_name, _type, _key_size, _value_size, _max_entries) \ 64 | struct bpf_map_def SEC("maps") _name = { \ 65 | .map_type = _type, \ 66 | .key_size = _key_size, \ 67 | .value_size = _value_size, \ 68 | .max_entries = _max_entries, \ 69 | } 70 | 71 | #else 72 | #define BPF_MAP_DEF(_name, _type, _key_size, _value_size, _max_entries) \ 73 | struct { \ 74 | __u32 (*type)[BPF_MAP_TYPE_PERCPU_ARRAY]; \ 75 | __u32 (*key_size)[sizeof(__u32)]; \ 76 | __u32 (*value_size)[sizeof(__u64)]; \ 77 | __u32 (*max_entries)[255]; \ 78 | } _name SEC(".maps"); 79 | #endif 80 | -------------------------------------------------------------------------------- /program_socket_filter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | //#include "bpf_helpers.h" 7 | import "C" 8 | 9 | import ( 10 | "fmt" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | // SocketFilterResult is eBPF program return code enum 16 | type SocketFilterResult int 17 | 18 | // SocketFilterAttachType is either SO_ATTACH_BPF or SO_ATTACH_REUSEPORT_EBPF 19 | type SocketFilterAttachType int 20 | 21 | const ( 22 | SocketFilterDeny SocketFilterResult = C.SOCKET_FILTER_DENY 23 | SocketFilterAllow SocketFilterResult = C.SOCKET_FILTER_ALLOW 24 | 25 | SocketAttachTypeFilter SocketFilterAttachType = SO_ATTACH_BPF 26 | SocketAttachTypeReusePort SocketFilterAttachType = SO_ATTACH_REUSEPORT_EBPF 27 | 28 | // Constants from Linux kernel, they dont' present in "golang.org/x/sys/unix" 29 | SO_ATTACH_BPF = 50 30 | SO_ATTACH_REUSEPORT_EBPF = 52 31 | SO_DETACH_FILTER = 27 32 | ) 33 | 34 | // SocketFilterAttachParams is accepted as argument to Program.Attach() 35 | type SocketFilterAttachParams struct { 36 | // SocketFd is socket file descriptor returned by unix.Socket(...) 37 | SocketFd int 38 | // AttachType is one of SocketAttachTypeFilter / SocketAttachTypeReusePort 39 | // depending on use case 40 | AttachType SocketFilterAttachType 41 | } 42 | 43 | func (t SocketFilterResult) String() string { 44 | switch t { 45 | case SocketFilterDeny: 46 | return "Deny" 47 | case SocketFilterAllow: 48 | return "Allow" 49 | } 50 | 51 | return "UNKNOWN" 52 | } 53 | 54 | func (t SocketFilterAttachType) String() string { 55 | switch t { 56 | case SocketAttachTypeFilter: 57 | return "AttachTypeFilter" 58 | case SocketAttachTypeReusePort: 59 | return "AttachTypeReusePort" 60 | } 61 | 62 | return "UNKNOWN" 63 | } 64 | 65 | type socketFilterProgram struct { 66 | BaseProgram 67 | 68 | sockFd int 69 | } 70 | 71 | func newSocketFilterProgram(bp BaseProgram) Program { 72 | bp.programType = ProgramTypeSocketFilter 73 | return &socketFilterProgram{ 74 | BaseProgram: bp, 75 | } 76 | } 77 | 78 | func (p *socketFilterProgram) Attach(data interface{}) error { 79 | params, ok := data.(SocketFilterAttachParams) 80 | if !ok { 81 | return fmt.Errorf("SocketFilterAttachParams expected, got %T", data) 82 | } 83 | p.sockFd = params.SocketFd 84 | 85 | err := unix.SetsockoptInt(p.sockFd, unix.SOL_SOCKET, int(params.AttachType), p.GetFd()) 86 | if err != nil { 87 | return fmt.Errorf("SetSockOpt with %v failed: %v", params.AttachType, err) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func (p *socketFilterProgram) Detach() error { 94 | err := unix.SetsockoptInt(p.sockFd, unix.SOL_SOCKET, SO_DETACH_FILTER, 0) 95 | if err != nil { 96 | return fmt.Errorf("SetSockOpt with SO_DETACH_FILTER failed: %v", err) 97 | } 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 10 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 11 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 12 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 13 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 16 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 17 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 18 | github.com/vishvananda/netlink v1.1.1-0.20200218174631-5f2fc868c2d0 h1:GNKyqfdQI0/lEYv+VHIzsECySOY+EDjT22Dl49OyGNA= 19 | github.com/vishvananda/netlink v1.1.1-0.20200218174631-5f2fc868c2d0/go.mod h1:FSQhuTO7eHT34mPzX+B04SUAjiqLxtXs1et0S6l9k4k= 20 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= 21 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 22 | github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe h1:mjAZxE1nh8yvuwhGHpdDqdhtNu2dgbpk93TwoXuk5so= 23 | github.com/vishvananda/netns v0.0.0-20200520041808-52d707b772fe/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 24 | golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 25 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6 h1:X9xIZ1YU8bLZA3l6gqDUHSFiD0GFI9S548h6C8nDtOY= 27 | golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2 h1:+j1SppRob9bAgoYmsdW9NNBdKZfgYuWpqnYHv78Qt8w= 31 | gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /perf_events_poller.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static int perf_events_poll(void *_fds, int cnt, int timeout) 13 | { 14 | int *fds = _fds; 15 | int *fds_end = fds + cnt; 16 | 17 | // Allocate N pollfd structs 18 | size_t pollfds_size = sizeof(struct pollfd) * cnt; 19 | void *pollfds_memory = malloc(pollfds_size); 20 | memset(pollfds_memory, 0, pollfds_size); 21 | 22 | // Initialize pollfds from GO array of uint32 fds 23 | struct pollfd *pollfds = pollfds_memory; 24 | struct pollfd *pollfds_end = pollfds + cnt; 25 | for (; fds != fds_end; fds++, pollfds++) { 26 | pollfds->fd = *fds; 27 | pollfds->events = POLLIN; 28 | } 29 | // Re-set pointers to start of arrays 30 | pollfds = pollfds_memory; 31 | fds = _fds; 32 | 33 | int ready_cnt = poll(pollfds, cnt, timeout); 34 | 35 | // Copy all ready descriptors back into golang array of uint32s 36 | for (int remain = ready_cnt; remain > 0 && pollfds != pollfds_end; pollfds++) { 37 | if (pollfds->revents & POLLIN) { 38 | *fds = pollfds->fd; 39 | fds++; 40 | remain--; 41 | } 42 | } 43 | 44 | free(pollfds_memory); 45 | return ready_cnt; 46 | } 47 | 48 | */ 49 | import "C" 50 | import ( 51 | "sync" 52 | "unsafe" 53 | ) 54 | 55 | type perfEventPoller struct { 56 | items map[int]*perfEventHandler 57 | wg sync.WaitGroup 58 | fds []uint32 59 | timeoutMs int 60 | 61 | stopChannel chan struct{} 62 | updateChannel chan *perfEventHandler 63 | } 64 | 65 | func newPerfEventPoller() *perfEventPoller { 66 | return &perfEventPoller{ 67 | items: make(map[int]*perfEventHandler), 68 | } 69 | } 70 | 71 | func (p *perfEventPoller) Add(handler *perfEventHandler) { 72 | p.items[int(handler.pmuFd)] = handler 73 | } 74 | 75 | func (p *perfEventPoller) Start(timeoutMs int) <-chan *perfEventHandler { 76 | // Create array of uint32 for fds to be used from C function 77 | p.fds = make([]uint32, len(p.items)) 78 | var idx int 79 | for fd := range p.items { 80 | p.fds[idx] = uint32(fd) 81 | idx++ 82 | } 83 | 84 | // Start poll loop 85 | p.timeoutMs = timeoutMs 86 | p.stopChannel = make(chan struct{}) 87 | p.updateChannel = make(chan *perfEventHandler) 88 | p.wg.Add(1) 89 | 90 | go p.loop() 91 | 92 | return p.updateChannel 93 | } 94 | 95 | func (p *perfEventPoller) Stop() { 96 | // Stop loop 97 | close(p.stopChannel) 98 | p.wg.Wait() 99 | close(p.updateChannel) 100 | } 101 | 102 | func (p *perfEventPoller) loop() { 103 | defer p.wg.Done() 104 | 105 | for { 106 | // Check stopChannel for close 107 | select { 108 | case <-p.stopChannel: 109 | return 110 | default: 111 | break 112 | } 113 | 114 | // Run poll() 115 | readyCnt := int(C.perf_events_poll( 116 | unsafe.Pointer(&p.fds[0]), 117 | C.int(len(p.items)), 118 | C.int(p.timeoutMs), 119 | )) 120 | 121 | // Send perfEventHandlers with pending updates, if any 122 | for i := 0; i < readyCnt; i++ { 123 | select { 124 | case p.updateChannel <- p.items[int(p.fds[i])]: 125 | 126 | case <-p.stopChannel: 127 | return 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /examples/xdp/xdp_dump/ebpf_prog/xdp_dump.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // XDP dump is simple program that dumps new IPv4 TCP connections through perf events. 5 | 6 | #include "bpf_helpers.h" 7 | 8 | // Ethernet header 9 | struct ethhdr { 10 | __u8 h_dest[6]; 11 | __u8 h_source[6]; 12 | __u16 h_proto; 13 | } __attribute__((packed)); 14 | 15 | // IPv4 header 16 | struct iphdr { 17 | __u8 ihl : 4; 18 | __u8 version : 4; 19 | __u8 tos; 20 | __u16 tot_len; 21 | __u16 id; 22 | __u16 frag_off; 23 | __u8 ttl; 24 | __u8 protocol; 25 | __u16 check; 26 | __u32 saddr; 27 | __u32 daddr; 28 | } __attribute__((packed)); 29 | 30 | // TCP header 31 | struct tcphdr { 32 | __u16 source; 33 | __u16 dest; 34 | __u32 seq; 35 | __u32 ack_seq; 36 | union { 37 | struct { 38 | // Field order has been converted LittleEndiand -> BigEndian 39 | // in order to simplify flag checking (no need to ntohs()) 40 | __u16 ns : 1, 41 | reserved : 3, 42 | doff : 4, 43 | fin : 1, 44 | syn : 1, 45 | rst : 1, 46 | psh : 1, 47 | ack : 1, 48 | urg : 1, 49 | ece : 1, 50 | cwr : 1; 51 | }; 52 | }; 53 | __u16 window; 54 | __u16 check; 55 | __u16 urg_ptr; 56 | }; 57 | 58 | // PerfEvent eBPF map 59 | BPF_MAP_DEF(perfmap) = { 60 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 61 | .max_entries = 128, 62 | }; 63 | BPF_MAP_ADD(perfmap); 64 | 65 | 66 | // PerfEvent item 67 | struct perf_event_item { 68 | __u32 src_ip, dst_ip; 69 | __u16 src_port, dst_port; 70 | }; 71 | _Static_assert(sizeof(struct perf_event_item) == 12, "wrong size of perf_event_item"); 72 | 73 | // XDP program // 74 | SEC("xdp") 75 | int xdp_dump(struct xdp_md *ctx) { 76 | void *data_end = (void *)(long)ctx->data_end; 77 | void *data = (void *)(long)ctx->data; 78 | __u64 packet_size = data_end - data; 79 | 80 | // L2 81 | struct ethhdr *ether = data; 82 | if (data + sizeof(*ether) > data_end) { 83 | return XDP_ABORTED; 84 | } 85 | 86 | // L3 87 | if (ether->h_proto != 0x08) { // htons(ETH_P_IP) -> 0x08 88 | // Non IPv4 89 | return XDP_PASS; 90 | } 91 | data += sizeof(*ether); 92 | struct iphdr *ip = data; 93 | if (data + sizeof(*ip) > data_end) { 94 | return XDP_ABORTED; 95 | } 96 | 97 | // L4 98 | if (ip->protocol != 0x06) { // IPPROTO_TCP -> 6 99 | // Non TCP 100 | return XDP_PASS; 101 | } 102 | data += ip->ihl * 4; 103 | struct tcphdr *tcp = data; 104 | if (data + sizeof(*tcp) > data_end) { 105 | return XDP_ABORTED; 106 | } 107 | 108 | // Emit perf event for every TCP SYN packet 109 | if (tcp->syn) { 110 | struct perf_event_item evt = { 111 | .src_ip = ip->saddr, 112 | .dst_ip = ip->daddr, 113 | .src_port = tcp->source, 114 | .dst_port = tcp->dest, 115 | }; 116 | // flags for bpf_perf_event_output() actually contain 2 parts (each 32bit long): 117 | // 118 | // bits 0-31: either 119 | // - Just index in eBPF map 120 | // or 121 | // - "BPF_F_CURRENT_CPU" kernel will use current CPU_ID as eBPF map index 122 | // 123 | // bits 32-63: may be used to tell kernel to amend first N bytes 124 | // of original packet (ctx) to the end of the data. 125 | 126 | // So total perf event length will be sizeof(evt) + packet_size 127 | __u64 flags = BPF_F_CURRENT_CPU | (packet_size << 32); 128 | bpf_perf_event_output(ctx, &perfmap, flags, &evt, sizeof(evt)); 129 | } 130 | 131 | return XDP_PASS; 132 | } 133 | 134 | char _license[] SEC("license") = "GPL"; 135 | -------------------------------------------------------------------------------- /examples/socket_filter/packet_counter/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "os/signal" 11 | "time" 12 | 13 | "golang.org/x/sys/unix" 14 | 15 | "github.com/dropbox/goebpf" 16 | ) 17 | 18 | const ( 19 | SO_BINDTODEVICE = 25 20 | ) 21 | 22 | var elf = flag.String("elf", "ebpf_prog/sock_filter.elf", "clang/llvm compiled binary file") 23 | var programName = flag.String("program", "packet_counter", "Name of SocketFilter program (function name)") 24 | var iface = flag.String("iface", "", "Interface to open raw socket on") 25 | 26 | func main() { 27 | flag.Parse() 28 | if *iface == "" { 29 | fatalError("Interface (-iface) is required.") 30 | } 31 | 32 | // Create eBPF system / load .ELF file compiled by clang/llvm 33 | bpf := goebpf.NewDefaultEbpfSystem() 34 | err := bpf.LoadElf(*elf) 35 | if err != nil { 36 | fatalError("LoadElf() failed: %v", err) 37 | } 38 | printBpfInfo(bpf) 39 | 40 | // Find counter eBPF map 41 | counter := bpf.GetMapByName("counter") 42 | if counter == nil { 43 | fatalError("eBPF map 'counter' not found") 44 | } 45 | 46 | // Program name matches function name in socket_filter.c: 47 | // int packet_counter(struct __sk_buff *skb) 48 | sf := bpf.GetProgramByName(*programName) 49 | if sf == nil { 50 | fatalError("Program '%s' not found.", *programName) 51 | } 52 | 53 | // Load SocketFilter program into kernel 54 | err = sf.Load() 55 | if err != nil { 56 | fatalError("sf.Load(): %v", err) 57 | } 58 | 59 | // Create RAW socket 60 | sock, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, unix.ETH_P_ALL<<8) // htons(unix.ETH_P_ALL) 61 | if err != nil { 62 | fatalError("unable to create raw socket: %v", err) 63 | } 64 | defer unix.Close(sock) 65 | 66 | // Bind raw socket to interface 67 | err = unix.SetsockoptString(sock, unix.SOL_SOCKET, SO_BINDTODEVICE, *iface) 68 | if err != nil { 69 | fatalError("SO_BINDTODEVICE to %s failed: %v", *iface, err) 70 | } 71 | 72 | // Attach eBPF program to socket as socketFilter 73 | err = sf.Attach(goebpf.SocketFilterAttachParams{ 74 | SocketFd: sock, 75 | AttachType: goebpf.SocketAttachTypeFilter, 76 | }) 77 | 78 | if err != nil { 79 | fatalError("sf.Attach(): %v", err) 80 | } 81 | defer sf.Detach() 82 | 83 | // Add CTRL+C handler 84 | ctrlC := make(chan os.Signal, 1) 85 | signal.Notify(ctrlC, os.Interrupt) 86 | 87 | // Print stat every second / exit on CTRL+C 88 | fmt.Println("SocketFilter program successfully loaded and attached. Counters refreshed every second.") 89 | fmt.Println() 90 | ticker := time.NewTicker(1 * time.Second) 91 | for { 92 | select { 93 | case <-ticker.C: 94 | value, err := counter.LookupInt(0) 95 | if err != nil { 96 | fatalError("LookupInt failed: %v", err) 97 | } 98 | fmt.Printf(" Packets: %d\r", value) 99 | case <-ctrlC: 100 | fmt.Println("\nDetaching program and exit") 101 | return 102 | } 103 | } 104 | } 105 | 106 | func fatalError(format string, args ...interface{}) { 107 | fmt.Fprintf(os.Stderr, format+"\n", args...) 108 | os.Exit(1) 109 | } 110 | 111 | func printBpfInfo(bpf goebpf.System) { 112 | fmt.Println("Maps:") 113 | for _, item := range bpf.GetMaps() { 114 | fmt.Printf("\t%s: %v, Fd %v\n", item.GetName(), item.GetType(), item.GetFd()) 115 | } 116 | fmt.Println("\nPrograms:") 117 | for _, prog := range bpf.GetPrograms() { 118 | fmt.Printf("\t%s: %v, size %d, license \"%s\"\n", 119 | prog.GetName(), prog.GetType(), prog.GetSize(), prog.GetLicense(), 120 | ) 121 | 122 | } 123 | fmt.Println() 124 | } 125 | -------------------------------------------------------------------------------- /mmap_ring_buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef __linux__ 12 | #include 13 | #else 14 | // mocks for Mac 15 | struct perf_event_mmap_page { 16 | int data_offset, data_size, data_head, data_tail; 17 | }; 18 | #endif 19 | 20 | static void *shmem_get_ptr(void *shmem) 21 | { 22 | struct perf_event_mmap_page *header = shmem; 23 | return shmem + header->data_offset; 24 | } 25 | 26 | static uint64_t shmem_get_size(void *shmem) 27 | { 28 | struct perf_event_mmap_page *header = shmem; 29 | return header->data_size; 30 | } 31 | 32 | static uint64_t shmem_get_head(void *shmem) 33 | { 34 | volatile struct perf_event_mmap_page *header = shmem; 35 | uint64_t head = header->data_head; 36 | asm volatile("" ::: "memory"); // smp_rmb() 37 | 38 | return head; 39 | } 40 | 41 | static uint64_t shmem_get_tail(void *shmem) 42 | { 43 | volatile struct perf_event_mmap_page *header = shmem; 44 | return header->data_tail; 45 | } 46 | 47 | // Helper to update ring buffer's tail with Memory Barrier 48 | static void shmem_set_tail(void *shmem, uint64_t tail) 49 | { 50 | volatile struct perf_event_mmap_page *header = shmem; 51 | 52 | __sync_synchronize(); // smp_mb() 53 | header->data_tail = tail; 54 | } 55 | 56 | static void shmem_memcpy(void *shmem, void *buffer, size_t size) 57 | { 58 | memcpy(buffer, shmem, size); 59 | } 60 | 61 | */ 62 | import "C" 63 | 64 | import ( 65 | "unsafe" 66 | ) 67 | 68 | type mmapRingBuffer struct { 69 | ptr unsafe.Pointer 70 | start unsafe.Pointer 71 | end uintptr 72 | size int 73 | 74 | head int 75 | tail int 76 | } 77 | 78 | // NewMmapRingBuffer creates mmapRingBuffer instance from 79 | // pre-created mmap memory pointer ptr 80 | func NewMmapRingBuffer(ptr unsafe.Pointer) *mmapRingBuffer { 81 | start := C.shmem_get_ptr(ptr) 82 | size := int(C.shmem_get_size(ptr)) 83 | 84 | res := &mmapRingBuffer{ 85 | ptr: ptr, 86 | start: start, 87 | size: size, 88 | end: uintptr(start) + uintptr(size), 89 | tail: int(C.shmem_get_tail(ptr)), 90 | } 91 | 92 | return res 93 | } 94 | 95 | // Read copies "size" bytes from mmaped memory and returns it as go slice 96 | func (b *mmapRingBuffer) Read(size int) []byte { 97 | if size > b.size { 98 | size = b.size 99 | } 100 | 101 | res := make([]byte, size) 102 | tailPtr := unsafe.Pointer(uintptr(b.start) + uintptr(b.tail%b.size)) 103 | 104 | if uintptr(tailPtr)+uintptr(size) > b.end { 105 | // Requested size requires buffer rollover 106 | // [------------------------T-] 107 | // e.g. requested 3 bytes, but current tail is just 2 bytes away from 108 | // the buffer end. 109 | // So read 2 bytes and 1 byte from the beginning 110 | consumed := int(b.end - uintptr(tailPtr)) 111 | C.shmem_memcpy( 112 | tailPtr, 113 | unsafe.Pointer(&res[0]), 114 | C.size_t(consumed), 115 | ) 116 | C.shmem_memcpy( 117 | b.start, 118 | unsafe.Pointer(&res[consumed]), 119 | C.size_t(size-consumed), 120 | ) 121 | } else { 122 | C.shmem_memcpy( 123 | tailPtr, 124 | unsafe.Pointer(&res[0]), 125 | C.size_t(size), 126 | ) 127 | } 128 | 129 | // Advance tail 130 | b.tail += size 131 | 132 | return res 133 | } 134 | 135 | // Helper to update tail in shmem metadata page 136 | func (b *mmapRingBuffer) UpdateTail() { 137 | C.shmem_set_tail( 138 | b.ptr, 139 | C.uint64_t(b.tail), 140 | ) 141 | } 142 | 143 | func (b *mmapRingBuffer) DataAvailable() bool { 144 | b.head = int(C.shmem_get_head(b.ptr)) 145 | 146 | return b.head != b.tail 147 | } 148 | -------------------------------------------------------------------------------- /examples/xdp/packet_counter/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/dropbox/goebpf" 15 | ) 16 | 17 | var iface = flag.String("iface", "", "Interface to bind XDP program to") 18 | var elf = flag.String("elf", "ebpf_prog/xdp.elf", "clang/llvm compiled binary file") 19 | var programName = flag.String("program", "packet_count", "Name of XDP program (function name)") 20 | 21 | func main() { 22 | flag.Parse() 23 | if *iface == "" { 24 | fatalError("-iface is required.") 25 | } 26 | 27 | // Create eBPF system / load .ELF files compiled by clang/llvm 28 | bpf := goebpf.NewDefaultEbpfSystem() 29 | err := bpf.LoadElf(*elf) 30 | if err != nil { 31 | fatalError("LoadElf() failed: %v", err) 32 | } 33 | printBpfInfo(bpf) 34 | 35 | // Find protocols eBPF map 36 | protocols := bpf.GetMapByName("protocols") 37 | if protocols == nil { 38 | fatalError("eBPF map 'protocols' not found") 39 | } 40 | 41 | // Program name matches function name in xdp.c: 42 | // int packet_count(struct xdp_md *ctx) 43 | xdp := bpf.GetProgramByName(*programName) 44 | if xdp == nil { 45 | fatalError("Program '%s' not found.", *programName) 46 | } 47 | 48 | // Load XDP program into kernel 49 | err = xdp.Load() 50 | if err != nil { 51 | fatalError("xdp.Load(): %v", err) 52 | } 53 | 54 | // Attach to interface 55 | err = xdp.Attach(*iface) 56 | if err != nil { 57 | fatalError("xdp.Attach(): %v", err) 58 | } 59 | defer xdp.Detach() 60 | 61 | // Add CTRL+C handler 62 | ctrlC := make(chan os.Signal, 1) 63 | signal.Notify(ctrlC, os.Interrupt) 64 | 65 | // Print stat every second / exit on CTRL+C 66 | fmt.Println("XDP program successfully loaded and attached. Counters refreshed every second.") 67 | fmt.Println() 68 | ticker := time.NewTicker(1 * time.Second) 69 | for { 70 | select { 71 | case <-ticker.C: 72 | // Print only first 132 numbers (HOPOPT - SCTP) 73 | for i := 0; i < 132; i++ { 74 | value, err := protocols.LookupInt(i) 75 | if err != nil { 76 | fatalError("LookupInt failed: %v", err) 77 | } 78 | if value > 0 { 79 | fmt.Printf("%s: %d ", getProtoName(i), value) 80 | } 81 | } 82 | fmt.Printf("\r") 83 | case <-ctrlC: 84 | fmt.Println("\nDetaching program and exit") 85 | return 86 | } 87 | } 88 | } 89 | 90 | func fatalError(format string, args ...interface{}) { 91 | fmt.Fprintf(os.Stderr, format+"\n", args...) 92 | os.Exit(1) 93 | } 94 | 95 | func printBpfInfo(bpf goebpf.System) { 96 | fmt.Println("Maps:") 97 | for _, item := range bpf.GetMaps() { 98 | fmt.Printf("\t%s: %v, Fd %v\n", item.GetName(), item.GetType(), item.GetFd()) 99 | } 100 | fmt.Println("\nPrograms:") 101 | for _, prog := range bpf.GetPrograms() { 102 | fmt.Printf("\t%s: %v, size %d, license \"%s\"\n", 103 | prog.GetName(), prog.GetType(), prog.GetSize(), prog.GetLicense(), 104 | ) 105 | 106 | } 107 | fmt.Println() 108 | } 109 | 110 | // Converts IPPROTO number into string for well known protocols 111 | func getProtoName(proto int) string { 112 | switch proto { 113 | case syscall.IPPROTO_ENCAP: 114 | return "IPPROTO_ENCAP" 115 | case syscall.IPPROTO_GRE: 116 | return "IPPROTO_GRE" 117 | case syscall.IPPROTO_ICMP: 118 | return "IPPROTO_ICMP" 119 | case syscall.IPPROTO_IGMP: 120 | return "IPPROTO_IGMP" 121 | case syscall.IPPROTO_IPIP: 122 | return "IPPROTO_IPIP" 123 | case syscall.IPPROTO_SCTP: 124 | return "IPPROTO_SCTP" 125 | case syscall.IPPROTO_TCP: 126 | return "IPPROTO_TCP" 127 | case syscall.IPPROTO_UDP: 128 | return "IPPROTO_UDP" 129 | default: 130 | return fmt.Sprintf("%v", proto) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /itest/ebpf_prog/xdp1.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | // Set of simple XDP programs for eBPF library integration tests 5 | 6 | #include "bpf_helpers.h" 7 | 8 | BPF_MAP_DEF(txcnt) = { 9 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 10 | .key_size = sizeof(__u32), 11 | .value_size = sizeof(__u64), 12 | .max_entries = 100, 13 | .persistent_path = "/sys/fs/bpf/txcnt", 14 | }; 15 | BPF_MAP_ADD(txcnt); 16 | 17 | BPF_MAP_DEF(rxcnt) = { 18 | .map_type = BPF_MAP_TYPE_HASH, 19 | .key_size = sizeof(__u64), 20 | .value_size = sizeof(__u32), 21 | .max_entries = 50, 22 | }; 23 | BPF_MAP_ADD(rxcnt); 24 | 25 | BPF_MAP_DEF(match_maps_tx) = { 26 | .map_type = BPF_MAP_TYPE_ARRAY_OF_MAPS, 27 | .key_size = sizeof(__u32), 28 | .max_entries = 10, 29 | .inner_map_def = &txcnt, 30 | }; 31 | BPF_MAP_ADD(match_maps_tx); 32 | 33 | BPF_MAP_DEF(match_maps_rx) = { 34 | .map_type = BPF_MAP_TYPE_HASH_OF_MAPS, 35 | .key_size = sizeof(__u32), 36 | .max_entries = 20, 37 | .inner_map_def = &rxcnt, 38 | .persistent_path = "/sys/fs/bpf/match_maps_rx", 39 | }; 40 | BPF_MAP_ADD(match_maps_rx); 41 | 42 | BPF_MAP_DEF(array_map) = { 43 | .map_type = BPF_MAP_TYPE_ARRAY, 44 | .key_size = sizeof(__u32), 45 | .value_size = sizeof(__u64), 46 | .max_entries = 10, 47 | }; 48 | BPF_MAP_ADD(array_map); 49 | 50 | BPF_MAP_DEF(perf_map) = { 51 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 52 | .max_entries = 128, 53 | }; 54 | BPF_MAP_ADD(perf_map); 55 | 56 | #define PROG_CNT 2 57 | BPF_MAP_DEF(programs) = { 58 | .map_type = BPF_MAP_TYPE_PROG_ARRAY, 59 | .max_entries = PROG_CNT, 60 | }; 61 | BPF_MAP_ADD(programs); 62 | 63 | SEC("xdp") 64 | int xdp0(struct xdp_md *ctx) 65 | { 66 | __u64 val = 1; 67 | 68 | bpf_map_lookup_elem(&array_map, &val); 69 | 70 | return XDP_PASS; 71 | } 72 | 73 | SEC("xdp") 74 | int xdp1(struct xdp_md *ctx) 75 | { 76 | __u64 val = 1; 77 | 78 | bpf_map_lookup_elem(&rxcnt, &val); 79 | 80 | return XDP_DROP; 81 | } 82 | 83 | SEC("xdp") 84 | int xdp_head_meta2(struct xdp_md *ctx) 85 | { 86 | __u64 *foo; 87 | void *data, *data_meta, *data_end; 88 | 89 | // Metadata test 90 | // Reserve space at the beginning of the packet for metadata 91 | int adj_len = 0 - (int)sizeof(*foo); // NOLINT 92 | bpf_xdp_adjust_meta(ctx, adj_len); 93 | data = (void *)(long)ctx->data; // NOLINT 94 | data_meta = (void *)(long)ctx->data_meta; // NOLINT 95 | 96 | // Make kernel's verifier happy - check for boundaries 97 | if (data_meta + sizeof(*foo) <= data) 98 | { 99 | // Set some meta info before packet 100 | foo = data_meta; 101 | *foo = 112; 102 | } 103 | 104 | // Encap / decap test 105 | // Extend packet head by 4 bytes (encapsulation use case) 106 | bpf_xdp_adjust_head(ctx, adj_len); 107 | data = (void *)(long)ctx->data; // NOLINT 108 | data_end = (void *)(long)ctx->data_end; // NOLINT 109 | 110 | // Make kernel's verifier happy - check for boundaries 111 | if (data + sizeof(*foo) <= data_end) 112 | { 113 | foo = data; 114 | *foo = 112; 115 | } 116 | 117 | return XDP_PASS; 118 | } 119 | 120 | SEC("xdp") 121 | int xdp_root3(struct xdp_md *ctx) 122 | { 123 | #pragma unroll 124 | for (__u32 i = 0; i < PROG_CNT; i++) 125 | { 126 | bpf_tail_call(ctx, &programs, 0); 127 | } 128 | 129 | return XDP_DROP; 130 | } 131 | 132 | SEC("xdp") 133 | int xdp_perf(struct xdp_md *ctx) 134 | { 135 | // Simple program that just emits perf event with packet size. 136 | __u32 packet_size = ctx->data_end - ctx->data; 137 | 138 | bpf_perf_event_output(ctx, &perf_map, BPF_F_CURRENT_CPU, &packet_size, sizeof(packet_size)); 139 | 140 | return XDP_PASS; 141 | } 142 | 143 | char _license[] SEC("license") = "GPL"; 144 | -------------------------------------------------------------------------------- /itest/tc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/dropbox/goebpf" 12 | "github.com/stretchr/testify/suite" 13 | ) 14 | 15 | const ( 16 | tcProgramFilename = "tc1.elf" 17 | ) 18 | 19 | type tcTestSuite struct { 20 | suite.Suite 21 | programFilename string 22 | programsCount int 23 | } 24 | 25 | // Basic sanity test of BPF core functionality like 26 | // ReadElf, create maps, load / attach programs 27 | func (ts *tcTestSuite) TestElfLoad() { 28 | // This compile ELF file contains 2 BPF(TC type) programs 29 | eb := goebpf.NewDefaultEbpfSystem() 30 | err := eb.LoadElf(ts.programFilename) 31 | ts.NoError(err) 32 | if err != nil { 33 | // ELF read error. 34 | ts.FailNowf("Unable to read %s", ts.programFilename) 35 | } 36 | 37 | // There should be 0 BPF maps recognized by loader 38 | maps := eb.GetMaps() 39 | ts.Require().Equal(0, len(maps)) 40 | 41 | // Non existing map 42 | ts.Nil(eb.GetMapByName("something")) 43 | 44 | // Also there should few TC eBPF programs recognized 45 | ts.Require().Equal(ts.programsCount, len(eb.GetPrograms())) 46 | 47 | // Check loaded programs and try to pin them 48 | tc1 := eb.GetProgramByName("tc1") 49 | tc1.Load() 50 | path := bpfPath + "/tc1_pin_test" 51 | err = tc1.Pin(path) 52 | ts.NoError(err) 53 | ts.FileExists(path) 54 | os.Remove(path) 55 | ts.Equal(goebpf.ProgramTypeSchedCls, tc1.GetType()) 56 | 57 | // Check loaded programs and try to pin them 58 | tc2 := eb.GetProgramByName("tc2") 59 | tc2.Load() 60 | path = bpfPath + "/tc2_pin_test" 61 | err = tc2.Pin(path) 62 | ts.NoError(err) 63 | ts.FileExists(path) 64 | os.Remove(path) 65 | ts.Equal(goebpf.ProgramTypeSchedAct, tc2.GetType()) 66 | 67 | // Check loaded programs and try to pin them 68 | tc3 := eb.GetProgramByName("tc3") 69 | tc3.Load() 70 | path = bpfPath + "/tc3_pin_test" 71 | err = tc3.Pin(path) 72 | ts.NoError(err) 73 | ts.FileExists(path) 74 | os.Remove(path) 75 | ts.Equal(goebpf.ProgramTypeSchedAct, tc3.GetType()) 76 | 77 | // Non existing program 78 | ts.Nil(eb.GetProgramByName("something")) 79 | 80 | //Run attach and detach, they shouldn't fail as these methods are not implemented for TC 81 | err = tc1.Attach(0) 82 | ts.Error(err) 83 | err = tc1.Detach() 84 | ts.Error(err) 85 | err = tc2.Attach(0) 86 | ts.Error(err) 87 | err = tc2.Detach() 88 | ts.Error(err) 89 | err = tc3.Attach(0) 90 | ts.Error(err) 91 | err = tc3.Detach() 92 | ts.Error(err) 93 | 94 | // Unload programs (not required for real use case) 95 | for _, program := range eb.GetPrograms() { 96 | err = program.Close() 97 | ts.NoError(err) 98 | } 99 | 100 | // Negative: close already closed program 101 | err = tc1.Close() 102 | ts.Error(err) 103 | 104 | } 105 | 106 | func (ts *tcTestSuite) TestProgramInfo() { 107 | // Load test program, don't attach (not required to get info) 108 | eb := goebpf.NewDefaultEbpfSystem() 109 | err := eb.LoadElf(ts.programFilename) 110 | ts.Require().NoError(err) 111 | prog := eb.GetProgramByName("tc1") 112 | err = prog.Load() 113 | ts.Require().NoError(err) 114 | 115 | // Get program info by FD (NOT ID, since this program is ours) 116 | info, err := goebpf.GetProgramInfoByFd(prog.GetFd()) 117 | ts.NoError(err) 118 | 119 | // Check base info 120 | ts.Equal(prog.GetName(), info.Name) 121 | ts.Equal(prog.GetFd(), info.Fd) 122 | ts.Equal(goebpf.ProgramTypeSchedCls, info.Type) 123 | // Check loaded time 124 | now := time.Now() 125 | ts.True(now.Sub(info.LoadTime) < time.Second*10) 126 | 127 | } 128 | 129 | // Run suite 130 | func TestTcSuite(t *testing.T) { 131 | suite.Run(t, &tcTestSuite{ 132 | programFilename: progPath(tcProgramFilename), 133 | programsCount: 3, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /ebpf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import "io" 7 | 8 | // System defines interface for eBPF system - top level 9 | // interface to interact with eBPF system 10 | type System interface { 11 | // Read previously compiled eBPF program at the given path 12 | LoadElf(path string) error 13 | // Read previously compiled eBPF program from an io.ReaderAt 14 | Load(reader io.ReaderAt) error 15 | // Get all defined eBPF maps 16 | GetMaps() map[string]Map 17 | // Returns Map or nil if not found 18 | GetMapByName(name string) Map 19 | // Get all eBPF programs 20 | GetPrograms() map[string]Program 21 | // Returns Program or nil if not found 22 | GetProgramByName(name string) Program 23 | } 24 | 25 | // Program defines eBPF program interface 26 | type Program interface { 27 | // Load program into Linux kernel 28 | Load() error 29 | // Pin (save, share) program into given location. 30 | // Location must be mounted as bpffs (mount bpffs -t bpffs /some/location) 31 | Pin(path string) error 32 | // Unload program from kernel 33 | Close() error 34 | // Attach program to something - depends on program type. 35 | // - XDP: Attach to network interface (data - iface name, or XdpAttachParams) 36 | // - SocketFilter: Attach to socket (data - socket fd) 37 | Attach(data interface{}) error 38 | // Detach previously attached program 39 | Detach() error 40 | // Returns program name as it defined in C code 41 | GetName() string 42 | // Returns section name for the program 43 | GetSection() string 44 | // Returns program file descriptor (given by kernel) 45 | GetFd() int 46 | // Returns size of program in bytes 47 | GetSize() int 48 | // Returns program's license 49 | GetLicense() string 50 | // Returns program type 51 | GetType() ProgramType 52 | } 53 | 54 | // Map defines interface to interact with eBPF maps 55 | type Map interface { 56 | Create() error 57 | GetFd() int 58 | GetName() string 59 | GetType() MapType 60 | Close() error 61 | // Makes a copy of map definition. This will NOT create map, just copies definition, "template". 62 | // Useful for array/map of maps use case 63 | CloneTemplate() Map 64 | // Generic lookup. Accepts any type which will be 65 | // converted to []byte eventually, returns bytes 66 | Lookup(interface{}) ([]byte, error) 67 | // The same, but does casting of return value to int / uint64 68 | LookupInt(interface{}) (int, error) 69 | LookupUint64(interface{}) (uint64, error) 70 | // The same, but does casting of return value to string 71 | LookupString(interface{}) (string, error) 72 | Insert(interface{}, interface{}) error 73 | Update(interface{}, interface{}) error 74 | Upsert(interface{}, interface{}) error 75 | Delete(interface{}) error 76 | // Implementation of bpf_map_get_next_key 77 | GetNextKey(interface{}) ([]byte, error) 78 | GetNextKeyString(interface{}) (string, error) 79 | GetNextKeyInt(interface{}) (int, error) 80 | GetNextKeyUint64(interface{}) (uint64, error) 81 | } 82 | 83 | const ( 84 | // Maximum buffer size for kernel's eBPF verifier error log messages 85 | logBufferSize = (256 * 1024) 86 | ) 87 | 88 | // System implementation 89 | type ebpfSystem struct { 90 | Programs map[string]Program // eBPF programs by name 91 | Maps map[string]Map // eBPF maps defined by Progs by name 92 | } 93 | 94 | // NewDefaultEbpfSystem creates default eBPF system 95 | func NewDefaultEbpfSystem() System { 96 | return &ebpfSystem{ 97 | Programs: make(map[string]Program), 98 | Maps: make(map[string]Map), 99 | } 100 | } 101 | 102 | // GetMaps returns all maps found in .elf file 103 | func (s *ebpfSystem) GetMaps() map[string]Map { 104 | return s.Maps 105 | } 106 | 107 | // GetPrograms returns all eBPF programs found in .elf file 108 | func (s *ebpfSystem) GetPrograms() map[string]Program { 109 | return s.Programs 110 | } 111 | 112 | // GetMapByName returns eBPF map by given name 113 | func (s *ebpfSystem) GetMapByName(name string) Map { 114 | if result, ok := s.Maps[name]; ok { 115 | return result 116 | } 117 | return nil 118 | } 119 | 120 | // GetProgramByName returns eBPF program by given name 121 | func (s *ebpfSystem) GetProgramByName(name string) Program { 122 | if result, ok := s.Programs[name]; ok { 123 | return result 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /examples/kprobe/exec_dump/ebpf_prog/kprobe.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | #include "bpf_helpers.h" 5 | 6 | #define BUFSIZE_PADDED (2 << 13) 7 | #define BUFSIZE ((BUFSIZE_PADDED - 1) >> 1) 8 | #define MAX_ARGLEN 256 9 | #define MAX_ARGS 20 10 | #define NARGS 6 11 | #define NULL ((void *)0) 12 | #define TASK_COMM_LEN 32 13 | 14 | typedef unsigned long args_t; 15 | 16 | typedef struct event { 17 | __u64 ktime_ns; 18 | __u32 pid; 19 | __u32 uid; 20 | __u32 gid; 21 | __s32 type; 22 | char comm[TASK_COMM_LEN]; 23 | } event_t; 24 | 25 | typedef struct buf { 26 | __u32 off; 27 | __u8 data[BUFSIZE_PADDED]; 28 | } buf_t; 29 | 30 | BPF_MAP_DEF(events) = { 31 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 32 | .max_entries = 1024, 33 | }; 34 | BPF_MAP_ADD(events); 35 | 36 | BPF_MAP_DEF(buffer) = { 37 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 38 | .key_size = sizeof(__u32), 39 | .value_size = BUFSIZE_PADDED, 40 | .max_entries = 1, 41 | }; 42 | BPF_MAP_ADD(buffer); 43 | 44 | static inline void get_args(struct pt_regs *ctx, unsigned long *args) { 45 | // if registers are valid then use them directly (kernel version < 4.17) 46 | if (ctx->orig_ax || ctx->bx || ctx->cx || ctx->dx) { 47 | args[0] = PT_REGS_PARM1(ctx); 48 | args[1] = PT_REGS_PARM2(ctx); 49 | args[2] = PT_REGS_PARM3(ctx); 50 | args[3] = PT_REGS_PARM4(ctx); 51 | args[4] = PT_REGS_PARM5(ctx); 52 | args[5] = PT_REGS_PARM6(ctx); 53 | } else { 54 | // otherwise it's a later kernel version so load register values from 55 | // ctx->di. 56 | struct pt_regs *regs = (struct pt_regs *)ctx->di; 57 | bpf_probe_read(&args[0], sizeof(*args), ®s->di); 58 | bpf_probe_read(&args[1], sizeof(*args), ®s->si); 59 | bpf_probe_read(&args[2], sizeof(*args), ®s->dx); 60 | bpf_probe_read(&args[3], sizeof(*args), ®s->r10); 61 | bpf_probe_read(&args[4], sizeof(*args), ®s->r8); 62 | bpf_probe_read(&args[5], sizeof(*args), ®s->r9); 63 | } 64 | } 65 | 66 | static inline buf_t *get_buf() { 67 | __u32 key = 0; 68 | return (buf_t *)bpf_map_lookup_elem(&buffer, &key); 69 | } 70 | 71 | static inline int buf_perf_output(struct pt_regs *ctx) { 72 | buf_t *buf = get_buf(); 73 | if (buf == NULL) { 74 | return -1; 75 | } 76 | int size = buf->off & BUFSIZE; 77 | buf->off = 0; 78 | return bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, 79 | (void *)buf->data, size); 80 | } 81 | 82 | static inline int buf_write(buf_t *buf, void *ptr, int size) { 83 | if (buf->off >= BUFSIZE) { 84 | return 0; 85 | } 86 | 87 | if (bpf_probe_read(&(buf->data[buf->off]), size, ptr) == 0) { 88 | buf->off += size; 89 | return size; 90 | } 91 | 92 | return -1; 93 | } 94 | 95 | static inline int buf_strcat(buf_t *buf, void *ptr) { 96 | if (buf->off >= BUFSIZE) { 97 | return 0; 98 | } 99 | 100 | int n = bpf_probe_read_str(&(buf->data[buf->off]), MAX_ARGLEN, ptr); 101 | if (n > 0) { 102 | buf->off += n; 103 | } 104 | 105 | return n; 106 | } 107 | 108 | static inline int buf_strcat_argp(buf_t *buf, void *ptr) { 109 | const char *argp = NULL; 110 | bpf_probe_read(&argp, sizeof(argp), ptr); 111 | if (argp) { 112 | return buf_strcat(buf, (void *)(argp)); 113 | } 114 | return 0; 115 | } 116 | 117 | static inline int buf_strcat_argv(buf_t *buf, void **ptr) { 118 | #pragma unroll 119 | for (int i = 0; i < MAX_ARGS; i++) { 120 | if (buf_strcat_argp(buf, &ptr[i]) == 0) { 121 | return 0; 122 | } 123 | } 124 | return 0; 125 | } 126 | 127 | SEC("kprobe/guess_execve") 128 | int execve_entry(struct pt_regs *ctx) { 129 | buf_t *buf = get_buf(); 130 | if (buf == NULL) { 131 | return 0; 132 | } 133 | 134 | args_t args[NARGS] = {}; 135 | get_args(ctx, args); 136 | 137 | event_t e = {0}; 138 | e.ktime_ns = bpf_ktime_get_ns(); 139 | e.pid = bpf_get_current_pid_tgid() >> 32; 140 | e.uid = bpf_get_current_uid_gid() >> 32; 141 | e.gid = bpf_get_current_uid_gid(); 142 | bpf_get_current_comm(&e.comm, sizeof(e.comm)); 143 | 144 | buf_write(buf, (void *)&e, sizeof(e)); 145 | buf_strcat(buf, (void *)args[0]); 146 | buf_strcat_argv(buf, (void *)args[1]); 147 | buf_perf_output(ctx); 148 | 149 | return 0; 150 | } 151 | 152 | char _license[] SEC("license") = "GPL"; 153 | -------------------------------------------------------------------------------- /examples/tc/packet_counter/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "os/signal" 11 | "time" 12 | 13 | "github.com/dropbox/goebpf" 14 | ) 15 | 16 | var iface = flag.String("iface", "", "Interface to bind TC program to") 17 | var elf = flag.String("elf", "ebpf_prog/tc.elf", "clang/llvm compiled binary file") 18 | var ingressFunction = flag.String("ingress", "tc_ingress", "Name of tc program (function name) for ingress traffic") 19 | var egressFunction = flag.String("egress", "tc_egress", "Name of tc program (function name) for egress traffic") 20 | 21 | const ( 22 | HASH_FLAG_DIR_INGRESS = 0 23 | HASH_FLAG_DIR_EGRESS = (1 << 0) 24 | HASH_FLAG_UNIT_PACKETS = 0 25 | HASH_FLAG_UNIT_BYTES = (1 << 1) 26 | ) 27 | 28 | func main() { 29 | flag.Parse() 30 | if *iface == "" { 31 | fatalError("-iface is required.") 32 | } 33 | 34 | // Create eBPF system / load .ELF files compiled by clang/llvm 35 | bpf := goebpf.NewDefaultEbpfSystem() 36 | err := bpf.LoadElf(*elf) 37 | if err != nil { 38 | fatalError("LoadElf() failed: %v", err) 39 | } 40 | printBpfInfo(bpf) 41 | 42 | // Find metrics eBPF map 43 | metrics := bpf.GetMapByName("metrics") 44 | if metrics == nil { 45 | fatalError("eBPF map 'metrics' not found") 46 | } 47 | 48 | // entrypoints are 49 | // int tc_ingress(struct __sk_buff *skb) 50 | // int tc_egress(struct __sk_buff *skb) 51 | var programs = []struct { 52 | name string 53 | direction goebpf.TcFlowDirection 54 | }{ 55 | {*ingressFunction, goebpf.TcDirectionIngress}, 56 | {*egressFunction, goebpf.TcDirectionEgress}, 57 | } 58 | for _, prog := range programs { 59 | program := bpf.GetProgramByName(prog.name) 60 | 61 | if program == nil { 62 | fatalError("No programs of type 'SchedACT' not found.") 63 | } 64 | 65 | err = program.Load() 66 | if err != nil { 67 | fatalError("program.Load() failed: %v", err) 68 | } 69 | 70 | attachParams := &goebpf.TcAttachParams{ 71 | Interface: *iface, 72 | Direction: prog.direction, 73 | DirectAction: false, 74 | EntryPoint: prog.name, 75 | ClobberIngress: true, 76 | } 77 | 78 | err = program.Attach(attachParams) 79 | defer program.Detach() 80 | 81 | if err != nil { 82 | fatalError("Failed to attach %s program: %v", prog.direction.String(), err) 83 | } 84 | } 85 | 86 | // Add CTRL+C handler 87 | ctrlC := make(chan os.Signal, 1) 88 | signal.Notify(ctrlC, os.Interrupt) 89 | 90 | // Print stat every second / exit on CTRL+C 91 | fmt.Println("TC program successfully loaded and attached. Counters refreshed every second.") 92 | fmt.Println() 93 | ticker := time.NewTicker(1 * time.Second) 94 | for { 95 | select { 96 | case <-ticker.C: 97 | // clear the screen 98 | fmt.Print("\x1B[2J") 99 | // move cursor to top left of screen 100 | fmt.Print("\x1B[1;1H") 101 | 102 | // currently 4 metric slots are used: {tx,rx} {packets,bytes} 103 | for i := 0; i < 4; i++ { 104 | value, err := metrics.LookupInt(i) 105 | if err != nil { 106 | fatalError("LookupInt failed: %v", err) 107 | } 108 | if value > 0 { 109 | fmt.Printf("% -20s: %d\n", getMetricName(i), value) 110 | } 111 | } 112 | case <-ctrlC: 113 | fmt.Println("\nDetaching program and exit") 114 | return 115 | } 116 | } 117 | } 118 | 119 | func fatalError(format string, args ...interface{}) { 120 | fmt.Fprintf(os.Stderr, format+"\n", args...) 121 | os.Exit(1) 122 | } 123 | 124 | func printBpfInfo(bpf goebpf.System) { 125 | fmt.Println("Maps:") 126 | for _, item := range bpf.GetMaps() { 127 | fmt.Printf("\t%s: %v, Fd %v\n", item.GetName(), item.GetType(), item.GetFd()) 128 | } 129 | fmt.Println("\nPrograms:") 130 | for _, prog := range bpf.GetPrograms() { 131 | fmt.Printf("\t%s: %v, size %d, license \"%s\"\n", 132 | prog.GetName(), prog.GetType(), prog.GetSize(), prog.GetLicense(), 133 | ) 134 | 135 | } 136 | fmt.Println() 137 | } 138 | 139 | func getMetricName(index int) string { 140 | unit := "packets" 141 | direction := "rx" 142 | 143 | if (index & HASH_FLAG_UNIT_BYTES) > 0 { 144 | unit = "bytes" 145 | } 146 | 147 | if (index & HASH_FLAG_DIR_EGRESS) > 0 { 148 | direction = "tx" 149 | } 150 | 151 | return fmt.Sprintf("[idx %d] %s %s", index, direction, unit) 152 | } 153 | -------------------------------------------------------------------------------- /examples/xdp/xdp_dump/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/binary" 9 | "encoding/hex" 10 | "flag" 11 | "fmt" 12 | "net" 13 | "os" 14 | "os/signal" 15 | 16 | "github.com/dropbox/goebpf" 17 | ) 18 | 19 | var iface = flag.String("iface", "", "Interface to bind XDP program to") 20 | var elf = flag.String("elf", "ebpf_prog/xdp_dump.elf", "clang/llvm compiled binary file") 21 | var programName = flag.String("program", "xdp_dump", "Name of XDP program (function name)") 22 | 23 | const ( 24 | // Size of structure used to pass metadata 25 | metadataSize = 12 26 | ) 27 | 28 | // In sync with xdp_dump.c "struct perf_event_item" 29 | type perfEventItem struct { 30 | SrcIp, DstIp uint32 31 | SrcPort, DstPort uint16 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | if *iface == "" { 37 | fatalError("-iface is required.") 38 | } 39 | 40 | fmt.Println("\nXDP dump example program\n") 41 | 42 | // Create eBPF system / load .ELF files compiled by clang/llvm 43 | bpf := goebpf.NewDefaultEbpfSystem() 44 | err := bpf.LoadElf(*elf) 45 | if err != nil { 46 | fatalError("LoadElf() failed: %v", err) 47 | } 48 | printBpfInfo(bpf) 49 | 50 | // Find special "PERF_EVENT" eBPF map 51 | perfmap := bpf.GetMapByName("perfmap") 52 | if perfmap == nil { 53 | fatalError("eBPF map 'perfmap' not found") 54 | } 55 | 56 | // Program name matches function name in xdp.c: 57 | // int xdp_dump(struct xdp_md *ctx) 58 | xdp := bpf.GetProgramByName(*programName) 59 | if xdp == nil { 60 | fatalError("Program '%s' not found.", *programName) 61 | } 62 | 63 | // Load XDP program into kernel 64 | err = xdp.Load() 65 | if err != nil { 66 | fatalError("xdp.Load(): %v", err) 67 | } 68 | 69 | // Attach to interface 70 | err = xdp.Attach(*iface) 71 | if err != nil { 72 | fatalError("xdp.Attach(): %v", err) 73 | } 74 | defer xdp.Detach() 75 | 76 | // Add CTRL+C handler 77 | ctrlC := make(chan os.Signal, 1) 78 | signal.Notify(ctrlC, os.Interrupt) 79 | 80 | // Start listening to Perf Events 81 | perf, _ := goebpf.NewPerfEvents(perfmap) 82 | perfEvents, err := perf.StartForAllProcessesAndCPUs(4096) 83 | if err != nil { 84 | fatalError("perf.StartForAllProcessesAndCPUs(): %v", err) 85 | } 86 | 87 | fmt.Println("XDP program successfully loaded and attached.") 88 | fmt.Println("All new TCP connection requests (SYN) coming to this host will be dumped here.") 89 | fmt.Println() 90 | 91 | go func() { 92 | var event perfEventItem 93 | for { 94 | if eventData, ok := <-perfEvents; ok { 95 | reader := bytes.NewReader(eventData) 96 | binary.Read(reader, binary.LittleEndian, &event) 97 | fmt.Printf("TCP: %v:%d -> %v:%d\n", 98 | intToIPv4(event.SrcIp), ntohs(event.SrcPort), 99 | intToIPv4(event.DstIp), ntohs(event.DstPort), 100 | ) 101 | if len(eventData)-metadataSize > 0 { 102 | // event contains packet sample as well 103 | fmt.Println(hex.Dump(eventData[metadataSize:])) 104 | } 105 | } else { 106 | // Update channel closed 107 | break 108 | } 109 | } 110 | }() 111 | 112 | // Wait until Ctrl+C pressed 113 | <-ctrlC 114 | 115 | // Stop perf events and print summary 116 | perf.Stop() 117 | fmt.Println("\nSummary:") 118 | fmt.Printf("\t%d Event(s) Received\n", perf.EventsReceived) 119 | fmt.Printf("\t%d Event(s) lost (e.g. small buffer, delays in processing)\n", perf.EventsLost) 120 | fmt.Println("\nDetaching program and exit...") 121 | } 122 | 123 | func fatalError(format string, args ...interface{}) { 124 | fmt.Fprintf(os.Stderr, format+"\n", args...) 125 | os.Exit(1) 126 | } 127 | 128 | func printBpfInfo(bpf goebpf.System) { 129 | fmt.Println("Maps:") 130 | for _, item := range bpf.GetMaps() { 131 | m := item.(*goebpf.EbpfMap) 132 | fmt.Printf("\t%s: %v, Fd %v\n", m.Name, m.Type, m.GetFd()) 133 | } 134 | fmt.Println("\nPrograms:") 135 | for _, prog := range bpf.GetPrograms() { 136 | fmt.Printf("\t%s: %v, size %d, license \"%s\"\n", 137 | prog.GetName(), prog.GetType(), prog.GetSize(), prog.GetLicense(), 138 | ) 139 | 140 | } 141 | fmt.Println() 142 | } 143 | 144 | func intToIPv4(ip uint32) net.IP { 145 | res := make([]byte, 4) 146 | binary.LittleEndian.PutUint32(res, ip) 147 | return net.IP(res) 148 | } 149 | 150 | func ntohs(value uint16) uint16 { 151 | return ((value & 0xff) << 8) | (value >> 8) 152 | } 153 | -------------------------------------------------------------------------------- /examples/xdp/bpf_redirect_map/ebpf_prog/xdp.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | 5 | // uncomment this to get prints at /sys/kernel/debug/tracing/trace 6 | // #define DEBUG 7 | 8 | #define AF_INET 2 /* Internet IP Protocol */ 9 | #define ETH_ALEN 6 10 | 11 | 12 | #include 13 | 14 | 15 | // Ethernet header 16 | // #include 17 | struct ethhdr { 18 | __u8 h_dest[6]; 19 | __u8 h_source[6]; 20 | __u16 h_proto; 21 | } __attribute__((packed)); 22 | 23 | // IPv4 header 24 | // #include 25 | struct iphdr { 26 | __u8 ihl : 4; 27 | __u8 version : 4; 28 | __u8 tos; 29 | __u16 tot_len; 30 | __u16 id; 31 | __u16 frag_off; 32 | __u8 ttl; 33 | __u8 protocol; 34 | __u16 check; 35 | __u32 saddr; 36 | __u32 daddr; 37 | } __attribute__((packed)); 38 | 39 | /* XDP enabled TX ports for redirect map */ 40 | BPF_MAP_DEF(if_redirect) = { 41 | .map_type = BPF_MAP_TYPE_DEVMAP, 42 | .key_size = sizeof(__u32), 43 | .value_size = sizeof(__u32), 44 | .max_entries = 64, 45 | }; 46 | BPF_MAP_ADD(if_redirect); 47 | 48 | /* This program matches ICMP packets (IPPROTO = 0X01) and redirects it back to the sender. 49 | Using bpf_fib_lookup, we use the kernel routing table to perform a FIB lookup and send 50 | packet back to whoever sent that to us (rewriting ip and mac addresses fields). 51 | This means that the XDP code can essentially route packets, provided that the kernel has 52 | the forwarding information. 53 | For more info on Linux and routing lookup: https://vincent.bernat.ch/en/blog/2017-ipv4-route-lookup-linux 54 | */ 55 | SEC("xdp") 56 | int xdp_test(struct xdp_md *ctx) 57 | { 58 | void *data_end = (void *)(long)ctx->data_end; 59 | void *data = (void *)(long)ctx->data; 60 | struct ethhdr *eth_header; 61 | struct iphdr *ip_header; 62 | eth_header = data; 63 | if ((void *)eth_header + sizeof(*eth_header) > data_end) { 64 | return XDP_PASS; 65 | } 66 | 67 | __u16 h_proto = eth_header->h_proto; 68 | 69 | /* anything that is not IPv4 (including ARP) goes up to the kernel */ 70 | if (h_proto != 0x08U) { // htons(ETH_P_IP) -> 0x08U 71 | return XDP_PASS; 72 | } 73 | ip_header = data + sizeof(*eth_header); 74 | if ((void *)ip_header + sizeof(*ip_header) > data_end) { 75 | return XDP_PASS; 76 | } 77 | 78 | if (ip_header->protocol != 0x01) { // IPPROTO_ICMP = 1 79 | return XDP_PASS; 80 | } 81 | 82 | // if icmp, we send it back to the gateway 83 | // Create bpf_fib_lookup to help us route the packet 84 | struct bpf_fib_lookup fib_params; 85 | 86 | // fill struct with zeroes, so we are sure no data is missing 87 | __builtin_memset(&fib_params, 0, sizeof(fib_params)); 88 | 89 | fib_params.family = AF_INET; 90 | // use daddr as source in the lookup, so we refleect packet back (as if it wcame from us) 91 | fib_params.ipv4_src = ip_header->daddr; 92 | // opposite here, the destination is the source of the icmp packet..remote end 93 | fib_params.ipv4_dst = ip_header->saddr; 94 | fib_params.ifindex = ctx->ingress_ifindex; 95 | 96 | bpf_printk("doing route lookup dst: %d\n", fib_params.ipv4_dst); 97 | int rc = bpf_fib_lookup(ctx, &fib_params, sizeof(fib_params), 0); 98 | if ((rc != BPF_FIB_LKUP_RET_SUCCESS) && (rc != BPF_FIB_LKUP_RET_NO_NEIGH)) { 99 | bpf_printk("Dropping packet\n"); 100 | return XDP_DROP; 101 | } else if (rc == BPF_FIB_LKUP_RET_NO_NEIGH) { 102 | // here we should let packet pass so we resolve arp. 103 | bpf_printk("Passing packet, lookup returned %d\n", BPF_FIB_LKUP_RET_NO_NEIGH); 104 | return XDP_PASS; 105 | } 106 | bpf_printk("route lookup success, ifindex: %d\n", fib_params.ifindex); 107 | bpf_printk("mac to use as dst is: %lu\n", fib_params.dmac); 108 | 109 | // Swap src with dst ip 110 | __u32 oldipdst = ip_header->daddr; 111 | ip_header->daddr = ip_header->saddr; 112 | ip_header->saddr = oldipdst; 113 | 114 | // copy resulting dmac/smac from the fib lookup 115 | memcpy(eth_header->h_dest, fib_params.dmac, ETH_ALEN); 116 | memcpy(eth_header->h_source, fib_params.smac, ETH_ALEN); 117 | 118 | // redirect packet to the resulting ifindex 119 | return bpf_redirect_map(&if_redirect, fib_params.ifindex, 0); 120 | 121 | } 122 | 123 | char _license[] SEC("license") = "GPL"; -------------------------------------------------------------------------------- /examples/xdp/basic_firewall/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "flag" 9 | "fmt" 10 | "net" 11 | "os" 12 | "os/signal" 13 | "strings" 14 | "time" 15 | 16 | "github.com/dropbox/goebpf" 17 | ) 18 | 19 | type ipAddressList []string 20 | 21 | var iface = flag.String("iface", "", "Interface to bind XDP program to") 22 | var elf = flag.String("elf", "ebpf_prog/xdp_fw.elf", "clang/llvm compiled binary file") 23 | var ipList ipAddressList 24 | 25 | func main() { 26 | flag.Var(&ipList, "drop", "IPv4 CIDR to DROP traffic from, repeatable") 27 | flag.Parse() 28 | if *iface == "" { 29 | fatalError("-iface is required.") 30 | } 31 | if len(ipList) == 0 { 32 | fatalError("at least one IPv4 address to DROP required (-drop)") 33 | } 34 | 35 | // Create eBPF system 36 | bpf := goebpf.NewDefaultEbpfSystem() 37 | // Load .ELF files compiled by clang/llvm 38 | err := bpf.LoadElf(*elf) 39 | if err != nil { 40 | fatalError("LoadElf() failed: %v", err) 41 | } 42 | printBpfInfo(bpf) 43 | 44 | // Get eBPF maps 45 | matches := bpf.GetMapByName("matches") 46 | if matches == nil { 47 | fatalError("eBPF map 'matches' not found") 48 | } 49 | blacklist := bpf.GetMapByName("blacklist") 50 | if blacklist == nil { 51 | fatalError("eBPF map 'blacklist' not found") 52 | } 53 | 54 | // Get XDP program. Name simply matches function from xdp_fw.c: 55 | // int firewall(struct xdp_md *ctx) { 56 | xdp := bpf.GetProgramByName("firewall") 57 | if xdp == nil { 58 | fatalError("Program 'firewall' not found.") 59 | } 60 | 61 | // Populate eBPF map with IPv4 addresses to block 62 | fmt.Println("Blacklisting IPv4 addresses...") 63 | for index, ip := range ipList { 64 | fmt.Printf("\t%s\n", ip) 65 | err := blacklist.Insert(goebpf.CreateLPMtrieKey(ip), index) 66 | if err != nil { 67 | fatalError("Unable to Insert into eBPF map: %v", err) 68 | } 69 | } 70 | fmt.Println() 71 | 72 | // Load XDP program into kernel 73 | err = xdp.Load() 74 | if err != nil { 75 | fatalError("xdp.Load(): %v", err) 76 | } 77 | 78 | // Attach to interface 79 | err = xdp.Attach(*iface) 80 | if err != nil { 81 | fatalError("xdp.Attach(): %v", err) 82 | } 83 | defer xdp.Detach() 84 | 85 | // Add CTRL+C handler 86 | ctrlC := make(chan os.Signal, 1) 87 | signal.Notify(ctrlC, os.Interrupt) 88 | 89 | fmt.Println("XDP program successfully loaded and attached. Counters refreshed every second.") 90 | fmt.Println("Press CTRL+C to stop.") 91 | fmt.Println() 92 | 93 | // Print stat every second / exit on CTRL+C 94 | ticker := time.NewTicker(1 * time.Second) 95 | for { 96 | select { 97 | case <-ticker.C: 98 | fmt.Println("IP DROPs") 99 | for i := 0; i < len(ipList); i++ { 100 | value, err := matches.LookupInt(i) 101 | if err != nil { 102 | fatalError("LookupInt failed: %v", err) 103 | } 104 | fmt.Printf("%18s %d\n", ipList[i], value) 105 | } 106 | fmt.Println() 107 | case <-ctrlC: 108 | fmt.Println("\nDetaching program and exit") 109 | return 110 | } 111 | } 112 | } 113 | 114 | func fatalError(format string, args ...interface{}) { 115 | fmt.Fprintf(os.Stderr, format+"\n", args...) 116 | os.Exit(1) 117 | } 118 | 119 | func printBpfInfo(bpf goebpf.System) { 120 | fmt.Println("Maps:") 121 | for _, item := range bpf.GetMaps() { 122 | fmt.Printf("\t%s: %v, Fd %v\n", item.GetName(), item.GetType(), item.GetFd()) 123 | } 124 | fmt.Println("\nPrograms:") 125 | for _, prog := range bpf.GetPrograms() { 126 | fmt.Printf("\t%s: %v, size %d, license \"%s\"\n", 127 | prog.GetName(), prog.GetType(), prog.GetSize(), prog.GetLicense(), 128 | ) 129 | 130 | } 131 | fmt.Println() 132 | } 133 | 134 | // Implements flag.Value 135 | func (i *ipAddressList) String() string { 136 | return fmt.Sprintf("%+v", *i) 137 | } 138 | 139 | // Implements flag.Value 140 | func (i *ipAddressList) Set(value string) error { 141 | if len(*i) == 16 { 142 | return errors.New("Up to 16 IPv4 addresses supported") 143 | } 144 | // Validate that value is correct IPv4 address 145 | if !strings.Contains(value, "/") { 146 | value += "/32" 147 | } 148 | if strings.Contains(value, ":") { 149 | return fmt.Errorf("%s is not an IPv4 address", value) 150 | } 151 | _, _, err := net.ParseCIDR(value) 152 | if err != nil { 153 | return err 154 | } 155 | // Valid, add to the list 156 | *i = append(*i, value) 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /program_xdp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | //#include "bpf_helpers.h" 7 | import "C" 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | 13 | "github.com/vishvananda/netlink" 14 | ) 15 | 16 | // XdpResult is eBPF program return code enum 17 | type XdpResult int 18 | 19 | // XDP program return codes 20 | const ( 21 | XdpAborted XdpResult = C.XDP_ABORTED 22 | XdpDrop XdpResult = C.XDP_DROP 23 | XdpPass XdpResult = C.XDP_PASS 24 | XdpTx XdpResult = C.XDP_TX 25 | XdpRedirect XdpResult = C.XDP_REDIRECT 26 | ) 27 | 28 | // XdpAttachMode selects a way how XDP program will be attached to interface 29 | type XdpAttachMode int 30 | 31 | const ( 32 | // XdpAttachModeNone stands for "best effort" - kernel automatically 33 | // selects best mode (would try Drv first, then fallback to Generic). 34 | // NOTE: Kernel will not fallback to Generic XDP if NIC driver failed 35 | // to install XDP program. 36 | XdpAttachModeNone XdpAttachMode = 0 37 | // XdpAttachModeSkb is "generic", kernel mode, less performant comparing to native, 38 | // but does not requires driver support. 39 | XdpAttachModeSkb XdpAttachMode = (1 << 1) 40 | // XdpAttachModeDrv is native, driver mode (support from driver side required) 41 | XdpAttachModeDrv XdpAttachMode = (1 << 2) 42 | // XdpAttachModeHw suitable for NICs with hardware XDP support 43 | XdpAttachModeHw XdpAttachMode = (1 << 3) 44 | ) 45 | 46 | // XdpAttachParams used to pass parameters to Attach() call. 47 | type XdpAttachParams struct { 48 | // Interface is string name of interface to attach program to 49 | Interface string 50 | // Mode is one of XdpAttachMode. 51 | Mode XdpAttachMode 52 | } 53 | 54 | func (t XdpResult) String() string { 55 | switch t { 56 | case XdpAborted: 57 | return "XDP_ABORTED" 58 | case XdpDrop: 59 | return "XDP_DROP" 60 | case XdpPass: 61 | return "XDP_PASS" 62 | case XdpTx: 63 | return "XDP_TX" 64 | case XdpRedirect: 65 | return "XDP_REDIRECT" 66 | } 67 | 68 | return "UNKNOWN" 69 | } 70 | 71 | // XDP eBPF program (implements Program interface) 72 | type xdpProgram struct { 73 | BaseProgram 74 | 75 | // Interface name and attach mode 76 | ifname string 77 | mode XdpAttachMode 78 | } 79 | 80 | func newXdpProgram(bp BaseProgram) Program { 81 | bp.programType = ProgramTypeXdp 82 | return &xdpProgram{ 83 | BaseProgram: bp, 84 | } 85 | } 86 | 87 | // Attach attaches eBPF(XDP) program to network interface. 88 | // There are 2 possible ways to do that: 89 | // 90 | // 1. Pass interface name as parameter, e.g. 91 | // xdpProgram.Attach("eth0") 92 | // 93 | // 2. Using XdpAttachParams structure: 94 | // xdpProgram.Attach( 95 | // &XdpAttachParams{Mode: XdpAttachModeSkb, Interface: "eth0" 96 | // }) 97 | func (p *xdpProgram) Attach(data interface{}) error { 98 | var ifaceName string 99 | var attachMode = XdpAttachModeNone // AutoSelect 100 | 101 | switch x := data.(type) { 102 | case string: 103 | ifaceName = x 104 | case *XdpAttachParams: 105 | ifaceName = x.Interface 106 | attachMode = x.Mode 107 | default: 108 | return fmt.Errorf("%T is not supported for Attach()", data) 109 | } 110 | 111 | // Lookup interface by given name, we need to extract iface index 112 | link, err := netlink.LinkByName(ifaceName) 113 | if err != nil { 114 | // Most likely no such interface 115 | return fmt.Errorf("LinkByName() failed: %v", err) 116 | } 117 | 118 | // Attach program 119 | if err := netlink.LinkSetXdpFdWithFlags(link, p.fd, int(attachMode)); err != nil { 120 | return fmt.Errorf("LinkSetXdpFd() failed: %v", err) 121 | } 122 | 123 | p.ifname = ifaceName 124 | p.mode = attachMode 125 | 126 | return nil 127 | } 128 | 129 | // Detach detaches program from network interface 130 | // Must be previously attached by Attach() call. 131 | func (p *xdpProgram) Detach() error { 132 | if p.ifname == "" { 133 | return errors.New("Program isn't attached") 134 | } 135 | // Lookup interface by given name, we need to extract iface index 136 | link, err := netlink.LinkByName(p.ifname) 137 | if err != nil { 138 | // Most likely no such interface 139 | return fmt.Errorf("LinkByName() failed: %v", err) 140 | } 141 | 142 | // Setting eBPF program with FD -1 actually removes it from interface 143 | if err := netlink.LinkSetXdpFdWithFlags(link, -1, int(p.mode)); err != nil { 144 | return fmt.Errorf("LinkSetXdpFd() failed: %v", err) 145 | } 146 | p.ifname = "" 147 | 148 | return nil 149 | } 150 | -------------------------------------------------------------------------------- /itest/kprobe_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | import ( 7 | "bytes" 8 | "os" 9 | "os/exec" 10 | "testing" 11 | "time" 12 | 13 | "github.com/dropbox/goebpf" 14 | "github.com/stretchr/testify/suite" 15 | ) 16 | 17 | type kprobeTestSuite struct { 18 | suite.Suite 19 | programFilename string 20 | programsCount int 21 | mapsCount int 22 | } 23 | 24 | // Basic sanity test of BPF core functionality like 25 | // ReadElf, create maps, load / attach programs 26 | func (ts *kprobeTestSuite) TestElfLoad() { 27 | 28 | eb := goebpf.NewDefaultEbpfSystem() 29 | err := eb.LoadElf(ts.programFilename) 30 | ts.Require().NoError(err) 31 | 32 | maps := eb.GetMaps() 33 | ts.Require().Equal(ts.mapsCount, len(maps)) 34 | 35 | perfMap := eb.GetMapByName("perf_map").(*goebpf.EbpfMap) 36 | ts.Require().NotNil(perfMap) 37 | ts.NotEqual(0, perfMap.GetFd()) 38 | ts.Equal(goebpf.MapTypePerfEventArray, perfMap.Type) 39 | 40 | ts.Require().Equal(ts.programsCount, len(eb.GetPrograms())) 41 | 42 | // Check that everything loaded correctly / load program into kernel 43 | for name, program := range eb.GetPrograms() { 44 | // Check params 45 | ts.Equal(goebpf.ProgramTypeKprobe, program.GetType()) 46 | ts.Equal(name, program.GetName()) 47 | ts.Equal("GPL", program.GetLicense()) 48 | // Load into kernel 49 | err = program.Load() 50 | ts.Require().NoError(err) 51 | ts.Require().NotEqual(0, program.GetFd()) 52 | } 53 | 54 | // Try to pin program into some filesystem 55 | kprobe0 := eb.GetProgramByName("kprobe0") 56 | path := bpfPath + "/kprobe_pin_test" 57 | err = kprobe0.Pin(path) 58 | ts.NoError(err) 59 | ts.FileExists(path) 60 | os.Remove(path) 61 | 62 | // Non existing program 63 | ts.Nil(eb.GetProgramByName("something")) 64 | 65 | // Attach program to first (lo) interface 66 | // P.S. XDP does not work on "lo" interface, however, you can still attach program to it 67 | // which is enough to test basic BPF functionality 68 | err = kprobe0.Attach(nil) 69 | ts.Require().NoError(err) 70 | err = kprobe0.Detach() 71 | ts.NoError(err) 72 | 73 | // Attach with parameters 74 | err = kprobe0.Attach("guess_execve") 75 | ts.Require().NoError(err) 76 | err = kprobe0.Detach() 77 | ts.NoError(err) 78 | 79 | // Unload programs (not required for real use case) 80 | for _, program := range eb.GetPrograms() { 81 | err = program.Close() 82 | ts.NoError(err) 83 | } 84 | 85 | // Negative: close already closed program 86 | err = kprobe0.Close() 87 | ts.Error(err) 88 | 89 | // Negative: attach to non existing symbol 90 | err = kprobe0.Attach("sys_does_not_exist") 91 | ts.Error(err) 92 | } 93 | 94 | func (ts *kprobeTestSuite) TestKprobeEvents() { 95 | 96 | eb := goebpf.NewDefaultEbpfSystem() 97 | err := eb.LoadElf(ts.programFilename) 98 | ts.NoError(err) 99 | 100 | // load and attach kprobes 101 | for _, program := range eb.GetPrograms() { 102 | err = program.Load() 103 | ts.Require().NoError(err) 104 | ts.Require().NotEqual(0, program.GetFd()) 105 | err = program.Attach(nil) 106 | ts.Require().NoError(err) 107 | } 108 | 109 | perfMap := eb.GetMapByName("perf_map").(*goebpf.EbpfMap) 110 | ts.Require().NotNil(perfMap) 111 | ts.NotEqual(0, perfMap.GetFd()) 112 | ts.Equal(goebpf.MapTypePerfEventArray, perfMap.Type) 113 | 114 | // Setup/Start perf events 115 | perfEvents, err := goebpf.NewPerfEvents(perfMap) 116 | ts.Require().NoError(err) 117 | perfCh, err := perfEvents.StartForAllProcessesAndCPUs(4096) 118 | ts.Require().NoError(err) 119 | 120 | // execute process to trigger kprobes 121 | ts.Require().NoError(exec.Command("whoami").Run()) 122 | 123 | cstring := func(b []byte) string { 124 | off := bytes.Index(b, []byte{0}) 125 | if off < 1 { 126 | return "" 127 | } 128 | return string(b[:off]) 129 | } 130 | 131 | // read perf events 132 | for i := 0; i < 2; i++ { 133 | select { 134 | case data := <-perfCh: 135 | switch i { 136 | case 0: 137 | // Allow both an underscore and dot here. 138 | // When `go test` runs this, it's written to a temporary path 139 | // like `/tmp/go-build3999159532/b001/itest.test`. 140 | // If directly compiled, the executable will be 141 | // named `./itest_test`. 142 | ts.Require().Regexp("^itest[_\\.]test$", cstring(data)) // parent comm 143 | case 1: 144 | ts.Require().Equal("whoami", cstring(data)) // child comm 145 | } 146 | case <-time.After(3 * time.Second): 147 | ts.Require().Fail("timeout while waiting for perf event") 148 | } 149 | } 150 | perfEvents.Stop() 151 | } 152 | 153 | // Run suite 154 | func TestKprobeSuite(t *testing.T) { 155 | suite.Run(t, &kprobeTestSuite{ 156 | programFilename: progPath("kprobe1.elf"), 157 | programsCount: 2, 158 | mapsCount: 1, 159 | }) 160 | } 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go eBPF 2 | [![Build Status](https://github.com/dropbox/goebpf/actions/workflows/go.yml/badge.svg)](https://github.com/dropbox/goebpf/actions?query=branch%3Amaster) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/dropbox/goebpf)](https://goreportcard.com/report/github.com/dropbox/goebpf) 4 | [![Documentation](https://godoc.org/github.com/dropbox/goebpf?status.svg)](http://godoc.org/github.com/dropbox/goebpf) 5 | 6 | A nice and convenient way to work with `eBPF` programs / perf events from Go. 7 | 8 | ## Requirements 9 | - Go 1.11+ 10 | - Linux Kernel 4.15+ 11 | 12 | ## Supported eBPF features 13 | - eBPF programs 14 | - `SocketFilter` 15 | - `XDP` 16 | - `Kprobe` / `Kretprobe` 17 | - `tc-cls` (`tc-act` is partially implemented, currently) 18 | - Perf Events 19 | 20 | Support for other program types / features can be added in future. 21 | Meanwhile your contributions are warmly welcomed.. :) 22 | 23 | ## Installation 24 | ```bash 25 | # Main library 26 | go get github.com/dropbox/goebpf 27 | 28 | # Mock version (if needed) 29 | go get github.com/dropbox/goebpf/goebpf_mock 30 | ``` 31 | 32 | ## Quick start 33 | Consider very simple example of Read / Load / Attach 34 | ```go 35 | // In order to be simple this examples does not handle errors 36 | bpf := goebpf.NewDefaultEbpfSystem() 37 | // Read clang compiled binary 38 | bpf.LoadElf("test.elf") 39 | // Load XDP program into kernel (name matches function name in C) 40 | xdp := bpf.GetProgramByName("xdp_test") 41 | xdp.Load() 42 | // Attach to interface 43 | xdp.Attach("eth0") 44 | defer xdp.Detach() 45 | // Work with maps 46 | test := bpf.GetMapByName("test") 47 | value, _ := test.LookupInt(0) 48 | fmt.Printf("Value at index 0 of map 'test': %d\n", value) 49 | ``` 50 | Like it? Check our [examples](https://github.com/dropbox/goebpf/tree/master/examples/) 51 | 52 | ## Perf Events 53 | Currently library has support for one, most popular use case of perf_events: where `eBPF` map key maps to `cpu_id`. 54 | So `eBPF` and `go` parts actually bind `cpu_id` to map index. It maybe as simple as: 55 | 56 | ```c 57 | // Define special, perf_events map where key maps to CPU_ID 58 | BPF_MAP_DEF(perfmap) = { 59 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 60 | .max_entries = 128, // Max supported CPUs 61 | }; 62 | BPF_MAP_ADD(perfmap); 63 | 64 | // ... 65 | 66 | // Emit perf event with "data" to map "perfmap" where index is current CPU_ID 67 | bpf_perf_event_output(ctx, &perfmap, BPF_F_CURRENT_CPU, &data, sizeof(data)); 68 | ``` 69 | 70 | And the `go` part: 71 | ```go 72 | perf, err := goebpf.NewPerfEvents("perfmap") 73 | // 4096 is ring buffer size 74 | perfEvents, err := perf.StartForAllProcessesAndCPUs(4096) 75 | defer perf.Stop() 76 | 77 | for { 78 | select { 79 | case data := <-perfEvents: 80 | fmt.Println(data) 81 | } 82 | } 83 | ``` 84 | Looks simple? Check our [full XDP dump example](https://github.com/dropbox/goebpf/tree/master/examples/xdp/xdp_dump) 85 | 86 | ## Kprobes 87 | Library currently has support for `kprobes` and `kretprobes`. 88 | It can be as simple as: 89 | 90 | ```c 91 | // kprobe handler function 92 | SEC("kprobe/guess_execve") 93 | int execve_entry(struct pt_regs *ctx) { 94 | // ... 95 | buf_perf_output(ctx); 96 | return 0; 97 | } 98 | ``` 99 | And the `go` part: 100 | ```go 101 | // Cleanup old probes 102 | err := goebpf.CleanupProbes() 103 | 104 | // Attach all probe programs 105 | for _, prog := range bpf.GetPrograms() { 106 | err := prog.Attach(nil) 107 | } 108 | 109 | // Create perf events 110 | eventsMap := p.bpf.GetMapByName("events") 111 | p.pe, err = goebpf.NewPerfEvents(eventsMap) 112 | events, err := p.pe.StartForAllProcessesAndCPUs(4096) 113 | defer events.Stop() 114 | 115 | for { 116 | select { 117 | case data := <-events: 118 | fmt.Println(data) // kProbe event 119 | } 120 | } 121 | ``` 122 | Simple? Check [exec dump example](https://github.com/dropbox/goebpf/tree/master/examples/kprobe/exec_dump) 123 | 124 | ## Good readings 125 | - [XDP Tutorials](https://github.com/xdp-project/xdp-tutorial) 126 | - [Cilium BPF and XDP Reference Guide](https://docs.cilium.io/en/latest/bpf/) 127 | - [Prototype Kernel: XDP](https://prototype-kernel.readthedocs.io/en/latest/networking/XDP/index.html) 128 | - [AF_XDP: Accelerating networking](https://lwn.net/Articles/750845/) 129 | - [eBPF, part 1: Past, Present, and Future](https://ferrisellis.com/posts/ebpf_past_present_future/) 130 | - [eBPF, part 2: Syscall and Map Types](https://ferrisellis.com/posts/ebpf_syscall_and_maps/) 131 | - [Oracle Blog: A Tour of eBPF Program Types](https://blogs.oracle.com/linux/notes-on-bpf-1) 132 | - [Oracle Blog: eBPF Helper Functions](https://blogs.oracle.com/linux/notes-on-bpf-2) 133 | - [Oracle Blog: Communicating with Userspace](https://blogs.oracle.com/linux/notes-on-bpf-3) 134 | -------------------------------------------------------------------------------- /examples/kprobe/exec_dump/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "log" 12 | "os" 13 | "os/signal" 14 | "strings" 15 | "sync" 16 | 17 | "github.com/dropbox/goebpf" 18 | ) 19 | 20 | var ( 21 | ErrProgramNotFound = errors.New("program not found") 22 | ErrMapNotFound = errors.New("map not found") 23 | ) 24 | 25 | type Event_t struct { 26 | KtimeNs uint64 27 | Pid uint32 28 | Uid uint32 29 | Gid uint32 30 | Type int32 31 | Comm [32]byte 32 | } 33 | 34 | type Program struct { 35 | bpf goebpf.System 36 | pe *goebpf.PerfEvents 37 | wg sync.WaitGroup 38 | } 39 | 40 | func main() { 41 | 42 | // cleanup old probes 43 | if err := goebpf.CleanupProbes(); err != nil { 44 | log.Println(err) 45 | } 46 | 47 | // load ebpf program 48 | p, err := LoadProgram("ebpf_prog/kprobe.elf") 49 | if err != nil { 50 | log.Fatalf("LoadProgram() failed: %v", err) 51 | } 52 | p.ShowInfo() 53 | 54 | // attach ebpf kprobes 55 | if err := p.AttachProbes(); err != nil { 56 | log.Fatalf("AttachProbes() failed: %v", err) 57 | } 58 | defer p.DetachProbes() 59 | 60 | // wait until Ctrl+C pressed 61 | ctrlC := make(chan os.Signal, 1) 62 | signal.Notify(ctrlC, os.Interrupt) 63 | <-ctrlC 64 | 65 | // display some stats 66 | fmt.Println() 67 | fmt.Printf("%d Event(s) Received\n", p.pe.EventsReceived) 68 | fmt.Printf("%d Event(s) lost (e.g. small buffer, delays in processing)\n", p.pe.EventsLost) 69 | } 70 | 71 | func LoadProgram(filename string) (*Program, error) { 72 | 73 | // create system 74 | bpf := goebpf.NewDefaultEbpfSystem() 75 | 76 | // load compiled ebpf elf file 77 | if err := bpf.LoadElf(filename); err != nil { 78 | return nil, err 79 | } 80 | 81 | // load programs 82 | for _, prog := range bpf.GetPrograms() { 83 | if err := prog.Load(); err != nil { 84 | return nil, err 85 | } 86 | } 87 | 88 | return &Program{bpf: bpf}, nil 89 | } 90 | 91 | func (p *Program) startPerfEvents(events <-chan []byte) { 92 | p.wg.Add(1) 93 | go func(events <-chan []byte) { 94 | defer p.wg.Done() 95 | 96 | // print header 97 | fmt.Printf("\nTIME PCOMM PID UID GID DESC\n\n") 98 | for { 99 | 100 | // receive exec events 101 | if b, ok := <-events; ok { 102 | 103 | // parse proc info 104 | var ev Event_t 105 | buf := bytes.NewBuffer(b) 106 | if err := binary.Read(buf, binary.LittleEndian, &ev); err != nil { 107 | fmt.Printf("error: %v\n", err) 108 | continue 109 | } 110 | 111 | // parse args 112 | tokens := bytes.Split(buf.Bytes(), []byte{0x00}) 113 | var args []string 114 | for _, arg := range tokens { 115 | if len(arg) > 0 { 116 | args = append(args, string(arg)) 117 | } 118 | } 119 | 120 | // build display strings 121 | var desc string 122 | if len(args) > 0 { 123 | desc = args[0] 124 | } 125 | if len(args) > 2 { 126 | desc += " " + strings.Join(args[2:], " ") 127 | } 128 | 129 | // display process execution event 130 | ts := goebpf.KtimeToTime(ev.KtimeNs) 131 | fmt.Printf("%s %-16s %-6d %-6d %-6d %s\n", 132 | ts.Format("15:04:05.000"), 133 | goebpf.NullTerminatedStringToString(ev.Comm[:]), 134 | ev.Pid, ev.Uid, ev.Gid, desc) 135 | 136 | } else { 137 | break 138 | } 139 | } 140 | }(events) 141 | } 142 | 143 | func (p *Program) stopPerfEvents() { 144 | p.pe.Stop() 145 | p.wg.Wait() 146 | } 147 | 148 | func (p *Program) AttachProbes() error { 149 | 150 | // attach all probe programs 151 | for _, prog := range p.bpf.GetPrograms() { 152 | if err := prog.Attach(nil); err != nil { 153 | return err 154 | } 155 | } 156 | 157 | // get handles to perf event map 158 | m := p.bpf.GetMapByName("events") 159 | if m == nil { 160 | return ErrMapNotFound 161 | } 162 | 163 | // create perf events 164 | var err error 165 | p.pe, err = goebpf.NewPerfEvents(m) 166 | if err != nil { 167 | return err 168 | } 169 | events, err := p.pe.StartForAllProcessesAndCPUs(4096) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | // start event listeners 175 | p.wg = sync.WaitGroup{} 176 | p.startPerfEvents(events) 177 | 178 | return nil 179 | } 180 | 181 | func (p *Program) DetachProbes() error { 182 | p.stopPerfEvents() 183 | for _, prog := range p.bpf.GetPrograms() { 184 | prog.Detach() 185 | prog.Close() 186 | } 187 | return nil 188 | } 189 | 190 | func (p *Program) ShowInfo() { 191 | fmt.Println() 192 | fmt.Println("Maps:") 193 | for _, item := range p.bpf.GetMaps() { 194 | m := item.(*goebpf.EbpfMap) 195 | fmt.Printf("\t%s: %v, Fd %v\n", m.Name, m.Type, m.GetFd()) 196 | } 197 | fmt.Println("\nPrograms:") 198 | for _, prog := range p.bpf.GetPrograms() { 199 | fmt.Printf("\t%s: %v (%s), size %d, license \"%s\"\n", 200 | prog.GetName(), prog.GetType(), prog.GetSection(), prog.GetSize(), prog.GetLicense(), 201 | ) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /bpf.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com 2 | * 3 | * This program is free software; you can redistribute it and/or 4 | * modify it under the terms of version 2 of the GNU General Public 5 | * License as published by the Free Software Foundation. 6 | */ 7 | 8 | // This is simplified version of linux/uapi/bpf.h 9 | 10 | #ifndef _BPF_H__ 11 | #define _BPF_H__ 12 | 13 | #ifdef __linux__ 14 | #include 15 | #include 16 | #elif __APPLE__ 17 | // In order to be able to install package on Mac - define some types 18 | #define __NR_bpf 515 19 | typedef unsigned short __u16; // NOLINT 20 | typedef unsigned char __u8; 21 | typedef unsigned int __u32; 22 | typedef unsigned long long __u64; // NOLINT 23 | typedef __u64 __aligned_u64; 24 | #else 25 | #error "Arch not supported" 26 | #endif 27 | 28 | #define ptr_to_u64(ptr) ((__u64)(unsigned long)(ptr)) 29 | 30 | /* List of supported BPF syscall commands */ 31 | enum bpf_cmd { 32 | BPF_MAP_CREATE, 33 | BPF_MAP_LOOKUP_ELEM, 34 | BPF_MAP_UPDATE_ELEM, 35 | BPF_MAP_DELETE_ELEM, 36 | BPF_MAP_GET_NEXT_KEY, 37 | BPF_PROG_LOAD, 38 | BPF_OBJ_PIN, 39 | BPF_OBJ_GET, 40 | BPF_PROG_ATTACH, 41 | BPF_PROG_DETACH, 42 | BPF_PROG_TEST_RUN, 43 | BPF_PROG_GET_NEXT_ID, 44 | BPF_MAP_GET_NEXT_ID, 45 | BPF_PROG_GET_FD_BY_ID, 46 | BPF_MAP_GET_FD_BY_ID, 47 | BPF_OBJ_GET_INFO_BY_FD, 48 | }; 49 | 50 | // Max length of eBPF object name 51 | #define BPF_OBJ_NAME_LEN 16U 52 | 53 | // Length of eBPF program tag size 54 | #define BPF_TAG_SIZE 8U 55 | 56 | // clang-format off 57 | union bpf_attr { 58 | struct { /* anonymous struct used by BPF_MAP_CREATE command */ 59 | __u32 map_type; /* one of enum bpf_map_type */ 60 | __u32 key_size; /* size of key in bytes */ 61 | __u32 value_size; /* size of value in bytes */ 62 | __u32 max_entries; /* max number of entries in a map */ 63 | __u32 map_flags; /* BPF_MAP_CREATE related 64 | * flags defined above. 65 | */ 66 | __u32 inner_map_fd; /* fd pointing to the inner map */ 67 | __u32 numa_node; /* numa node (effective only if 68 | * BPF_F_NUMA_NODE is set). 69 | */ 70 | char map_name[BPF_OBJ_NAME_LEN]; 71 | }; 72 | 73 | struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */ 74 | __u32 map_fd; 75 | __aligned_u64 key; 76 | union { 77 | __aligned_u64 value; 78 | __aligned_u64 next_key; 79 | }; 80 | __u64 flags; 81 | }; 82 | 83 | struct { /* anonymous struct used by BPF_PROG_LOAD command */ 84 | __u32 prog_type; /* one of enum bpf_prog_type */ 85 | __u32 insn_cnt; 86 | __aligned_u64 insns; 87 | __aligned_u64 license; 88 | __u32 log_level; /* verbosity level of verifier */ 89 | __u32 log_size; /* size of user buffer */ 90 | __aligned_u64 log_buf; /* user supplied buffer */ 91 | __u32 kern_version; /* checked when prog_type=kprobe */ 92 | __u32 prog_flags; 93 | char prog_name[BPF_OBJ_NAME_LEN]; 94 | __u32 prog_ifindex; /* ifindex of netdev to prep for */ 95 | }; 96 | 97 | struct { /* anonymous struct used by BPF_OBJ_* commands */ 98 | __aligned_u64 pathname; 99 | __u32 bpf_fd; 100 | __u32 file_flags; 101 | }; 102 | 103 | struct { /* anonymous struct used by BPF_PROG_ATTACH/DETACH commands */ 104 | __u32 target_fd; /* container object to attach to */ 105 | __u32 attach_bpf_fd; /* eBPF program to attach */ 106 | __u32 attach_type; 107 | __u32 attach_flags; 108 | }; 109 | 110 | struct { /* anonymous struct used by BPF_*_GET_*_ID */ 111 | union { 112 | __u32 start_id; 113 | __u32 prog_id; 114 | __u32 map_id; 115 | }; 116 | __u32 next_id; 117 | __u32 open_flags; 118 | }; 119 | 120 | struct { /* anonymous struct used by BPF_OBJ_GET_INFO_BY_FD */ 121 | __u32 bpf_fd; 122 | __u32 info_len; 123 | __aligned_u64 info; 124 | } info; 125 | } __attribute__((aligned(8))); 126 | 127 | struct bpf_prog_info { 128 | __u32 type; 129 | __u32 id; 130 | __u8 tag[BPF_TAG_SIZE]; 131 | __u32 jited_prog_len; 132 | __u32 xlated_prog_len; 133 | __aligned_u64 jited_prog_insns; 134 | __aligned_u64 xlated_prog_insns; 135 | __u64 load_time; // ns since boottime 136 | __u32 created_by_uid; 137 | __u32 nr_map_ids; 138 | __aligned_u64 map_ids; 139 | char name[BPF_OBJ_NAME_LEN]; 140 | __u32 ifindex; 141 | __u32 gpl_compatible:1; 142 | __u64 netns_dev; 143 | __u64 netns_ino; 144 | __u32 nr_jited_ksyms; 145 | __u32 nr_jited_func_lens; 146 | __aligned_u64 jited_ksyms; 147 | __aligned_u64 jited_func_lens; 148 | } __attribute__((aligned(8))); 149 | // clang-format on 150 | 151 | #endif /* _BPF_H__ */ 152 | -------------------------------------------------------------------------------- /perf_events_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef __linux__ 14 | #include 15 | #include 16 | #else 17 | // mocks for Mac 18 | #define PERF_SAMPLE_RAW 1U << 10 19 | #define PERF_TYPE_SOFTWARE 1 20 | #define PERF_COUNT_SW_BPF_OUTPUT 10 21 | #define PERF_EVENT_IOC_DISABLE 0 22 | #define PERF_EVENT_IOC_ENABLE 1 23 | #define __NR_perf_event_open 364 24 | struct perf_event_attr { 25 | int type, config, sample_type, wakeup_events; 26 | }; 27 | #endif 28 | 29 | // Opens perf event on given cpu_id and/or pid 30 | // Returns pmu_fd (processor monitoring unit fd) 31 | static int perf_event_open(int cpu_id, int pid, void *error_buf, size_t error_size) 32 | { 33 | struct perf_event_attr attr = { 34 | .sample_type = PERF_SAMPLE_RAW, 35 | .type = PERF_TYPE_SOFTWARE, 36 | .config = PERF_COUNT_SW_BPF_OUTPUT, 37 | .wakeup_events = 1, 38 | }; 39 | 40 | // Open perf events for given CPU 41 | #ifdef __linux 42 | int pmu_fd = syscall(__NR_perf_event_open, &attr, pid, cpu_id, -1, 0); 43 | if (pmu_fd <= 0) { 44 | strncpy(error_buf, strerror(errno), error_size); 45 | } 46 | return pmu_fd; 47 | #else 48 | return 0; 49 | #endif 50 | } 51 | 52 | // Enables perf events on pmu_fd create by perf_event_open() 53 | static int perf_event_enable(int pmu_fd, void *error_buf, size_t error_size) 54 | { 55 | int res = ioctl(pmu_fd, PERF_EVENT_IOC_ENABLE, 0); 56 | if (res < 0) { 57 | strncpy(error_buf, strerror(errno), error_size); 58 | } 59 | 60 | return res; 61 | } 62 | 63 | // Disables perf events on pmu_fd create by perf_event_open() 64 | static int perf_event_disable(int pmu_fd) 65 | { 66 | return ioctl(pmu_fd, PERF_EVENT_IOC_DISABLE, 0); 67 | } 68 | 69 | // Makes shared memory between kernel and user spaces (mmap) 70 | // returns pointer to shared memory 71 | static void *perf_event_mmap(int perf_map_fd, size_t size, void *error_buf, size_t error_size) 72 | { 73 | void *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, perf_map_fd, 0); 74 | if (buf == MAP_FAILED) { 75 | strncpy(error_buf, strerror(errno), error_size); 76 | return NULL; 77 | } 78 | 79 | return buf; 80 | } 81 | */ 82 | import "C" 83 | 84 | import ( 85 | "fmt" 86 | "unsafe" 87 | ) 88 | 89 | // perfEventHandler is responsible for open / close / configure system perf_events 90 | type perfEventHandler struct { 91 | pmuFd C.int 92 | shMem unsafe.Pointer 93 | shMemSize int 94 | 95 | ringBuffer *mmapRingBuffer 96 | } 97 | 98 | // newPerfEventHandler opens perf_event on given CPU / PID 99 | // it also mmap memory of bufferSize to new perf event fd. 100 | func newPerfEventHandler(cpu, pid int, bufferSize int) (*perfEventHandler, error) { 101 | var errorBuf [errCodeBufferSize]byte 102 | 103 | res := &perfEventHandler{ 104 | shMemSize: calculateMmapSize(bufferSize), 105 | } 106 | 107 | // Create perf event fd 108 | res.pmuFd = C.perf_event_open( 109 | C.int(cpu), 110 | C.int(pid), 111 | unsafe.Pointer(&errorBuf[0]), C.size_t(unsafe.Sizeof(errorBuf)), 112 | ) 113 | if res.pmuFd <= 0 { 114 | return nil, fmt.Errorf("Unable to perf_event_open(): %v", 115 | NullTerminatedStringToString(errorBuf[:])) 116 | } 117 | 118 | // Create shared memory between kernel and userspace (mmap) 119 | res.shMem = C.perf_event_mmap( 120 | res.pmuFd, 121 | C.size_t(res.shMemSize), 122 | unsafe.Pointer(&errorBuf[0]), C.size_t(unsafe.Sizeof(errorBuf)), 123 | ) 124 | if res.shMem == nil { 125 | C.close(res.pmuFd) 126 | res.pmuFd = 0 127 | return nil, fmt.Errorf("Unable to mmap(): %v", 128 | NullTerminatedStringToString(errorBuf[:])) 129 | } 130 | res.ringBuffer = NewMmapRingBuffer(res.shMem) 131 | 132 | return res, nil 133 | } 134 | 135 | // Enable enables perf events on this fd 136 | func (pe *perfEventHandler) Enable() error { 137 | var errorBuf [errCodeBufferSize]byte 138 | 139 | res := C.perf_event_enable( 140 | pe.pmuFd, 141 | unsafe.Pointer(&errorBuf[0]), C.size_t(unsafe.Sizeof(errorBuf)), // error message 142 | ) 143 | if res < 0 { 144 | return fmt.Errorf("Unable to perf_event_enable(): %v", 145 | NullTerminatedStringToString(errorBuf[:])) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | // Disable disables perf events on this fd 152 | func (pe *perfEventHandler) Disable() { 153 | if pe.pmuFd > 0 { 154 | C.perf_event_disable(pe.pmuFd) 155 | pe.pmuFd = 0 156 | } 157 | } 158 | 159 | // Release releases allocated resources: 160 | // - close perf_event fd 161 | // - unmap shared memory 162 | func (pe *perfEventHandler) Release() { 163 | pe.Disable() 164 | 165 | if pe.shMem != nil { 166 | C.munmap(pe.shMem, C.size_t(pe.shMemSize)) 167 | pe.shMem = nil 168 | } 169 | 170 | if pe.pmuFd > 0 { 171 | C.close(pe.pmuFd) 172 | pe.pmuFd = 0 173 | } 174 | } 175 | 176 | // Helper to calculate aligned memory size for mmap. 177 | // First memory page is reserved for mmap metadata, 178 | // so allocating +1 page. 179 | func calculateMmapSize(size int) int { 180 | pageSize := int(C.getpagesize()) 181 | pageCnt := size / pageSize 182 | 183 | // Extra page for mmap metadata header 184 | return (pageCnt + 2) * pageSize 185 | } 186 | -------------------------------------------------------------------------------- /program_base.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "bpf.h" 13 | #include "bpf_helpers.h" 14 | 15 | // Load eBPF program into kernel 16 | static int ebpf_prog_load(const char *name, __u32 prog_type, const void *insns, __u32 insns_cnt, 17 | const char *license, __u32 kern_version, void *log_buf, size_t log_size) 18 | { 19 | union bpf_attr attr = {}; 20 | 21 | // Try to load program without trace info - it takes too much memory 22 | // for verifier to put all trace messages even for correct programs 23 | // and may cause load error because of log buffer is too small. 24 | attr.prog_type = prog_type; 25 | attr.insn_cnt = insns_cnt; 26 | attr.insns = ptr_to_u64(insns); 27 | attr.license = ptr_to_u64(license); 28 | attr.log_buf = ptr_to_u64(NULL); 29 | attr.log_size = 0; 30 | attr.log_level = 0; 31 | attr.kern_version = kern_version; 32 | // program name 33 | strncpy((char*)&attr.prog_name, name, BPF_OBJ_NAME_LEN - 1); 34 | 35 | #ifdef __linux__ 36 | int res = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); 37 | if (res == -1) { 38 | // Try again with log 39 | attr.log_buf = ptr_to_u64(log_buf); 40 | attr.log_size = log_size; 41 | attr.log_level = 1; 42 | res = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); 43 | } 44 | return res; 45 | #else 46 | return 0; 47 | #endif 48 | } 49 | 50 | */ 51 | import "C" 52 | import ( 53 | "errors" 54 | "fmt" 55 | "unsafe" 56 | ) 57 | 58 | // ProgramType is eBPF program types enum 59 | type ProgramType int 60 | 61 | // Must be in sync with enum bpf_prog_type from 62 | const ( 63 | ProgramTypeUnspec ProgramType = iota 64 | ProgramTypeSocketFilter 65 | ProgramTypeKprobe 66 | ProgramTypeSchedCls 67 | ProgramTypeSchedAct 68 | ProgramTypeTracepoint 69 | ProgramTypeXdp 70 | ProgramTypePerfEvent 71 | ProgramTypeCgroupSkb 72 | ProgramTypeCgroupSock 73 | ProgramTypeLwtIn 74 | ProgramTypeLwtOut 75 | ProgramTypeLwtXmit 76 | ProgramTypeSockOps 77 | ) 78 | 79 | func (t ProgramType) String() string { 80 | switch t { 81 | case ProgramTypeSocketFilter: 82 | return "SocketFilter" 83 | case ProgramTypeKprobe: 84 | return "Kprobe" 85 | case ProgramTypeSchedCls: 86 | return "SchedCLS" 87 | case ProgramTypeSchedAct: 88 | return "SchedACT" 89 | case ProgramTypeTracepoint: 90 | return "Tracepoint" 91 | case ProgramTypeXdp: 92 | return "XDP" 93 | case ProgramTypePerfEvent: 94 | return "PerfEvent" 95 | case ProgramTypeCgroupSkb: 96 | return "CgroupSkb" 97 | case ProgramTypeCgroupSock: 98 | return "CgroupSock" 99 | case ProgramTypeLwtIn: 100 | return "LWTin" 101 | case ProgramTypeLwtOut: 102 | return "LWTout" 103 | case ProgramTypeLwtXmit: 104 | return "LWTxmit" 105 | case ProgramTypeSockOps: 106 | return "SockOps" 107 | } 108 | 109 | return "Unknown" 110 | } 111 | 112 | // BaseProgram is common shared fields of eBPF programs 113 | type BaseProgram struct { 114 | fd int // File Descriptor 115 | name string 116 | section string 117 | programType ProgramType 118 | license string // License 119 | bytecode []byte // eBPF instructions (each instruction - 8 bytes) 120 | kernelVersion int // Kernel requires version to match running for "kprobe" programs 121 | } 122 | 123 | // Load loads program into linux kernel 124 | func (prog *BaseProgram) Load() error { 125 | 126 | // sanity check program name length 127 | if len(prog.name) >= C.BPF_OBJ_NAME_LEN { 128 | return fmt.Errorf("Program name is too long. (max %d)", C.BPF_OBJ_NAME_LEN) 129 | } 130 | 131 | // Buffer for kernel's verified debug messages 132 | var logBuf [logBufferSize]byte 133 | name := C.CString(prog.name) 134 | defer C.free(unsafe.Pointer(name)) 135 | license := C.CString(prog.license) 136 | defer C.free(unsafe.Pointer(license)) 137 | 138 | // Load eBPF program 139 | res := int(C.ebpf_prog_load( 140 | name, 141 | C.__u32(prog.GetType()), 142 | unsafe.Pointer(&prog.bytecode[0]), 143 | C.__u32(prog.GetSize())/bpfInstructionLen, 144 | license, 145 | C.__u32(prog.kernelVersion), 146 | unsafe.Pointer(&logBuf[0]), 147 | C.size_t(unsafe.Sizeof(logBuf)))) 148 | 149 | if res == -1 { 150 | return fmt.Errorf("ebpf_prog_load() failed: %s", 151 | NullTerminatedStringToString(logBuf[:])) 152 | } 153 | prog.fd = res 154 | 155 | return nil 156 | } 157 | 158 | // Close unloads program from kernel 159 | func (prog *BaseProgram) Close() error { 160 | if prog.fd == 0 { 161 | return errors.New("Already closed / not created") 162 | } 163 | err := closeFd(prog.fd) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | prog.fd = 0 169 | return nil 170 | } 171 | 172 | // Pin saves ("pins") file description into special file on filesystem 173 | // so it can be accessed from other processes. 174 | // WARNING: destination filesystem must be mounted as "bpf" (mount -t bpf) 175 | func (prog *BaseProgram) Pin(path string) error { 176 | return ebpfObjPin(prog.fd, path) 177 | } 178 | 179 | // GetName returns program name as defined in C code 180 | func (prog *BaseProgram) GetName() string { 181 | return prog.name 182 | } 183 | 184 | // GetSection returns section name for the program 185 | func (prog *BaseProgram) GetSection() string { 186 | return prog.section 187 | } 188 | 189 | // GetType returns program type 190 | func (prog *BaseProgram) GetType() ProgramType { 191 | return prog.programType 192 | } 193 | 194 | // GetFd returns program's file description 195 | func (prog *BaseProgram) GetFd() int { 196 | return prog.fd 197 | } 198 | 199 | // GetSize returns eBPF bytecode size in bytes 200 | func (prog *BaseProgram) GetSize() int { 201 | return len(prog.bytecode) 202 | } 203 | 204 | // GetLicense returns program's license 205 | func (prog *BaseProgram) GetLicense() string { 206 | return prog.license 207 | } 208 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | /* 5 | Package goebpf provides simple and convenient interface to Linux eBPF system. 6 | 7 | # Overview 8 | 9 | Extended Berkeley Packet Filter (eBPF) is a highly flexible and efficient virtual machine 10 | in the Linux kernel allowing to execute bytecode at various hook points in a safe manner. 11 | It is actually close to kernel modules which can provide the same functionality, 12 | but without cost of kernel panic if something went wrong. 13 | 14 | The library is intended to simplify work with eBPF programs. 15 | It takes care of low level routine implementation to make it easy to load/run/manage eBPF programs. 16 | Currently supported functionality: 17 | - Read / parse clang/llmv compiled binaries for eBPF programs / maps 18 | - Creates / loads eBPF program / eBPF maps into kernel 19 | - Provides simple interface to interact with eBPF maps 20 | - Has mock versions of eBPF objects (program, map, etc) in order to make writing unittests simple. 21 | 22 | # XDP 23 | 24 | eXpress Data Path - provides a bare metal, high performance, programmable packet processing 25 | at the closest at possible point to network driver. That makes it ideal for speed without 26 | compromising programmability. Key benefits includes following: 27 | 28 | - It does not require any specialized hardware (program works in kernel’s "VM") 29 | - It does not require kernel bypass 30 | - It does not replace the TCP/IP stack 31 | 32 | Considering very simple and highly effective way to DROP all packets from given source IPv4 address: 33 | 34 | XDP program (written in C): 35 | 36 | // Simple map to count dropped packets 37 | BPF_MAP_DEF(drops) = { 38 | .map_type = BPF_MAP_TYPE_PERCPU_ARRAY, 39 | .key_size = 4, 40 | .value_size = 8, 41 | .max_entries = 1, 42 | }; 43 | BPF_MAP_ADD(drops); 44 | 45 | SEC("xdp") 46 | int xdp_drop(struct xdp_md *ctx) 47 | { 48 | if (found) { // If some condition (e.g. SRC IP) matches... 49 | __u32 idx = 0; 50 | // Increase stat by 1 51 | __u64 *stat = bpf_map_lookup_elem(&drops, &idx); 52 | if (stat) { 53 | *stat += 1; 54 | } 55 | return XDP_DROP; 56 | } 57 | return XDP_PASS; 58 | } 59 | 60 | Once compiled can be used by goebpf in the following way: 61 | 62 | bpf := goebpf.NewDefaultEbpfSystem() 63 | err := bpf.LoadElf("xdp.elf") 64 | program := bpf.GetProgramByName("xdp_drop") // name matches function name in C 65 | err = program.Load() // Load program into kernel 66 | err = program.Attach("eth0") // Attach to interface 67 | defer program.Detach() 68 | 69 | // Interact with program is simply done through maps: 70 | drops := bpf.GetMapByName("drops") // name also matches BPF_MAP_ADD(drops) 71 | val, err := drops.LookupInt(0) // Get value from map at index 0 72 | if err == nil { 73 | fmt.Printf("Drops: %d\n", val) 74 | } 75 | 76 | # PerfEvents 77 | 78 | Perf Events (originally Performance Counters for Linux) is powerful kernel instrument for 79 | tracing, profiling and a lot of other cases like general events to user space. 80 | 81 | Usually it is implemented using special eBPF map type "BPF_MAP_TYPE_PERF_EVENT_ARRAY" as 82 | a container to send events into. 83 | 84 | A simple example could be to log all TCP SYN packets into user space from XDP program: 85 | 86 | BPF_MAP_DEF(perfmap) = { 87 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 88 | .max_entries = 128, // Up to 128 CPUs 89 | }; 90 | BPF_MAP_ADD(perfmap); 91 | 92 | SEC("xdp") 93 | int xdp_dump(struct xdp_md *ctx) { 94 | // ... 95 | if (tcp->syn) { 96 | // Log event to user space 97 | bpf_perf_event_output(ctx, &perfmap, BPF_F_CURRENT_CPU, &evt, sizeof(evt)); 98 | } 99 | } 100 | 101 | 102 | bpf := goebpf.NewDefaultEbpfSystem() 103 | bpf.LoadElf("xdp.elf") 104 | program := bpf.GetProgramByName("xdp_dump") // name matches function name in C 105 | err = program.Load() // Load program into kernel 106 | err = program.Attach("eth0") // Attach to interface 107 | defer program.Detach() 108 | 109 | // Start listening to Perf Events 110 | perf, err := goebpf.NewPerfEvents(perfmap) 111 | // 4096 is ring buffer size 112 | perfEvents, err := perf.StartForAllProcessesAndCPUs(4096) 113 | defer perf.Stop() 114 | 115 | for { 116 | select { 117 | case eventData := <-perfEvents: 118 | fmt.Println(eventData) 119 | } 120 | } 121 | 122 | # Kprobes 123 | 124 | There are currently two types of supported probes: kprobes, and kretprobes 125 | (also called return probes). A kprobe can be inserted on virtually 126 | any instruction in the kernel. A return probe fires when a specified 127 | function returns. 128 | 129 | For example, you can trigger eBPF code to run when a kernel function starts 130 | by attaching the program to a “kprobe” event. Because it runs in the kernel, 131 | eBPF code is extremely high performance. 132 | 133 | A simple example could be to log all process execution events into user space 134 | from Kprobe program: 135 | 136 | BPF_MAP_DEF(events) = { 137 | .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 138 | .max_entries = 1024, 139 | }; 140 | BPF_MAP_ADD(events); 141 | 142 | SEC("kprobe/guess_execve") 143 | int execve_entry(struct pt_regs *ctx) { 144 | // ... 145 | event_t e = {0}; 146 | e.ktime_ns = bpf_ktime_get_ns(); 147 | e.pid = bpf_get_current_pid_tgid() >> 32; 148 | e.uid = bpf_get_current_uid_gid() >> 32; 149 | e.gid = bpf_get_current_uid_gid(); 150 | bpf_get_current_comm(&e.comm, sizeof(e.comm)); 151 | 152 | buf_write(buf, (void *)&e, sizeof(e)); 153 | buf_strcat(buf, (void *)args[0]); 154 | buf_strcat_argv(buf, (void *)args[1]); 155 | buf_perf_output(ctx); 156 | 157 | return 0; 158 | } 159 | 160 | 161 | // Cleanup old probes 162 | err := goebpf.CleanupProbes() 163 | 164 | // Load eBPF compiled binary 165 | bpf := goebpf.NewDefaultEbpfSystem() 166 | bpf.LoadElf("kprobe.elf") 167 | program := bpf.GetProgramByName("kprobe") // name matches function name in C 168 | 169 | // Attach kprobes 170 | err = p.AttachProbes() 171 | // Detach them once done 172 | defer p.DetachProbes() 173 | */ 174 | package goebpf 175 | -------------------------------------------------------------------------------- /perf_events.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #ifdef __linux__ 8 | #include 9 | #include 10 | #define PERF_EVENT_HEADER_SIZE (sizeof(struct perf_event_header)) 11 | #else 12 | // mocks for Mac 13 | #define PERF_EVENT_HEADER_SIZE 8 14 | #define PERF_RECORD_SAMPLE 9 15 | #define PERF_RECORD_LOST 2 16 | 17 | int get_nprocs() 18 | { 19 | return 1; 20 | } 21 | #endif 22 | 23 | */ 24 | import "C" 25 | 26 | import ( 27 | "bytes" 28 | "encoding/binary" 29 | "fmt" 30 | "sync" 31 | ) 32 | 33 | // PerfEvents is a way to interact with Linux's PerfEvents for eBPF cases. 34 | type PerfEvents struct { 35 | // Statistics 36 | EventsReceived int 37 | EventsLost int 38 | EventsUnknowType int 39 | 40 | // PollTimeoutMs is timeout for blocking call of poll() 41 | // Defaults to 100ms 42 | PollTimeoutMs int 43 | poller *perfEventPoller 44 | 45 | perfMap Map 46 | updatesChannel chan []byte 47 | stopChannel chan struct{} 48 | wg sync.WaitGroup 49 | 50 | handlers []*perfEventHandler 51 | } 52 | 53 | // Go definition for C structs from 54 | // http://man7.org/linux/man-pages/man2/perf_event_open.2.html 55 | // 56 | // struct perf_event_header { 57 | // __u32 type; 58 | // __u16 misc; 59 | // __u16 size; 60 | // } 61 | type perfEventHeader struct { 62 | Type uint32 63 | Misc uint16 64 | Size uint16 65 | } 66 | 67 | // struct perf_event_lost { 68 | // uint64_t id; 69 | // uint64_t lost; 70 | // 71 | // not added: struct sample_id sample_id; 72 | // } 73 | type perfEventLost struct { 74 | Id uint64 75 | Lost uint64 76 | } 77 | 78 | // NewPerfEvents creates new instance of PerfEvents for eBPF map "m". 79 | // "m" must be a type of "MapTypePerfEventArray" 80 | func NewPerfEvents(m Map) (*PerfEvents, error) { 81 | if m.GetType() != MapTypePerfEventArray { 82 | return nil, fmt.Errorf("Invalid map type '%v'", m.GetType()) 83 | } 84 | 85 | return &PerfEvents{ 86 | perfMap: m, 87 | PollTimeoutMs: 100, 88 | }, nil 89 | } 90 | 91 | // StartForAllProcessesAndCPUs starts PerfEvent polling on all CPUs for all system processes 92 | // This mode requires specially organized map: index matches CPU ID. 93 | // "bufferSize" is ring buffer size for perfEvents. Per CPU. 94 | // All updates will be sent into returned channel. 95 | func (pe *PerfEvents) StartForAllProcessesAndCPUs(bufferSize int) (<-chan []byte, error) { 96 | // Get ONLINE CPU count. 97 | // There maybe confusion between get_nprocs() and GetNumOfPossibleCpus() functions: 98 | // - get_nprocs() returns ONLINE CPUs 99 | // - GetNumOfPossibleCpus() returns POSSIBLE (including currently offline) CPUs 100 | // So space for eBPF maps should be reserved for ALL possible CPUs, 101 | // but perfEvents may work only on online CPUs 102 | nCpus := int(C.get_nprocs()) 103 | 104 | // Create perfEvent handler for all possible CPUs 105 | var err error 106 | var handler *perfEventHandler 107 | pe.handlers = make([]*perfEventHandler, nCpus) 108 | for cpu := 0; cpu < nCpus; cpu++ { 109 | handler, err = newPerfEventHandler(cpu, -1, bufferSize) // All processes 110 | if err != nil { 111 | // Error handling to be done after for loop 112 | break 113 | } 114 | err = pe.perfMap.Update(cpu, int(handler.pmuFd)) 115 | if err != nil { 116 | // Error handling to be done after for loop 117 | break 118 | } 119 | handler.Enable() 120 | pe.handlers[cpu] = handler 121 | } 122 | // Handle loop errors: release allocated resources / return error 123 | if err != nil { 124 | for _, handler := range pe.handlers { 125 | if handler != nil { 126 | handler.Release() 127 | } 128 | } 129 | return nil, err 130 | } 131 | 132 | pe.startLoop() 133 | return pe.updatesChannel, nil 134 | } 135 | 136 | // Stop stops event polling loop 137 | func (pe *PerfEvents) Stop() { 138 | // Stop poller firstly 139 | pe.poller.Stop() 140 | // Stop poll loop 141 | close(pe.stopChannel) 142 | // Wait until poll loop stopped, then close updates channel 143 | pe.wg.Wait() 144 | close(pe.updatesChannel) 145 | 146 | // Release resources 147 | for _, handler := range pe.handlers { 148 | handler.Release() 149 | } 150 | } 151 | 152 | func (pe *PerfEvents) startLoop() { 153 | pe.stopChannel = make(chan struct{}) 154 | pe.updatesChannel = make(chan []byte) 155 | pe.wg.Add(1) 156 | 157 | go pe.loop() 158 | } 159 | 160 | func (pe *PerfEvents) loop() { 161 | // Setup poller to poll all handlers (one handler per CPU) 162 | pe.poller = newPerfEventPoller() 163 | for _, handler := range pe.handlers { 164 | pe.poller.Add(handler) 165 | } 166 | 167 | // Start poller 168 | pollerCh := pe.poller.Start(pe.PollTimeoutMs) 169 | defer func() { 170 | pe.wg.Done() 171 | }() 172 | 173 | // Wait until at least one perf event fd becomes readable (has new data) 174 | for { 175 | select { 176 | case handler, ok := <-pollerCh: 177 | if !ok { 178 | return 179 | } 180 | 181 | pe.handlePerfEvent(handler) 182 | 183 | case <-pe.stopChannel: 184 | return 185 | } 186 | } 187 | } 188 | 189 | func (pe *PerfEvents) handlePerfEvent(handler *perfEventHandler) { 190 | // Process all new samples at once 191 | for handler.ringBuffer.DataAvailable() { 192 | // Read perfEvent header 193 | var header perfEventHeader 194 | reader := bytes.NewReader( 195 | handler.ringBuffer.Read(C.PERF_EVENT_HEADER_SIZE), 196 | ) 197 | binary.Read(reader, binary.LittleEndian, &header) 198 | 199 | // Read PerfEvent data (header.Size is total size of event: header + data) 200 | data := handler.ringBuffer.Read( 201 | int(header.Size - C.PERF_EVENT_HEADER_SIZE), 202 | ) 203 | 204 | // Process event 205 | switch header.Type { 206 | case C.PERF_RECORD_SAMPLE: 207 | // Sample defined as: 208 | // struct perf_event_sample { 209 | // struct perf_event_header header; 210 | // uint32_t data_size; 211 | // char data[]; 212 | // }; 213 | // We've already parsed header, so parse only data_size 214 | dataSize := binary.LittleEndian.Uint32(data) 215 | // Send data into channel 216 | pe.updatesChannel <- data[4 : dataSize+4] 217 | pe.EventsReceived++ 218 | 219 | case C.PERF_RECORD_LOST: 220 | // This is special record type - contains how many record (events) 221 | // lost due to small buffer or slow event processing. 222 | var lost perfEventLost 223 | reader := bytes.NewReader(data) 224 | binary.Read(reader, binary.LittleEndian, &lost) 225 | pe.EventsLost += int(lost.Lost) 226 | 227 | default: 228 | pe.EventsUnknowType++ 229 | } 230 | } 231 | 232 | // This is ring buffer: move tail forward to indicate 233 | // that we've processed some data 234 | handler.ringBuffer.UpdateTail() 235 | } 236 | -------------------------------------------------------------------------------- /itest/xdp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package itest 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | "time" 10 | 11 | "github.com/dropbox/goebpf" 12 | "github.com/stretchr/testify/suite" 13 | ) 14 | 15 | const ( 16 | xdpProgramFilename = "xdp1.elf" 17 | ) 18 | 19 | type xdpTestSuite struct { 20 | suite.Suite 21 | programFilename string 22 | programsCount int 23 | mapsCount int 24 | } 25 | 26 | // Basic sanity test of BPF core functionality like 27 | // ReadElf, create maps, load / attach programs 28 | func (ts *xdpTestSuite) TestElfLoad() { 29 | // This compile ELF file contains 2 BPF(XDP type) programs with 2 BPF maps 30 | eb := goebpf.NewDefaultEbpfSystem() 31 | err := eb.LoadElf(ts.programFilename) 32 | ts.NoError(err) 33 | if err != nil { 34 | // ELF read error. 35 | ts.FailNowf("Unable to read %s", ts.programFilename) 36 | } 37 | 38 | // There should be 6 BPF maps recognized by loader 39 | maps := eb.GetMaps() 40 | ts.Require().Equal(ts.mapsCount, len(maps)) 41 | 42 | txcnt := maps["txcnt"].(*goebpf.EbpfMap) 43 | ts.NotEqual(0, txcnt.GetFd()) 44 | ts.Equal(goebpf.MapTypePerCPUArray, txcnt.Type) 45 | ts.Equal(4, txcnt.KeySize) 46 | ts.Equal(8, txcnt.ValueSize) 47 | ts.Equal(100, txcnt.MaxEntries) 48 | ts.Equal("/sys/fs/bpf/txcnt", txcnt.PersistentPath) 49 | 50 | rxcnt := maps["rxcnt"].(*goebpf.EbpfMap) 51 | ts.NotEqual(0, rxcnt.GetFd()) 52 | ts.Equal(goebpf.MapTypeHash, rxcnt.Type) 53 | ts.Equal(8, rxcnt.KeySize) 54 | ts.Equal(4, rxcnt.ValueSize) 55 | ts.Equal(50, rxcnt.MaxEntries) 56 | 57 | moftxcnt := maps["match_maps_tx"].(*goebpf.EbpfMap) 58 | ts.NotEqual(0, moftxcnt.GetFd()) 59 | ts.Equal(goebpf.MapTypeArrayOfMaps, moftxcnt.Type) 60 | ts.Equal(4, moftxcnt.KeySize) 61 | ts.Equal(4, moftxcnt.ValueSize) 62 | ts.Equal(10, moftxcnt.MaxEntries) 63 | 64 | mofrxcnt := maps["match_maps_rx"].(*goebpf.EbpfMap) 65 | ts.NotEqual(0, mofrxcnt.GetFd()) 66 | ts.Equal(goebpf.MapTypeHashOfMaps, mofrxcnt.Type) 67 | ts.Equal(4, mofrxcnt.KeySize) 68 | ts.Equal(4, mofrxcnt.ValueSize) 69 | ts.Equal(20, mofrxcnt.MaxEntries) 70 | ts.Equal("/sys/fs/bpf/match_maps_rx", mofrxcnt.PersistentPath) 71 | 72 | progmap := eb.GetMapByName("programs").(*goebpf.EbpfMap) 73 | ts.Require().NotNil(progmap) 74 | ts.NotEqual(0, progmap.GetFd()) 75 | ts.Equal(goebpf.MapTypeProgArray, progmap.Type) 76 | ts.Equal(4, progmap.KeySize) 77 | ts.Equal(4, progmap.ValueSize) 78 | ts.Equal(2, progmap.MaxEntries) 79 | 80 | // Non existing map 81 | ts.Nil(eb.GetMapByName("something")) 82 | 83 | // Also there should few XDP eBPF programs recognized 84 | ts.Require().Equal(ts.programsCount, len(eb.GetPrograms())) 85 | 86 | // Check that everything loaded correctly / load program into kernel 87 | for name, program := range eb.GetPrograms() { 88 | // Check params 89 | ts.Equal(goebpf.ProgramTypeXdp, program.GetType()) 90 | ts.Equal(name, program.GetName()) 91 | ts.Equal("GPL", program.GetLicense()) 92 | // Load into kernel 93 | err = program.Load() 94 | ts.Require().NoError(err) 95 | ts.Require().NotEqual(0, program.GetFd()) 96 | } 97 | 98 | // Try to pin program into some filesystem 99 | xdp0 := eb.GetProgramByName("xdp0") 100 | path := bpfPath + "/xdp_pin_test" 101 | err = xdp0.Pin(path) 102 | ts.NoError(err) 103 | ts.FileExists(path) 104 | os.Remove(path) 105 | 106 | // Non existing program 107 | ts.Nil(eb.GetProgramByName("something")) 108 | 109 | // Additional test for special map type - PROGS_ARRAY 110 | // To be sure that we can insert prog_fd into map 111 | xdp1 := eb.GetProgramByName("xdp1") 112 | err = progmap.Update(0, xdp0.GetFd()) 113 | ts.NoError(err) 114 | err = progmap.Update(1, xdp1.GetFd()) 115 | ts.NoError(err) 116 | // And delete from it 117 | err = progmap.Delete(0) 118 | ts.NoError(err) 119 | err = progmap.Delete(1) 120 | ts.NoError(err) 121 | 122 | // Attach program to first (lo) interface 123 | // P.S. XDP does not work on "lo" interface, however, you can still attach program to it 124 | // which is enough to test basic BPF functionality 125 | err = xdp0.Attach("lo") 126 | ts.Require().NoError(err) 127 | err = xdp0.Detach() 128 | ts.NoError(err) 129 | 130 | // Attach with parameters 131 | err = xdp0.Attach(&goebpf.XdpAttachParams{ 132 | Interface: "lo", 133 | Mode: goebpf.XdpAttachModeSkb, 134 | }) 135 | ts.Require().NoError(err) 136 | err = xdp0.Detach() 137 | ts.NoError(err) 138 | 139 | // "lo" interface does not support XDP natively, so should fail 140 | err = xdp0.Attach(&goebpf.XdpAttachParams{ 141 | Interface: "lo", 142 | Mode: goebpf.XdpAttachModeDrv, 143 | }) 144 | ts.Require().Error(err) 145 | 146 | // Unload programs (not required for real use case) 147 | for _, program := range eb.GetPrograms() { 148 | err = program.Close() 149 | ts.NoError(err) 150 | } 151 | 152 | // Negative: close already closed program 153 | err = xdp0.Close() 154 | ts.Error(err) 155 | 156 | // Negative: attach to non existing interface 157 | err = xdp0.Attach("dummyiface") 158 | ts.Error(err) 159 | } 160 | 161 | func (ts *xdpTestSuite) TestProgramInfo() { 162 | // Load test program, don't attach (not required to get info) 163 | eb := goebpf.NewDefaultEbpfSystem() 164 | err := eb.LoadElf(ts.programFilename) 165 | ts.Require().NoError(err) 166 | prog := eb.GetProgramByName("xdp0") 167 | err = prog.Load() 168 | ts.Require().NoError(err) 169 | 170 | // Get program info by FD (NOT ID, since this program is ours) 171 | info, err := goebpf.GetProgramInfoByFd(prog.GetFd()) 172 | ts.NoError(err) 173 | 174 | // Check base info 175 | ts.Equal(prog.GetName(), info.Name) 176 | ts.Equal(prog.GetFd(), info.Fd) 177 | ts.Equal(goebpf.ProgramTypeXdp, info.Type) 178 | ts.True(info.JitedProgramLen > 50) 179 | ts.True(info.XlatedProgramLen > 60) 180 | // Check loaded time 181 | now := time.Now() 182 | ts.True(now.Sub(info.LoadTime) < time.Second*10) 183 | 184 | // Check maps 185 | // xdp_prog1 uses only one map - array_map 186 | origMap := eb.GetMapByName("array_map").(*goebpf.EbpfMap) 187 | infoMap := info.Maps["array_map"].(*goebpf.EbpfMap) 188 | err = infoMap.Create() 189 | ts.NoError(err) 190 | // Check major fields (cannot compare one to one since at least fd different) 191 | ts.Equal(origMap.Name, infoMap.Name) 192 | ts.Equal(origMap.Type, infoMap.Type) 193 | ts.Equal(origMap.KeySize, infoMap.KeySize) 194 | ts.Equal(origMap.ValueSize, infoMap.ValueSize) 195 | ts.Equal(origMap.MaxEntries, infoMap.MaxEntries) 196 | // Ensure that infoMap mirrors origMap 197 | err = origMap.Update(0, 123) 198 | ts.NoError(err) 199 | val, err := infoMap.LookupInt(0) 200 | ts.NoError(err) 201 | ts.Equal(123, val) 202 | } 203 | 204 | // Run suite 205 | func TestXdpSuite(t *testing.T) { 206 | suite.Run(t, &xdpTestSuite{ 207 | programFilename: progPath(xdpProgramFilename), 208 | programsCount: 5, 209 | mapsCount: 7, 210 | }) 211 | } 212 | -------------------------------------------------------------------------------- /goebpf_mock/mock_map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf_mock 5 | 6 | import ( 7 | "testing" 8 | "unsafe" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/dropbox/goebpf" 14 | mock_wrapper "github.com/dropbox/goebpf/goebpf_mock/wrapper" 15 | ) 16 | 17 | // cgo-test does not support import C code 18 | // workaround is move C stuff into separated package and include it 19 | // only from tests 20 | var _ = mock_wrapper.Dummy 21 | 22 | func TestMockMapCloneTemplate(t *testing.T) { 23 | var dummy int 24 | m := &MockMap{ 25 | fd: unsafe.Pointer(&dummy), 26 | Name: "mockmap1", 27 | Type: goebpf.MapTypeHash, 28 | KeySize: 4, 29 | ValueSize: 4, 30 | MaxEntries: 100, 31 | } 32 | 33 | cloned := m.CloneTemplate() 34 | // Ensure that fd hasn't copied 35 | assert.Equal(t, 0, cloned.GetFd()) 36 | // Compare the rest 37 | cloned.(*MockMap).fd = unsafe.Pointer(&dummy) 38 | assert.Equal(t, m, cloned) 39 | } 40 | 41 | func TestMockMapCreateRuntime(t *testing.T) { 42 | // Dynamically create mockMap 43 | m := &MockMap{ 44 | Name: "runtime1", 45 | Type: goebpf.MapTypeArray, 46 | KeySize: 4, 47 | ValueSize: 4, 48 | MaxEntries: 100, 49 | } 50 | err := m.Create() 51 | require.NoError(t, err) 52 | 53 | // Lookup non inserted/updated item 54 | value, err := m.LookupInt(4) 55 | assert.NoError(t, err) 56 | assert.Equal(t, 0, value) 57 | // Update non existing item 58 | err = m.Update(5, 100) 59 | assert.NoError(t, err) 60 | // Check it 61 | value, err = m.LookupInt(5) 62 | assert.NoError(t, err) 63 | assert.Equal(t, 100, value) 64 | 65 | // Re-create map 66 | err = m.Destroy() 67 | assert.NoError(t, err) 68 | err = m.Create() 69 | assert.NoError(t, err) 70 | // Ensure that 5th value defaults to zero instead of 100 71 | value, err = m.LookupInt(5) 72 | assert.NoError(t, err) 73 | assert.Equal(t, 0, value) 74 | } 75 | 76 | func TestMockMapArray(t *testing.T) { 77 | // Destroy / Create 78 | m := MockMaps["map_array"].(*MockMap) 79 | err := m.Destroy() 80 | require.NoError(t, err) 81 | err = m.Create() 82 | require.NoError(t, err) 83 | 84 | // Deleting items from array doesn't make sense - they are fixed size 85 | // Should not raise error 86 | err = m.Delete(4) 87 | assert.NoError(t, err) 88 | 89 | // Lookup non existing element - default value expected 90 | value, err := m.LookupInt(4) 91 | assert.NoError(t, err) 92 | assert.Equal(t, 0, value) 93 | 94 | // Update 95 | err = m.Update(4, 100) 96 | assert.NoError(t, err) 97 | 98 | // Lookup 99 | value, err = m.LookupInt(4) 100 | assert.NoError(t, err) 101 | assert.Equal(t, 100, value) 102 | 103 | // Delete 104 | err = m.Delete(4) 105 | assert.NoError(t, err) 106 | 107 | // For BPF arrays - all items are present anytime 108 | // if item wasn't "inserted" before - expect default value - zero 109 | value, err = m.LookupInt(4) 110 | assert.NoError(t, err) 111 | assert.Equal(t, 0, value) 112 | } 113 | 114 | func TestMockMapHash(t *testing.T) { 115 | // Destroy / Create 116 | m := MockMaps["map_hash"].(*MockMap) 117 | err := m.Destroy() 118 | require.NoError(t, err) 119 | err = m.Create() 120 | require.NoError(t, err) 121 | 122 | // Delete non existing item - prohibited 123 | err = m.Delete(11) 124 | assert.Error(t, err) 125 | 126 | // Update non existing item is the same - error 127 | err = m.Update(11, 100) 128 | assert.Error(t, err) 129 | 130 | err = m.Insert(11, 100) 131 | assert.NoError(t, err) 132 | 133 | // Lookup / update / lookup (on existing item) 134 | value, err := m.LookupInt(11) 135 | assert.NoError(t, err) 136 | assert.Equal(t, 100, value) 137 | 138 | err = m.Update(11, 200) 139 | assert.NoError(t, err) 140 | 141 | value, err = m.LookupInt(11) 142 | assert.NoError(t, err) 143 | assert.Equal(t, 200, value) 144 | 145 | //upsert non existing item 146 | err = m.Upsert(12, 101) 147 | assert.NoError(t, err) 148 | 149 | value, err = m.LookupInt(12) 150 | assert.NoError(t, err) 151 | assert.Equal(t, 101, value) 152 | 153 | //upsert existing item 154 | err = m.Upsert(12, 102) 155 | assert.NoError(t, err) 156 | 157 | value, err = m.LookupInt(12) 158 | assert.NoError(t, err) 159 | assert.Equal(t, 102, value) 160 | 161 | // Delete 162 | err = m.Delete(11) 163 | assert.NoError(t, err) 164 | 165 | // Delete (now non existing item) 166 | err = m.Delete(11) 167 | assert.Error(t, err) 168 | } 169 | 170 | func TestArrayOfMaps(t *testing.T) { 171 | // Inner map 172 | template := MockMap{ 173 | Type: goebpf.MapTypeArray, 174 | KeySize: 4, 175 | ValueSize: 4, 176 | MaxEntries: 10, 177 | } 178 | 179 | // Create special map which contains other maps as value 180 | outer := &MockMap{ 181 | Type: goebpf.MapTypeArrayOfMaps, 182 | MaxEntries: 10, 183 | } 184 | err := outer.Create() 185 | assert.NoError(t, err) 186 | 187 | // Create new map based on template / add one value 188 | inner := template.CloneTemplate() 189 | err = inner.Create() 190 | assert.NoError(t, err) 191 | // Put it into outer map (map of maps) 192 | err = outer.Update(0, inner.GetFd()) 193 | assert.NoError(t, err) 194 | 195 | // Ensure that outer map contains proper fd 196 | fd, err := outer.LookupInt(0) 197 | assert.NoError(t, err) 198 | assert.Equal(t, inner.GetFd(), fd) 199 | } 200 | 201 | func TestGetNextKeyString(t *testing.T) { 202 | // Create map 203 | m := MockMap{ 204 | Type: goebpf.MapTypeHash, 205 | KeySize: 4, 206 | ValueSize: 4, 207 | MaxEntries: 10, 208 | } 209 | err := m.Create() 210 | assert.NoError(t, err) 211 | 212 | mapData := map[string]string{ 213 | "key1": "val1", 214 | "key2": "val2", 215 | "key3": "val3", 216 | "": "val4", 217 | } 218 | 219 | result := map[string]string{} 220 | 221 | // Insert items into hash map 222 | for key, value := range mapData { 223 | err = m.Insert(key, value) 224 | assert.NoError(t, err) 225 | } 226 | currentKey, err := m.GetNextKeyString(nil) 227 | assert.NoError(t, err) 228 | for { 229 | val, err := m.LookupString(currentKey) 230 | assert.NoError(t, err) 231 | assert.Equal(t, mapData[currentKey], string(val)) 232 | result[currentKey] = val 233 | nextKey, err := m.GetNextKeyString(currentKey) 234 | if err != nil { 235 | break 236 | } 237 | currentKey = nextKey 238 | } 239 | assert.Equal(t, mapData, result) 240 | } 241 | 242 | func TestGetNextKeyInt(t *testing.T) { 243 | // Create map 244 | m := MockMap{ 245 | Type: goebpf.MapTypeHash, 246 | KeySize: 4, 247 | ValueSize: 4, 248 | MaxEntries: 10, 249 | } 250 | err := m.Create() 251 | assert.NoError(t, err) 252 | 253 | mapData := map[int]int{ 254 | 1234: 4321, 255 | 5678: 8765, 256 | 9012: 2109, 257 | 0: 123, 258 | } 259 | 260 | result := map[int]int{} 261 | 262 | // Insert items into hash map 263 | for key, value := range mapData { 264 | err = m.Insert(key, value) 265 | assert.NoError(t, err) 266 | } 267 | currentKey, err := m.GetNextKeyInt(nil) 268 | assert.NoError(t, err) 269 | for { 270 | val, err := m.LookupInt(currentKey) 271 | assert.NoError(t, err) 272 | assert.Equal(t, mapData[currentKey], int(val)) 273 | result[currentKey] = val 274 | nextKey, err := m.GetNextKeyInt(currentKey) 275 | if err != nil { 276 | break 277 | } 278 | currentKey = nextKey 279 | } 280 | assert.Equal(t, mapData, result) 281 | } 282 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestParseNumOfPossibleCpus(t *testing.T) { 13 | runs := map[string]int{ 14 | "0": 1, 15 | "0-0": 1, 16 | "0-1": 2, 17 | "0-14": 15, 18 | "0-1\r\n": 2, 19 | } 20 | 21 | for str, numExpected := range runs { 22 | num, err := parseNumOfPossibleCpus(str) 23 | assert.NoError(t, err) 24 | assert.Equal(t, numExpected, num) 25 | } 26 | 27 | // Negative runs 28 | runsNegative := []string{ 29 | "", 30 | "1", 31 | "1-1", 32 | "1-", 33 | "-1", 34 | "\r\n", 35 | } 36 | 37 | for _, str := range runsNegative { 38 | num, err := parseNumOfPossibleCpus(str) 39 | assert.Error(t, err) 40 | assert.Equal(t, 0, num) 41 | } 42 | } 43 | 44 | // Negative test for closeFd() 45 | func TestCloseFd(t *testing.T) { 46 | err := closeFd(1111) // Some non-existing fd 47 | assert.Error(t, err) 48 | assert.Equal(t, "close() failed: Bad file descriptor", err.Error()) 49 | } 50 | 51 | func TestNullTerminatedStringToString(t *testing.T) { 52 | assert.Equal(t, "abc", NullTerminatedStringToString([]byte{'a', 'b', 'c'})) 53 | assert.Equal(t, "def", NullTerminatedStringToString([]byte{'d', 'e', 'f', 0, 'g'})) 54 | assert.Equal(t, "", NullTerminatedStringToString([]byte{0, 'h'})) 55 | assert.Equal(t, "", NullTerminatedStringToString([]byte{0})) 56 | } 57 | 58 | func TestKeyValueToBytes(t *testing.T) { 59 | type run struct { 60 | val interface{} 61 | size int 62 | bytes []byte 63 | } 64 | // key, key_size, expected 65 | runs := []run{ 66 | // regular integers 67 | {0x0, 0, []byte{}}, 68 | {0xff, 1, []byte{0xff}}, 69 | {0xff00, 2, []byte{0, 0xff}}, 70 | {0x7ffefdfc, 4, []byte{0xfc, 0xfd, 0xfe, 0x7f}}, 71 | {0x7fffffff, 4, []byte{0xff, 0xff, 0xff, 0x7f}}, 72 | {0x7fffffffffffffff, 8, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}, 73 | {0x7f, 1, []byte{0x7f}}, 74 | {0x7f, 2, []byte{0x7f, 0}}, 75 | {0x7f, 4, []byte{0x7f, 0, 0, 0}}, 76 | {0x7f, 8, []byte{0x7f, 0, 0, 0, 0, 0, 0, 0}}, 77 | // negative regular integers 78 | {-1, 8, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 79 | {-100, 8, []byte{0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 80 | // fixed size integers 81 | {uint8(0), 1, []byte{0}}, 82 | {uint8(1), 2, []byte{1, 0}}, 83 | {uint8(0x7f), 4, []byte{0x7f, 0, 0, 0}}, 84 | {uint8(0xff), 8, []byte{0xff, 0, 0, 0, 0, 0, 0, 0}}, 85 | 86 | {uint16(0), 2, []byte{0, 0}}, 87 | {uint16(0xff), 4, []byte{0xff, 0, 0, 0}}, 88 | {uint16(0xffff), 8, []byte{0xff, 0xff, 0, 0, 0, 0, 0, 0}}, 89 | 90 | {uint32(0x0), 4, []byte{0x0, 0, 0, 0}}, 91 | {uint32(0xff), 4, []byte{0xff, 0, 0, 0}}, 92 | {uint32(0xffff), 4, []byte{0xff, 0xff, 0, 0}}, 93 | {uint32(0xffffff), 4, []byte{0xff, 0xff, 0xff, 0}}, 94 | {uint32(0xffffffff), 4, []byte{0xff, 0xff, 0xff, 0xff}}, 95 | {uint32(0xffffffff), 8, []byte{0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0}}, 96 | 97 | {uint64(0), 8, []byte{0, 0, 0, 0, 0, 0, 0, 0}}, 98 | {uint64(0xffffffff), 8, []byte{0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0}}, 99 | {uint64(0xffffffffffffffff), 8, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, 100 | 101 | // strings 102 | {"", 0, []byte{}}, 103 | {"", 1, []byte{0}}, 104 | {"a", 1, []byte{'a'}}, 105 | {"b", 4, []byte{'b', 0, 0, 0}}, 106 | {"bc", 4, []byte{'b', 'c', 0, 0}}, 107 | {"aaaa", 4, []byte{'a', 'a', 'a', 'a'}}, 108 | 109 | // byte array 110 | {[]byte{}, 0, []byte{}}, 111 | {[]byte{}, 1, []byte{0}}, 112 | {[]byte{'a'}, 1, []byte{'a'}}, 113 | {[]byte{'b', 0, 0, 0}, 4, []byte{'b', 0, 0, 0}}, 114 | 115 | // CreateLPMtrieKey, IPv4 116 | {CreateLPMtrieKey("192.168.1.0/24"), 8, []byte{0x18, 0x0, 0x0, 0x0, 0xc0, 0xa8, 0x01, 0x0}}, 117 | {CreateLPMtrieKey("192.168.1.55/24"), 8, []byte{0x18, 0x0, 0x0, 0x0, 0xc0, 0xa8, 0x01, 0x0}}, 118 | {CreateLPMtrieKey("192.168.1.1"), 8, []byte{0x20, 0x0, 0x0, 0x0, 0xc0, 0xa8, 0x01, 0x01}}, 119 | {CreateLPMtrieKey("0.0.0.0/0"), 8, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, 120 | 121 | // CreateLPMtrieKey, IPv6 122 | {CreateLPMtrieKey("::/0"), 20, []byte{0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 123 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, 124 | {CreateLPMtrieKey("::1/64"), 20, []byte{0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 125 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, 126 | {CreateLPMtrieKey("FE80::8329"), 20, []byte{0x80, 0x0, 0x0, 0x0, 0xFE, 0x80, 0x00, 0x00, 0x0, 127 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x83, 0x29}}, 128 | } 129 | 130 | for _, r := range runs { 131 | res, err := KeyValueToBytes(r.val, r.size) 132 | assert.NoError(t, err) 133 | assert.Equal(t, r.bytes, res) 134 | } 135 | } 136 | 137 | func TestKeyValueToBytesNegative(t *testing.T) { 138 | type run struct { 139 | val interface{} 140 | size int 141 | } 142 | runs := []run{ 143 | // regular integers 144 | {0x1ff, 1}, // 0x1ff requires 2 bytes of storage 145 | {0x10ffff, 2}, // at least 4 bytes 146 | {0x10ffffffff, 4}, // at least 5 bytes 147 | {-1, 4}, // negative integer requires 8 bytes 148 | // typed integers 149 | {uint8(0), 0}, 150 | {uint16(1), 1}, 151 | {uint32(1), 3}, 152 | {uint64(1), 7}, 153 | // strings (too long) 154 | {"1", 0}, 155 | {"1dasdasdsa", 3}, 156 | // bytes (doesn't fit) 157 | {[]byte{'a'}, 0}, 158 | {[]byte{'a', 0, 1, 2, 3}, 3}, 159 | } 160 | 161 | for _, r := range runs { 162 | _, err := KeyValueToBytes(r.val, r.size) 163 | assert.Error(t, err) 164 | } 165 | } 166 | 167 | func TestParseFlexibleInteger(t *testing.T) { 168 | type run struct { 169 | rawValue []byte 170 | expected uint64 171 | } 172 | 173 | // Reversed test data from TestKeyValueToBytes() 174 | runs := []run{ 175 | {[]byte{0xff}, 0xff}, 176 | {[]byte{0, 0xff}, 0xff00}, 177 | {[]byte{0xe8, 0x3}, 1000}, 178 | {[]byte{0xfc, 0xfd, 0xfe, 0x7f}, 0x7ffefdfc}, 179 | {[]byte{0xff, 0xff, 0xff, 0x7f}, 0x7fffffff}, 180 | {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, 0x7fffffffffffffff}, 181 | {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0xffffffffffffffff}, 182 | {[]byte{0x7f, 0, 0, 0}, 0x7f}, 183 | {[]byte{0x7f, 0, 0, 0, 0, 0, 0, 0}, 0x7f}, 184 | } 185 | 186 | for _, r := range runs { 187 | val := ParseFlexibleIntegerLittleEndian(r.rawValue) 188 | assert.Equal(t, r.expected, val) 189 | } 190 | } 191 | 192 | func TestParseFlexibleMultiInteger(t *testing.T) { 193 | type run struct { 194 | valueSize int 195 | rawValue []byte 196 | expected uint64 197 | } 198 | 199 | runs := []run{ 200 | // uint8 201 | {1, []byte{255}, 255}, 202 | {1, []byte{255, 1}, 255 + 1}, 203 | {1, []byte{255, 1, 2}, 255 + 1 + 2}, 204 | // uint16 205 | {2, []byte{0x01, 0x0}, 1}, 206 | {2, []byte{0xe8, 0x3}, 1000}, 207 | {2, []byte{0xe8, 0x3, 0x1, 0x0}, 1000 + 1}, 208 | // uint32 209 | {4, []byte{0x01, 0x0, 0x0, 0x0}, 1}, 210 | {4, []byte{0xA0, 0x86, 0x01, 0x0}, 100000}, 211 | {4, []byte{0xA0, 0x86, 0x01, 0x0, 0x01, 0x0, 0x0, 0x0}, 100000 + 1}, 212 | // uint64 213 | {8, []byte{0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0}, 0x100000000}, 214 | {8, []byte{0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 0x100000000 + 1}, 215 | } 216 | 217 | for _, r := range runs { 218 | m := &EbpfMap{ 219 | Type: MapTypePerCPUArray, 220 | ValueSize: r.valueSize, 221 | valueRealSize: len(r.rawValue), 222 | } 223 | val := m.parseFlexibleMultiInteger(r.rawValue) 224 | assert.Equal(t, r.expected, val) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /program_kprobe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Dropbox, Inc. 2 | // Full license can be found in the LICENSE file. 3 | 4 | package goebpf 5 | 6 | /* 7 | #ifdef __linux__ 8 | #include 9 | #include 10 | #include 11 | #define SYSCALL(...) syscall(__VA_ARGS__) 12 | #else 13 | // macOS 14 | #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) 15 | #define LINUX_VERSION_CODE 266002 16 | #define SYSCALL(...) (0) 17 | #define PERF_SAMPLE_RAW 1U << 10 18 | #define PERF_TYPE_TRACEPOINT 2 19 | #define PERF_COUNT_SW_BPF_OUTPUT 10 20 | #define PERF_EVENT_IOC_DISABLE 0 21 | #define PERF_EVENT_IOC_ENABLE 1 22 | #define PERF_EVENT_IOC_SET_BPF 8 23 | #define PERF_FLAG_FD_CLOEXEC (1UL << 3) 24 | #define __NR_perf_event_open 364 25 | struct perf_event_attr { 26 | int type, config, sample_type, sample_period, wakeup_events; 27 | }; 28 | #endif 29 | 30 | #include 31 | #include 32 | #include "bpf_helpers.h" 33 | 34 | static int kprobe_perf_event_open(int progFd, long id) { 35 | 36 | struct perf_event_attr attr = {}; 37 | attr.config = id; 38 | attr.type = PERF_TYPE_TRACEPOINT; 39 | attr.sample_period = 1; 40 | attr.wakeup_events = 1; 41 | int pfd = SYSCALL(__NR_perf_event_open, &attr, -1, 0, -1, PERF_FLAG_FD_CLOEXEC); 42 | if (pfd < 0) { 43 | //fprintf(stderr, "perf_event_open(%s/id): %s\n", event_path, 44 | //strerror(errno)); 45 | return -1; 46 | } 47 | 48 | if (ioctl(pfd, PERF_EVENT_IOC_SET_BPF, progFd) < 0) { 49 | perror("ioctl(PERF_EVENT_IOC_SET_BPF)"); 50 | return -2; 51 | } 52 | if (ioctl(pfd, PERF_EVENT_IOC_ENABLE, 0) < 0) { 53 | perror("ioctl(PERF_EVENT_IOC_ENABLE)"); 54 | return -3; 55 | } 56 | 57 | return pfd; 58 | } 59 | */ 60 | import "C" 61 | import ( 62 | "errors" 63 | "fmt" 64 | "io/ioutil" 65 | "os" 66 | "strconv" 67 | "strings" 68 | "sync" 69 | ) 70 | 71 | const ( 72 | // namespace provides a unique namespace for kprobe labels 73 | namespace = "goebpf" 74 | // sysKprobeEvents 75 | sysKprobeEvents = "/sys/kernel/debug/tracing/kprobe_events" 76 | // sysKprobe 77 | sysKprobe = "/sys/kernel/debug/tracing/events" 78 | // kretprobeMaxActive 79 | kretprobeMaxActive = 4096 80 | ) 81 | 82 | var ( 83 | // kprobeLock enforces synchronous debugfs reads/writes 84 | kprobeLock sync.Mutex 85 | ) 86 | 87 | // KprobeAttachType specified whether a Kprobe program is 88 | // attached as an entry or exit probe. 89 | type KprobeAttachType int 90 | 91 | const ( 92 | KprobeAttachTypeEntry KprobeAttachType = iota 93 | KprobeAttachTypeReturn 94 | ) 95 | 96 | // Prefix returns the string prefix used by debugfs actions. 97 | func (t KprobeAttachType) Prefix() string { 98 | switch t { 99 | case KprobeAttachTypeReturn: 100 | return "r" 101 | default: 102 | fallthrough 103 | case KprobeAttachTypeEntry: 104 | return "p" 105 | } 106 | } 107 | 108 | // String returns a human readable string for a kprobe attach type. 109 | func (t KprobeAttachType) String() string { 110 | switch t { 111 | case KprobeAttachTypeEntry: 112 | return "kprobe" 113 | case KprobeAttachTypeReturn: 114 | return "kretprobe" 115 | default: 116 | return "UNKNOWN" 117 | } 118 | } 119 | 120 | // kprobe eBPF program (implements Program interface) 121 | type kprobeProgram struct { 122 | BaseProgram 123 | kprobe *kprobe 124 | } 125 | 126 | func newProbeProgram(bp BaseProgram, attachType KprobeAttachType) Program { 127 | // sanity check license 128 | if bp.GetLicense() != "GPL" { 129 | fmt.Fprintf(os.Stderr, "ERROR: %s program requires license to be 'GPL'.\n", attachType.String()) 130 | return nil 131 | } 132 | 133 | // parse and sanity check symbol 134 | section := bp.GetSection() 135 | tokens := strings.Split(section, "/") 136 | if len(tokens) < 2 { 137 | fmt.Fprintf(os.Stderr, "ERROR: invalid section name e.g. use format '%s/SyS_execve'.\n", attachType.String()) 138 | return nil 139 | } 140 | 141 | // update base program info 142 | bp.programType = ProgramTypeKprobe 143 | bp.kernelVersion = int(C.LINUX_VERSION_CODE) 144 | 145 | return &kprobeProgram{ 146 | BaseProgram: bp, 147 | kprobe: newKprobe(attachType, tokens[1]), 148 | } 149 | } 150 | 151 | // newKprobeProgram is a helper to create a new kprobe (entry) program. 152 | func newKprobeProgram(bp BaseProgram) Program { 153 | return newProbeProgram(bp, KprobeAttachTypeEntry) 154 | } 155 | 156 | // newKretprobeProgram is a helper to create a new kprobe (exit) program. 157 | func newKretprobeProgram(bp BaseProgram) Program { 158 | return newProbeProgram(bp, KprobeAttachTypeReturn) 159 | } 160 | 161 | // Attach attaches eBPF(Kprobe) program to a probe point. 162 | // There are 2 possible ways to do that: 163 | // 164 | // 1. Pass attach point as parameter, e.g. 165 | // xdpProgram.Attach("SyS_execve") 166 | // 167 | // 2. Using the ebpf program section identifier. 168 | // SEC("kprobe/SyS_execve") 169 | func (p *kprobeProgram) Attach(data interface{}) error { 170 | 171 | // (optional) symbol override by parameter 172 | switch v := data.(type) { 173 | case string: 174 | if len(v) > 0 { 175 | p.kprobe.symbol = v 176 | } 177 | } 178 | 179 | // attempt to attach kprobe to the symbol provided 180 | if err := p.kprobe.Attach(p.GetFd()); err != nil { 181 | return err 182 | } 183 | 184 | // enable the attached kprobe 185 | if err := p.kprobe.Enable(); err != nil { 186 | return err 187 | } 188 | 189 | return nil 190 | } 191 | 192 | // Detach detaches program from kprobe attach point. 193 | // Must be previously attached by Attach() call. 194 | func (p *kprobeProgram) Detach() error { 195 | p.kprobe.Disable() 196 | return p.kprobe.Detach() 197 | } 198 | 199 | // kprobe represents an individual kprobe_event handler. 200 | type kprobe struct { 201 | // fd is the file descriptor as returned by debugfs 202 | fd int 203 | // event is the kprobe entry as written to debugs 204 | event string 205 | // symbol is the target symbol to attach the kprobe to (i.e. SyS_execve) 206 | symbol string 207 | // attachType is whether the kprobe is attached to the entry or exit point 208 | attachType KprobeAttachType 209 | } 210 | 211 | // newKprobe creates a new kprobe struct and creates debugfs event string. 212 | func newKprobe(attachType KprobeAttachType, sym string) *kprobe { 213 | 214 | p := &kprobe{ 215 | attachType: attachType, 216 | event: fmt.Sprintf("%s_%s_%d", sym, namespace, os.Getpid()), 217 | symbol: sym, 218 | } 219 | 220 | return p 221 | } 222 | 223 | // kprobePath returns the relevant debugfs path for the kprobe. 224 | func (p *kprobe) kprobePath(cmd string) string { 225 | return sysKprobe + "/" + p.attachType.String() + "s/" + p.event + "/" + cmd 226 | } 227 | 228 | // entry returns the relevant debugfs entry formatted string for the kprobe. 229 | // e.g. r4096:kretprobes/SyS_execve_goebpf_1234 SyS_execve 230 | // 231 | // :/