├── .gitignore ├── asm ├── doc.go ├── opcode_string.go ├── register.go ├── jump_string.go ├── dsl_test.go ├── load_store_string.go ├── func_string.go ├── alu_string.go ├── instruction_test.go ├── jump.go ├── alu.go ├── load_store.go ├── opcode.go ├── opcode_test.go ├── instruction.go └── func.go ├── testdata ├── rewrite.elf ├── invalid_map.elf ├── perf_output.elf ├── loader-clang-7.elf ├── loader-clang-8.elf ├── loader-clang-6.0.elf ├── invalid_map.c ├── Makefile ├── perf_output.c ├── rewrite.c ├── common.h └── loader.c ├── readme.md ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── go.mod ├── ptr_32_be.go ├── ptr_32_le.go ├── feature.go ├── ptr_64.go ├── go.sum ├── examples ├── readme.md ├── sockex1_kern.c ├── Makefile └── bpf_helpers.h ├── syscalls_test.go ├── marshalers_test.go ├── .semaphore └── semaphore.yml ├── license.md ├── marshaler_example_test.go ├── types_string.go ├── cmd ├── ebpf-dump │ └── main.go └── ebpf-test │ └── main.go ├── doc.go ├── run-tests.sh ├── CODE_OF_CONDUCT.md ├── collection_test.go ├── abi.go ├── elf_test.go ├── editor.go ├── abi_test.go ├── example_sock_extract_dist_test.go ├── editor_test.go ├── marshalers.go ├── types.go ├── prog_test.go ├── collection.go ├── example_sock_elf_test.go ├── syscalls.go ├── perf_test.go ├── elf.go └── prog.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | /coverage.txt 3 | /linux-*.bz 4 | -------------------------------------------------------------------------------- /asm/doc.go: -------------------------------------------------------------------------------- 1 | // Package asm is an assembler for eBPF bytecode. 2 | package asm 3 | -------------------------------------------------------------------------------- /testdata/rewrite.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/rewrite.elf -------------------------------------------------------------------------------- /testdata/invalid_map.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/invalid_map.elf -------------------------------------------------------------------------------- /testdata/perf_output.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/perf_output.elf -------------------------------------------------------------------------------- /testdata/loader-clang-7.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/loader-clang-7.elf -------------------------------------------------------------------------------- /testdata/loader-clang-8.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/loader-clang-8.elf -------------------------------------------------------------------------------- /testdata/loader-clang-6.0.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newtools/ebpf/HEAD/testdata/loader-clang-6.0.elf -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | eBPF 2 | ------- 3 | 4 | This library is not maintained anymore. Please use https://github.com/cilium/ebpf which has an almost compatible API. 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please provide a clear and concise explanation of what this PR does/fixes and why ebpf needs it. 2 | 3 | (Optional) 4 | Fixes # 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/newtools/ebpf 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/pkg/errors v0.8.1 7 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 8 | ) 9 | -------------------------------------------------------------------------------- /ptr_32_be.go: -------------------------------------------------------------------------------- 1 | // +build armbe mips mips64p32 2 | 3 | package ebpf 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // ptr wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type syscallPtr struct { 12 | ptr unsafe.Pointer 13 | pad uint32 14 | } 15 | -------------------------------------------------------------------------------- /ptr_32_le.go: -------------------------------------------------------------------------------- 1 | // +build 386 amd64p32 arm mipsle mips64p32le 2 | 3 | package ebpf 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // ptr wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type syscallPtr struct { 12 | ptr unsafe.Pointer 13 | pad uint32 14 | } 15 | -------------------------------------------------------------------------------- /feature.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type featureTest struct { 8 | Fn func() bool 9 | 10 | once sync.Once 11 | result bool 12 | } 13 | 14 | func (ft *featureTest) Result() bool { 15 | ft.once.Do(func() { 16 | ft.result = ft.Fn() 17 | }) 18 | return ft.result 19 | } 20 | -------------------------------------------------------------------------------- /ptr_64.go: -------------------------------------------------------------------------------- 1 | // +build !386,!amd64p32,!arm,!mipsle,!mips64p32le 2 | // +build !armbe,!mips,!mips64p32 3 | 4 | package ebpf 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // ptr wraps an unsafe.Pointer to be 64bit to 11 | // conform to the syscall specification. 12 | type syscallPtr struct { 13 | ptr unsafe.Pointer 14 | } 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 2 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 h1:vsphBvatvfbhlb4PO1BYSr9dzugGxJ/SQHoNufZJq1w= 4 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 5 | -------------------------------------------------------------------------------- /testdata/invalid_map.c: -------------------------------------------------------------------------------- 1 | /* This file excercises the ELF loader. It is not a valid BPF program. 2 | */ 3 | 4 | #include "common.h" 5 | 6 | char __license[] __section("license") = "MIT"; 7 | 8 | struct map invalid_map __section("maps") = { 9 | .type = BPF_MAP_TYPE_ARRAY, 10 | .key_size = 4, 11 | .value_size = 2, 12 | .max_entries = 1, 13 | .flags = 0, 14 | .dummy = 1, 15 | }; 16 | -------------------------------------------------------------------------------- /testdata/Makefile: -------------------------------------------------------------------------------- 1 | LLVM_PREFIX ?= /usr/bin 2 | CLANG ?= $(LLVM_PREFIX)/clang 3 | 4 | all: loader-clang-6.0.elf loader-clang-7.elf loader-clang-8.elf rewrite.elf perf_output.elf invalid_map.elf 5 | 6 | clean: 7 | -$(RM) *.elf 8 | 9 | loader-%.elf: loader.c 10 | $* -target bpf -O2 -g \ 11 | -Wall -Werror \ 12 | -c $< -o $@ 13 | 14 | %.elf : %.c 15 | $(CLANG) -target bpf -O2 -g \ 16 | -Wall -Werror \ 17 | -c $< -o $@ 18 | 19 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | Examples 2 | -------- 3 | 4 | To build the examples folder you'll need to pull the linux repository. Once it's on your host 5 | you can build this folder: 6 | 7 | ```sh 8 | make kernel_src=/path/to/kernel/src 9 | ``` 10 | 11 | It is possible to build bpf programs without downloading the entire kernel, but you'll have to go 12 | hunting through the kernel source code for the particulars you need. It's much easier to just download it. 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **What should ebpf do?** 8 | A clear and concise description of what you think ebpf should do that it is not doing now. 9 | 10 | **Why should ebpf do this?** 11 | A clear and concise explanation of why this functionality should be a part of ebpf (as apposed to being either one-off code that you write, or pre-existing functionality in another library). 12 | 13 | **Additional context** 14 | Add any other context or thoughts you have here. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Logs** 17 | If available. 18 | 19 | **Linux Information (please complete the following information):** 20 | - Version [output from `uname -a`]: 21 | - Distribution [e.g. Fedora]: 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /asm/opcode_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output opcode_string.go -type=Class"; DO NOT EDIT. 2 | 3 | package asm 4 | 5 | import "strconv" 6 | 7 | const ( 8 | _Class_name_0 = "LdClassLdXClassStClassStXClassALUClassJumpClass" 9 | _Class_name_1 = "ALU64Class" 10 | ) 11 | 12 | var ( 13 | _Class_index_0 = [...]uint8{0, 7, 15, 22, 30, 38, 47} 14 | ) 15 | 16 | func (i Class) String() string { 17 | switch { 18 | case 0 <= i && i <= 5: 19 | return _Class_name_0[_Class_index_0[i]:_Class_index_0[i+1]] 20 | case i == 7: 21 | return _Class_name_1 22 | default: 23 | return "Class(" + strconv.FormatInt(int64(i), 10) + ")" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /testdata/perf_output.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | char __license[] __section("license") = "GPL"; 4 | 5 | struct map events __section("maps") = { 6 | .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 7 | }; 8 | 9 | __section("xdp/single") int output_single(void *ctx) { 10 | unsigned char buf[] = { 11 | 1, 2, 3, 4, 5 12 | }; 13 | 14 | return perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &buf[0], sizeof(buf)); 15 | } 16 | 17 | __section("xdp/large") int output_large(void *ctx) { 18 | unsigned char buf[180]; 19 | __builtin_memset(buf, 0, sizeof(buf)); 20 | 21 | return perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &buf[0], sizeof(buf)); 22 | } 23 | -------------------------------------------------------------------------------- /syscalls_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestObjName(t *testing.T) { 8 | for in, valid := range map[string]bool{ 9 | "test": true, 10 | "": true, 11 | "a-b": false, 12 | "yeah so": false, 13 | "more_than_16_characters_long": true, 14 | } { 15 | name, err := newBPFObjName(in) 16 | if result := err == nil; result != valid { 17 | t.Errorf("Name '%s' classified incorrectly", name) 18 | } 19 | if name[len(name)-1] != 0 { 20 | t.Errorf("Name '%s' is not null terminated", name) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /marshalers_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "testing" 7 | ) 8 | 9 | func TestParseCPUs(t *testing.T) { 10 | for str, result := range map[string]int{ 11 | "0-1": 2, 12 | "0": 1, 13 | } { 14 | fh, err := ioutil.TempFile("", "ebpf") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | if _, err := io.WriteString(fh, str); err != nil { 20 | t.Fatal(err) 21 | } 22 | fh.Close() 23 | 24 | n, err := parseCPUs(fh.Name()) 25 | if err != nil { 26 | t.Error("Can't parse", str, err) 27 | } else if n != result { 28 | t.Error("Parsing", str, "returns", n, "instead of", result) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /asm/register.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Register is the source or destination of most operations. 8 | type Register uint8 9 | 10 | // R0 contains return values. 11 | const R0 Register = 0 12 | 13 | // Registers for function arguments. 14 | const ( 15 | R1 Register = R0 + 1 + iota 16 | R2 17 | R3 18 | R4 19 | R5 20 | ) 21 | 22 | // Callee saved registers preserved by function calls. 23 | const ( 24 | R6 Register = R5 + 1 + iota 25 | R7 26 | R8 27 | R9 28 | ) 29 | 30 | // Read-only frame pointer to access stack. 31 | const ( 32 | R10 Register = R9 + 1 33 | RFP = R10 34 | ) 35 | 36 | func (r Register) String() string { 37 | v := uint8(r) 38 | if v == 10 { 39 | return "rfp" 40 | } 41 | return fmt.Sprintf("r%d", v) 42 | } 43 | -------------------------------------------------------------------------------- /testdata/rewrite.c: -------------------------------------------------------------------------------- 1 | /* This file tests rewriting constants from C compiled code. 2 | */ 3 | 4 | #include "common.h" 5 | 6 | char __license[] __section("license") = "MIT"; 7 | 8 | struct map map_val __section("maps") = { 9 | .type = 1, 10 | .key_size = sizeof(unsigned int), 11 | .value_size = sizeof(unsigned int), 12 | .max_entries = 1, 13 | }; 14 | 15 | #define CONSTANT "constant" 16 | 17 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 18 | 19 | __section("socket") int rewrite() { 20 | unsigned long acc = 0; 21 | LOAD_CONSTANT(CONSTANT, acc); 22 | return acc; 23 | } 24 | 25 | __section("socket/map") int rewrite_map() { 26 | unsigned int key = 0; 27 | unsigned int *value = map_lookup_elem(&map_val, &key); 28 | if (!value) { 29 | return 0; 30 | } 31 | return *value; 32 | } 33 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: CI Build 3 | 4 | agent: 5 | machine: 6 | type: e1-standard-2 7 | os_image: ubuntu1804 8 | 9 | blocks: 10 | - name: Run tests 11 | task: 12 | prologue: 13 | commands: 14 | - checkout 15 | - sem-version go 1.12 16 | - go get -d ./... 17 | - go build ./... 18 | - sudo pip3 install https://github.com/amluto/virtme/archive/538f1e756139a6b57a4780e7ceb3ac6bcaa4fe6f.zip 19 | - sudo apt-get -y update 20 | - sudo apt-get install -y qemu-system-x86 21 | jobs: 22 | - name: Test on 5.0.13 23 | commands: 24 | - timeout -s KILL 60s ./run-tests.sh 5.0.13 25 | - name: Test on 4.19.40 26 | commands: 27 | - timeout -s KILL 60s ./run-tests.sh 4.19.40 28 | -------------------------------------------------------------------------------- /testdata/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef unsigned int uint32_t; 4 | typedef unsigned long uint64_t; 5 | 6 | #define __section(NAME) __attribute__((section(NAME), used)) 7 | 8 | #define BPF_MAP_TYPE_ARRAY (1) 9 | #define BPF_MAP_TYPE_PERF_EVENT_ARRAY (4) 10 | #define BPF_MAP_TYPE_ARRAY_OF_MAPS (12) 11 | #define BPF_MAP_TYPE_HASH_OF_MAPS (13) 12 | 13 | #define BPF_F_NO_PREALLOC (1U << 0) 14 | #define BPF_F_CURRENT_CPU (0xffffffffULL) 15 | 16 | struct map { 17 | uint32_t type; 18 | uint32_t key_size; 19 | uint32_t value_size; 20 | uint32_t max_entries; 21 | uint32_t flags; 22 | uint32_t inner_map_idx; 23 | uint32_t dummy; 24 | }; 25 | 26 | static void* (*map_lookup_elem)(const void *map, const void *key) = (void*)1; 27 | static int (*perf_event_output)(const void *ctx, const void *map, uint64_t index, const void *data, uint64_t size) = (void*)25; 28 | static uint32_t (*get_smp_processor_id)(void) = (void*)8; 29 | -------------------------------------------------------------------------------- /asm/jump_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output jump_string.go -type=JumpOp"; DO NOT EDIT. 2 | 3 | package asm 4 | 5 | import "strconv" 6 | 7 | const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp" 8 | 9 | var _JumpOp_map = map[JumpOp]string{ 10 | 0: _JumpOp_name[0:2], 11 | 16: _JumpOp_name[2:5], 12 | 32: _JumpOp_name[5:8], 13 | 48: _JumpOp_name[8:11], 14 | 64: _JumpOp_name[11:15], 15 | 80: _JumpOp_name[15:18], 16 | 96: _JumpOp_name[18:22], 17 | 112: _JumpOp_name[22:26], 18 | 128: _JumpOp_name[26:30], 19 | 144: _JumpOp_name[30:34], 20 | 160: _JumpOp_name[34:37], 21 | 176: _JumpOp_name[37:40], 22 | 192: _JumpOp_name[40:44], 23 | 208: _JumpOp_name[44:48], 24 | 255: _JumpOp_name[48:61], 25 | } 26 | 27 | func (i JumpOp) String() string { 28 | if str, ok := _JumpOp_map[i]; ok { 29 | return str 30 | } 31 | return "JumpOp(" + strconv.FormatInt(int64(i), 10) + ")" 32 | } 33 | -------------------------------------------------------------------------------- /examples/sockex1_kern.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "bpf_helpers.h" 6 | 7 | struct bpf_map_def SEC("maps") my_map = { 8 | .type = BPF_MAP_TYPE_ARRAY, 9 | .key_size = sizeof(u32), 10 | .value_size = sizeof(long), 11 | .max_entries = 256, 12 | }; 13 | 14 | struct bpf_map_def SEC("maps") test_map = { 15 | .type = BPF_MAP_TYPE_ARRAY, 16 | .key_size = sizeof(u32), 17 | .value_size = sizeof(long), 18 | .max_entries = 256, 19 | }; 20 | 21 | SEC("socket1") 22 | int bpf_prog1(struct __sk_buff *skb) 23 | { 24 | int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol)); 25 | long *value; 26 | 27 | if (skb->pkt_type != PACKET_OUTGOING) 28 | return 0; 29 | 30 | value = bpf_map_lookup_elem(&my_map, &index); 31 | if (value) 32 | __sync_fetch_and_add(value, skb->len); 33 | 34 | return 0; 35 | } 36 | char _license[] SEC("license") = "GPL"; 37 | -------------------------------------------------------------------------------- /asm/dsl_test.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDSL(t *testing.T) { 8 | testcases := []struct { 9 | name string 10 | have Instruction 11 | want Instruction 12 | }{ 13 | {"Call", MapLookupElement.Call(), Instruction{OpCode: 0x85, Constant: 1}}, 14 | {"Exit", Return(), Instruction{OpCode: 0x95}}, 15 | {"LoadAbs", LoadAbs(2, Byte), Instruction{OpCode: 0x30, Constant: 2}}, 16 | {"Store", StoreMem(RFP, -4, R0, Word), Instruction{ 17 | OpCode: 0x63, 18 | Dst: RFP, 19 | Src: R0, 20 | Offset: -4, 21 | }}, 22 | {"Add.Imm", Add.Imm(R1, 22), Instruction{OpCode: 0x07, Dst: R1, Constant: 22}}, 23 | {"Add.Reg", Add.Reg(R1, R2), Instruction{OpCode: 0x0f, Dst: R1, Src: R2}}, 24 | {"Add.Imm32", Add.Imm32(R1, 22), Instruction{ 25 | OpCode: 0x04, Dst: R1, Constant: 22, 26 | }}, 27 | } 28 | 29 | for _, tc := range testcases { 30 | if tc.have != tc.want { 31 | t.Errorf("%s: have %v, want %v", tc.name, tc.have, tc.want) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Nathan Sweet 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /marshaler_example_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Assert that customEncoding implements the correct interfaces. 9 | var _ Marshaler = (*customEncoding)(nil) 10 | 11 | type customEncoding struct { 12 | data string 13 | } 14 | 15 | func (ce *customEncoding) MarshalBinary() ([]byte, error) { 16 | return []byte(strings.ToUpper(ce.data)), nil 17 | } 18 | 19 | func (ce *customEncoding) UnmarshalBinary(buf []byte) error { 20 | ce.data = string(buf) 21 | return nil 22 | } 23 | 24 | // ExampleMarshaler shows how to use custom encoding with map methods. 25 | func ExampleMarshaler() { 26 | hash := createHash() 27 | defer hash.Close() 28 | 29 | if err := hash.Put(&customEncoding{"hello"}, uint32(111)); err != nil { 30 | panic(err) 31 | } 32 | 33 | var ( 34 | key customEncoding 35 | value uint32 36 | entries = hash.Iterate() 37 | ) 38 | 39 | for entries.Next(&key, &value) { 40 | fmt.Printf("key: %s, value: %d\n", key.data, value) 41 | } 42 | 43 | if err := entries.Err(); err != nil { 44 | panic(err) 45 | } 46 | 47 | // Output: key: HELLO, value: 111 48 | } 49 | -------------------------------------------------------------------------------- /types_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output types_string.go -type=MapType,ProgType"; DO NOT EDIT. 2 | 3 | package ebpf 4 | 5 | import "strconv" 6 | 7 | const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMaps" 8 | 9 | var _MapType_index = [...]uint8{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136} 10 | 11 | func (i MapType) String() string { 12 | if i >= MapType(len(_MapType_index)-1) { 13 | return "MapType(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _MapType_name[_MapType_index[i]:_MapType_index[i+1]] 16 | } 17 | 18 | const _ProgType_name = "UnrecognizedSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOps" 19 | 20 | var _ProgType_index = [...]uint8{0, 12, 24, 30, 38, 46, 56, 59, 68, 77, 87, 92, 98, 105, 112} 21 | 22 | func (i ProgType) String() string { 23 | if i >= ProgType(len(_ProgType_index)-1) { 24 | return "ProgType(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _ProgType_name[_ProgType_index[i]:_ProgType_index[i+1]] 27 | } 28 | -------------------------------------------------------------------------------- /cmd/ebpf-dump/main.go: -------------------------------------------------------------------------------- 1 | // Program ebpf-dump writes the contents of an ELF file to stdout. 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/newtools/ebpf" 10 | ) 11 | 12 | func main() { 13 | flag.Usage = func() { 14 | fmt.Fprintf(os.Stderr, "%s: \n", os.Args[0]) 15 | flag.PrintDefaults() 16 | } 17 | 18 | flag.Parse() 19 | 20 | if flag.NArg() < 1 { 21 | flag.Usage() 22 | os.Exit(1) 23 | } 24 | 25 | spec, err := ebpf.LoadCollectionSpec(flag.Arg(0)) 26 | if err != nil { 27 | panic(err) 28 | } 29 | fmt.Println("Maps:") 30 | for k, v := range spec.Maps { 31 | fmt.Printf("\t%s:\n", k) 32 | fmt.Printf("\t\tMapType: %s\n", v.Type) 33 | fmt.Printf("\t\tKeySize: %d\n", v.KeySize) 34 | fmt.Printf("\t\tValueSize: %d\n", v.ValueSize) 35 | fmt.Printf("\t\tMaxEntries: %d\n", v.MaxEntries) 36 | fmt.Printf("\t\tFlags: %d\n", v.Flags) 37 | } 38 | fmt.Println("\nPrograms:") 39 | for k, v := range spec.Programs { 40 | fmt.Printf("\t%s:\n", k) 41 | fmt.Printf("\t\tProgType: %s\n", v.Type) 42 | fmt.Printf("\t\tLicense: %s\n", v.License) 43 | fmt.Printf("\t\tKernelVersion: %d\n", v.KernelVersion) 44 | fmt.Printf("\t\tInstructions:\n") 45 | fmt.Printf("%.3s", v.Instructions) 46 | } 47 | fmt.Println("") 48 | } 49 | -------------------------------------------------------------------------------- /asm/load_store_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output load_store_string.go -type=Mode,Size"; DO NOT EDIT. 2 | 3 | package asm 4 | 5 | import "strconv" 6 | 7 | const ( 8 | _Mode_name_0 = "ImmMode" 9 | _Mode_name_1 = "AbsMode" 10 | _Mode_name_2 = "IndMode" 11 | _Mode_name_3 = "MemMode" 12 | _Mode_name_4 = "XAddMode" 13 | _Mode_name_5 = "InvalidMode" 14 | ) 15 | 16 | func (i Mode) String() string { 17 | switch { 18 | case i == 0: 19 | return _Mode_name_0 20 | case i == 32: 21 | return _Mode_name_1 22 | case i == 64: 23 | return _Mode_name_2 24 | case i == 96: 25 | return _Mode_name_3 26 | case i == 192: 27 | return _Mode_name_4 28 | case i == 255: 29 | return _Mode_name_5 30 | default: 31 | return "Mode(" + strconv.FormatInt(int64(i), 10) + ")" 32 | } 33 | } 34 | 35 | const ( 36 | _Size_name_0 = "Word" 37 | _Size_name_1 = "Half" 38 | _Size_name_2 = "Byte" 39 | _Size_name_3 = "DWord" 40 | _Size_name_4 = "InvalidSize" 41 | ) 42 | 43 | func (i Size) String() string { 44 | switch { 45 | case i == 0: 46 | return _Size_name_0 47 | case i == 8: 48 | return _Size_name_1 49 | case i == 16: 50 | return _Size_name_2 51 | case i == 24: 52 | return _Size_name_3 53 | case i == 255: 54 | return _Size_name_4 55 | default: 56 | return "Size(" + strconv.FormatInt(int64(i), 10) + ")" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /asm/func_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output func_string.go -type=BuiltinFunc"; DO NOT EDIT. 2 | 3 | package asm 4 | 5 | import "strconv" 6 | 7 | const _BuiltinFunc_name = "MapLookupElementMapUpdateElementMapDeleteElementProbeReadKtimeGetNSTracePrintkGetPRandomu32GetSMPProcessorIDSKBStoreBytesCSUMReplaceL3CSUMReplaceL4TailCallCloneRedirectGetCurrentPIDTGIDGetCurrentUIDGIDGetCurrentCommGetCGroupClassIDSKBVlanPushSKBVlanPopSKBGetTunnelKeySKBSetTunnelKeyPerfEventReadRedirectGetRouteRealmPerfEventOutputGetStackIDCsumDiffSKBGetTunnelOptSKBSetTunnelOptSKBChangeProtoSKBChangeTypeSKBUnderCGroupGetHashRecalcGetCurrentTaskProbeWriteUserCurrentTaskUnderCGroupSKBChangeTailSKBPullDataCSUMUpdateSetHashInvalidGetNUMANodeIDSKBChangeHeadXDPAdjustHeadProbeReadStrGetSocketCookieGetSocketUIDSetHashSetSockOptSKBAdjustRoom" 8 | 9 | var _BuiltinFunc_index = [...]uint16{0, 16, 32, 48, 57, 67, 78, 91, 108, 121, 134, 147, 155, 168, 185, 201, 215, 231, 242, 252, 267, 282, 295, 303, 316, 331, 341, 349, 364, 379, 393, 406, 420, 433, 447, 461, 483, 496, 507, 517, 531, 544, 557, 570, 582, 597, 609, 616, 626, 639} 10 | 11 | func (i BuiltinFunc) String() string { 12 | i -= 1 13 | if i < 0 || i >= BuiltinFunc(len(_BuiltinFunc_index)-1) { 14 | return "BuiltinFunc(" + strconv.FormatInt(int64(i+1), 10) + ")" 15 | } 16 | return _BuiltinFunc_name[_BuiltinFunc_index[i]:_BuiltinFunc_index[i+1]] 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ebpf-test/main.go: -------------------------------------------------------------------------------- 1 | // Program ebpf-test allows testing eBPF from an ELF file. 2 | // 3 | // The input to the program is read from stdin and the output is written 4 | // to stdout. The binary uses the exit code of the BPF. 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "os" 12 | 13 | "github.com/newtools/ebpf" 14 | ) 15 | 16 | func main() { 17 | flag.Usage = func() { 18 | fmt.Fprintf(os.Stderr, "%s: \n", os.Args[0]) 19 | flag.PrintDefaults() 20 | } 21 | 22 | flag.Parse() 23 | 24 | if flag.NArg() < 2 { 25 | flag.Usage() 26 | os.Exit(42) 27 | } 28 | 29 | path := flag.Arg(0) 30 | coll, err := ebpf.LoadCollection(path) 31 | if err != nil { 32 | fmt.Fprintf(os.Stderr, "Can't load %s: %v\n", path, err) 33 | os.Exit(42) 34 | } 35 | 36 | progName := flag.Arg(1) 37 | prog, ok := coll.Programs[progName] 38 | if !ok { 39 | fmt.Fprintf(os.Stderr, "%v does not contain program %v\n", path, progName) 40 | os.Exit(42) 41 | } 42 | 43 | in, err := ioutil.ReadAll(os.Stdin) 44 | if err != nil { 45 | fmt.Fprintf(os.Stderr, "Can't read stdin: %v\n", err) 46 | os.Exit(42) 47 | } 48 | 49 | ret, out, err := prog.Test(in) 50 | if err != nil { 51 | fmt.Fprintf(os.Stderr, "Can't run %v: %v\n", progName, err) 52 | os.Exit(42) 53 | } 54 | 55 | if out != nil { 56 | if _, err := os.Stdout.Write(out); err != nil { 57 | fmt.Fprintf(os.Stderr, "Can't write output: %v\n", err) 58 | os.Exit(42) 59 | } 60 | } 61 | 62 | os.Exit(int(ret)) 63 | } 64 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package ebpf is a toolkit for working with eBPF programs. 2 | // 3 | // eBPF programs are small snippets of code which are executed directly 4 | // in a VM in the Linux kernel, which makes them very fast and flexible. 5 | // Many Linux subsystems now accept eBPF programs. This makes it possible 6 | // to implement highly application specific logic inside the kernel, 7 | // without having to modify the actual kernel itself. 8 | // 9 | // Since eBPF is a relatively young concept, documentation and user space 10 | // support is still lacking. Most of the available tools are written in C, and 11 | // reside in the kernel's source tree. The more mature external projects like 12 | // libbcc focus on using eBPF for instrumentation and debugging. This 13 | // leads to certain trade-offs which are not acceptable when 14 | // writing production services. 15 | // 16 | // This package is instead designed for long-running processes which 17 | // want to use eBPF to implement part of their application logic. It has no 18 | // run-time dependencies outside of the library and the Linux kernel itself. 19 | // eBPF code should be compiled ahead of time using clang, and shipped with 20 | // your application as any other resource. 21 | // 22 | // The two main parts are an ELF loader, which reads object files emitted by 23 | // clang, and facilities to modify and load eBPF programs into the kernel. 24 | // 25 | // This package doesn't include code required to attach eBPF to Linux 26 | // subsystems, since this varies per subsystem. See the examples for possible 27 | // solutions. 28 | package ebpf 29 | -------------------------------------------------------------------------------- /testdata/loader.c: -------------------------------------------------------------------------------- 1 | /* This file excercises the ELF loader. It is not a valid BPF program. 2 | */ 3 | 4 | #include "common.h" 5 | 6 | char __license[] __section("license") = "MIT"; 7 | 8 | struct map hash_map __section("maps") = { 9 | .type = BPF_MAP_TYPE_ARRAY, 10 | .key_size = 4, 11 | .value_size = 2, 12 | .max_entries = 1, 13 | .flags = 0, 14 | }; 15 | 16 | struct map hash_map2 __section("maps") = { 17 | .type = BPF_MAP_TYPE_ARRAY, 18 | .key_size = 4, 19 | .value_size = 1, 20 | .max_entries = 2, 21 | .flags = BPF_F_NO_PREALLOC, 22 | }; 23 | 24 | struct map array_of_hash_map __section("maps") = { 25 | .type = BPF_MAP_TYPE_ARRAY_OF_MAPS, 26 | .key_size = sizeof(uint32_t), 27 | .max_entries = 2, 28 | .inner_map_idx = 0, // points to "hash_map" 29 | }; 30 | 31 | struct map hash_of_hash_map __section("maps") = { 32 | .type = BPF_MAP_TYPE_HASH_OF_MAPS, 33 | .key_size = sizeof(uint32_t), 34 | .max_entries = 2, 35 | .inner_map_idx = 1, // points to "hash_map2" 36 | }; 37 | 38 | int __attribute__((noinline)) helper_func2(int arg) { 39 | return arg > 5; 40 | } 41 | 42 | int __attribute__((noinline)) helper_func(int arg) { 43 | // Enforce bpf-to-bpf call in .text section 44 | return helper_func2(arg); 45 | } 46 | 47 | __section("xdp") int xdp_prog() { 48 | unsigned int key = 0; 49 | map_lookup_elem(&hash_map, &key); 50 | map_lookup_elem(&hash_map2, &key); 51 | return helper_func(1); 52 | } 53 | 54 | // This function has no relocations, and is thus parsed differently. 55 | __section("socket") int no_relocation() { 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # Build BPF programs by using the kernel build system. 2 | # make kernel_src=/path/to/kernel/src 3 | 4 | # kbuild trick to avoid linker error. Can be omitted if a module is built. 5 | obj- := dummy.o 6 | 7 | # Tell kbuild to always build the programs 8 | always += sockex1_kern.o 9 | 10 | HOSTCFLAGS += -I$(objtree)/usr/include 11 | HOSTCFLAGS += -I$(srctree)/tools/lib/ 12 | HOSTCFLAGS += -I$(srctree)/tools/perf 13 | 14 | HOSTCFLAGS_bpf_load.o += -I$(objtree)/usr/include -Wno-unused-variable 15 | HOSTLOADLIBES_sockex1 += -lelf 16 | 17 | # Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline: 18 | # make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang 19 | LLVM_PREFIX ?= /usr/bin 20 | CLANG ?= $(LLVM_PREFIX)/clang 21 | LLC ?= $(LLVM_PREFIX)/llc 22 | 23 | # Trick to allow make to be run from this directory 24 | all: 25 | $(MAKE) -C $(kernel_src) $(CURDIR)/ 26 | 27 | clean: 28 | $(MAKE) -C $(kernel_src) M=$(CURDIR) clean 29 | @rm -f *~ 30 | 31 | # asm/sysreg.h - inline assembly used by it is incompatible with llvm. 32 | # But, there is no easy way to fix it, so just exclude it since it is 33 | # useless for BPF samples. 34 | $(obj)/%.o: $(src)/%.c 35 | $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) \ 36 | -D__KERNEL__ -D__ASM_SYSREG_H -Wno-unused-value -Wno-pointer-sign \ 37 | -Wno-compare-distinct-pointer-types \ 38 | -Wno-gnu-variable-sized-type-not-at-end \ 39 | -Wno-address-of-packed-member -Wno-tautological-compare \ 40 | -O2 -S -emit-llvm -c $< -o -| $(LLC) -march=bpf -filetype=obj -o $@ 41 | -------------------------------------------------------------------------------- /asm/alu_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output alu_string.go -type=Source,Endianness,ALUOp"; DO NOT EDIT. 2 | 3 | package asm 4 | 5 | import "strconv" 6 | 7 | const ( 8 | _Source_name_0 = "ImmSource" 9 | _Source_name_1 = "RegSource" 10 | _Source_name_2 = "InvalidSource" 11 | ) 12 | 13 | func (i Source) String() string { 14 | switch { 15 | case i == 0: 16 | return _Source_name_0 17 | case i == 8: 18 | return _Source_name_1 19 | case i == 255: 20 | return _Source_name_2 21 | default: 22 | return "Source(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | } 25 | 26 | const ( 27 | _Endianness_name_0 = "LE" 28 | _Endianness_name_1 = "BE" 29 | _Endianness_name_2 = "InvalidEndian" 30 | ) 31 | 32 | func (i Endianness) String() string { 33 | switch { 34 | case i == 0: 35 | return _Endianness_name_0 36 | case i == 8: 37 | return _Endianness_name_1 38 | case i == 255: 39 | return _Endianness_name_2 40 | default: 41 | return "Endianness(" + strconv.FormatInt(int64(i), 10) + ")" 42 | } 43 | } 44 | 45 | const _ALUOp_name = "AddSubMulDivOrAndLShRShNegModXorMovArShSwapInvalidALUOp" 46 | 47 | var _ALUOp_map = map[ALUOp]string{ 48 | 0: _ALUOp_name[0:3], 49 | 16: _ALUOp_name[3:6], 50 | 32: _ALUOp_name[6:9], 51 | 48: _ALUOp_name[9:12], 52 | 64: _ALUOp_name[12:14], 53 | 80: _ALUOp_name[14:17], 54 | 96: _ALUOp_name[17:20], 55 | 112: _ALUOp_name[20:23], 56 | 128: _ALUOp_name[23:26], 57 | 144: _ALUOp_name[26:29], 58 | 160: _ALUOp_name[29:32], 59 | 176: _ALUOp_name[32:35], 60 | 192: _ALUOp_name[35:39], 61 | 208: _ALUOp_name[39:43], 62 | 255: _ALUOp_name[43:55], 63 | } 64 | 65 | func (i ALUOp) String() string { 66 | if str, ok := _ALUOp_map[i]; ok { 67 | return str 68 | } 69 | return "ALUOp(" + strconv.FormatInt(int64(i), 10) + ")" 70 | } 71 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Test the current package under a different kernel. 3 | # Requires virtme and qemu to be installed. 4 | 5 | set -eu 6 | set -o pipefail 7 | 8 | if [[ "${1:-}" = "--in-vm" ]]; then 9 | shift 10 | 11 | readonly home="$(mktemp --directory)" 12 | 13 | mount -t bpf bpf /sys/fs/bpf 14 | export CGO_ENABLED=0 15 | export HOME="$home" 16 | 17 | echo Running tests... 18 | /usr/local/bin/go test -mod=vendor -coverprofile="$1/coverage.txt" -covermode=atomic -v ./... 19 | touch "$1/success" 20 | exit 0 21 | fi 22 | 23 | # Force Go modules, so that vendoring and building are easier. 24 | export GO111MODULE=on 25 | 26 | # Pull all dependencies, so that we can run tests without the 27 | # vm having network access. 28 | go mod vendor 29 | 30 | # Use sudo if /dev/kvm isn't accessible by the current user. 31 | sudo="" 32 | if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then 33 | sudo="sudo" 34 | fi 35 | readonly sudo 36 | 37 | readonly kernel_version="${1:-}" 38 | if [[ -z "${kernel_version}" ]]; then 39 | echo "Expecting kernel version as first argument" 40 | exit 1 41 | fi 42 | 43 | readonly kernel="linux-${kernel_version}.bz" 44 | readonly output="$(mktemp -d)" 45 | readonly tmp_dir="$(mktemp -d)" 46 | 47 | test -e "${tmp_dir}/${kernel}" || { 48 | echo Fetching ${kernel} 49 | curl --fail -L "https://github.com/newtools/ci-kernels/blob/master/${kernel}?raw=true" -o "${tmp_dir}/${kernel}" 50 | } 51 | 52 | echo Testing on ${kernel_version} 53 | $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 256M --pwd --rwdir=/run/output="${output}" --script-sh "$(realpath "$0") --in-vm /run/output" 54 | 55 | if [[ ! -e "${output}/success" ]]; then 56 | echo "Test failed on ${kernel_version}" 57 | exit 1 58 | else 59 | echo "Test successful on ${kernel_version}" 60 | if [[ -v CODECOV_TOKEN ]]; then 61 | curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" 62 | chmod +x "${tmp_dir}/codecov.sh" 63 | "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt" 64 | fi 65 | fi 66 | 67 | $sudo rm -r "${output}" 68 | -------------------------------------------------------------------------------- /asm/instruction_test.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "io/ioutil" 9 | "math" 10 | "testing" 11 | ) 12 | 13 | var test64bitImmProg = []byte{ 14 | // r0 = math.MinInt32 - 1 15 | 0x18, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x7f, 16 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 17 | } 18 | 19 | func TestRead64bitImmediate(t *testing.T) { 20 | var insns Instructions 21 | _, err := insns.Unmarshal(bytes.NewReader(test64bitImmProg), binary.LittleEndian) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | if len(insns) != 1 { 27 | t.Fatal("Expected one instruction, got", len(insns)) 28 | } 29 | 30 | if c := insns[0].Constant; c != math.MinInt32-1 { 31 | t.Errorf("Expected immediate to be %v, got %v", math.MinInt32-1, c) 32 | } 33 | } 34 | 35 | func TestWrite64bitImmediate(t *testing.T) { 36 | insns := Instructions{ 37 | LoadImm(R0, math.MinInt32-1, DWord), 38 | } 39 | 40 | var buf bytes.Buffer 41 | if err := insns.Marshal(&buf, binary.LittleEndian); err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | if prog := buf.Bytes(); !bytes.Equal(prog, test64bitImmProg) { 46 | t.Errorf("Marshalled program does not match:\n%s", hex.Dump(prog)) 47 | } 48 | } 49 | 50 | func TestSignedJump(t *testing.T) { 51 | insns := Instructions{ 52 | JSGT.Imm(R0, -1, "foo"), 53 | } 54 | 55 | insns[0].Offset = 1 56 | 57 | err := insns.Marshal(ioutil.Discard, binary.LittleEndian) 58 | if err != nil { 59 | t.Error("Can't marshal signed jump:", err) 60 | } 61 | } 62 | 63 | // ExampleInstructions_Format shows the different options available 64 | // to format an instruction stream. 65 | func ExampleInstructions_Format() { 66 | insns := Instructions{ 67 | MapLookupElement.Call().Sym("my_func"), 68 | LoadImm(R0, 42, DWord), 69 | Return(), 70 | } 71 | 72 | fmt.Println("Default format:") 73 | fmt.Printf("%v", insns) 74 | 75 | fmt.Println("Don't indent instructions:") 76 | fmt.Printf("%.0v", insns) 77 | 78 | fmt.Println("Indent using spaces:") 79 | fmt.Printf("% v", insns) 80 | 81 | fmt.Println("Control symbol indentation:") 82 | fmt.Printf("%2v", insns) 83 | 84 | // Output: Default format: 85 | // my_func: 86 | // 0: Call MapLookupElement 87 | // 1: LdImmDW dst: r0 imm: 42 88 | // 3: Exit 89 | // Don't indent instructions: 90 | // my_func: 91 | // 0: Call MapLookupElement 92 | // 1: LdImmDW dst: r0 imm: 42 93 | // 3: Exit 94 | // Indent using spaces: 95 | // my_func: 96 | // 0: Call MapLookupElement 97 | // 1: LdImmDW dst: r0 imm: 42 98 | // 3: Exit 99 | // Control symbol indentation: 100 | // my_func: 101 | // 0: Call MapLookupElement 102 | // 1: LdImmDW dst: r0 imm: 42 103 | // 3: Exit 104 | } 105 | -------------------------------------------------------------------------------- /asm/jump.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | //go:generate stringer -output jump_string.go -type=JumpOp 4 | 5 | // JumpOp affect control flow. 6 | // 7 | // msb lsb 8 | // +----+-+---+ 9 | // |OP |s|cls| 10 | // +----+-+---+ 11 | type JumpOp uint8 12 | 13 | const jumpMask OpCode = aluMask 14 | 15 | const ( 16 | // InvalidJumpOp is returned by getters when invoked 17 | // on non branch OpCodes 18 | InvalidJumpOp JumpOp = 0xff 19 | // Ja jumps by offset unconditionally 20 | Ja JumpOp = 0x00 21 | // JEq jumps by offset if r == imm 22 | JEq JumpOp = 0x10 23 | // JGT jumps by offset if r > imm 24 | JGT JumpOp = 0x20 25 | // JGE jumps by offset if r >= imm 26 | JGE JumpOp = 0x30 27 | // JSet jumps by offset if r & imm 28 | JSet JumpOp = 0x40 29 | // JNE jumps by offset if r != imm 30 | JNE JumpOp = 0x50 31 | // JSGT jumps by offset if signed r > signed imm 32 | JSGT JumpOp = 0x60 33 | // JSGE jumps by offset if signed r >= signed imm 34 | JSGE JumpOp = 0x70 35 | // Call builtin or user defined function from imm 36 | Call JumpOp = 0x80 37 | // Exit ends execution, with value in r0 38 | Exit JumpOp = 0x90 39 | // JLT jumps by offset if r < imm 40 | JLT JumpOp = 0xa0 41 | // JLE jumps by offset if r <= imm 42 | JLE JumpOp = 0xb0 43 | // JSLT jumps by offset if signed r < signed imm 44 | JSLT JumpOp = 0xc0 45 | // JSLE jumps by offset if signed r <= signed imm 46 | JSLE JumpOp = 0xd0 47 | ) 48 | 49 | // Return emits an exit instruction. 50 | // 51 | // Requires a return value in R0. 52 | func Return() Instruction { 53 | return Instruction{ 54 | OpCode: OpCode(JumpClass).SetJumpOp(Exit), 55 | } 56 | } 57 | 58 | // Op returns the OpCode for a given jump source. 59 | func (op JumpOp) Op(source Source) OpCode { 60 | return OpCode(JumpClass).SetJumpOp(op).SetSource(source) 61 | } 62 | 63 | // Imm compares dst to value, and adjusts PC by offset if the condition is fulfilled. 64 | func (op JumpOp) Imm(dst Register, value int32, label string) Instruction { 65 | if op == Exit || op == Call || op == Ja { 66 | return Instruction{OpCode: InvalidOpCode} 67 | } 68 | 69 | return Instruction{ 70 | OpCode: OpCode(JumpClass).SetJumpOp(op).SetSource(ImmSource), 71 | Dst: dst, 72 | Offset: -1, 73 | Constant: int64(value), 74 | Reference: label, 75 | } 76 | } 77 | 78 | // Reg compares dst to src, and adjusts PC by offset if the condition is fulfilled. 79 | func (op JumpOp) Reg(dst, src Register, label string) Instruction { 80 | if op == Exit || op == Call || op == Ja { 81 | return Instruction{OpCode: InvalidOpCode} 82 | } 83 | 84 | return Instruction{ 85 | OpCode: OpCode(JumpClass).SetJumpOp(op).SetSource(RegSource), 86 | Dst: dst, 87 | Src: src, 88 | Offset: -1, 89 | Reference: label, 90 | } 91 | } 92 | 93 | // Label adjusts PC to the address of the label. 94 | func (op JumpOp) Label(label string) Instruction { 95 | if op == Call { 96 | return Instruction{ 97 | OpCode: OpCode(JumpClass).SetJumpOp(Call), 98 | Src: R1, 99 | Constant: -1, 100 | Reference: label, 101 | } 102 | } 103 | 104 | return Instruction{ 105 | OpCode: OpCode(JumpClass).SetJumpOp(op), 106 | Offset: -1, 107 | Reference: label, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nathanjsweet at gmail dot com or i at lmb dot io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /collection_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/newtools/ebpf/asm" 7 | ) 8 | 9 | func TestCollectionSpecNotModified(t *testing.T) { 10 | cs := CollectionSpec{ 11 | Maps: map[string]*MapSpec{ 12 | "my-map": { 13 | Type: Array, 14 | KeySize: 4, 15 | ValueSize: 4, 16 | MaxEntries: 1, 17 | }, 18 | }, 19 | Programs: map[string]*ProgramSpec{ 20 | "test": { 21 | Type: SocketFilter, 22 | Instructions: asm.Instructions{ 23 | asm.LoadMapPtr(asm.R1, 0), 24 | asm.LoadImm(asm.R0, 0, asm.DWord), 25 | asm.Return(), 26 | }, 27 | License: "MIT", 28 | }, 29 | }, 30 | } 31 | 32 | cs.Programs["test"].Instructions[0].Reference = "my-map" 33 | 34 | coll, err := NewCollection(&cs) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | coll.Close() 39 | 40 | if cs.Programs["test"].Instructions[0].Constant != 0 { 41 | t.Error("Creating a collection modifies input spec") 42 | } 43 | } 44 | 45 | func TestCollectionSpecCopy(t *testing.T) { 46 | cs := &CollectionSpec{ 47 | Maps: map[string]*MapSpec{ 48 | "my-map": { 49 | Type: Array, 50 | KeySize: 4, 51 | ValueSize: 4, 52 | MaxEntries: 1, 53 | }, 54 | }, 55 | Programs: map[string]*ProgramSpec{ 56 | "test": { 57 | Type: SocketFilter, 58 | Instructions: asm.Instructions{ 59 | asm.LoadMapPtr(asm.R1, 0), 60 | asm.LoadImm(asm.R0, 0, asm.DWord), 61 | asm.Return(), 62 | }, 63 | License: "MIT", 64 | }, 65 | }, 66 | } 67 | cpy := cs.Copy() 68 | 69 | if cpy == cs { 70 | t.Error("Copy returned the same pointner") 71 | } 72 | 73 | if cpy.Maps["my-map"] == cs.Maps["my-map"] { 74 | t.Error("Copy returned same Maps") 75 | } 76 | 77 | if cpy.Programs["test"] == cs.Programs["test"] { 78 | t.Error("Copy returned same Programs") 79 | } 80 | } 81 | 82 | func TestCollectionSpecOverwriteMaps(t *testing.T) { 83 | insns := asm.Instructions{ 84 | // R1 map 85 | asm.LoadMapPtr(asm.R1, 0), 86 | // R2 key 87 | asm.Mov.Reg(asm.R2, asm.R10), 88 | asm.Add.Imm(asm.R2, -4), 89 | asm.StoreImm(asm.R2, 0, 0, asm.Word), 90 | // Lookup map[0] 91 | asm.MapLookupElement.Call(), 92 | asm.JEq.Imm(asm.R0, 0, "ret"), 93 | asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 94 | asm.Return().Sym("ret"), 95 | } 96 | insns[0].Reference = "test-map" 97 | 98 | cs := &CollectionSpec{ 99 | Maps: map[string]*MapSpec{ 100 | "test-map": { 101 | Type: Array, 102 | KeySize: 4, 103 | ValueSize: 4, 104 | MaxEntries: 1, 105 | }, 106 | }, 107 | Programs: map[string]*ProgramSpec{ 108 | "test-prog": { 109 | Type: SocketFilter, 110 | Instructions: insns, 111 | License: "MIT", 112 | }, 113 | }, 114 | } 115 | 116 | // Override the map with another one 117 | newMap, err := NewMap(cs.Maps["test-map"]) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | defer newMap.Close() 122 | 123 | err = newMap.Put(uint32(0), uint32(2)) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | 128 | err = Edit(&cs.Programs["test-prog"].Instructions).RewriteMap("test-map", newMap) 129 | if err != nil { 130 | t.Fatal(err) 131 | } 132 | 133 | coll, err := NewCollection(cs) 134 | if err != nil { 135 | t.Fatal(err) 136 | } 137 | defer coll.Close() 138 | 139 | oldMap := coll.Maps["test-map"] 140 | err = oldMap.Put(uint32(0), uint32(5)) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | 145 | ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14)) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | t.Log(ret) 151 | 152 | if ret != 2 { 153 | t.Fatal("new / override map not used") 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /asm/alu.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | //go:generate stringer -output alu_string.go -type=Source,Endianness,ALUOp 4 | 5 | // Source of ALU / ALU64 / Branch operations 6 | // 7 | // msb lsb 8 | // +----+-+---+ 9 | // |op |S|cls| 10 | // +----+-+---+ 11 | type Source uint8 12 | 13 | const sourceMask OpCode = 0x08 14 | 15 | // Source bitmask 16 | const ( 17 | // InvalidSource is returned by getters when invoked 18 | // on non ALU / branch OpCodes. 19 | InvalidSource Source = 0xff 20 | // ImmSource src is from constant 21 | ImmSource Source = 0x00 22 | // RegSource src is from register 23 | RegSource Source = 0x08 24 | ) 25 | 26 | // The Endianness of a byte swap instruction. 27 | type Endianness uint8 28 | 29 | const endianMask = sourceMask 30 | 31 | // Endian flags 32 | const ( 33 | InvalidEndian Endianness = 0xff 34 | // Convert to little endian 35 | LE Endianness = 0x00 36 | // Convert to big endian 37 | BE Endianness = 0x08 38 | ) 39 | 40 | // ALUOp are ALU / ALU64 operations 41 | // 42 | // msb lsb 43 | // +----+-+---+ 44 | // |OP |s|cls| 45 | // +----+-+---+ 46 | type ALUOp uint8 47 | 48 | const aluMask OpCode = 0xf0 49 | 50 | const ( 51 | // InvalidALUOp is returned by getters when invoked 52 | // on non ALU OpCodes 53 | InvalidALUOp ALUOp = 0xff 54 | // Add - addition 55 | Add ALUOp = 0x00 56 | // Sub - subtraction 57 | Sub ALUOp = 0x10 58 | // Mul - multiplication 59 | Mul ALUOp = 0x20 60 | // Div - division 61 | Div ALUOp = 0x30 62 | // Or - bitwise or 63 | Or ALUOp = 0x40 64 | // And - bitwise and 65 | And ALUOp = 0x50 66 | // LSh - bitwise shift left 67 | LSh ALUOp = 0x60 68 | // RSh - bitwise shift right 69 | RSh ALUOp = 0x70 70 | // Neg - sign/unsign signing bit 71 | Neg ALUOp = 0x80 72 | // Mod - modulo 73 | Mod ALUOp = 0x90 74 | // Xor - bitwise xor 75 | Xor ALUOp = 0xa0 76 | // Mov - move value from one place to another 77 | Mov ALUOp = 0xb0 78 | // ArSh - arithmatic shift 79 | ArSh ALUOp = 0xc0 80 | // Swap - endian conversions 81 | Swap ALUOp = 0xd0 82 | ) 83 | 84 | // HostTo converts from host to another endianness. 85 | func HostTo(endian Endianness, dst Register, size Size) Instruction { 86 | var imm int64 87 | switch size { 88 | case Half: 89 | imm = 16 90 | case Word: 91 | imm = 32 92 | case DWord: 93 | imm = 64 94 | default: 95 | return Instruction{OpCode: InvalidOpCode} 96 | } 97 | 98 | return Instruction{ 99 | OpCode: OpCode(ALUClass).SetALUOp(Swap).SetSource(Source(endian)), 100 | Dst: dst, 101 | Constant: imm, 102 | } 103 | } 104 | 105 | // Op returns the OpCode for an ALU operation with a given source. 106 | func (op ALUOp) Op(source Source) OpCode { 107 | return OpCode(ALU64Class).SetALUOp(op).SetSource(source) 108 | } 109 | 110 | // Reg emits `dst (op) src`. 111 | func (op ALUOp) Reg(dst, src Register) Instruction { 112 | return Instruction{ 113 | OpCode: op.Op(RegSource), 114 | Dst: dst, 115 | Src: src, 116 | } 117 | } 118 | 119 | // Imm emits `dst (op) value`. 120 | func (op ALUOp) Imm(dst Register, value int32) Instruction { 121 | return Instruction{ 122 | OpCode: op.Op(ImmSource), 123 | Dst: dst, 124 | Constant: int64(value), 125 | } 126 | } 127 | 128 | // Op32 returns the OpCode for a 32-bit ALU operation with a given source. 129 | func (op ALUOp) Op32(source Source) OpCode { 130 | return OpCode(ALUClass).SetALUOp(op).SetSource(source) 131 | } 132 | 133 | // Reg32 emits `dst (op) src`, zeroing the upper 32 bit of dst. 134 | func (op ALUOp) Reg32(dst, src Register) Instruction { 135 | return Instruction{ 136 | OpCode: op.Op32(RegSource), 137 | Dst: dst, 138 | Src: src, 139 | } 140 | } 141 | 142 | // Imm32 emits `dst (op) value`, zeroing the upper 32 bit of dst. 143 | func (op ALUOp) Imm32(dst Register, value int32) Instruction { 144 | return Instruction{ 145 | OpCode: op.Op32(ImmSource), 146 | Dst: dst, 147 | Constant: int64(value), 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /abi.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | // CollectionABI describes the interface of an eBPF collection. 8 | type CollectionABI struct { 9 | Maps map[string]*MapABI 10 | Programs map[string]*ProgramABI 11 | } 12 | 13 | // CheckSpec verifies that all maps and programs mentioned 14 | // in the ABI are present in the spec. 15 | func (abi *CollectionABI) CheckSpec(cs *CollectionSpec) error { 16 | for name := range abi.Maps { 17 | if cs.Maps[name] == nil { 18 | return errors.Errorf("missing map %s", name) 19 | } 20 | } 21 | 22 | for name := range abi.Programs { 23 | if cs.Programs[name] == nil { 24 | return errors.Errorf("missing program %s", name) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | // Check verifies that all items in a collection conform to this ABI. 32 | func (abi *CollectionABI) Check(coll *Collection) error { 33 | for name, mapABI := range abi.Maps { 34 | m := coll.Maps[name] 35 | if m == nil { 36 | return errors.Errorf("missing map %s", name) 37 | } 38 | if err := mapABI.Check(m); err != nil { 39 | return errors.Wrapf(err, "map %s", name) 40 | } 41 | } 42 | 43 | for name, progABI := range abi.Programs { 44 | p := coll.Programs[name] 45 | if p == nil { 46 | return errors.Errorf("missing program %s", name) 47 | } 48 | if err := progABI.Check(p); err != nil { 49 | return errors.Wrapf(err, "program %s", name) 50 | } 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // MapABI describes a Map. 57 | // 58 | // Members which have the zero value of their type 59 | // are not checked. 60 | type MapABI struct { 61 | Type MapType 62 | KeySize uint32 63 | ValueSize uint32 64 | MaxEntries uint32 65 | InnerMap *MapABI 66 | } 67 | 68 | func newMapABIFromSpec(spec *MapSpec) *MapABI { 69 | var inner *MapABI 70 | if spec.InnerMap != nil { 71 | inner = newMapABIFromSpec(spec.InnerMap) 72 | } 73 | 74 | return &MapABI{ 75 | spec.Type, 76 | spec.KeySize, 77 | spec.ValueSize, 78 | spec.MaxEntries, 79 | inner, 80 | } 81 | } 82 | 83 | func newMapABIFromFd(fd *bpfFD) (*MapABI, error) { 84 | info, err := bpfGetMapInfoByFD(fd) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | mapType := MapType(info.mapType) 90 | if mapType == ArrayOfMaps || mapType == HashOfMaps { 91 | return nil, errors.New("can't get map info for nested maps") 92 | } 93 | 94 | return &MapABI{ 95 | mapType, 96 | info.keySize, 97 | info.valueSize, 98 | info.maxEntries, 99 | nil, 100 | }, nil 101 | } 102 | 103 | // Check verifies that a Map conforms to the ABI. 104 | func (abi *MapABI) Check(m *Map) error { 105 | return abi.check(&m.abi) 106 | } 107 | 108 | func (abi *MapABI) check(other *MapABI) error { 109 | if abi.Type != UnspecifiedMap && other.Type != abi.Type { 110 | return errors.Errorf("expected map type %s, have %s", abi.Type, other.Type) 111 | } 112 | if err := checkUint32("key size", abi.KeySize, other.KeySize); err != nil { 113 | return err 114 | } 115 | if err := checkUint32("value size", abi.ValueSize, other.ValueSize); err != nil { 116 | return err 117 | } 118 | if err := checkUint32("max entries", abi.MaxEntries, other.MaxEntries); err != nil { 119 | return err 120 | } 121 | 122 | if abi.InnerMap == nil { 123 | if abi.Type == ArrayOfMaps || abi.Type == HashOfMaps { 124 | return errors.New("missing inner map ABI") 125 | } 126 | 127 | return nil 128 | } 129 | 130 | if other.InnerMap == nil { 131 | return errors.New("missing inner map") 132 | } 133 | 134 | return errors.Wrap(abi.InnerMap.check(other.InnerMap), "inner map") 135 | } 136 | 137 | // ProgramABI describes a Program. 138 | // 139 | // Members which have the zero value of their type 140 | // are not checked. 141 | type ProgramABI struct { 142 | Type ProgType 143 | } 144 | 145 | func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI { 146 | return &ProgramABI{ 147 | spec.Type, 148 | } 149 | } 150 | 151 | func newProgramABIFromFd(fd *bpfFD) (*ProgramABI, error) { 152 | info, err := bpfGetProgInfoByFD(fd) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | return &ProgramABI{ 158 | Type: ProgType(info.progType), 159 | }, nil 160 | } 161 | 162 | // Check verifies that a Program conforms to the ABI. 163 | func (abi *ProgramABI) Check(prog *Program) error { 164 | if abi.Type != Unrecognized && prog.abi.Type != abi.Type { 165 | return errors.Errorf("expected program type %s, have %s", abi.Type, prog.abi.Type) 166 | } 167 | 168 | return nil 169 | } 170 | 171 | func checkUint32(name string, want, have uint32) error { 172 | if want != 0 && have != want { 173 | return errors.Errorf("expected %s to be %d, have %d", name, want, have) 174 | } 175 | return nil 176 | } 177 | -------------------------------------------------------------------------------- /elf_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "path/filepath" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestLoadCollectionSpec(t *testing.T) { 10 | files, err := filepath.Glob("testdata/loader-*.elf") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | for _, file := range files { 16 | t.Run(file, func(t *testing.T) { 17 | spec, err := LoadCollectionSpec(file) 18 | if err != nil { 19 | t.Fatal("Can't parse ELF:", err) 20 | } 21 | 22 | hashMapSpec := &MapSpec{ 23 | "hash_map", 24 | Hash, 25 | 4, 26 | 2, 27 | 1, 28 | 0, 29 | nil, 30 | } 31 | checkMapSpec(t, spec.Maps, "hash_map", hashMapSpec) 32 | checkMapSpec(t, spec.Maps, "array_of_hash_map", &MapSpec{ 33 | "hash_map", ArrayOfMaps, 4, 0, 2, 0, hashMapSpec, 34 | }) 35 | 36 | hashMap2Spec := &MapSpec{ 37 | "", 38 | Hash, 39 | 4, 40 | 1, 41 | 2, 42 | 1, 43 | nil, 44 | } 45 | checkMapSpec(t, spec.Maps, "hash_map2", hashMap2Spec) 46 | checkMapSpec(t, spec.Maps, "hash_of_hash_map", &MapSpec{ 47 | "", HashOfMaps, 4, 0, 2, 0, hashMap2Spec, 48 | }) 49 | 50 | checkProgramSpec(t, spec.Programs, "xdp_prog", &ProgramSpec{ 51 | Type: XDP, 52 | License: "MIT", 53 | KernelVersion: 0, 54 | }) 55 | checkProgramSpec(t, spec.Programs, "no_relocation", &ProgramSpec{ 56 | Type: SocketFilter, 57 | License: "MIT", 58 | KernelVersion: 0, 59 | }) 60 | 61 | t.Log(spec.Programs["xdp_prog"].Instructions) 62 | 63 | coll, err := NewCollection(spec) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | defer coll.Close() 68 | 69 | hash := coll.DetachMap("hash_map") 70 | if hash == nil { 71 | t.Fatal("Program not returned from DetachMap") 72 | } 73 | defer hash.Close() 74 | 75 | if _, ok := coll.Programs["hash_map"]; ok { 76 | t.Error("DetachMap doesn't remove map from Maps") 77 | } 78 | 79 | prog := coll.DetachProgram("xdp_prog") 80 | if prog == nil { 81 | t.Fatal("Program not returned from DetachProgram") 82 | } 83 | defer prog.Close() 84 | 85 | if _, ok := coll.Programs["xdp_prog"]; ok { 86 | t.Error("DetachProgram doesn't remove program from Programs") 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func checkMapSpec(t *testing.T, maps map[string]*MapSpec, name string, want *MapSpec) { 93 | t.Helper() 94 | 95 | have, ok := maps[name] 96 | if !ok { 97 | t.Errorf("Missing map %s", name) 98 | return 99 | } 100 | 101 | mapSpecEqual(t, name, have, want) 102 | } 103 | 104 | func mapSpecEqual(t *testing.T, name string, have, want *MapSpec) { 105 | t.Helper() 106 | 107 | if have.Type != want.Type { 108 | t.Errorf("%s: expected type %v, got %v", name, want.Type, have.Type) 109 | } 110 | 111 | if have.KeySize != want.KeySize { 112 | t.Errorf("%s: expected key size %v, got %v", name, want.KeySize, have.KeySize) 113 | } 114 | 115 | if have.ValueSize != want.ValueSize { 116 | t.Errorf("%s: expected value size %v, got %v", name, want.ValueSize, have.ValueSize) 117 | } 118 | 119 | if have.MaxEntries != want.MaxEntries { 120 | t.Errorf("%s: expected max entries %v, got %v", name, want.MaxEntries, have.MaxEntries) 121 | } 122 | 123 | if have.Flags != want.Flags { 124 | t.Errorf("%s: expected flags %v, got %v", name, want.Flags, have.Flags) 125 | } 126 | 127 | switch { 128 | case have.InnerMap != nil && want.InnerMap == nil: 129 | t.Errorf("%s: extraneous InnerMap", name) 130 | case have.InnerMap == nil && want.InnerMap != nil: 131 | t.Errorf("%s: missing InnerMap", name) 132 | case have.InnerMap != nil && want.InnerMap != nil: 133 | mapSpecEqual(t, name+".InnerMap", have.InnerMap, want.InnerMap) 134 | } 135 | } 136 | 137 | func checkProgramSpec(t *testing.T, progs map[string]*ProgramSpec, name string, want *ProgramSpec) { 138 | t.Helper() 139 | 140 | have, ok := progs[name] 141 | if !ok { 142 | t.Fatalf("Missing program %s", name) 143 | return 144 | } 145 | 146 | if have.License != want.License { 147 | t.Errorf("%s: expected %v license, got %v", name, want.License, have.License) 148 | } 149 | 150 | if have.Type != want.Type { 151 | t.Errorf("%s: expected %v program, got %v", name, want.Type, have.Type) 152 | } 153 | 154 | if want.Instructions != nil && !reflect.DeepEqual(have.Instructions, want.Instructions) { 155 | t.Log("Expected program") 156 | t.Log(want.Instructions) 157 | t.Log("Actual program") 158 | t.Log(want.Instructions) 159 | t.Error("Instructions do not match") 160 | } 161 | } 162 | 163 | func TestLoadInvalidMap(t *testing.T) { 164 | _, err := LoadCollectionSpec("testdata/invalid_map.elf") 165 | t.Log(err) 166 | if err == nil { 167 | t.Fatal("should be fail") 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /asm/load_store.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | //go:generate stringer -output load_store_string.go -type=Mode,Size 4 | 5 | // Mode for load and store operations 6 | // 7 | // msb lsb 8 | // +---+--+---+ 9 | // |MDE|sz|cls| 10 | // +---+--+---+ 11 | type Mode uint8 12 | 13 | const modeMask OpCode = 0xe0 14 | 15 | const ( 16 | // InvalidMode is returned by getters when invoked 17 | // on non load / store OpCodes 18 | InvalidMode Mode = 0xff 19 | // ImmMode - immediate value 20 | ImmMode Mode = 0x00 21 | // AbsMode - immediate value + offset 22 | AbsMode Mode = 0x20 23 | // IndMode - indirect (imm+src) 24 | IndMode Mode = 0x40 25 | // MemMode - load from memory 26 | MemMode Mode = 0x60 27 | // XAddMode - add atomically across processors. 28 | XAddMode Mode = 0xc0 29 | ) 30 | 31 | // Size of load and store operations 32 | // 33 | // msb lsb 34 | // +---+--+---+ 35 | // |mde|SZ|cls| 36 | // +---+--+---+ 37 | type Size uint8 38 | 39 | const sizeMask OpCode = 0x18 40 | 41 | const ( 42 | // InvalidSize is returned by getters when invoked 43 | // on non load / store OpCodes 44 | InvalidSize Size = 0xff 45 | // DWord - double word; 64 bits 46 | DWord Size = 0x18 47 | // Word - word; 32 bits 48 | Word Size = 0x00 49 | // Half - half-word; 16 bits 50 | Half Size = 0x08 51 | // Byte - byte; 8 bits 52 | Byte Size = 0x10 53 | ) 54 | 55 | // Sizeof returns the size in bytes. 56 | func (s Size) Sizeof() int { 57 | switch s { 58 | case DWord: 59 | return 8 60 | case Word: 61 | return 4 62 | case Half: 63 | return 2 64 | case Byte: 65 | return 1 66 | default: 67 | return -1 68 | } 69 | } 70 | 71 | // LoadMemOp returns the OpCode to load a value of given size from memory. 72 | func LoadMemOp(size Size) OpCode { 73 | return OpCode(LdXClass).SetMode(MemMode).SetSize(size) 74 | } 75 | 76 | // LoadMem emits `dst = *(size *)(src + offset)`. 77 | func LoadMem(dst, src Register, offset int16, size Size) Instruction { 78 | return Instruction{ 79 | OpCode: LoadMemOp(size), 80 | Dst: dst, 81 | Src: src, 82 | Offset: offset, 83 | } 84 | } 85 | 86 | // LoadImmOp returns the OpCode to load an immediate of given size. 87 | // 88 | // As of kernel 4.20, only DWord size is accepted. 89 | func LoadImmOp(size Size) OpCode { 90 | return OpCode(LdClass).SetMode(ImmMode).SetSize(size) 91 | } 92 | 93 | // LoadImm emits `dst = (size)value`. 94 | // 95 | // As of kernel 4.20, only DWord size is accepted. 96 | func LoadImm(dst Register, value int64, size Size) Instruction { 97 | return Instruction{ 98 | OpCode: LoadImmOp(size), 99 | Dst: dst, 100 | Constant: value, 101 | } 102 | } 103 | 104 | // LoadMapPtr stores a pointer to a map in dst. 105 | func LoadMapPtr(dst Register, fd int) Instruction { 106 | return Instruction{ 107 | OpCode: LoadImmOp(DWord), 108 | Dst: dst, 109 | Src: R1, 110 | Constant: int64(fd), 111 | } 112 | } 113 | 114 | // LoadIndOp returns the OpCode for loading a value of given size from an sk_buff. 115 | func LoadIndOp(size Size) OpCode { 116 | return OpCode(LdClass).SetMode(IndMode).SetSize(size) 117 | } 118 | 119 | // LoadInd emits `dst = ntoh(*(size *)(((sk_buff *)R6)->data + src + offset))`. 120 | func LoadInd(dst, src Register, offset int32, size Size) Instruction { 121 | return Instruction{ 122 | OpCode: LoadIndOp(size), 123 | Dst: dst, 124 | Src: src, 125 | Constant: int64(offset), 126 | } 127 | } 128 | 129 | // LoadAbsOp returns the OpCode for loading a value of given size from an sk_buff. 130 | func LoadAbsOp(size Size) OpCode { 131 | return OpCode(LdClass).SetMode(AbsMode).SetSize(size) 132 | } 133 | 134 | // LoadAbs emits `r0 = ntoh(*(size *)(((sk_buff *)R6)->data + offset))`. 135 | func LoadAbs(offset int32, size Size) Instruction { 136 | return Instruction{ 137 | OpCode: LoadAbsOp(size), 138 | Dst: R0, 139 | Constant: int64(offset), 140 | } 141 | } 142 | 143 | // StoreMemOp returns the OpCode for storing a register of given size in memory. 144 | func StoreMemOp(size Size) OpCode { 145 | return OpCode(StXClass).SetMode(MemMode).SetSize(size) 146 | } 147 | 148 | // StoreMem emits `*(size *)(dst + offset) = src` 149 | func StoreMem(dst Register, offset int16, src Register, size Size) Instruction { 150 | return Instruction{ 151 | OpCode: StoreMemOp(size), 152 | Dst: dst, 153 | Src: src, 154 | Offset: offset, 155 | } 156 | } 157 | 158 | // StoreImmOp returns the OpCode for storing an immediate of given size in memory. 159 | func StoreImmOp(size Size) OpCode { 160 | return OpCode(StClass).SetMode(MemMode).SetSize(size) 161 | } 162 | 163 | // StoreImm emits `*(size *)(dst + offset) = value`. 164 | func StoreImm(dst Register, offset int16, value int64, size Size) Instruction { 165 | return Instruction{ 166 | OpCode: StoreImmOp(size), 167 | Dst: dst, 168 | Offset: offset, 169 | Constant: value, 170 | } 171 | } 172 | 173 | // XAddOp returns the OpCode to atomically add a register to a value in memory. 174 | func XAddOp(size Size) OpCode { 175 | return OpCode(StXClass).SetMode(XAddMode).SetSize(size) 176 | } 177 | 178 | // XAdd atomically adds src to *dst. 179 | func XAdd(dst, src Register, size Size) Instruction { 180 | return Instruction{ 181 | OpCode: XAddOp(size), 182 | Dst: dst, 183 | Src: src, 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /editor.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/newtools/ebpf/asm" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // Editor modifies eBPF instructions. 11 | type Editor struct { 12 | instructions *asm.Instructions 13 | ReferenceOffsets map[string][]int 14 | } 15 | 16 | // Edit creates a new Editor. 17 | // 18 | // The editor retains a reference to insns and modifies its 19 | // contents. 20 | func Edit(insns *asm.Instructions) *Editor { 21 | refs := insns.ReferenceOffsets() 22 | return &Editor{insns, refs} 23 | } 24 | 25 | // RewriteMap rewrites a symbol to point at a Map. 26 | // 27 | // Use IsUnreferencedSymbol if you want to rewrite potentially 28 | // unused maps. 29 | func (ed *Editor) RewriteMap(symbol string, m *Map) error { 30 | return ed.rewriteMap(symbol, m, true) 31 | } 32 | 33 | func (ed *Editor) rewriteMap(symbol string, m *Map, overwrite bool) error { 34 | indices := ed.ReferenceOffsets[symbol] 35 | if len(indices) == 0 { 36 | return &unreferencedSymbolError{symbol} 37 | } 38 | 39 | fd, err := m.fd.value() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | loadOp := asm.LoadImmOp(asm.DWord) 45 | 46 | for _, index := range indices { 47 | load := &(*ed.instructions)[index] 48 | if load.OpCode != loadOp { 49 | return errors.Errorf("symbol %v: missing load instruction", symbol) 50 | } 51 | 52 | if !overwrite && load.Constant != 0 { 53 | return nil 54 | } 55 | 56 | load.Src = 1 57 | load.Constant = int64(fd) 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // RewriteConstant rewrites all loads of a symbol to a constant value. 64 | // 65 | // This is a way to parameterize clang-compiled eBPF byte code at load 66 | // time. 67 | // 68 | // The following macro should be used to access the constant: 69 | // 70 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 71 | // 72 | // int xdp() { 73 | // bool my_constant; 74 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 75 | // 76 | // if (my_constant) ... 77 | // 78 | // Caveats: 79 | // - The symbol name you pick must be unique 80 | // 81 | // - Failing to rewrite a symbol will not result in an error, 82 | // 0 will be loaded instead (subject to change) 83 | // 84 | // Use IsUnreferencedSymbol if you want to rewrite potentially 85 | // unused symbols. 86 | func (ed *Editor) RewriteConstant(symbol string, value uint64) error { 87 | indices := ed.ReferenceOffsets[symbol] 88 | if len(indices) == 0 { 89 | return &unreferencedSymbolError{symbol} 90 | } 91 | 92 | ldDWImm := asm.LoadImmOp(asm.DWord) 93 | for _, index := range indices { 94 | load := &(*ed.instructions)[index] 95 | if load.OpCode != ldDWImm { 96 | return errors.Errorf("symbol %v: load: found %v instead of %v", symbol, load.OpCode, ldDWImm) 97 | } 98 | 99 | load.Constant = int64(value) 100 | } 101 | return nil 102 | } 103 | 104 | // Link resolves bpf-to-bpf calls. 105 | // 106 | // Each section may contain multiple functions / labels, and is only linked 107 | // if the program being edited references one of these functions. 108 | // 109 | // Sections must not require linking themselves. 110 | func (ed *Editor) Link(sections ...asm.Instructions) error { 111 | sections = append(sections, *ed.instructions) 112 | 113 | // A map of symbols to the libraries which contain them. 114 | symbols := make(map[string]*asm.Instructions) 115 | for i, section := range sections { 116 | offsets, err := section.SymbolOffsets() 117 | if err != nil { 118 | return err 119 | } 120 | for symbol := range offsets { 121 | if symbols[symbol] != nil { 122 | return errors.Errorf("symbol %s is present in multiple sections", symbol) 123 | } 124 | symbols[symbol] = §ions[i] 125 | } 126 | } 127 | 128 | // Appending to ed.instructions would invalidate the pointers in 129 | // ed, so instead we append to a new slice and join them at the end. 130 | var linkedInsns asm.Instructions 131 | 132 | // A list of already linked sections to avoid linking multiple times. 133 | linkedSections := map[*asm.Instructions]struct{}{ 134 | ed.instructions: struct{}{}, 135 | } 136 | 137 | for symbol, indices := range ed.ReferenceOffsets { 138 | for _, index := range indices { 139 | ins := &(*ed.instructions)[index] 140 | 141 | if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.R1 { 142 | continue 143 | } 144 | 145 | if ins.Constant != -1 { 146 | // This is already a valid call, no need to link again. 147 | continue 148 | } 149 | 150 | section := symbols[symbol] 151 | if section == nil { 152 | return errors.Errorf("symbol %s missing from libaries", symbol) 153 | } 154 | 155 | if _, ok := linkedSections[section]; !ok { 156 | linkedInsns = append(linkedInsns, *section...) 157 | linkedSections[section] = struct{}{} 158 | } 159 | } 160 | } 161 | 162 | // ed.instructions has been fixed up. Append linked instructions and 163 | // recalculate ed. 164 | *ed.instructions = append(*ed.instructions, linkedInsns...) 165 | *ed = *Edit(ed.instructions) 166 | return nil 167 | } 168 | 169 | type unreferencedSymbolError struct { 170 | symbol string 171 | } 172 | 173 | func (use *unreferencedSymbolError) Error() string { 174 | return fmt.Sprintf("unreferenced symbol %s", use.symbol) 175 | } 176 | 177 | // IsUnreferencedSymbol returns true if err was caused by 178 | // an unreferenced symbol. 179 | func IsUnreferencedSymbol(err error) bool { 180 | _, ok := err.(*unreferencedSymbolError) 181 | return ok 182 | } 183 | -------------------------------------------------------------------------------- /abi_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCollectionABI(t *testing.T) { 8 | cabi := &CollectionABI{ 9 | Maps: map[string]*MapABI{ 10 | "a": { 11 | Type: ArrayOfMaps, 12 | KeySize: 4, 13 | ValueSize: 2, 14 | MaxEntries: 3, 15 | InnerMap: &MapABI{ 16 | Type: Array, 17 | }, 18 | }, 19 | }, 20 | Programs: map[string]*ProgramABI{ 21 | "1": {Type: SocketFilter}, 22 | }, 23 | } 24 | 25 | if err := cabi.CheckSpec(abiFixtureCollectionSpec()); err != nil { 26 | t.Error("ABI check found error:", err) 27 | } 28 | 29 | cs := abiFixtureCollectionSpec() 30 | delete(cs.Maps, "a") 31 | if err := cabi.CheckSpec(cs); err == nil { 32 | t.Error("Did not detect missing map") 33 | } 34 | 35 | cs = abiFixtureCollectionSpec() 36 | delete(cs.Programs, "1") 37 | if err := cabi.CheckSpec(cs); err == nil { 38 | t.Error("Did not detect missing program") 39 | } 40 | 41 | if err := cabi.Check(abiFixtureCollection()); err != nil { 42 | t.Error("ABI check found error:", err) 43 | 44 | } 45 | 46 | coll := abiFixtureCollection() 47 | coll.Maps["a"].abi.KeySize = 12 48 | if err := cabi.Check(coll); err == nil { 49 | t.Error("Check not check map ABI") 50 | } 51 | 52 | delete(coll.Maps, "a") 53 | if err := cabi.Check(coll); err == nil { 54 | t.Error("Check did not detect missing map") 55 | } 56 | 57 | coll = abiFixtureCollection() 58 | coll.Programs["1"].abi.Type = TracePoint 59 | if err := cabi.Check(coll); err == nil { 60 | t.Error("Did not check program ABI") 61 | } 62 | 63 | delete(coll.Programs, "1") 64 | if err := cabi.Check(coll); err == nil { 65 | t.Error("Check did not detect missing program") 66 | } 67 | } 68 | 69 | func TestMapABI(t *testing.T) { 70 | mabi := &MapABI{ 71 | Type: ArrayOfMaps, 72 | KeySize: 4, 73 | ValueSize: 2, 74 | MaxEntries: 3, 75 | InnerMap: &MapABI{ 76 | Type: Array, 77 | }, 78 | } 79 | 80 | if err := mabi.Check(abiFixtureMap()); err != nil { 81 | t.Error("ABI check found error:", err) 82 | } 83 | 84 | fm := abiFixtureMap() 85 | fm.abi.Type = Hash 86 | if err := mabi.Check(fm); err == nil { 87 | t.Error("Did not detect incorrect type") 88 | } 89 | 90 | fm = abiFixtureMap() 91 | fm.abi.KeySize = 3 92 | if err := mabi.Check(fm); err == nil { 93 | t.Error("Did not detect incorrect key size") 94 | } 95 | 96 | fm = abiFixtureMap() 97 | fm.abi.ValueSize = 23 98 | if err := mabi.Check(fm); err == nil { 99 | t.Error("Did not detect incorrect value size") 100 | } 101 | 102 | fm = abiFixtureMap() 103 | fm.abi.MaxEntries = 23 104 | if err := mabi.Check(fm); err == nil { 105 | t.Error("Did not detect incorrect max entries") 106 | } 107 | 108 | fm = abiFixtureMap() 109 | mabi.InnerMap.Type = Hash 110 | if err := mabi.Check(fm); err == nil { 111 | t.Error("Did not detect incorrect inner map type") 112 | } 113 | 114 | fm = abiFixtureMap() 115 | mabi.InnerMap = nil 116 | if err := mabi.Check(fm); err == nil { 117 | t.Error("Did not detect missing inner map ABI") 118 | } 119 | } 120 | 121 | func TestProgramABI(t *testing.T) { 122 | fabi := &ProgramABI{Type: SocketFilter} 123 | 124 | if err := fabi.Check(abiFixtureProgram()); err != nil { 125 | t.Error("ABI check found error:", err) 126 | } 127 | 128 | fp := abiFixtureProgram() 129 | fp.abi.Type = TracePoint 130 | if err := fabi.Check(fp); err == nil { 131 | t.Error("Did not detect incorrect type") 132 | } 133 | } 134 | 135 | func abiFixtureCollectionSpec() *CollectionSpec { 136 | return &CollectionSpec{ 137 | Maps: map[string]*MapSpec{ 138 | "a": abiFixtureMapSpec(), 139 | }, 140 | Programs: map[string]*ProgramSpec{ 141 | "1": abiFixtureProgramSpec(), 142 | }, 143 | } 144 | } 145 | 146 | func abiFixtureCollection() *Collection { 147 | return &Collection{ 148 | Maps: map[string]*Map{ 149 | "a": abiFixtureMap(), 150 | }, 151 | Programs: map[string]*Program{ 152 | "1": abiFixtureProgram(), 153 | }, 154 | } 155 | } 156 | 157 | func abiFixtureMapSpec() *MapSpec { 158 | return &MapSpec{ 159 | Type: ArrayOfMaps, 160 | KeySize: 4, 161 | ValueSize: 2, 162 | MaxEntries: 3, 163 | InnerMap: &MapSpec{ 164 | Type: Array, 165 | KeySize: 2, 166 | }, 167 | } 168 | } 169 | 170 | func abiFixtureMap() *Map { 171 | return &Map{ 172 | abi: *newMapABIFromSpec(abiFixtureMapSpec()), 173 | } 174 | } 175 | 176 | func abiFixtureProgramSpec() *ProgramSpec { 177 | return &ProgramSpec{ 178 | Type: SocketFilter, 179 | } 180 | } 181 | 182 | func abiFixtureProgram() *Program { 183 | return &Program{ 184 | abi: *newProgramABIFromSpec(abiFixtureProgramSpec()), 185 | } 186 | } 187 | 188 | func ExampleCollectionABI() { 189 | abi := CollectionABI{ 190 | Maps: map[string]*MapABI{ 191 | "a": { 192 | Type: Array, 193 | // Members which aren't specified are not checked 194 | }, 195 | // Use an empty ABI if you just want to make sure 196 | // something is present. 197 | "b": {}, 198 | }, 199 | Programs: map[string]*ProgramABI{ 200 | "1": {Type: XDP}, 201 | }, 202 | } 203 | 204 | spec, err := LoadCollectionSpec("from-somewhere.elf") 205 | if err != nil { 206 | panic(err) 207 | } 208 | 209 | // CheckSpec only makes sure that all entries of the ABI 210 | // are present. It doesn't check whether the ABI is correct. 211 | // See below for how to do that. 212 | if err := abi.CheckSpec(spec); err != nil { 213 | panic(err) 214 | } 215 | 216 | coll, err := NewCollection(spec) 217 | if err != nil { 218 | panic(err) 219 | } 220 | 221 | // Check finally compares the ABI and the collection, and 222 | // makes sure that they match. 223 | if err := abi.Check(coll); err != nil { 224 | panic(err) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /example_sock_extract_dist_test.go: -------------------------------------------------------------------------------- 1 | package ebpf_test 2 | 3 | // This code is derived from https://github.com/cloudflare/cloudflare-blog/tree/master/2018-03-ebpf 4 | // 5 | // Copyright (c) 2015-2017 Cloudflare, Inc. All rights reserved. 6 | // 7 | // Redistribution and use in source and binary forms, with or without 8 | // modification, are permitted provided that the following conditions are 9 | // met: 10 | // 11 | // * Redistributions of source code must retain the above copyright 12 | // notice, this list of conditions and the following disclaimer. 13 | // * Redistributions in binary form must reproduce the above 14 | // copyright notice, this list of conditions and the following disclaimer 15 | // in the documentation and/or other materials provided with the 16 | // distribution. 17 | // * Neither the name of the Cloudflare, Inc. nor the names of its 18 | // contributors may be used to endorse or promote products derived from 19 | // this software without specific prior written permission. 20 | // 21 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | import ( 34 | "fmt" 35 | "net" 36 | "syscall" 37 | 38 | "github.com/newtools/ebpf" 39 | "github.com/newtools/ebpf/asm" 40 | ) 41 | 42 | // ExampleExtractDistance shows how to attach an eBPF socket filter to 43 | // extract the network distance of an IP host. 44 | func Example_extractDistance() { 45 | filter, TTLs, err := newDistanceFilter() 46 | if err != nil { 47 | panic(err) 48 | } 49 | defer filter.Close() 50 | defer TTLs.Close() 51 | 52 | // Attach filter before the call to connect() 53 | dialer := net.Dialer{ 54 | Control: func(network, address string, c syscall.RawConn) (err error) { 55 | const SO_ATTACH_BPF = 50 56 | 57 | err = c.Control(func(fd uintptr) { 58 | err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_ATTACH_BPF, filter.FD()) 59 | }) 60 | return err 61 | }, 62 | } 63 | 64 | conn, err := dialer.Dial("tcp", "1.1.1.1:53") 65 | if err != nil { 66 | panic(err) 67 | } 68 | conn.Close() 69 | 70 | minDist, err := minDistance(TTLs) 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | fmt.Println("1.1.1.1:53 is", minDist, "hops away") 76 | } 77 | 78 | func newDistanceFilter() (*ebpf.Program, *ebpf.Map, error) { 79 | const ETH_P_IPV6 uint16 = 0x86DD 80 | 81 | ttls, err := ebpf.NewMap(&ebpf.MapSpec{ 82 | Type: ebpf.Hash, 83 | KeySize: 4, 84 | ValueSize: 8, 85 | MaxEntries: 4, 86 | }) 87 | if err != nil { 88 | return nil, nil, err 89 | } 90 | 91 | insns := asm.Instructions{ 92 | // r1 has ctx 93 | // r0 = ctx[16] (aka protocol) 94 | asm.LoadMem(asm.R0, asm.R1, 16, asm.Word), 95 | 96 | // Perhaps ipv6 97 | asm.LoadImm(asm.R2, int64(ETH_P_IPV6), asm.DWord), 98 | asm.HostTo(asm.BE, asm.R2, asm.Half), 99 | asm.JEq.Reg(asm.R0, asm.R2, "ipv6"), 100 | 101 | // otherwise assume ipv4 102 | // 8th byte in IPv4 is TTL 103 | // LDABS requires ctx in R6 104 | asm.Mov.Reg(asm.R6, asm.R1), 105 | asm.LoadAbs(-0x100000+8, asm.Byte), 106 | asm.Ja.Label("store-ttl"), 107 | 108 | // 7th byte in IPv6 is Hop count 109 | // LDABS requires ctx in R6 110 | asm.Mov.Reg(asm.R6, asm.R1).Sym("ipv6"), 111 | asm.LoadAbs(-0x100000+7, asm.Byte), 112 | 113 | // stash the load result into FP[-4] 114 | asm.StoreMem(asm.RFP, -4, asm.R0, asm.Word).Sym("store-ttl"), 115 | // stash the &FP[-4] into r2 116 | asm.Mov.Reg(asm.R2, asm.RFP), 117 | asm.Add.Imm(asm.R2, -4), 118 | 119 | // r1 must point to map 120 | asm.LoadMapPtr(asm.R1, ttls.FD()), 121 | asm.MapLookupElement.Call(), 122 | 123 | // load ok? inc. Otherwise? jmp to mapupdate 124 | asm.JEq.Imm(asm.R0, 0, "update-map"), 125 | asm.Mov.Imm(asm.R1, 1), 126 | asm.XAdd(asm.R0, asm.R1, asm.DWord), 127 | asm.Ja.Label("exit"), 128 | 129 | // MapUpdate 130 | // r1 has map ptr 131 | asm.LoadMapPtr(asm.R1, ttls.FD()).Sym("update-map"), 132 | // r2 has key -> &FP[-4] 133 | asm.Mov.Reg(asm.R2, asm.RFP), 134 | asm.Add.Imm(asm.R2, -4), 135 | // r3 has value -> &FP[-16] , aka 1 136 | asm.StoreImm(asm.RFP, -16, 1, asm.DWord), 137 | asm.Mov.Reg(asm.R3, asm.RFP), 138 | asm.Add.Imm(asm.R3, -16), 139 | // r4 has flags, 0 140 | asm.Mov.Imm(asm.R4, 0), 141 | asm.MapUpdateElement.Call(), 142 | 143 | // set exit code to -1, don't trunc packet 144 | asm.Mov.Imm(asm.R0, -1).Sym("exit"), 145 | asm.Return(), 146 | } 147 | 148 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 149 | Name: "distance_filter", 150 | Type: ebpf.SocketFilter, 151 | License: "GPL", 152 | Instructions: insns, 153 | }) 154 | if err != nil { 155 | ttls.Close() 156 | return nil, nil, err 157 | } 158 | 159 | return prog, ttls, nil 160 | } 161 | 162 | func minDistance(TTLs *ebpf.Map) (int, error) { 163 | var ( 164 | entries = TTLs.Iterate() 165 | ttl uint32 166 | minDist uint32 = 255 167 | count uint64 168 | ) 169 | for entries.Next(&ttl, &count) { 170 | var dist uint32 171 | switch { 172 | case ttl > 128: 173 | dist = 255 - ttl 174 | case ttl > 64: 175 | dist = 128 - ttl 176 | case ttl > 32: 177 | dist = 64 - ttl 178 | default: 179 | dist = 32 - ttl 180 | } 181 | if minDist > dist { 182 | minDist = dist 183 | } 184 | } 185 | return int(minDist), entries.Err() 186 | } 187 | -------------------------------------------------------------------------------- /editor_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/newtools/ebpf/asm" 9 | ) 10 | 11 | // ExampleEditor_rewriteConstant shows how to change constants in 12 | // compiled eBPF byte code. 13 | // 14 | // The C should look something like this: 15 | // 16 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 17 | // 18 | // int xdp() { 19 | // bool my_constant; 20 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 21 | // 22 | // if (my_constant) ... 23 | func ExampleEditor_rewriteConstant() { 24 | // This assembly is roughly equivalent to what clang 25 | // would emit for the C above. 26 | insns := asm.Instructions{ 27 | asm.LoadImm(asm.R0, 0, asm.DWord), 28 | asm.Return(), 29 | } 30 | 31 | insns[0].Reference = "my_ret" 32 | 33 | editor := Edit(&insns) 34 | if err := editor.RewriteConstant("my_ret", 42); err != nil { 35 | panic(err) 36 | } 37 | 38 | fmt.Printf("%0.0s", insns) 39 | 40 | // Output: 0: LdImmDW dst: r0 imm: 42 41 | // 2: Exit 42 | } 43 | 44 | func TestEditorRewriteConstant(t *testing.T) { 45 | spec, err := LoadCollectionSpec("testdata/rewrite.elf") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | progSpec := spec.Programs["rewrite"] 51 | editor := Edit(&progSpec.Instructions) 52 | 53 | if err := editor.RewriteConstant("constant", 0x01); err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if err := editor.RewriteConstant("bogus", 0x01); !IsUnreferencedSymbol(err) { 58 | t.Error("Rewriting unreferenced symbol doesn't return appropriate error") 59 | } 60 | 61 | t.Log(progSpec.Instructions) 62 | 63 | prog, err := NewProgram(progSpec) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | defer prog.Close() 68 | 69 | ret, _, err := prog.Test(make([]byte, 14)) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | const N = 1 // number of rewrites 75 | if expected := uint32(1<di) 101 | #define PT_REGS_PARM2(x) ((x)->si) 102 | #define PT_REGS_PARM3(x) ((x)->dx) 103 | #define PT_REGS_PARM4(x) ((x)->cx) 104 | #define PT_REGS_PARM5(x) ((x)->r8) 105 | #define PT_REGS_RET(x) ((x)->sp) 106 | #define PT_REGS_FP(x) ((x)->bp) 107 | #define PT_REGS_RC(x) ((x)->ax) 108 | #define PT_REGS_SP(x) ((x)->sp) 109 | #define PT_REGS_IP(x) ((x)->ip) 110 | 111 | #elif defined(__s390x__) 112 | 113 | #define PT_REGS_PARM1(x) ((x)->gprs[2]) 114 | #define PT_REGS_PARM2(x) ((x)->gprs[3]) 115 | #define PT_REGS_PARM3(x) ((x)->gprs[4]) 116 | #define PT_REGS_PARM4(x) ((x)->gprs[5]) 117 | #define PT_REGS_PARM5(x) ((x)->gprs[6]) 118 | #define PT_REGS_RET(x) ((x)->gprs[14]) 119 | #define PT_REGS_FP(x) ((x)->gprs[11]) /* Works only with CONFIG_FRAME_POINTER */ 120 | #define PT_REGS_RC(x) ((x)->gprs[2]) 121 | #define PT_REGS_SP(x) ((x)->gprs[15]) 122 | #define PT_REGS_IP(x) ((x)->psw.addr) 123 | 124 | #elif defined(__aarch64__) 125 | 126 | #define PT_REGS_PARM1(x) ((x)->regs[0]) 127 | #define PT_REGS_PARM2(x) ((x)->regs[1]) 128 | #define PT_REGS_PARM3(x) ((x)->regs[2]) 129 | #define PT_REGS_PARM4(x) ((x)->regs[3]) 130 | #define PT_REGS_PARM5(x) ((x)->regs[4]) 131 | #define PT_REGS_RET(x) ((x)->regs[30]) 132 | #define PT_REGS_FP(x) ((x)->regs[29]) /* Works only with CONFIG_FRAME_POINTER */ 133 | #define PT_REGS_RC(x) ((x)->regs[0]) 134 | #define PT_REGS_SP(x) ((x)->sp) 135 | #define PT_REGS_IP(x) ((x)->pc) 136 | 137 | #elif defined(__powerpc__) 138 | 139 | #define PT_REGS_PARM1(x) ((x)->gpr[3]) 140 | #define PT_REGS_PARM2(x) ((x)->gpr[4]) 141 | #define PT_REGS_PARM3(x) ((x)->gpr[5]) 142 | #define PT_REGS_PARM4(x) ((x)->gpr[6]) 143 | #define PT_REGS_PARM5(x) ((x)->gpr[7]) 144 | #define PT_REGS_RC(x) ((x)->gpr[3]) 145 | #define PT_REGS_SP(x) ((x)->sp) 146 | #define PT_REGS_IP(x) ((x)->nip) 147 | 148 | #endif 149 | 150 | #ifdef __powerpc__ 151 | #define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; }) 152 | #define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP 153 | #else 154 | #define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ \ 155 | bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); }) 156 | #define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ \ 157 | bpf_probe_read(&(ip), sizeof(ip), \ 158 | (void *)(PT_REGS_FP(ctx) + sizeof(ip))); }) 159 | #endif 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /marshalers.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "encoding" 6 | "encoding/binary" 7 | "fmt" 8 | "os" 9 | "reflect" 10 | "runtime" 11 | "sync" 12 | "unsafe" 13 | 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | var nativeEndian binary.ByteOrder 18 | 19 | func init() { 20 | if isBigEndian() { 21 | nativeEndian = binary.BigEndian 22 | } else { 23 | nativeEndian = binary.LittleEndian 24 | } 25 | } 26 | 27 | func isBigEndian() (ret bool) { 28 | i := int(0x1) 29 | bs := (*[int(unsafe.Sizeof(i))]byte)(unsafe.Pointer(&i)) 30 | return bs[0] == 0 31 | } 32 | 33 | // Marshaler allows controlling the binary representation used for getting 34 | // and setting keys on a map. 35 | type Marshaler interface { 36 | encoding.BinaryMarshaler 37 | encoding.BinaryUnmarshaler 38 | } 39 | 40 | func marshalPtr(data interface{}, length int) (syscallPtr, error) { 41 | if ptr, ok := data.(unsafe.Pointer); ok { 42 | return newPtr(ptr), nil 43 | } 44 | 45 | buf, err := marshalBytes(data, length) 46 | if err != nil { 47 | return syscallPtr{}, err 48 | } 49 | 50 | return newPtr(unsafe.Pointer(&buf[0])), nil 51 | } 52 | 53 | func marshalBytes(data interface{}, length int) (buf []byte, err error) { 54 | switch value := data.(type) { 55 | case encoding.BinaryMarshaler: 56 | buf, err = value.MarshalBinary() 57 | case string: 58 | buf = []byte(value) 59 | case []byte: 60 | buf = value 61 | case unsafe.Pointer: 62 | err = errors.New("can't marshal from unsafe.Pointer") 63 | default: 64 | var wr bytes.Buffer 65 | err = binary.Write(&wr, nativeEndian, value) 66 | err = errors.Wrapf(err, "encoding %T", value) 67 | buf = wr.Bytes() 68 | } 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | if len(buf) != length { 74 | return nil, errors.Errorf("%T doesn't marshal to %d bytes", data, length) 75 | } 76 | return buf, nil 77 | } 78 | 79 | func makeBuffer(dst interface{}, length int) (syscallPtr, []byte) { 80 | if ptr, ok := dst.(unsafe.Pointer); ok { 81 | return newPtr(ptr), nil 82 | } 83 | 84 | buf := make([]byte, length) 85 | return newPtr(unsafe.Pointer(&buf[0])), buf 86 | } 87 | 88 | func unmarshalBytes(data interface{}, buf []byte) error { 89 | switch value := data.(type) { 90 | case unsafe.Pointer: 91 | sh := &reflect.SliceHeader{ 92 | Data: uintptr(value), 93 | Len: len(buf), 94 | Cap: len(buf), 95 | } 96 | 97 | dst := *(*[]byte)(unsafe.Pointer(sh)) 98 | copy(dst, buf) 99 | runtime.KeepAlive(value) 100 | return nil 101 | case encoding.BinaryUnmarshaler: 102 | return value.UnmarshalBinary(buf) 103 | case *string: 104 | *value = string(buf) 105 | return nil 106 | case *[]byte: 107 | *value = buf 108 | return nil 109 | case string: 110 | return errors.New("require pointer to string") 111 | case []byte: 112 | return errors.New("require pointer to []byte") 113 | default: 114 | rd := bytes.NewReader(buf) 115 | err := binary.Read(rd, nativeEndian, value) 116 | return errors.Wrapf(err, "decoding %T", value) 117 | } 118 | } 119 | 120 | // marshalPerCPUValue encodes a slice containing one value per 121 | // possible CPU into a buffer of bytes. 122 | // 123 | // Values are initialized to zero if the slice has less elements than CPUs. 124 | // 125 | // slice must have a type like []elementType. 126 | func marshalPerCPUValue(slice interface{}, elemLength int) (syscallPtr, error) { 127 | sliceType := reflect.TypeOf(slice) 128 | if sliceType.Kind() != reflect.Slice { 129 | return syscallPtr{}, errors.New("per-CPU value requires slice") 130 | } 131 | 132 | possibleCPUs, err := possibleCPUs() 133 | if err != nil { 134 | return syscallPtr{}, err 135 | } 136 | 137 | sliceValue := reflect.ValueOf(slice) 138 | sliceLen := sliceValue.Len() 139 | if sliceLen > possibleCPUs { 140 | return syscallPtr{}, errors.Errorf("per-CPU value exceeds number of CPUs") 141 | } 142 | 143 | alignedElemLength := align(elemLength, 8) 144 | buf := make([]byte, alignedElemLength*possibleCPUs) 145 | 146 | for i := 0; i < sliceLen; i++ { 147 | elem := sliceValue.Index(i).Interface() 148 | elemBytes, err := marshalBytes(elem, elemLength) 149 | if err != nil { 150 | return syscallPtr{}, err 151 | } 152 | 153 | offset := i * alignedElemLength 154 | copy(buf[offset:offset+elemLength], elemBytes) 155 | } 156 | 157 | return newPtr(unsafe.Pointer(&buf[0])), nil 158 | } 159 | 160 | // unmarshalPerCPUValue decodes a buffer into a slice containing one value per 161 | // possible CPU. 162 | // 163 | // valueOut must have a type like *[]elementType 164 | func unmarshalPerCPUValue(slicePtr interface{}, elemLength int, buf []byte) error { 165 | slicePtrType := reflect.TypeOf(slicePtr) 166 | if slicePtrType.Kind() != reflect.Ptr || slicePtrType.Elem().Kind() != reflect.Slice { 167 | return errors.Errorf("per-cpu value requires pointer to slice") 168 | } 169 | 170 | possibleCPUs, err := possibleCPUs() 171 | if err != nil { 172 | return err 173 | } 174 | 175 | sliceType := slicePtrType.Elem() 176 | slice := reflect.MakeSlice(sliceType, possibleCPUs, possibleCPUs) 177 | 178 | sliceElemType := sliceType.Elem() 179 | sliceElemIsPointer := sliceElemType.Kind() == reflect.Ptr 180 | if sliceElemIsPointer { 181 | sliceElemType = sliceElemType.Elem() 182 | } 183 | 184 | step := len(buf) / possibleCPUs 185 | if step < elemLength { 186 | return errors.Errorf("per-cpu element length is larger than available data") 187 | } 188 | for i := 0; i < possibleCPUs; i++ { 189 | var elem interface{} 190 | if sliceElemIsPointer { 191 | newElem := reflect.New(sliceElemType) 192 | slice.Index(i).Set(newElem) 193 | elem = newElem.Interface() 194 | } else { 195 | elem = slice.Index(i).Addr().Interface() 196 | } 197 | 198 | // Make a copy, since unmarshal can hold on to itemBytes 199 | elemBytes := make([]byte, elemLength) 200 | copy(elemBytes, buf[:elemLength]) 201 | 202 | err := unmarshalBytes(elem, elemBytes) 203 | if err != nil { 204 | return errors.Wrapf(err, "cpu %d", i) 205 | } 206 | 207 | buf = buf[step:] 208 | } 209 | 210 | reflect.ValueOf(slicePtr).Elem().Set(slice) 211 | return nil 212 | } 213 | 214 | var sysCPU struct { 215 | once sync.Once 216 | err error 217 | num int 218 | } 219 | 220 | // possibleCPUs returns the max number of CPUs a system may possibly have 221 | // Logical CPU numbers must be of the form 0-n 222 | func possibleCPUs() (int, error) { 223 | sysCPU.once.Do(func() { 224 | sysCPU.num, sysCPU.err = parseCPUs("/sys/devices/system/cpu/possible") 225 | }) 226 | 227 | return sysCPU.num, sysCPU.err 228 | } 229 | 230 | var onlineCPU struct { 231 | once sync.Once 232 | err error 233 | num int 234 | } 235 | 236 | // onlineCPUs returns the number of currently online CPUs 237 | // Logical CPU numbers must be of the form 0-n 238 | func onlineCPUs() (int, error) { 239 | onlineCPU.once.Do(func() { 240 | onlineCPU.num, onlineCPU.err = parseCPUs("/sys/devices/system/cpu/online") 241 | }) 242 | 243 | return onlineCPU.num, onlineCPU.err 244 | } 245 | 246 | // parseCPUs parses the number of cpus from sysfs, 247 | // in the format of "/sys/devices/system/cpu/{possible,online,..}. 248 | // Logical CPU numbers must be of the form 0-n 249 | func parseCPUs(path string) (int, error) { 250 | file, err := os.Open(path) 251 | if err != nil { 252 | return 0, err 253 | } 254 | defer file.Close() 255 | 256 | var low, high int 257 | n, _ := fmt.Fscanf(file, "%d-%d", &low, &high) 258 | if n < 1 || low != 0 { 259 | return 0, errors.Wrapf(err, "%s has unknown format", path) 260 | } 261 | if n == 1 { 262 | high = low 263 | } 264 | 265 | // cpus is 0 indexed 266 | return high + 1, nil 267 | } 268 | 269 | func align(n, alignment int) int { 270 | return (int(n) + alignment - 1) / alignment * alignment 271 | } 272 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | //go:generate stringer -output types_string.go -type=MapType,ProgType 4 | 5 | // MapType indicates the type map structure 6 | // that will be initialized in the kernel. 7 | type MapType uint32 8 | 9 | // All the various map types that can be created 10 | const ( 11 | UnspecifiedMap MapType = iota 12 | // Hash is a hash map 13 | Hash 14 | // Array is an array map 15 | Array 16 | // ProgramArray - A program array map is a special kind of array map whose map 17 | // values contain only file descriptors referring to other eBPF 18 | // programs. Thus, both the key_size and value_size must be 19 | // exactly four bytes. This map is used in conjunction with the 20 | // TailCall helper. 21 | ProgramArray 22 | // PerfEventArray - A perf event array is used in conjunction with PerfEventRead 23 | // and PerfEventOutput calls, to read the raw bpf_perf_data from the registers. 24 | PerfEventArray 25 | // PerCPUHash - This data structure is useful for people who have high performance 26 | // network needs and can reconcile adds at the end of some cycle, so that 27 | // hashes can be lock free without the use of XAdd, which can be costly. 28 | PerCPUHash 29 | // PerCPUArray - This data structure is useful for people who have high performance 30 | // network needs and can reconcile adds at the end of some cycle, so that 31 | // hashes can be lock free without the use of XAdd, which can be costly. 32 | // Each CPU gets a copy of this hash, the contents of all of which can be reconciled 33 | // later. 34 | PerCPUArray 35 | // StackTrace - This holds whole user and kernel stack traces, it can be retrieved with 36 | // GetStackID 37 | StackTrace 38 | // CGroupArray - This is a very niche structure used to help SKBInCGroup determine 39 | // if an skb is from a socket belonging to a specific cgroup 40 | CGroupArray 41 | // LRUHash - This allows you to create a small hash structure that will purge the 42 | // least recently used items rather than thow an error when you run out of memory 43 | LRUHash 44 | // LRUCPUHash - This is NOT like PerCPUHash, this structure is shared among the CPUs, 45 | // it has more to do with including the CPU id with the LRU calculation so that if a 46 | // particular CPU is using a value over-and-over again, then it will be saved, but if 47 | // a value is being retrieved a lot but sparsely across CPUs it is not as important, basically 48 | // giving weight to CPU locality over overall usage. 49 | LRUCPUHash 50 | // LPMTrie - This is an implementation of Longest-Prefix-Match Trie structure. It is useful, 51 | // for storing things like IP addresses which can be bit masked allowing for keys of differing 52 | // values to refer to the same reference based on their masks. See wikipedia for more details. 53 | LPMTrie 54 | // ArrayOfMaps - Each item in the array is another map. The inner map mustn't be a map of maps 55 | // itself. 56 | ArrayOfMaps 57 | // HashOfMaps - Each item in the hash map is another map. The inner map mustn't be a map of maps 58 | // itself. 59 | HashOfMaps 60 | ) 61 | 62 | // hasPerCPUValue returns true if the Map stores a value per CPU. 63 | func (mt MapType) hasPerCPUValue() bool { 64 | if mt == PerCPUHash || mt == PerCPUArray { 65 | return true 66 | } 67 | return false 68 | } 69 | 70 | const ( 71 | _MapCreate = iota 72 | _MapLookupElem 73 | _MapUpdateElem 74 | _MapDeleteElem 75 | _MapGetNextKey 76 | _ProgLoad 77 | _ObjPin 78 | _ObjGet 79 | _ProgAttach 80 | _ProgDetach 81 | _ProgTestRun 82 | _ProgGetNextID 83 | _MapGetNextID 84 | _ProgGetFDByID 85 | _MapGetFDByID 86 | _ObjGetInfoByFD 87 | ) 88 | 89 | const ( 90 | _Any = iota 91 | _NoExist 92 | _Exist 93 | ) 94 | 95 | // All flags used by eBPF helper functions 96 | const ( 97 | // RecomputeCSUM SKBStoreBytes flags 98 | RecomputeCSUM = uint64(1) 99 | // FInvalidateHash SKBStoreBytes flags 100 | FInvalidateHash = uint64(1 << 1) 101 | 102 | // FHdrFieldMask CSUMReplaceL4 and CSUMReplaceL3 flags. 103 | // First 4 bits are for passing the header field size. 104 | FHdrFieldMask = uint64(0xF) 105 | 106 | // FPseudoHdr CSUMReplaceL4 flags 107 | FPseudoHdr = uint64(1 << 4) 108 | // FMarkMangled0 CSUMReplaceL4 flags 109 | FMarkMangled0 = uint64(1 << 5) 110 | // FMakrEnforce CSUMReplaceL4 flags 111 | FMakrEnforce = uint64(1 << 6) 112 | 113 | // FIngress CloneRedirect and Redirect flags 114 | FIngress = uint64(1) 115 | 116 | // FTunInfoIPV6 SKBSetTunnelKey and SKBGetTunnelKey flags 117 | FTunInfoIPV6 = uint(1) 118 | 119 | // FSkipFieldMask GetStackID flags 120 | FSkipFieldMask = uint64(0xff) 121 | // FUserStack GetStackID flags 122 | FUserStack = uint64(1 << 8) 123 | // FFastStackCMP GetStackID flags 124 | FFastStackCMP = uint64(1 << 9) 125 | // FReuseStackID GetStackID flags 126 | FReuseStackID = uint64(1 << 10) 127 | 128 | // FZeroCSUMTx SKBSetTunnelKey flag 129 | FZeroCSUMTX = uint64(1 << 1) 130 | // FZeroCSUMTx SKBSetTunnelKey flag 131 | FDontFragment = uint64(1 << 2) 132 | 133 | // FindIndexMask PerfEventOutput and PerfEventRead flags. 134 | FIndexMask = uint64(0xffffffff) 135 | // FCurrentCPU PerfEventOutput and PerfEventRead flags. 136 | FCurrentCPU = FIndexMask 137 | 138 | // FCtxLenMask PerfEventOutput for SKBuff input context. 139 | FCtxLenMask = uint64(0xfffff << 32) 140 | 141 | // AdjRoomNet Mode for SKBAdjustRoom helper. 142 | AdjRoomNet = 0 143 | ) 144 | 145 | // ProgType of the eBPF program 146 | type ProgType uint32 147 | 148 | // eBPF program types 149 | const ( 150 | // Unrecognized program type 151 | Unrecognized ProgType = iota 152 | // SocketFilter socket or seccomp filter 153 | SocketFilter 154 | // Kprobe program 155 | Kprobe 156 | // SchedCLS traffic control shaper 157 | SchedCLS 158 | // SchedACT routing control shaper 159 | SchedACT 160 | // TracePoint program 161 | TracePoint 162 | // XDP program 163 | XDP 164 | // PerfEvent program 165 | PerfEvent 166 | // CGroupSKB program 167 | CGroupSKB 168 | // CGroupSock program 169 | CGroupSock 170 | // LWTIn program 171 | LWTIn 172 | // LWTOut program 173 | LWTOut 174 | // LWTXmit program 175 | LWTXmit 176 | // SockOps program 177 | SockOps 178 | // SkSKB program 179 | SkSKB 180 | // CGroupDevice program 181 | CGroupDevice 182 | // SkMsg program 183 | SkMsg 184 | // RawTracepoint program 185 | RawTracepoint 186 | // CGroupSockAddr program 187 | CGroupSockAddr 188 | // LWTSeg6Local program 189 | LWTSeg6Local 190 | // LircMode2 program 191 | LircMode2 192 | // SkReuseport program 193 | SkReuseport 194 | // FlowDissector program 195 | FlowDissector 196 | // CGroupSysctl program 197 | CGroupSysctl 198 | // RawTracepointWritable program 199 | RawTracepointWritable 200 | // CGroupSockopt program 201 | CGroupSockopt 202 | ) 203 | 204 | // AttachType of the eBPF program, needed to differentiate allowed context accesses in 205 | // some newer program types like CGroupSockAddr. Should be set to AttachNone if not required. 206 | // Will cause invalid argument (EINVAL) at program load time if set incorrectly. 207 | type AttachType uint32 208 | 209 | // AttachNone is an alias for AttachCGroupInetIngress for readability reasons 210 | const AttachNone AttachType = 0 211 | 212 | const ( 213 | AttachCGroupInetIngress AttachType = iota 214 | AttachCGroupInetEgress 215 | AttachCGroupInetSockCreate 216 | AttachCGroupSockOps 217 | AttachSkSKBStreamParser 218 | AttachSkSKBStreamVerdict 219 | AttachCGroupDevice 220 | AttachSkMsgVerdict 221 | AttachCGroupInet4Bind 222 | AttachCGroupInet6Bind 223 | AttachCGroupInet4Connect 224 | AttachCGroupInet6Connect 225 | AttachCGroupInet4PostBind 226 | AttachCGroupInet6PostBind 227 | AttachCGroupUDP4Sendmsg 228 | AttachCGroupUDP6Sendmsg 229 | AttachLircMode2 230 | AttachFlowDissector 231 | AttachCGroupSysctl 232 | AttachCGroupUDP4Recvmsg 233 | AttachCGroupUDP6Recvmsg 234 | AttachCGroupGetsockopt 235 | AttachCGroupSetsockopt 236 | ) 237 | -------------------------------------------------------------------------------- /prog_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/newtools/ebpf/asm" 13 | ) 14 | 15 | func TestProgramRun(t *testing.T) { 16 | pat := []byte{0xDE, 0xAD, 0xBE, 0xEF} 17 | buf := make([]byte, 14) 18 | 19 | // r1 : ctx_start 20 | // r1+4: ctx_end 21 | ins := asm.Instructions{ 22 | // r2 = *(r1+4) 23 | asm.LoadMem(asm.R2, asm.R1, 4, asm.Word), 24 | // r1 = *(r1+0) 25 | asm.LoadMem(asm.R1, asm.R1, 0, asm.Word), 26 | // r3 = r1 27 | asm.Mov.Reg(asm.R3, asm.R1), 28 | // r3 += len(buf) 29 | asm.Add.Imm(asm.R3, int32(len(buf))), 30 | // if r3 > r2 goto +len(pat) 31 | asm.JGT.Reg(asm.R3, asm.R2, "out"), 32 | } 33 | for i, b := range pat { 34 | ins = append(ins, asm.StoreImm(asm.R1, int16(i), int64(b), asm.Byte)) 35 | } 36 | ins = append(ins, 37 | // return 42 38 | asm.LoadImm(asm.R0, 42, asm.DWord).Sym("out"), 39 | asm.Return(), 40 | ) 41 | 42 | t.Log(ins) 43 | 44 | prog, err := NewProgram(&ProgramSpec{"test", XDP, AttachNone, ins, "MIT", 0}) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | defer prog.Close() 49 | 50 | p2, err := prog.Clone() 51 | if err != nil { 52 | t.Fatal("Can't clone program") 53 | } 54 | defer p2.Close() 55 | 56 | prog.Close() 57 | prog = p2 58 | 59 | ret, out, err := prog.Test(buf) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | if ret != 42 { 65 | t.Error("Expected return value to be 42, got", ret) 66 | } 67 | 68 | if !bytes.Equal(out[:len(pat)], pat) { 69 | t.Errorf("Expected %v, got %v", pat, out) 70 | } 71 | } 72 | 73 | func TestProgramClose(t *testing.T) { 74 | prog, err := NewProgram(&ProgramSpec{ 75 | Type: SocketFilter, 76 | Instructions: asm.Instructions{ 77 | asm.LoadImm(asm.R0, 0, asm.DWord), 78 | asm.Return(), 79 | }, 80 | License: "MIT", 81 | }) 82 | if err != nil { 83 | t.Fatal(err) 84 | } 85 | 86 | if err := prog.Close(); err != nil { 87 | t.Fatal("Can't close program:", err) 88 | } 89 | } 90 | 91 | func TestProgramPin(t *testing.T) { 92 | prog, err := NewProgram(&ProgramSpec{ 93 | Type: SocketFilter, 94 | Instructions: asm.Instructions{ 95 | asm.LoadImm(asm.R0, 0, asm.DWord), 96 | asm.Return(), 97 | }, 98 | License: "MIT", 99 | }) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | defer prog.Close() 104 | 105 | tmp, err := ioutil.TempDir("/sys/fs/bpf", "ebpf-test") 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | defer os.RemoveAll(tmp) 110 | 111 | path := filepath.Join(tmp, "program") 112 | if err := prog.Pin(path); err != nil { 113 | t.Fatal(err) 114 | } 115 | prog.Close() 116 | 117 | prog, err = LoadPinnedProgram(path) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | defer prog.Close() 122 | 123 | if prog.abi.Type != SocketFilter { 124 | t.Error("Expected pinned program to have type SocketFilter, but got", prog.abi.Type) 125 | } 126 | } 127 | 128 | func TestProgramVerifierOutputOnError(t *testing.T) { 129 | _, err := NewProgram(&ProgramSpec{ 130 | Type: SocketFilter, 131 | Instructions: asm.Instructions{ 132 | asm.Return(), 133 | }, 134 | License: "MIT", 135 | }) 136 | if err == nil { 137 | t.Fatal("Expected program to be invalid") 138 | } 139 | 140 | if strings.Index(err.Error(), "exit") == -1 { 141 | t.Error("No verifier output in error message") 142 | } 143 | } 144 | 145 | func TestProgramVerifierOutput(t *testing.T) { 146 | spec := &ProgramSpec{ 147 | Type: SocketFilter, 148 | Instructions: asm.Instructions{ 149 | asm.LoadImm(asm.R0, 0, asm.DWord), 150 | asm.Return(), 151 | }, 152 | License: "MIT", 153 | } 154 | 155 | prog, err := NewProgramWithOptions(spec, ProgramOptions{ 156 | LogLevel: 2, 157 | }) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | defer prog.Close() 162 | 163 | t.Log(prog.VerifierLog) 164 | if prog.VerifierLog == "" { 165 | t.Error("Expected VerifierLog to not be empty") 166 | } 167 | } 168 | 169 | func TestProgramName(t *testing.T) { 170 | prog, err := NewProgram(&ProgramSpec{ 171 | Name: "test", 172 | Type: SocketFilter, 173 | Instructions: asm.Instructions{ 174 | asm.LoadImm(asm.R0, 0, asm.DWord), 175 | asm.Return(), 176 | }, 177 | License: "MIT", 178 | }) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | defer prog.Close() 183 | 184 | info, err := bpfGetProgInfoByFD(prog.fd) 185 | if err != nil { 186 | t.Fatal(err) 187 | } 188 | 189 | if name := convertCString(info.name[:]); name != "test" { 190 | t.Errorf("Name is not test, got '%s'", name) 191 | } 192 | } 193 | 194 | func TestSanitizeName(t *testing.T) { 195 | for input, want := range map[string]string{ 196 | "test": "test", 197 | "t-est": "test", 198 | "t_est": "t_est", 199 | "hörnchen": "hrnchen", 200 | } { 201 | if have := SanitizeName(input, -1); have != want { 202 | t.Errorf("Wanted '%s' got '%s'", want, have) 203 | } 204 | } 205 | } 206 | 207 | func TestProgramCloneNil(t *testing.T) { 208 | p, err := (*Program)(nil).Clone() 209 | if err != nil { 210 | t.Fatal(err) 211 | } 212 | 213 | if p != nil { 214 | t.Fatal("Cloning a nil Program doesn't return nil") 215 | } 216 | } 217 | 218 | func TestProgramMarshaling(t *testing.T) { 219 | const idx = uint32(0) 220 | 221 | arr := createProgramArray(t) 222 | defer arr.Close() 223 | 224 | prog, err := NewProgram(&ProgramSpec{ 225 | Type: SocketFilter, 226 | Instructions: asm.Instructions{ 227 | asm.LoadImm(asm.R0, 0, asm.DWord), 228 | asm.Return(), 229 | }, 230 | License: "MIT", 231 | }) 232 | if err != nil { 233 | t.Fatal(err) 234 | } 235 | defer prog.Close() 236 | 237 | if err := arr.Put(idx, prog); err != nil { 238 | t.Fatal("Can't put program:", err) 239 | } 240 | 241 | if _, err := arr.Get(idx, Program{}); err == nil { 242 | t.Fatal("Get accepts Program") 243 | } 244 | 245 | var prog2 *Program 246 | defer prog2.Close() 247 | 248 | if _, err := arr.Get(idx, prog2); err == nil { 249 | t.Fatal("Get accepts *Program") 250 | } 251 | 252 | if _, err := arr.Get(idx, &prog2); err != nil { 253 | t.Fatal("Can't unmarshal program:", err) 254 | } 255 | 256 | if prog2 == nil { 257 | t.Fatal("Unmarshalling set program to nil") 258 | } 259 | } 260 | 261 | func createProgramArray(t *testing.T) *Map { 262 | t.Helper() 263 | 264 | arr, err := NewMap(&MapSpec{ 265 | Type: ProgramArray, 266 | KeySize: 4, 267 | ValueSize: 4, 268 | MaxEntries: 1, 269 | }) 270 | if err != nil { 271 | t.Fatal(err) 272 | } 273 | return arr 274 | } 275 | 276 | // Use NewProgramWithOptions if you'd like to get the verifier output 277 | // for a program, or if you want to change the buffer size used when 278 | // generating error messages. 279 | func ExampleNewProgramWithOptions() { 280 | spec := &ProgramSpec{ 281 | Type: SocketFilter, 282 | Instructions: asm.Instructions{ 283 | asm.LoadImm(asm.R0, 0, asm.DWord), 284 | asm.Return(), 285 | }, 286 | License: "MIT", 287 | } 288 | 289 | prog, err := NewProgramWithOptions(spec, ProgramOptions{ 290 | LogLevel: 2, 291 | LogSize: 1024, 292 | }) 293 | if err != nil { 294 | panic(err) 295 | } 296 | defer prog.Close() 297 | 298 | fmt.Println("The verifier output is:") 299 | fmt.Println(prog.VerifierLog) 300 | } 301 | 302 | // It's possible to read a program directly from a ProgramArray. 303 | func ExampleProgram_unmarshalFromMap() { 304 | progArray, err := LoadPinnedMap("/path/to/map") 305 | if err != nil { 306 | panic(err) 307 | } 308 | defer progArray.Close() 309 | 310 | // Load a single program 311 | var prog *Program 312 | if ok, err := progArray.Get(uint32(0), &prog); !ok { 313 | panic("key not found") 314 | } else if err != nil { 315 | panic(err) 316 | } 317 | defer prog.Close() 318 | 319 | fmt.Println("first prog:", prog) 320 | 321 | // Iterate all programs 322 | var ( 323 | key uint32 324 | entries = progArray.Iterate() 325 | ) 326 | 327 | for entries.Next(&key, &prog) { 328 | fmt.Println(key, "is", prog) 329 | } 330 | 331 | if err := entries.Err(); err != nil { 332 | panic(err) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /asm/opcode_test.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // These are the old names, retained here to check what 8 | // changes have been made. 9 | func TestOpCodeString(t *testing.T) { 10 | t.Skip() 11 | 12 | testcases := map[string]OpCode{ 13 | // AddImm add dst, imm | dst += imm 14 | "AddImm": 0x07, 15 | // AddSrc add dst, src | dst += src 16 | "AddSrc": 0x0f, 17 | // SubImm sub dst, imm | dst -= imm 18 | "SubImm": 0x17, 19 | // SubSrc sub dst, src | dst -= src 20 | "SubSrc": 0x1f, 21 | // MulImm mul dst, imm | dst *= imm 22 | "MulImm": 0x27, 23 | // MulSrc mul dst, src | dst *= src 24 | "MulSrc": 0x2f, 25 | // DivImm div dst, imm | dst /= imm 26 | "DivImm": 0x37, 27 | // DivSrc div dst, src | dst /= src 28 | "DivSrc": 0x3f, 29 | // OrImm or dst, imm | dst |= imm 30 | "OrImm": 0x47, 31 | // OrSrc or dst, src | dst |= src 32 | "OrSrc": 0x4f, 33 | // AndImm and dst, imm | dst &= imm 34 | "AndImm": 0x57, 35 | // AndSrc and dst, src | dst &= src 36 | "AndSrc": 0x5f, 37 | // LShImm lsh dst, imm | dst <<= imm 38 | "LShImm": 0x67, 39 | // LShSrc lsh dst, src | dst <<= src 40 | "LShSrc": 0x6f, 41 | // RShImm rsh dst, imm | dst >>= imm (logical) 42 | "RShImm": 0x77, 43 | // RShSrc rsh dst, src | dst >>= src (logical) 44 | "RShSrc": 0x7f, 45 | // Neg neg dst | dst = -dst 46 | "Neg": 0x87, 47 | // ModImm mod dst, imm | dst %= imm 48 | "ModImm": 0x97, 49 | // ModSrc mod dst, src | dst %= src 50 | "ModSrc": 0x9f, 51 | // XorImm xor dst, imm | dst ^= imm 52 | "XorImm": 0xa7, 53 | // XorSrc xor dst, src | dst ^= src 54 | "XorSrc": 0xaf, 55 | // MovImm mov dst, imm | dst = imm 56 | "MovImm": 0xb7, 57 | // MovSrc mov dst, src | dst = src 58 | "MovSrc": 0xbf, 59 | // ArShImm arsh dst, imm | dst >>= imm (arithmetic) 60 | "ArShImm": 0xc7, 61 | // ArShSrc arsh dst, src | dst >>= src (arithmetic) 62 | "ArShSrc": 0xcf, 63 | // Add32Imm add32 dst, imm | dst += imm 64 | "Add32Imm": 0x04, 65 | // Add32Src add32 dst, src | dst += src 66 | "Add32Src": 0x0c, 67 | // Sub32Imm sub32 dst, imm | dst -= imm 68 | "Sub32Imm": 0x14, 69 | // Sub32Src sub32 dst, src | dst -= src 70 | "Sub32Src": 0x1c, 71 | // Mul32Imm mul32 dst, imm | dst *= imm 72 | "Mul32Imm": 0x24, 73 | // Mul32Src mul32 dst, src | dst *= src 74 | "Mul32Src": 0x2c, 75 | // Div32Imm div32 dst, imm | dst /= imm 76 | "Div32Imm": 0x34, 77 | // Div32Src div32 dst, src | dst /= src 78 | "Div32Src": 0x3c, 79 | // Or32Imm or32 dst, imm | dst |= imm 80 | "Or32Imm": 0x44, 81 | // Or32Src or32 dst, src | dst |= src 82 | "Or32Src": 0x4c, 83 | // And32Imm and32 dst, imm | dst &= imm 84 | "And32Imm": 0x54, 85 | // And32Src and32 dst, src | dst &= src 86 | "And32Src": 0x5c, 87 | // LSh32Imm lsh32 dst, imm | dst <<= imm 88 | "LSh32Imm": 0x64, 89 | // LSh32Src lsh32 dst, src | dst <<= src 90 | "LSh32Src": 0x6c, 91 | // RSh32Imm rsh32 dst, imm | dst >>= imm (logical) 92 | "RSh32Imm": 0x74, 93 | // RSh32Src rsh32 dst, src | dst >>= src (logical) 94 | "RSh32Src": 0x7c, 95 | // Neg32 neg32 dst | dst = -dst 96 | "Neg32": 0x84, 97 | // Mod32Imm mod32 dst, imm | dst %= imm 98 | "Mod32Imm": 0x94, 99 | // Mod32Src mod32 dst, src | dst %= src 100 | "Mod32Src": 0x9c, 101 | // Xor32Imm xor32 dst, imm | dst ^= imm 102 | "Xor32Imm": 0xa4, 103 | // Xor32Src xor32 dst, src | dst ^= src 104 | "Xor32Src": 0xac, 105 | // Mov32Imm mov32 dst, imm | dst eBPF only 106 | "Mov32Imm": 0xb4, 107 | // Mov32Src mov32 dst, src | dst eBPF only 108 | "Mov32Src": 0xbc, 109 | // LE16 le16 dst, imm == 16 | dst = htole16(dst) 110 | "LE16": 0xd4, 111 | // LE32 le32 dst, imm == 32 | dst = htole32(dst) 112 | "LE32": 0xd4, 113 | // LE64 le64 dst, imm == 64 | dst = htole64(dst) 114 | "LE64": 0xd4, 115 | // BE16 be16 dst, imm == 16 | dst = htobe16(dst) 116 | "BE16": 0xdc, 117 | // BE32 be32 dst, imm == 32 | dst = htobe32(dst) 118 | "BE32": 0xdc, 119 | // BE64 be64 dst, imm == 64 | dst = htobe64(dst) 120 | "BE64": 0xdc, 121 | // LdDW lddw (src), dst, imm | dst = imm 122 | "LdDW": 0x18, 123 | // XAddStSrc xadd dst, src | *dst += src 124 | "XAddStSrc": 0xdb, 125 | // LdAbsB ldabsb imm | r0 = (uint8_t *) (mem + imm) 126 | "LdAbsB": 0x30, 127 | // LdXW ldxw dst, [src+off] | dst = *(uint32_t *) (src + off) 128 | "LdXW": 0x61, 129 | // LdXH ldxh dst, [src+off] | dst = *(uint16_t *) (src + off) 130 | "LdXH": 0x69, 131 | // LdXB ldxb dst, [src+off] | dst = *(uint8_t *) (src + off) 132 | "LdXB": 0x71, 133 | // LdXDW ldxdw dst, [src+off] | dst = *(uint64_t *) (src + off) 134 | "LdXDW": 0x79, 135 | // StB stb [dst+off], imm | *(uint8_t *) (dst + off) = imm 136 | "StB": 0x72, 137 | // StH sth [dst+off], imm | *(uint16_t *) (dst + off) = imm 138 | "StH": 0x6a, 139 | // StW stw [dst+off], imm | *(uint32_t *) (dst + off) = imm 140 | "StW": 0x62, 141 | // StDW stdw [dst+off], imm | *(uint64_t *) (dst + off) = imm 142 | "StDW": 0x7a, 143 | // StXB stxb [dst+off], src | *(uint8_t *) (dst + off) = src 144 | "StXB": 0x73, 145 | // StXH stxh [dst+off], src | *(uint16_t *) (dst + off) = src 146 | "StXH": 0x6b, 147 | // StXW stxw [dst+off], src | *(uint32_t *) (dst + off) = src 148 | "StXW": 0x63, 149 | // StXDW stxdw [dst+off], src | *(uint64_t *) (dst + off) = src 150 | "StXDW": 0x7b, 151 | // LdAbsH ldabsh imm | r0 = (uint16_t *) (imm) 152 | // Abs and Ind reference memory directly. This is always the context, 153 | // of whatever the eBPF program is. For example in a sock filter program 154 | // the memory context is the sk_buff struct. 155 | "LdAbsH": 0x28, 156 | // LdAbsW ldabsw imm | r0 = (uint32_t *) (imm) 157 | "LdAbsW": 0x20, 158 | // LdAbsDW ldabsdw imm | r0 = (uint64_t *) (imm) 159 | "LdAbsDW": 0x38, 160 | // LdIndB ldindb src, dst, imm | dst = (uint64_t *) (src + imm) 161 | "LdIndB": 0x50, 162 | // LdIndH ldindh src, dst, imm | dst = (uint16_t *) (src + imm) 163 | "LdIndH": 0x48, 164 | // LdIndW ldindw src, dst, imm | dst = (uint32_t *) (src + imm) 165 | "LdIndW": 0x40, 166 | // LdIndDW ldinddw src, dst, imm | dst = (uint64_t *) (src + imm) 167 | "LdIndDW": 0x58, 168 | // Ja ja +off | PC += off 169 | "Ja": 0x05, 170 | // JEqImm jeq dst, imm, +off | PC += off if dst == imm 171 | "JEqImm": 0x15, 172 | // JEqSrc jeq dst, src, +off | PC += off if dst == src 173 | "JEqSrc": 0x1d, 174 | // JGTImm jgt dst, imm, +off | PC += off if dst > imm 175 | "JGTImm": 0x25, 176 | // JGTSrc jgt dst, src, +off | PC += off if dst > src 177 | "JGTSrc": 0x2d, 178 | // JGEImm jge dst, imm, +off | PC += off if dst >= imm 179 | "JGEImm": 0x35, 180 | // JGESrc jge dst, src, +off | PC += off if dst >= src 181 | "JGESrc": 0x3d, 182 | // JSETImm jset dst, imm, +off | PC += off if dst & imm 183 | "JSETImm": 0x45, 184 | // JSETSrc jset dst, src, +off | PC += off if dst & src 185 | "JSETSrc": 0x4d, 186 | // JNEImm jne dst, imm, +off | PC += off if dst != imm 187 | "JNEImm": 0x55, 188 | // JNESrc jne dst, src, +off | PC += off if dst != src 189 | "JNESrc": 0x5d, 190 | // JSGTImm jsgt dst, imm, +off | PC += off if dst > imm (signed) 191 | "JSGTImm": 0x65, 192 | // JSGTSrc jsgt dst, src, +off | PC += off if dst > src (signed) 193 | "JSGTSrc": 0x6d, 194 | // JSGEImm jsge dst, imm, +off | PC += off if dst >= imm (signed) 195 | "JSGEImm": 0x75, 196 | // JSGESrc jsge dst, src, +off | PC += off if dst >= src (signed) 197 | "JSGESrc": 0x7d, 198 | // Call call imm | Function call 199 | "Call": 0x85, 200 | // Exit exit | return r0 201 | "Exit": 0x95, 202 | } 203 | 204 | for want, op := range testcases { 205 | if have := op.String(); want != have { 206 | t.Errorf("Expected %s, got %s", want, have) 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /collection.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // CollectionOptions control loading a collection into the kernel. 12 | type CollectionOptions struct { 13 | Programs ProgramOptions 14 | } 15 | 16 | // CollectionSpec describes a collection. 17 | type CollectionSpec struct { 18 | Maps map[string]*MapSpec 19 | Programs map[string]*ProgramSpec 20 | } 21 | 22 | // Copy returns a recursive copy of the spec. 23 | func (cs *CollectionSpec) Copy() *CollectionSpec { 24 | if cs == nil { 25 | return nil 26 | } 27 | 28 | cpy := CollectionSpec{ 29 | Maps: make(map[string]*MapSpec, len(cs.Maps)), 30 | Programs: make(map[string]*ProgramSpec, len(cs.Programs)), 31 | } 32 | 33 | for name, spec := range cs.Maps { 34 | cpy.Maps[name] = spec.Copy() 35 | } 36 | 37 | for name, spec := range cs.Programs { 38 | cpy.Programs[name] = spec.Copy() 39 | } 40 | 41 | return &cpy 42 | } 43 | 44 | // LoadCollectionSpec parse an object file and convert it to a collection 45 | func LoadCollectionSpec(file string) (*CollectionSpec, error) { 46 | f, err := os.Open(file) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer f.Close() 51 | 52 | return LoadCollectionSpecFromReader(f) 53 | } 54 | 55 | // Collection is a collection of Programs and Maps associated 56 | // with their symbols 57 | type Collection struct { 58 | Programs map[string]*Program 59 | Maps map[string]*Map 60 | } 61 | 62 | // NewCollection creates a Collection from a specification. 63 | // 64 | // Only maps referenced by at least one of the programs are initialized. 65 | func NewCollection(spec *CollectionSpec) (*Collection, error) { 66 | return NewCollectionWithOptions(spec, CollectionOptions{}) 67 | } 68 | 69 | // NewCollectionWithOptions creates a Collection from a specification. 70 | // 71 | // Only maps referenced by at least one of the programs are initialized. 72 | func NewCollectionWithOptions(spec *CollectionSpec, opts CollectionOptions) (*Collection, error) { 73 | maps := make(map[string]*Map) 74 | for mapName, mapSpec := range spec.Maps { 75 | m, err := NewMap(mapSpec) 76 | if err != nil { 77 | return nil, errors.Wrapf(err, "map %s", mapName) 78 | } 79 | maps[mapName] = m 80 | } 81 | 82 | progs := make(map[string]*Program) 83 | for progName, origProgSpec := range spec.Programs { 84 | progSpec := origProgSpec.Copy() 85 | editor := Edit(&progSpec.Instructions) 86 | 87 | // Rewrite any Symbol which is a valid Map. 88 | for sym := range editor.ReferenceOffsets { 89 | m, ok := maps[sym] 90 | if !ok { 91 | continue 92 | } 93 | 94 | // don't overwrite maps already rewritten, users can rewrite programs in the spec themselves 95 | if err := editor.rewriteMap(sym, m, false); err != nil { 96 | return nil, errors.Wrapf(err, "program %s", progName) 97 | } 98 | } 99 | 100 | prog, err := NewProgramWithOptions(progSpec, opts.Programs) 101 | if err != nil { 102 | return nil, errors.Wrapf(err, "program %s", progName) 103 | } 104 | progs[progName] = prog 105 | } 106 | 107 | return &Collection{ 108 | progs, 109 | maps, 110 | }, nil 111 | } 112 | 113 | // LoadCollection parses an object file and converts it to a collection. 114 | func LoadCollection(file string) (*Collection, error) { 115 | spec, err := LoadCollectionSpec(file) 116 | if err != nil { 117 | return nil, err 118 | } 119 | return NewCollection(spec) 120 | } 121 | 122 | // Close frees all maps and programs associated with the collection. 123 | // 124 | // The collection mustn't be used afterwards. 125 | func (coll *Collection) Close() { 126 | for _, prog := range coll.Programs { 127 | prog.Close() 128 | } 129 | for _, m := range coll.Maps { 130 | m.Close() 131 | } 132 | } 133 | 134 | // DetachMap removes the named map from the Collection. 135 | // 136 | // This means that a later call to Close() will not affect this map. 137 | // 138 | // Returns nil if no map of that name exists. 139 | func (coll *Collection) DetachMap(name string) *Map { 140 | m := coll.Maps[name] 141 | delete(coll.Maps, name) 142 | return m 143 | } 144 | 145 | // DetachProgram removes the named program from the Collection. 146 | // 147 | // This means that a later call to Close() will not affect this program. 148 | // 149 | // Returns nil if no program of that name exists. 150 | func (coll *Collection) DetachProgram(name string) *Program { 151 | p := coll.Programs[name] 152 | delete(coll.Programs, name) 153 | return p 154 | } 155 | 156 | // Pin persits a Collection beyond the lifetime of the process that created it 157 | // 158 | // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional 159 | func (coll *Collection) Pin(dirName string, fileMode os.FileMode) error { 160 | err := mkdirIfNotExists(dirName, fileMode) 161 | if err != nil { 162 | return err 163 | } 164 | if len(coll.Maps) > 0 { 165 | mapPath := filepath.Join(dirName, "maps") 166 | err = mkdirIfNotExists(mapPath, fileMode) 167 | if err != nil { 168 | return err 169 | } 170 | for k, v := range coll.Maps { 171 | err := v.Pin(filepath.Join(mapPath, k)) 172 | if err != nil { 173 | return errors.Wrapf(err, "map %s", k) 174 | } 175 | } 176 | } 177 | if len(coll.Programs) > 0 { 178 | progPath := filepath.Join(dirName, "programs") 179 | err = mkdirIfNotExists(progPath, fileMode) 180 | if err != nil { 181 | return err 182 | } 183 | for k, v := range coll.Programs { 184 | err = v.Pin(filepath.Join(progPath, k)) 185 | if err != nil { 186 | return errors.Wrapf(err, "program %s", k) 187 | } 188 | } 189 | } 190 | return nil 191 | } 192 | 193 | func mkdirIfNotExists(dirName string, fileMode os.FileMode) error { 194 | _, err := os.Stat(dirName) 195 | if err != nil && os.IsNotExist(err) { 196 | err = os.Mkdir(dirName, fileMode) 197 | } 198 | if err != nil { 199 | return err 200 | } 201 | return nil 202 | } 203 | 204 | // LoadPinnedCollection loads a Collection from the pinned directory. 205 | // 206 | // Requires at least Linux 4.13, use LoadPinnedCollectionExplicit on 207 | // earlier versions. 208 | func LoadPinnedCollection(dirName string) (*Collection, error) { 209 | return loadCollection( 210 | dirName, 211 | func(_ string, path string) (*Map, error) { 212 | return LoadPinnedMap(path) 213 | }, 214 | func(_ string, path string) (*Program, error) { 215 | return LoadPinnedProgram(path) 216 | }, 217 | ) 218 | } 219 | 220 | // LoadPinnedCollectionExplicit loads a Collection from the pinned directory with explicit parameters. 221 | func LoadPinnedCollectionExplicit(dirName string, maps map[string]*MapABI, progs map[string]*ProgramABI) (*Collection, error) { 222 | return loadCollection( 223 | dirName, 224 | func(name string, path string) (*Map, error) { 225 | return LoadPinnedMapExplicit(path, maps[name]) 226 | }, 227 | func(name string, path string) (*Program, error) { 228 | return LoadPinnedProgramExplicit(path, progs[name]) 229 | }, 230 | ) 231 | } 232 | 233 | func loadCollection(dirName string, loadMap func(string, string) (*Map, error), loadProgram func(string, string) (*Program, error)) (*Collection, error) { 234 | maps, err := readFileNames(filepath.Join(dirName, "maps")) 235 | if err != nil { 236 | return nil, err 237 | } 238 | progs, err := readFileNames(filepath.Join(dirName, "programs")) 239 | if err != nil { 240 | return nil, err 241 | } 242 | bpfColl := &Collection{ 243 | Maps: make(map[string]*Map), 244 | Programs: make(map[string]*Program), 245 | } 246 | for _, mf := range maps { 247 | name := filepath.Base(mf) 248 | m, err := loadMap(name, mf) 249 | if err != nil { 250 | return nil, errors.Wrapf(err, "map %s", name) 251 | } 252 | bpfColl.Maps[name] = m 253 | } 254 | for _, pf := range progs { 255 | name := filepath.Base(pf) 256 | prog, err := loadProgram(name, pf) 257 | if err != nil { 258 | return nil, errors.Wrapf(err, "program %s", name) 259 | } 260 | bpfColl.Programs[name] = prog 261 | } 262 | return bpfColl, nil 263 | } 264 | 265 | func readFileNames(dirName string) ([]string, error) { 266 | var fileNames []string 267 | files, err := ioutil.ReadDir(dirName) 268 | if err != nil && err != os.ErrNotExist { 269 | return nil, err 270 | } 271 | for _, fi := range files { 272 | fileNames = append(fileNames, fi.Name()) 273 | } 274 | return fileNames, nil 275 | } 276 | -------------------------------------------------------------------------------- /example_sock_elf_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package ebpf_test 4 | 5 | import ( 6 | "bytes" 7 | "flag" 8 | "fmt" 9 | "os" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/newtools/ebpf" 14 | ) 15 | 16 | var program = [...]byte{ 17 | 0177, 0105, 0114, 0106, 0002, 0001, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 18 | 0001, 0000, 0367, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 19 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 20 | 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0010, 0000, 0001, 0000, 21 | 0277, 0026, 0000, 0000, 0000, 0000, 0000, 0000, 0060, 0000, 0000, 0000, 0027, 0000, 0000, 0000, 22 | 0143, 0012, 0374, 0377, 0000, 0000, 0000, 0000, 0141, 0141, 0004, 0000, 0000, 0000, 0000, 0000, 23 | 0125, 0001, 0010, 0000, 0004, 0000, 0000, 0000, 0277, 0242, 0000, 0000, 0000, 0000, 0000, 0000, 24 | 0007, 0002, 0000, 0000, 0374, 0377, 0377, 0377, 0030, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 25 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0205, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 26 | 0025, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0141, 0141, 0000, 0000, 0000, 0000, 0000, 0000, 27 | 0333, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0267, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 28 | 0225, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0004, 0000, 0000, 0000, 29 | 0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 30 | 0004, 0000, 0000, 0000, 0010, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 31 | 0107, 0120, 0114, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 32 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 33 | 0065, 0000, 0000, 0000, 0000, 0000, 0003, 0000, 0150, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 34 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0034, 0000, 0000, 0000, 0020, 0000, 0006, 0000, 35 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 36 | 0110, 0000, 0000, 0000, 0020, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 37 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0014, 0000, 0000, 0000, 0020, 0000, 0005, 0000, 38 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 39 | 0023, 0000, 0000, 0000, 0020, 0000, 0005, 0000, 0024, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 40 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0070, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 41 | 0001, 0000, 0000, 0000, 0004, 0000, 0000, 0000, 0000, 0056, 0164, 0145, 0170, 0164, 0000, 0155, 42 | 0141, 0160, 0163, 0000, 0155, 0171, 0137, 0155, 0141, 0160, 0000, 0164, 0145, 0163, 0164, 0137, 43 | 0155, 0141, 0160, 0000, 0137, 0154, 0151, 0143, 0145, 0156, 0163, 0145, 0000, 0056, 0163, 0164, 44 | 0162, 0164, 0141, 0142, 0000, 0056, 0163, 0171, 0155, 0164, 0141, 0142, 0000, 0114, 0102, 0102, 45 | 0060, 0137, 0063, 0000, 0056, 0162, 0145, 0154, 0163, 0157, 0143, 0153, 0145, 0164, 0061, 0000, 46 | 0142, 0160, 0146, 0137, 0160, 0162, 0157, 0147, 0061, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 47 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 48 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 49 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 50 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 51 | 0045, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 52 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0210, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 53 | 0122, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 54 | 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 55 | 0001, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 56 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 57 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 58 | 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 59 | 0100, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0006, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 60 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0100, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 61 | 0170, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 62 | 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 63 | 0074, 0000, 0000, 0000, 0011, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 64 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0170, 0001, 0000, 0000, 0000, 0000, 0000, 0000, 65 | 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0007, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 66 | 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0020, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 67 | 0007, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 68 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0270, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 69 | 0050, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 70 | 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 71 | 0035, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0003, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 72 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0340, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 73 | 0004, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 74 | 0001, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 75 | 0055, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 76 | 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0350, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 77 | 0220, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0001, 0000, 0000, 0000, 0002, 0000, 0000, 0000, 78 | 0010, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 0030, 0000, 0000, 0000, 0000, 0000, 0000, 0000, 79 | } 80 | 81 | const sockexPin = "/sys/fs/bpf/sockex1" 82 | 83 | // ExampleSocketELF demonstrates how to load an eBPF program from an ELF, 84 | // pin it into the filesystem and attach it to a raw socket. 85 | func Example_socketELF() { 86 | const SO_ATTACH_BPF = 50 87 | 88 | index := flag.Int("index", 0, "specify ethernet index") 89 | flag.Parse() 90 | fi, err := os.Lstat(sockexPin) 91 | if err != nil && !os.IsNotExist(err) { 92 | panic(err) 93 | } 94 | var coll *ebpf.Collection 95 | if fi != nil { 96 | coll, err = ebpf.LoadPinnedCollection(sockexPin) 97 | if err != nil { 98 | panic(err) 99 | } 100 | } else { 101 | spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(program[:])) 102 | if err != nil { 103 | panic(err) 104 | } 105 | coll, err = ebpf.NewCollection(spec) 106 | if err != nil { 107 | panic(err) 108 | } 109 | err = coll.Pin(sockexPin, 0600) 110 | if err != nil { 111 | panic(err) 112 | } 113 | } 114 | defer coll.Close() 115 | 116 | sock, err := openRawSock(*index) 117 | if err != nil { 118 | panic(err) 119 | } 120 | defer syscall.Close(sock) 121 | 122 | prog := coll.DetachProgram("bpf_prog1") 123 | if prog == nil { 124 | panic("no program named bpf_prog1 found") 125 | } 126 | defer prog.Close() 127 | 128 | if err := syscall.SetsockoptInt(sock, syscall.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil { 129 | panic(err) 130 | } 131 | 132 | fmt.Printf("Filtering on eth index: %d\n", *index) 133 | fmt.Println("Packet stats:") 134 | 135 | protoStats := coll.DetachMap("my_map") 136 | if protoStats == nil { 137 | panic(fmt.Errorf("no map named my_map found")) 138 | } 139 | defer protoStats.Close() 140 | 141 | for { 142 | const ( 143 | ICMP = 0x01 144 | TCP = 0x06 145 | UDP = 0x11 146 | ) 147 | 148 | time.Sleep(time.Second) 149 | var icmp uint64 150 | var tcp uint64 151 | var udp uint64 152 | ok, err := protoStats.Get(uint32(ICMP), &icmp) 153 | if err != nil { 154 | panic(err) 155 | } 156 | assertTrue(ok, "icmp key not found") 157 | ok, err = protoStats.Get(uint32(TCP), &tcp) 158 | if err != nil { 159 | panic(err) 160 | } 161 | assertTrue(ok, "tcp key not found") 162 | ok, err = protoStats.Get(uint32(UDP), &udp) 163 | if err != nil { 164 | panic(err) 165 | } 166 | assertTrue(ok, "udp key not found") 167 | fmt.Printf("\r\033[m\tICMP: %d TCP: %d UDP: %d", icmp, tcp, udp) 168 | } 169 | } 170 | 171 | func assertTrue(b bool, msg string) { 172 | if !b { 173 | panic(fmt.Errorf("%s", msg)) 174 | } 175 | } 176 | 177 | func openRawSock(index int) (int, error) { 178 | const ETH_P_ALL uint16 = 0x00<<8 | 0x03 179 | sock, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, int(ETH_P_ALL)) 180 | if err != nil { 181 | return 0, err 182 | } 183 | sll := syscall.SockaddrLinklayer{} 184 | sll.Protocol = ETH_P_ALL 185 | sll.Ifindex = index 186 | if err := syscall.Bind(sock, &sll); err != nil { 187 | return 0, err 188 | } 189 | return sock, nil 190 | } 191 | -------------------------------------------------------------------------------- /asm/instruction.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "math" 8 | "strings" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // InstructionSize is the size of a BPF instruction in bytes 14 | const InstructionSize = 8 15 | 16 | // Instruction is a single eBPF instruction. 17 | type Instruction struct { 18 | OpCode OpCode 19 | Dst Register 20 | Src Register 21 | Offset int16 22 | Constant int64 23 | Reference string 24 | Symbol string 25 | } 26 | 27 | // Sym creates a symbol. 28 | func (ins Instruction) Sym(name string) Instruction { 29 | ins.Symbol = name 30 | return ins 31 | } 32 | 33 | // Format implements fmt.Formatter. 34 | func (ins Instruction) Format(f fmt.State, c rune) { 35 | if c != 'v' { 36 | fmt.Fprintf(f, "{UNRECOGNIZED: %c}", c) 37 | return 38 | } 39 | 40 | op := ins.OpCode 41 | 42 | if op == InvalidOpCode { 43 | fmt.Fprint(f, "INVALID") 44 | return 45 | } 46 | 47 | // Omit trailing space for Exit 48 | if op.JumpOp() == Exit { 49 | fmt.Fprint(f, op) 50 | return 51 | } 52 | 53 | fmt.Fprintf(f, "%v ", op) 54 | switch cls := op.Class(); cls { 55 | case LdClass, LdXClass, StClass, StXClass: 56 | switch op.Mode() { 57 | case ImmMode: 58 | fmt.Fprintf(f, "dst: %s imm: %d", ins.Dst, ins.Constant) 59 | case AbsMode: 60 | fmt.Fprintf(f, "imm: %d", ins.Constant) 61 | case IndMode: 62 | fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant) 63 | case MemMode: 64 | fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant) 65 | case XAddMode: 66 | fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src) 67 | } 68 | 69 | case ALU64Class, ALUClass: 70 | fmt.Fprintf(f, "dst: %s ", ins.Dst) 71 | if op.ALUOp() == Swap || op.Source() == ImmSource { 72 | fmt.Fprintf(f, "imm: %d", ins.Constant) 73 | } else { 74 | fmt.Fprintf(f, "src: %s", ins.Src) 75 | } 76 | 77 | case JumpClass: 78 | switch jop := op.JumpOp(); jop { 79 | case Call: 80 | if ins.Src == R1 { 81 | // bpf-to-bpf call 82 | fmt.Fprint(f, ins.Constant) 83 | } else { 84 | fmt.Fprint(f, BuiltinFunc(ins.Constant)) 85 | } 86 | 87 | default: 88 | fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset) 89 | if op.Source() == ImmSource { 90 | fmt.Fprintf(f, "imm: %d", ins.Constant) 91 | } else { 92 | fmt.Fprintf(f, "src: %s", ins.Src) 93 | } 94 | } 95 | } 96 | 97 | if ins.Reference != "" { 98 | fmt.Fprintf(f, " <%s>", ins.Reference) 99 | } 100 | } 101 | 102 | // Instructions is an eBPF program. 103 | type Instructions []Instruction 104 | 105 | func (insns Instructions) String() string { 106 | return fmt.Sprint(insns) 107 | } 108 | 109 | // SymbolOffsets returns the set of symbols and their offset in 110 | // the instructions. 111 | func (insns Instructions) SymbolOffsets() (map[string]int, error) { 112 | offsets := make(map[string]int) 113 | 114 | for i, ins := range insns { 115 | if ins.Symbol == "" { 116 | continue 117 | } 118 | 119 | if _, ok := offsets[ins.Symbol]; ok { 120 | return nil, errors.Errorf("duplicate symbol %s", ins.Symbol) 121 | } 122 | 123 | offsets[ins.Symbol] = i 124 | } 125 | 126 | return offsets, nil 127 | } 128 | 129 | // ReferenceOffsets returns the set of references and their offset in 130 | // the instructions. 131 | func (insns Instructions) ReferenceOffsets() map[string][]int { 132 | offsets := make(map[string][]int) 133 | 134 | for i, ins := range insns { 135 | if ins.Reference == "" { 136 | continue 137 | } 138 | 139 | offsets[ins.Reference] = append(offsets[ins.Reference], i) 140 | } 141 | 142 | return offsets 143 | } 144 | 145 | func (insns Instructions) marshalledOffsets() (map[string]int, error) { 146 | symbols := make(map[string]int) 147 | 148 | marshalledPos := 0 149 | for _, ins := range insns { 150 | currentPos := marshalledPos 151 | marshalledPos += ins.OpCode.marshalledInstructions() 152 | 153 | if ins.Symbol == "" { 154 | continue 155 | } 156 | 157 | if _, ok := symbols[ins.Symbol]; ok { 158 | return nil, errors.Errorf("duplicate symbol %s", ins.Symbol) 159 | } 160 | 161 | symbols[ins.Symbol] = currentPos 162 | } 163 | 164 | return symbols, nil 165 | } 166 | 167 | // Format implements fmt.Formatter. 168 | // 169 | // You can control indentation of symbols by 170 | // specifying a width. Setting a precision controls the indentation of 171 | // instructions. 172 | // The default character is a tab, which can be overriden by specifying 173 | // the ' ' space flag. 174 | func (insns Instructions) Format(f fmt.State, c rune) { 175 | if c != 's' && c != 'v' { 176 | fmt.Fprintf(f, "{UNKNOWN FORMAT '%c'}", c) 177 | return 178 | } 179 | 180 | // Precision is better in this case, because it allows 181 | // specifying 0 padding easily. 182 | padding, ok := f.Precision() 183 | if !ok { 184 | padding = 1 185 | } 186 | 187 | indent := strings.Repeat("\t", padding) 188 | if f.Flag(' ') { 189 | indent = strings.Repeat(" ", padding) 190 | } 191 | 192 | symPadding, ok := f.Width() 193 | if !ok { 194 | symPadding = padding - 1 195 | } 196 | if symPadding < 0 { 197 | symPadding = 0 198 | } 199 | 200 | symIndent := strings.Repeat("\t", symPadding) 201 | if f.Flag(' ') { 202 | symIndent = strings.Repeat(" ", symPadding) 203 | } 204 | 205 | // Figure out how many digits we need to represent the highest 206 | // offset. 207 | highestOffset := 0 208 | for _, ins := range insns { 209 | highestOffset += ins.OpCode.marshalledInstructions() 210 | } 211 | offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset)))) 212 | 213 | offset := 0 214 | for _, ins := range insns { 215 | if ins.Symbol != "" { 216 | fmt.Fprintf(f, "%s%s:\n", symIndent, ins.Symbol) 217 | } 218 | fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, offset, ins) 219 | offset += ins.OpCode.marshalledInstructions() 220 | } 221 | 222 | return 223 | } 224 | 225 | // Marshal encodes a BPF program into the kernel format. 226 | func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error { 227 | loadImmDW := LoadImmOp(DWord) 228 | 229 | absoluteOffsets, err := insns.marshalledOffsets() 230 | if err != nil { 231 | return err 232 | } 233 | 234 | num := 0 235 | for i, ins := range insns { 236 | if ins.OpCode == InvalidOpCode { 237 | return errors.Errorf("invalid operation at position %d", i) 238 | } 239 | 240 | isLoadImmDW := ins.OpCode == loadImmDW 241 | 242 | cons := int32(ins.Constant) 243 | switch { 244 | case isLoadImmDW: 245 | // Encode least significant 32bit first for 64bit operations. 246 | cons = int32(uint32(ins.Constant)) 247 | 248 | case ins.OpCode.JumpOp() == Call && ins.Constant == -1: 249 | // Rewrite bpf to bpf call 250 | offset, ok := absoluteOffsets[ins.Reference] 251 | if !ok { 252 | return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) 253 | } 254 | 255 | cons = int32(offset - num - 1) 256 | 257 | case ins.OpCode.Class() == JumpClass && ins.Offset == -1: 258 | // Rewrite jump to label 259 | offset, ok := absoluteOffsets[ins.Reference] 260 | if !ok { 261 | return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference) 262 | } 263 | 264 | ins.Offset = int16(offset - num - 1) 265 | } 266 | 267 | bpfi := bpfInstruction{ 268 | ins.OpCode, 269 | newBPFRegisters(ins.Dst, ins.Src), 270 | ins.Offset, 271 | cons, 272 | } 273 | 274 | if err := binary.Write(w, bo, &bpfi); err != nil { 275 | return err 276 | } 277 | num++ 278 | 279 | if !isLoadImmDW { 280 | continue 281 | } 282 | 283 | bpfi = bpfInstruction{ 284 | Constant: int32(ins.Constant >> 32), 285 | } 286 | 287 | if err := binary.Write(w, bo, &bpfi); err != nil { 288 | return err 289 | } 290 | num++ 291 | } 292 | return nil 293 | } 294 | 295 | // Unmarshal decodes a BPF program from the kernel format. 296 | func (insns *Instructions) Unmarshal(r io.Reader, bo binary.ByteOrder) (map[uint64]int, error) { 297 | *insns = nil 298 | 299 | // Since relocations point at an offset, we need to keep track which 300 | // offset maps to which instruction. 301 | var ( 302 | offsets = make(map[uint64]int) 303 | offset uint64 304 | ) 305 | for { 306 | offsets[offset] = len(*insns) 307 | 308 | var ins bpfInstruction 309 | err := binary.Read(r, bo, &ins) 310 | 311 | if err == io.EOF { 312 | return offsets, nil 313 | } 314 | 315 | if err != nil { 316 | return nil, errors.Errorf("invalid instruction at offset %x", offset) 317 | } 318 | 319 | requiredInsns := ins.OpCode.marshalledInstructions() 320 | offset += uint64(requiredInsns) * InstructionSize 321 | 322 | cons := int64(ins.Constant) 323 | if requiredInsns == 2 { 324 | var ins2 bpfInstruction 325 | if err := binary.Read(r, bo, &ins2); err != nil { 326 | return nil, errors.Errorf("invalid instruction at offset %x", offset) 327 | } 328 | if ins2.OpCode != 0 || ins2.Offset != 0 || ins2.Registers != 0 { 329 | return nil, errors.Errorf("instruction at offset %x: 64bit immediate has non-zero fields", offset) 330 | } 331 | cons = int64(uint64(uint32(ins2.Constant))<<32 | uint64(uint32(ins.Constant))) 332 | } 333 | 334 | *insns = append(*insns, Instruction{ 335 | OpCode: ins.OpCode, 336 | Dst: ins.Registers.Dst(), 337 | Src: ins.Registers.Src(), 338 | Offset: ins.Offset, 339 | Constant: cons, 340 | }) 341 | } 342 | } 343 | 344 | type bpfInstruction struct { 345 | OpCode OpCode 346 | Registers bpfRegisters 347 | Offset int16 348 | Constant int32 349 | } 350 | 351 | type bpfRegisters uint8 352 | 353 | func newBPFRegisters(dst, src Register) bpfRegisters { 354 | return bpfRegisters((src << 4) | (dst & 0xF)) 355 | } 356 | 357 | func (r bpfRegisters) Dst() Register { 358 | return Register(r & 0xF) 359 | } 360 | 361 | func (r bpfRegisters) Src() Register { 362 | return Register(r >> 4) 363 | } 364 | -------------------------------------------------------------------------------- /syscalls.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "path/filepath" 6 | "runtime" 7 | "strconv" 8 | "strings" 9 | "unsafe" 10 | 11 | "github.com/pkg/errors" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | const ( 16 | bpfObjNameLen = 16 17 | bpfTagSize = 8 18 | ) 19 | 20 | var errClosedFd = errors.New("use of closed file descriptor") 21 | 22 | type bpfFD struct { 23 | raw int64 24 | } 25 | 26 | func newBPFFD(value uint32) *bpfFD { 27 | fd := &bpfFD{int64(value)} 28 | runtime.SetFinalizer(fd, (*bpfFD).close) 29 | return fd 30 | } 31 | 32 | func (fd *bpfFD) String() string { 33 | return strconv.FormatInt(fd.raw, 10) 34 | } 35 | 36 | func (fd *bpfFD) value() (uint32, error) { 37 | if fd.raw < 0 { 38 | return 0, errClosedFd 39 | } 40 | 41 | return uint32(fd.raw), nil 42 | } 43 | 44 | func (fd *bpfFD) close() error { 45 | if fd.raw < 0 { 46 | return nil 47 | } 48 | 49 | value := int(fd.raw) 50 | fd.raw = -1 51 | 52 | runtime.SetFinalizer(fd, nil) 53 | return unix.Close(value) 54 | } 55 | 56 | func (fd *bpfFD) dup() (*bpfFD, error) { 57 | if fd.raw < 0 { 58 | return nil, errClosedFd 59 | } 60 | 61 | dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0) 62 | if err != nil { 63 | return nil, errors.Wrap(err, "can't dup fd") 64 | } 65 | 66 | return newBPFFD(uint32(dup)), nil 67 | } 68 | 69 | // bpfObjName is a null-terminated string made up of 70 | // 'A-Za-z0-9_' characters. 71 | type bpfObjName [bpfObjNameLen]byte 72 | 73 | // newBPFObjName truncates the result if it is too long. 74 | func newBPFObjName(name string) (bpfObjName, error) { 75 | idx := strings.IndexFunc(name, invalidBPFObjNameChar) 76 | if idx != -1 { 77 | return bpfObjName{}, errors.Errorf("invalid character '%c' in name '%s'", name[idx], name) 78 | } 79 | 80 | var result bpfObjName 81 | copy(result[:bpfObjNameLen-1], name) 82 | return result, nil 83 | } 84 | 85 | func invalidBPFObjNameChar(char rune) bool { 86 | switch { 87 | case char >= 'A' && char <= 'Z': 88 | fallthrough 89 | case char >= 'a' && char <= 'z': 90 | fallthrough 91 | case char >= '0' && char <= '9': 92 | fallthrough 93 | case char == '_': 94 | return false 95 | default: 96 | return true 97 | } 98 | } 99 | 100 | type bpfMapCreateAttr struct { 101 | mapType MapType 102 | keySize uint32 103 | valueSize uint32 104 | maxEntries uint32 105 | flags uint32 106 | innerMapFd uint32 // since 4.12 56f668dfe00d 107 | numaNode uint32 // since 4.14 96eabe7a40aa 108 | mapName bpfObjName // since 4.15 ad5b177bd73f 109 | } 110 | 111 | type bpfMapOpAttr struct { 112 | mapFd uint32 113 | padding uint32 114 | key syscallPtr 115 | value syscallPtr 116 | flags uint64 117 | } 118 | 119 | type bpfMapInfo struct { 120 | mapType uint32 121 | id uint32 122 | keySize uint32 123 | valueSize uint32 124 | maxEntries uint32 125 | flags uint32 126 | mapName bpfObjName // since 4.15 ad5b177bd73f 127 | } 128 | 129 | type bpfPinObjAttr struct { 130 | fileName syscallPtr 131 | fd uint32 132 | padding uint32 133 | } 134 | 135 | type bpfProgLoadAttr struct { 136 | progType ProgType 137 | insCount uint32 138 | instructions syscallPtr 139 | license syscallPtr 140 | logLevel uint32 141 | logSize uint32 142 | logBuf syscallPtr 143 | kernelVersion uint32 // since 4.1 2541517c32be 144 | progFlags uint32 // since 4.11 e07b98d9bffe 145 | progName bpfObjName // since 4.15 067cae47771c 146 | progIfIndex uint32 // since 4.15 1f6f4cb7ba21 147 | expectedAttachType AttachType // since 4.17 5e43f899b03a 148 | } 149 | 150 | type bpfProgInfo struct { 151 | progType uint32 152 | id uint32 153 | tag [bpfTagSize]byte 154 | jitedLen uint32 155 | xlatedLen uint32 156 | jited syscallPtr 157 | xlated syscallPtr 158 | loadTime uint64 // since 4.15 cb4d2b3f03d8 159 | createdByUID uint32 160 | nrMapIDs uint32 161 | mapIds syscallPtr 162 | name bpfObjName 163 | } 164 | 165 | type bpfProgTestRunAttr struct { 166 | fd uint32 167 | retval uint32 168 | dataSizeIn uint32 169 | dataSizeOut uint32 170 | dataIn syscallPtr 171 | dataOut syscallPtr 172 | repeat uint32 173 | duration uint32 174 | } 175 | 176 | type bpfObjGetInfoByFDAttr struct { 177 | fd uint32 178 | infoLen uint32 179 | info syscallPtr // May be either bpfMapInfo or bpfProgInfo 180 | } 181 | 182 | type bpfGetFDByIDAttr struct { 183 | id uint32 184 | next uint32 185 | } 186 | 187 | func newPtr(ptr unsafe.Pointer) syscallPtr { 188 | return syscallPtr{ptr: ptr} 189 | } 190 | 191 | func bpfProgLoad(attr *bpfProgLoadAttr) (*bpfFD, error) { 192 | for { 193 | fd, err := bpfCall(_ProgLoad, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 194 | // As of ~4.20 the verifier can be interrupted by a signal, 195 | // and returns EAGAIN in that case. 196 | if err == unix.EAGAIN { 197 | continue 198 | } 199 | 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | return newBPFFD(uint32(fd)), nil 205 | } 206 | } 207 | 208 | func bpfMapCreate(attr *bpfMapCreateAttr) (*bpfFD, error) { 209 | fd, err := bpfCall(_MapCreate, unsafe.Pointer(attr), unsafe.Sizeof(*attr)) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | return newBPFFD(uint32(fd)), nil 215 | } 216 | 217 | func bpfMapLookupElem(m *bpfFD, key, valueOut syscallPtr) error { 218 | fd, err := m.value() 219 | if err != nil { 220 | return err 221 | } 222 | 223 | attr := bpfMapOpAttr{ 224 | mapFd: fd, 225 | key: key, 226 | value: valueOut, 227 | } 228 | _, err = bpfCall(_MapLookupElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 229 | return err 230 | } 231 | 232 | func bpfMapUpdateElem(m *bpfFD, key, valueOut syscallPtr, flags uint64) error { 233 | fd, err := m.value() 234 | if err != nil { 235 | return err 236 | } 237 | 238 | attr := bpfMapOpAttr{ 239 | mapFd: fd, 240 | key: key, 241 | value: valueOut, 242 | flags: flags, 243 | } 244 | _, err = bpfCall(_MapUpdateElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 245 | return err 246 | } 247 | 248 | func bpfMapDeleteElem(m *bpfFD, key syscallPtr) error { 249 | fd, err := m.value() 250 | if err != nil { 251 | return err 252 | } 253 | 254 | attr := bpfMapOpAttr{ 255 | mapFd: fd, 256 | key: key, 257 | } 258 | _, err = bpfCall(_MapDeleteElem, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 259 | return err 260 | } 261 | 262 | func bpfMapGetNextKey(m *bpfFD, key, nextKeyOut syscallPtr) error { 263 | fd, err := m.value() 264 | if err != nil { 265 | return err 266 | } 267 | 268 | attr := bpfMapOpAttr{ 269 | mapFd: fd, 270 | key: key, 271 | value: nextKeyOut, 272 | } 273 | _, err = bpfCall(_MapGetNextKey, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 274 | return err 275 | } 276 | 277 | const bpfFSType = 0xcafe4a11 278 | 279 | func bpfPinObject(fileName string, fd *bpfFD) error { 280 | dirName := filepath.Dir(fileName) 281 | var statfs unix.Statfs_t 282 | if err := unix.Statfs(dirName, &statfs); err != nil { 283 | return err 284 | } 285 | if statfs.Type != bpfFSType { 286 | return errors.Errorf("%s is not on a bpf filesystem", fileName) 287 | } 288 | 289 | value, err := fd.value() 290 | if err != nil { 291 | return err 292 | } 293 | 294 | _, err = bpfCall(_ObjPin, unsafe.Pointer(&bpfPinObjAttr{ 295 | fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])), 296 | fd: value, 297 | }), 16) 298 | return errors.Wrapf(err, "pin object %s", fileName) 299 | } 300 | 301 | func bpfGetObject(fileName string) (*bpfFD, error) { 302 | ptr, err := bpfCall(_ObjGet, unsafe.Pointer(&bpfPinObjAttr{ 303 | fileName: newPtr(unsafe.Pointer(&[]byte(fileName)[0])), 304 | }), 16) 305 | if err != nil { 306 | return nil, errors.Wrapf(err, "get object %s", fileName) 307 | } 308 | return newBPFFD(uint32(ptr)), nil 309 | } 310 | 311 | func bpfGetObjectInfoByFD(fd *bpfFD, info unsafe.Pointer, size uintptr) error { 312 | value, err := fd.value() 313 | if err != nil { 314 | return err 315 | } 316 | 317 | // available from 4.13 318 | attr := bpfObjGetInfoByFDAttr{ 319 | fd: value, 320 | infoLen: uint32(size), 321 | info: newPtr(info), 322 | } 323 | _, err = bpfCall(_ObjGetInfoByFD, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 324 | return errors.Wrapf(err, "fd %d", value) 325 | } 326 | 327 | func bpfGetProgInfoByFD(fd *bpfFD) (*bpfProgInfo, error) { 328 | var info bpfProgInfo 329 | err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)) 330 | return &info, errors.Wrap(err, "can't get program info") 331 | } 332 | 333 | func bpfGetMapInfoByFD(fd *bpfFD) (*bpfMapInfo, error) { 334 | var info bpfMapInfo 335 | err := bpfGetObjectInfoByFD(fd, unsafe.Pointer(&info), unsafe.Sizeof(info)) 336 | return &info, errors.Wrap(err, "can't get map info:") 337 | } 338 | 339 | var haveObjName = featureTest{ 340 | Fn: func() bool { 341 | name, err := newBPFObjName("feature_test") 342 | if err != nil { 343 | // This really is a fatal error, but it should be caught 344 | // by the unit tests not working. 345 | return false 346 | } 347 | 348 | attr := bpfMapCreateAttr{ 349 | mapType: Array, 350 | keySize: 4, 351 | valueSize: 4, 352 | maxEntries: 1, 353 | mapName: name, 354 | } 355 | 356 | fd, err := bpfMapCreate(&attr) 357 | if err != nil { 358 | return false 359 | } 360 | 361 | _ = fd.close() 362 | return true 363 | }, 364 | } 365 | 366 | func bpfGetMapFDByID(id uint32) (*bpfFD, error) { 367 | // available from 4.13 368 | attr := bpfGetFDByIDAttr{ 369 | id: id, 370 | } 371 | ptr, err := bpfCall(_MapGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 372 | if err != nil { 373 | return nil, errors.Wrapf(err, "can't get fd for map id %d", id) 374 | } 375 | return newBPFFD(uint32(ptr)), nil 376 | } 377 | 378 | func bpfGetProgramFDByID(id uint32) (*bpfFD, error) { 379 | // available from 4.13 380 | attr := bpfGetFDByIDAttr{ 381 | id: id, 382 | } 383 | ptr, err := bpfCall(_ProgGetFDByID, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 384 | if err != nil { 385 | return nil, errors.Wrapf(err, "can't get fd for program id %d", id) 386 | } 387 | return newBPFFD(uint32(ptr)), nil 388 | } 389 | 390 | func bpfCall(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) { 391 | r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) 392 | runtime.KeepAlive(attr) 393 | 394 | var err error 395 | if errNo != 0 { 396 | err = errNo 397 | } 398 | 399 | return r1, err 400 | } 401 | 402 | func convertCString(in []byte) string { 403 | inLen := bytes.IndexByte(in, 0) 404 | if inLen == -1 { 405 | return "" 406 | } 407 | return string(in[:inLen]) 408 | } 409 | -------------------------------------------------------------------------------- /perf_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "syscall" 9 | "testing" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | func TestPerfReader(t *testing.T) { 15 | coll, err := LoadCollection("testdata/perf_output.elf") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer coll.Close() 20 | 21 | rd, err := NewPerfReader(PerfReaderOptions{ 22 | Map: coll.DetachMap("events"), 23 | PerCPUBuffer: 4096, 24 | Watermark: 1, 25 | }) 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | defer rd.Close() 30 | 31 | prog := coll.DetachProgram("output_single") 32 | defer prog.Close() 33 | 34 | ret, _, err := prog.Test(make([]byte, 14)) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | if errno := syscall.Errno(-int32(ret)); errno != 0 { 40 | t.Fatal("Expected 0 as return value, got", errno) 41 | } 42 | 43 | sample := <-rd.Samples 44 | want := []byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0} 45 | if !bytes.Equal(sample.Data, want) { 46 | t.Log(sample.Data) 47 | t.Error("Sample doesn't match expected output") 48 | } 49 | } 50 | 51 | func TestPerfReaderLostSample(t *testing.T) { 52 | // To generate a lost sample perf record: 53 | // 54 | // 1. Fill the perf ring buffer almost completely, with the output_large program. 55 | // The buffer is sized in number of pages, which are architecture dependant. 56 | // 57 | // 2. Write an extra event that doesn't fit in the space remaining. 58 | // 59 | // 3. Write a smaller event that does fit, with output_single program. 60 | // Lost sample records are generated opportunistically, when the kernel 61 | // is writing an event and realizes that there were events lost previously. 62 | // 63 | // The event size is hardcoded in the test BPF programs, there's no way 64 | // to parametrize it without rebuilding the programs. 65 | // 66 | // The event size needs to be selected so that, for any page size, there are at least 67 | // 48 bytes left in the perf ring page after filling it with a whole number of events: 68 | // 69 | // - PERF_RECORD_LOST: 8 (perf_event_header) + 16 (PERF_RECORD_LOST) 70 | // 71 | // - output_single: 8 (perf_event_header) + 4 (size) + 5 (payload) + 7 (padding to 64bits) 72 | // 73 | // By selecting an event size of the form 2^n + 2^(n+1), for any page size 2^(n+m), m >= 0, 74 | // the number of bytes left, x, after filling a page with a whole number of events is: 75 | // 76 | // 2^(n+m) 2^n * 2^m 77 | // x = 2^n * frac(---------------) <=> x = 2^n * frac(---------------) 78 | // 2^n + 2^(n+1) 2^n + 2^n * 2 79 | // 80 | // 2^n * 2^m 81 | // <=> x = 2^n * frac(---------------) 82 | // 2^n * (1 + 2) 83 | // 84 | // 2^m 85 | // <=> x = 2^n * frac(-----) 86 | // 3 87 | // 88 | // 1 2 89 | // <=> x = 2^n * - or x = 2^n * - 90 | // 3 3 91 | // 92 | // Selecting n = 6, we have: 93 | // 94 | // x = 64 or x = 128, no matter the page size 2^(6+m) 95 | // 96 | // event size = 2^6 + 2^7 = 192 97 | // 98 | // Accounting for perf headers, output_large uses a 180 byte payload: 99 | // 100 | // 8 (perf_event_header) + 4 (size) + 180 (payload) 101 | const eventSize = 192 102 | 103 | pageSize := os.Getpagesize() 104 | 105 | remainder := pageSize % eventSize 106 | if !(remainder == 64 || remainder == 128) { 107 | // Page size isn't 2^(6+m), m >= 0 108 | t.Fatal("unsupported page size:", pageSize) 109 | } 110 | 111 | coll, err := LoadCollection("testdata/perf_output.elf") 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | defer coll.Close() 116 | 117 | rd, err := NewPerfReader(PerfReaderOptions{ 118 | Map: coll.DetachMap("events"), 119 | PerCPUBuffer: pageSize, 120 | // Notify 30 bytes _after_ the last output_large event that can fit in one page, 121 | // ie after the lost record is written, and when the output_small event is written. 122 | Watermark: pageSize - (remainder - 30), 123 | }) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | defer rd.Close() 128 | 129 | prog := coll.DetachProgram("output_large") 130 | defer prog.Close() 131 | 132 | // Fill the ring with the maximum number of output_large events that will fit, 133 | // and generate a lost event by writing an additional event. 134 | ret, _, err := prog.Benchmark(make([]byte, 14), (pageSize/eventSize)+1) 135 | if err != nil { 136 | t.Fatal(err) 137 | } 138 | 139 | if errno := syscall.Errno(-int32(ret)); errno != 0 { 140 | t.Fatal("Expected 0 as return value, got", errno) 141 | } 142 | 143 | // Generate a small event to trigger the lost record 144 | prog = coll.DetachProgram("output_single") 145 | defer prog.Close() 146 | 147 | ret, _, err = prog.Test(make([]byte, 14)) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | if errno := syscall.Errno(-int32(ret)); errno != 0 { 153 | t.Fatal("Expected 0 as return value, got", errno) 154 | } 155 | 156 | // Check we received all the samples 157 | for sample := range rd.Samples { 158 | // Wait for the small sample, as an indicator that the reader has processed 159 | // the lost event. 160 | if len(sample.Data) == 12 { 161 | break 162 | } 163 | } 164 | 165 | if lost := rd.LostSamples(); lost != 1 { 166 | t.Error("Expected 1 lost sample, got", lost) 167 | } 168 | } 169 | 170 | func TestPerfReaderClose(t *testing.T) { 171 | coll, err := LoadCollection("testdata/perf_output.elf") 172 | if err != nil { 173 | t.Fatal(err) 174 | } 175 | defer coll.Close() 176 | 177 | rd, err := NewPerfReader(PerfReaderOptions{ 178 | Map: coll.DetachMap("events"), 179 | PerCPUBuffer: 4096, 180 | Watermark: 1, 181 | }) 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | defer rd.Close() 186 | 187 | prog := coll.DetachProgram("output_single") 188 | defer prog.Close() 189 | 190 | // more samples than the channel capacity 191 | for i := 0; i < cap(rd.Samples)*2; i++ { 192 | ret, _, err := prog.Test(make([]byte, 14)) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | 197 | if errno := syscall.Errno(-int32(ret)); errno != 0 { 198 | t.Fatal("Expected 0 as return value, got", errno) 199 | } 200 | } 201 | 202 | // Close shouldn't block on us not reading 203 | rd.Close() 204 | 205 | // And we should be able to call it multiple times 206 | rd.Close() 207 | } 208 | 209 | func TestPerfReaderFlushAndClose(t *testing.T) { 210 | coll, err := LoadCollection("testdata/perf_output.elf") 211 | if err != nil { 212 | t.Fatal(err) 213 | } 214 | defer coll.Close() 215 | 216 | rd, err := NewPerfReader(PerfReaderOptions{ 217 | Map: coll.DetachMap("events"), 218 | PerCPUBuffer: 4096, 219 | Watermark: 1, 220 | }) 221 | if err != nil { 222 | t.Fatal(err) 223 | } 224 | defer rd.Close() 225 | 226 | prog := coll.DetachProgram("output_single") 227 | defer prog.Close() 228 | 229 | // more samples than the channel capacity 230 | numSamples := cap(rd.Samples) * 2 231 | for i := 0; i < numSamples; i++ { 232 | ret, _, err := prog.Test(make([]byte, 14)) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | 237 | if errno := syscall.Errno(-int32(ret)); errno != 0 { 238 | t.Fatal("Expected 0 as return value, got", errno) 239 | } 240 | } 241 | 242 | done := make(chan struct{}) 243 | go func() { 244 | rd.FlushAndClose() 245 | // Should be able to call this multiple times 246 | rd.FlushAndClose() 247 | close(done) 248 | }() 249 | 250 | received := 0 251 | for range rd.Samples { 252 | received++ 253 | } 254 | 255 | if received != numSamples { 256 | t.Fatalf("Expected %d samples got %d", numSamples, received) 257 | } 258 | 259 | <-done 260 | } 261 | 262 | func TestRingBuffer(t *testing.T) { 263 | buf := make([]byte, 2) 264 | 265 | ring := makeRing(2, 0) 266 | n, err := ring.Read(buf) 267 | if err != io.EOF { 268 | t.Error("Expected io.EOF, got", err) 269 | } 270 | if n != 2 { 271 | t.Errorf("Expected to read 2 bytes, got %d", n) 272 | } 273 | if !bytes.Equal(buf, []byte{0, 1}) { 274 | t.Error("Expected [0, 1], got", buf) 275 | } 276 | n, err = ring.Read(buf) 277 | if err != io.EOF { 278 | t.Error("Expected io.EOF, got", err) 279 | } 280 | if n != 0 { 281 | t.Error("Expected to read 0 bytes, got", n) 282 | } 283 | 284 | // Wrapping read 285 | ring = makeRing(2, 1) 286 | n, err = io.ReadFull(ring, buf) 287 | if err != nil { 288 | t.Error("Error while reading:", err) 289 | } 290 | if n != 2 { 291 | t.Errorf("Expected to read 2 byte, got %d", n) 292 | } 293 | if !bytes.Equal(buf, []byte{1, 0}) { 294 | t.Error("Expected [1, 0], got", buf) 295 | } 296 | } 297 | 298 | func makeRing(size, offset int) *ringReader { 299 | if size%2 != 0 { 300 | panic("size must be power of two") 301 | } 302 | 303 | ring := make([]byte, size) 304 | for i := range ring { 305 | ring[i] = byte(i) 306 | } 307 | 308 | meta := unix.PerfEventMmapPage{ 309 | Data_head: uint64(len(ring) + offset), 310 | Data_tail: uint64(offset), 311 | Data_size: uint64(len(ring)), 312 | } 313 | 314 | return newRingReader(&meta, ring) 315 | } 316 | 317 | // ExamplePerfReader submits a perf event using BPF, 318 | // and then reads it in user space. 319 | // 320 | // The BPF will look something like this: 321 | // 322 | // struct map events __section("maps") = { 323 | // .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 324 | // }; 325 | // 326 | // __section("xdp") int output_single(void *ctx) { 327 | // unsigned char buf[] = { 328 | // 1, 2, 3, 4, 5 329 | // }; 330 | // 331 | // return perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &buf[0], 5); 332 | // } 333 | // 334 | // Also see BPF_F_CTXLEN_MASK if you want to sample packet data 335 | // from SKB or XDP programs. 336 | func ExamplePerfReader() { 337 | coll, err := LoadCollection("testdata/perf_output.elf") 338 | if err != nil { 339 | panic(err) 340 | } 341 | defer coll.Close() 342 | 343 | rd, err := NewPerfReader(PerfReaderOptions{ 344 | Map: coll.DetachMap("events"), 345 | PerCPUBuffer: 4096, 346 | // Notify immediately 347 | Watermark: 1, 348 | }) 349 | if err != nil { 350 | panic(err) 351 | } 352 | defer rd.Close() 353 | 354 | prog := coll.DetachProgram("output_single") 355 | defer prog.Close() 356 | 357 | ret, _, err := prog.Test(make([]byte, 14)) 358 | if err != nil { 359 | panic(err) 360 | } 361 | 362 | if ret != 0 { 363 | panic("expected 0 return value") 364 | } 365 | 366 | select { 367 | case sample := <-rd.Samples: 368 | // Data is padded with 0 for alignment 369 | fmt.Println("Sample:", sample.Data) 370 | case err := <-rd.Error: 371 | panic(err) 372 | } 373 | 374 | // Output: Sample: [1 2 3 4 5 0 0 0 0 0 0 0] 375 | } 376 | -------------------------------------------------------------------------------- /elf.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "encoding/binary" 7 | "io" 8 | "strings" 9 | 10 | "github.com/newtools/ebpf/asm" 11 | 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | type elfCode struct { 16 | *elf.File 17 | symtab *symtab 18 | } 19 | 20 | // LoadCollectionSpecFromReader parses an io.ReaderAt that represents an ELF layout 21 | // into a CollectionSpec. 22 | func LoadCollectionSpecFromReader(code io.ReaderAt) (*CollectionSpec, error) { 23 | f, err := elf.NewFile(code) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer f.Close() 28 | 29 | symbols, err := f.Symbols() 30 | if err != nil { 31 | return nil, errors.Wrap(err, "load symbols") 32 | } 33 | 34 | ec := &elfCode{f, newSymtab(symbols)} 35 | 36 | var licenseSection, versionSection *elf.Section 37 | progSections := make(map[int]*elf.Section) 38 | relSections := make(map[int]*elf.Section) 39 | mapSections := make(map[int]*elf.Section) 40 | for i, sec := range ec.Sections { 41 | switch { 42 | case strings.HasPrefix(sec.Name, "license"): 43 | licenseSection = sec 44 | case strings.HasPrefix(sec.Name, "version"): 45 | versionSection = sec 46 | case strings.HasPrefix(sec.Name, "maps"): 47 | mapSections[i] = sec 48 | case sec.Type == elf.SHT_REL: 49 | if int(sec.Info) >= len(ec.Sections) { 50 | return nil, errors.Errorf("found relocation section %v for missing section %v", i, sec.Info) 51 | } 52 | 53 | // Store relocations under the section index of the target 54 | idx := int(sec.Info) 55 | if relSections[idx] != nil { 56 | return nil, errors.Errorf("section %d has multiple relocation sections", idx) 57 | } 58 | relSections[idx] = sec 59 | case sec.Type == elf.SHT_PROGBITS && (sec.Flags&elf.SHF_EXECINSTR) != 0 && sec.Size > 0: 60 | progSections[i] = sec 61 | } 62 | } 63 | 64 | license, err := loadLicense(licenseSection) 65 | if err != nil { 66 | return nil, errors.Wrap(err, "load license") 67 | } 68 | 69 | version, err := loadVersion(versionSection, ec.ByteOrder) 70 | if err != nil { 71 | return nil, errors.Wrap(err, "load version") 72 | } 73 | 74 | maps, err := ec.loadMaps(mapSections) 75 | if err != nil { 76 | return nil, errors.Wrap(err, "load maps") 77 | } 78 | 79 | progs, libs, err := ec.loadPrograms(progSections, relSections, license, version) 80 | if err != nil { 81 | return nil, errors.Wrap(err, "load programs") 82 | } 83 | 84 | if len(libs) > 0 { 85 | for name, prog := range progs { 86 | editor := Edit(&prog.Instructions) 87 | if err := editor.Link(libs...); err != nil { 88 | return nil, errors.Wrapf(err, "program %s", name) 89 | } 90 | } 91 | } 92 | 93 | return &CollectionSpec{maps, progs}, nil 94 | } 95 | 96 | func loadLicense(sec *elf.Section) (string, error) { 97 | if sec == nil { 98 | return "", errors.Errorf("missing license section") 99 | } 100 | data, err := sec.Data() 101 | if err != nil { 102 | return "", errors.Wrapf(err, "section %s", sec.Name) 103 | } 104 | return string(bytes.TrimRight(data, "\000")), nil 105 | } 106 | 107 | func loadVersion(sec *elf.Section, bo binary.ByteOrder) (uint32, error) { 108 | if sec == nil { 109 | return 0, nil 110 | } 111 | 112 | var version uint32 113 | err := binary.Read(sec.Open(), bo, &version) 114 | return version, errors.Wrapf(err, "section %s", sec.Name) 115 | } 116 | 117 | func (ec *elfCode) loadPrograms(progSections, relSections map[int]*elf.Section, license string, version uint32) (map[string]*ProgramSpec, []asm.Instructions, error) { 118 | progs := make(map[string]*ProgramSpec) 119 | var libs []asm.Instructions 120 | for idx, prog := range progSections { 121 | funcSym := ec.symtab.forSectionOffset(idx, 0) 122 | if funcSym == nil { 123 | return nil, nil, errors.Errorf("section %v: no label at start", prog.Name) 124 | } 125 | 126 | var insns asm.Instructions 127 | offsets, err := insns.Unmarshal(prog.Open(), ec.ByteOrder) 128 | if err != nil { 129 | return nil, nil, errors.Wrapf(err, "program %s", funcSym.Name) 130 | } 131 | 132 | err = assignSymbols(ec.symtab.forSection(idx), offsets, insns) 133 | if err != nil { 134 | return nil, nil, errors.Wrapf(err, "program %s", funcSym.Name) 135 | } 136 | 137 | if rels := relSections[idx]; rels != nil { 138 | err = ec.applyRelocations(insns, rels, offsets) 139 | if err != nil { 140 | return nil, nil, errors.Wrapf(err, "program %s: section %s", funcSym.Name, rels.Name) 141 | } 142 | } 143 | 144 | if progType, attachType := getProgType(prog.Name); progType == Unrecognized { 145 | // There is no single name we can use for "library" sections, 146 | // since they may contain multiple functions. We'll decode the 147 | // labels they contain later on, and then link sections that way. 148 | libs = append(libs, insns) 149 | } else { 150 | progs[funcSym.Name] = &ProgramSpec{ 151 | Name: funcSym.Name, 152 | Type: progType, 153 | AttachType: attachType, 154 | License: license, 155 | KernelVersion: version, 156 | Instructions: insns, 157 | } 158 | } 159 | } 160 | return progs, libs, nil 161 | } 162 | 163 | func (ec *elfCode) loadMaps(mapSections map[int]*elf.Section) (map[string]*MapSpec, error) { 164 | maps := make(map[string]*MapSpec) 165 | for idx, sec := range mapSections { 166 | // TODO: Iterate symbols 167 | n := len(ec.symtab.forSection(idx)) 168 | if n == 0 { 169 | return nil, errors.Errorf("section %v: no symbols", sec.Name) 170 | } 171 | 172 | data, err := sec.Data() 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | if len(data)%n != 0 { 178 | return nil, errors.Errorf("map descriptors are not of equal size") 179 | } 180 | 181 | size := len(data) / n 182 | var ordered []*MapSpec 183 | for i := 0; i < n; i++ { 184 | rd := bytes.NewReader(data[i*size : i*size+size]) 185 | mapSym := ec.symtab.forSectionOffset(idx, uint64(i*size)) 186 | if mapSym == nil { 187 | return nil, errors.Errorf("section %s: missing symbol for map #%d", sec.Name, i) 188 | } 189 | 190 | name := mapSym.Name 191 | if maps[name] != nil { 192 | return nil, errors.Errorf("section %v: map %v already exists", sec.Name, name) 193 | } 194 | 195 | var spec MapSpec 196 | var inner uint32 197 | switch { 198 | case binary.Read(rd, ec.ByteOrder, &spec.Type) != nil: 199 | return nil, errors.Errorf("map %v: missing type", name) 200 | case binary.Read(rd, ec.ByteOrder, &spec.KeySize) != nil: 201 | return nil, errors.Errorf("map %v: missing key size", name) 202 | case binary.Read(rd, ec.ByteOrder, &spec.ValueSize) != nil: 203 | return nil, errors.Errorf("map %v: missing value size", name) 204 | case binary.Read(rd, ec.ByteOrder, &spec.MaxEntries) != nil: 205 | return nil, errors.Errorf("map %v: missing max entries", name) 206 | case binary.Read(rd, ec.ByteOrder, &spec.Flags) != nil: 207 | return nil, errors.Errorf("map %v: missing flags", name) 208 | case rd.Len() > 0 && binary.Read(rd, ec.ByteOrder, &inner) != nil: 209 | return nil, errors.Errorf("map %v: can't read inner map index", name) 210 | } 211 | 212 | for rd.Len() > 0 { 213 | b, err := rd.ReadByte() 214 | if err != nil { 215 | return nil, err 216 | } 217 | if b != 0 { 218 | return nil, errors.Errorf("map %v: unknown and non-zero fields in definition", name) 219 | } 220 | } 221 | 222 | if spec.Type == ArrayOfMaps || spec.Type == HashOfMaps { 223 | if int(inner) > len(ordered) { 224 | return nil, errors.Errorf("map %v: invalid inner map index %d", name, inner) 225 | } 226 | 227 | innerSpec := ordered[int(inner)] 228 | if innerSpec.InnerMap != nil { 229 | return nil, errors.Errorf("map %v: can't nest map of map", name) 230 | } 231 | spec.InnerMap = innerSpec.Copy() 232 | } 233 | 234 | maps[name] = &spec 235 | ordered = append(ordered, &spec) 236 | } 237 | } 238 | return maps, nil 239 | } 240 | 241 | func getProgType(v string) (ProgType, AttachType) { 242 | types := map[string]ProgType{ 243 | // From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/lib/bpf/libbpf.c#n3568 244 | "socket": SocketFilter, 245 | "seccomp": SocketFilter, 246 | "kprobe/": Kprobe, 247 | "kretprobe/": Kprobe, 248 | "tracepoint/": TracePoint, 249 | "xdp": XDP, 250 | "perf_event": PerfEvent, 251 | "sockops": SockOps, 252 | "sk_skb": SkSKB, 253 | "sk_msg": SkMsg, 254 | "lirc_mode2": LircMode2, 255 | "flow_dissector": FlowDissector, 256 | 257 | "cgroup_skb/": CGroupSKB, 258 | "cgroup/dev": CGroupDevice, 259 | "cgroup/skb": CGroupSKB, 260 | "cgroup/sock": CGroupSock, 261 | "cgroup/post_bind": CGroupSock, 262 | "cgroup/bind": CGroupSockAddr, 263 | "cgroup/connect": CGroupSockAddr, 264 | "cgroup/sendmsg": CGroupSockAddr, 265 | "cgroup/recvmsg": CGroupSockAddr, 266 | "cgroup/sysctl": CGroupSysctl, 267 | "cgroup/getsockopt": CGroupSockopt, 268 | "cgroup/setsockopt": CGroupSockopt, 269 | "classifier": SchedCLS, 270 | "action": SchedACT, 271 | } 272 | attachTypes := map[string]AttachType{ 273 | "cgroup_skb/ingress": AttachCGroupInetIngress, 274 | "cgroup_skb/egress": AttachCGroupInetEgress, 275 | "cgroup/sock": AttachCGroupInetSockCreate, 276 | "cgroup/post_bind4": AttachCGroupInet4PostBind, 277 | "cgroup/post_bind6": AttachCGroupInet6PostBind, 278 | "cgroup/dev": AttachCGroupDevice, 279 | "sockops": AttachCGroupSockOps, 280 | "sk_skb/stream_parser": AttachSkSKBStreamParser, 281 | "sk_skb/stream_verdict": AttachSkSKBStreamVerdict, 282 | "sk_msg": AttachSkSKBStreamVerdict, 283 | "lirc_mode2": AttachLircMode2, 284 | "flow_dissector": AttachFlowDissector, 285 | "cgroup/bind4": AttachCGroupInet4Bind, 286 | "cgroup/bind6": AttachCGroupInet6Bind, 287 | "cgroup/connect4": AttachCGroupInet4Connect, 288 | "cgroup/connect6": AttachCGroupInet6Connect, 289 | "cgroup/sendmsg4": AttachCGroupUDP4Sendmsg, 290 | "cgroup/sendmsg6": AttachCGroupUDP6Sendmsg, 291 | "cgroup/recvmsg4": AttachCGroupUDP4Recvmsg, 292 | "cgroup/recvmsg6": AttachCGroupUDP6Recvmsg, 293 | "cgroup/sysctl": AttachCGroupSysctl, 294 | "cgroup/getsockopt": AttachCGroupGetsockopt, 295 | "cgroup/setsockopt": AttachCGroupSetsockopt, 296 | } 297 | attachType := AttachNone 298 | for k, t := range attachTypes { 299 | if strings.HasPrefix(v, k) { 300 | attachType = t 301 | } 302 | } 303 | 304 | for k, t := range types { 305 | if strings.HasPrefix(v, k) { 306 | return t, attachType 307 | } 308 | } 309 | return Unrecognized, AttachNone 310 | } 311 | 312 | func assignSymbols(symbolOffsets map[uint64]*elf.Symbol, insOffsets map[uint64]int, insns asm.Instructions) error { 313 | for offset, sym := range symbolOffsets { 314 | i, ok := insOffsets[offset] 315 | if !ok { 316 | return errors.Errorf("symbol %s: no instruction at offset %d", sym.Name, offset) 317 | } 318 | insns[i].Symbol = sym.Name 319 | } 320 | return nil 321 | } 322 | 323 | func (ec *elfCode) applyRelocations(insns asm.Instructions, sec *elf.Section, offsets map[uint64]int) error { 324 | if sec.Entsize < 16 { 325 | return errors.New("rls are less than 16 bytes") 326 | } 327 | 328 | r := sec.Open() 329 | for off := uint64(0); off < sec.Size; off += sec.Entsize { 330 | ent := io.LimitReader(r, int64(sec.Entsize)) 331 | 332 | var rel elf.Rel64 333 | if binary.Read(ent, ec.ByteOrder, &rel) != nil { 334 | return errors.Errorf("can't parse relocation at offset %v", off) 335 | } 336 | 337 | sym, err := ec.symtab.forRelocation(rel) 338 | if err != nil { 339 | return errors.Wrapf(err, "relocation at offset %v", off) 340 | } 341 | 342 | idx, ok := offsets[rel.Off] 343 | if !ok { 344 | return errors.Errorf("symbol %v: invalid instruction offset %x", sym, rel.Off) 345 | } 346 | insns[idx].Reference = sym.Name 347 | } 348 | return nil 349 | } 350 | 351 | type symtab struct { 352 | Symbols []elf.Symbol 353 | index map[int]map[uint64]*elf.Symbol 354 | } 355 | 356 | func newSymtab(symbols []elf.Symbol) *symtab { 357 | index := make(map[int]map[uint64]*elf.Symbol) 358 | for i, sym := range symbols { 359 | switch elf.ST_TYPE(sym.Info) { 360 | case elf.STT_NOTYPE: 361 | // Older versions of LLVM doesn't tag 362 | // symbols correctly. 363 | break 364 | case elf.STT_OBJECT: 365 | break 366 | case elf.STT_FUNC: 367 | break 368 | default: 369 | continue 370 | } 371 | 372 | if sym.Name == "" { 373 | continue 374 | } 375 | 376 | idx := int(sym.Section) 377 | if _, ok := index[idx]; !ok { 378 | index[idx] = make(map[uint64]*elf.Symbol) 379 | } 380 | index[idx][sym.Value] = &symbols[i] 381 | } 382 | return &symtab{ 383 | symbols, 384 | index, 385 | } 386 | } 387 | 388 | func (st *symtab) forSection(sec int) map[uint64]*elf.Symbol { 389 | return st.index[sec] 390 | } 391 | 392 | func (st *symtab) forSectionOffset(sec int, offset uint64) *elf.Symbol { 393 | offsets := st.index[sec] 394 | if offsets == nil { 395 | return nil 396 | } 397 | return offsets[offset] 398 | } 399 | 400 | func (st *symtab) forRelocation(rel elf.Rel64) (*elf.Symbol, error) { 401 | symNo := int(elf.R_SYM64(rel.Info) - 1) 402 | if symNo >= len(st.Symbols) { 403 | return nil, errors.Errorf("symbol %v doesnt exist", symNo) 404 | } 405 | return &st.Symbols[symNo], nil 406 | } 407 | -------------------------------------------------------------------------------- /prog.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math" 7 | "path/filepath" 8 | "strings" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/newtools/ebpf/asm" 13 | "golang.org/x/sys/unix" 14 | 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | // Errors returned by the implementation 19 | var ( 20 | ErrNotSupported = errors.New("ebpf: not supported by kernel") 21 | ) 22 | 23 | const ( 24 | // Number of bytes to pad the output buffer for BPF_PROG_TEST_RUN. 25 | // This is currently the maximum of spare space allocated for SKB 26 | // and XDP programs, and equal to XDP_PACKET_HEADROOM + NET_IP_ALIGN. 27 | outputPad = 256 + 2 28 | ) 29 | 30 | // DefaultVerifierLogSize is the default number of bytes allocated for the 31 | // verifier log. 32 | const DefaultVerifierLogSize = 64 * 1024 33 | 34 | // ProgramOptions control loading a program into the kernel. 35 | type ProgramOptions struct { 36 | // Controls the detail emitted by the kernel verifier. Set to non-zero 37 | // to enable logging. 38 | LogLevel uint32 39 | // Controls the output buffer size for the verifier. Defaults to 40 | // DefaultVerifierLogSize. 41 | LogSize int 42 | } 43 | 44 | // ProgramSpec defines a Program 45 | type ProgramSpec struct { 46 | // Name is passed to the kernel as a debug aid. Must only contain 47 | // alpha numeric and '_' characters. 48 | Name string 49 | Type ProgType 50 | AttachType AttachType 51 | Instructions asm.Instructions 52 | License string 53 | KernelVersion uint32 54 | } 55 | 56 | // Copy returns a copy of the spec. 57 | func (ps *ProgramSpec) Copy() *ProgramSpec { 58 | if ps == nil { 59 | return nil 60 | } 61 | 62 | cpy := *ps 63 | cpy.Instructions = make(asm.Instructions, len(ps.Instructions)) 64 | copy(cpy.Instructions, ps.Instructions) 65 | return &cpy 66 | } 67 | 68 | // Program represents BPF program loaded into the kernel. 69 | // 70 | // It is not safe to close a Program which is used by other goroutines. 71 | type Program struct { 72 | // Contains the output of the kernel verifier if enabled, 73 | // otherwise it is empty. 74 | VerifierLog string 75 | 76 | fd *bpfFD 77 | name string 78 | abi ProgramABI 79 | } 80 | 81 | // NewProgram creates a new Program. 82 | // 83 | // Loading a program for the first time will perform 84 | // feature detection by loading small, temporary programs. 85 | func NewProgram(spec *ProgramSpec) (*Program, error) { 86 | return NewProgramWithOptions(spec, ProgramOptions{}) 87 | } 88 | 89 | // NewProgramWithOptions creates a new Program. 90 | // 91 | // Loading a program for the first time will perform 92 | // feature detection by loading small, temporary programs. 93 | func NewProgramWithOptions(spec *ProgramSpec, opts ProgramOptions) (*Program, error) { 94 | attr, err := convertProgramSpec(spec, haveObjName.Result()) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | logSize := DefaultVerifierLogSize 100 | if opts.LogSize > 0 { 101 | logSize = opts.LogSize 102 | } 103 | 104 | var logBuf []byte 105 | if opts.LogLevel > 0 { 106 | logBuf = make([]byte, logSize) 107 | attr.logLevel = opts.LogLevel 108 | attr.logSize = uint32(len(logBuf)) 109 | attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0])) 110 | } 111 | 112 | fd, err := bpfProgLoad(attr) 113 | if err == nil { 114 | prog := newProgram(fd, spec.Name, &ProgramABI{spec.Type}) 115 | prog.VerifierLog = convertCString(logBuf) 116 | return prog, nil 117 | } 118 | 119 | truncated := errors.Cause(err) == unix.ENOSPC 120 | if opts.LogLevel == 0 { 121 | // Re-run with the verifier enabled to get better error messages. 122 | logBuf = make([]byte, logSize) 123 | attr.logLevel = 1 124 | attr.logSize = uint32(len(logBuf)) 125 | attr.logBuf = newPtr(unsafe.Pointer(&logBuf[0])) 126 | 127 | _, nerr := bpfProgLoad(attr) 128 | truncated = errors.Cause(nerr) == unix.ENOSPC 129 | } 130 | 131 | logs := convertCString(logBuf) 132 | if truncated { 133 | logs += "\n(truncated...)" 134 | } 135 | 136 | return nil, &loadError{err, logs} 137 | } 138 | 139 | func newProgram(fd *bpfFD, name string, abi *ProgramABI) *Program { 140 | return &Program{ 141 | name: name, 142 | fd: fd, 143 | abi: *abi, 144 | } 145 | } 146 | 147 | func convertProgramSpec(spec *ProgramSpec, includeName bool) (*bpfProgLoadAttr, error) { 148 | if len(spec.Instructions) == 0 { 149 | return nil, errors.New("Instructions cannot be empty") 150 | } 151 | 152 | if len(spec.License) == 0 { 153 | return nil, errors.New("License cannot be empty") 154 | } 155 | 156 | buf := bytes.NewBuffer(make([]byte, 0, len(spec.Instructions)*asm.InstructionSize)) 157 | err := spec.Instructions.Marshal(buf, nativeEndian) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | bytecode := buf.Bytes() 163 | insCount := uint32(len(bytecode) / asm.InstructionSize) 164 | lic := []byte(spec.License) 165 | attr := &bpfProgLoadAttr{ 166 | progType: spec.Type, 167 | expectedAttachType: spec.AttachType, 168 | insCount: insCount, 169 | instructions: newPtr(unsafe.Pointer(&bytecode[0])), 170 | license: newPtr(unsafe.Pointer(&lic[0])), 171 | } 172 | 173 | name, err := newBPFObjName(spec.Name) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | if includeName { 179 | attr.progName = name 180 | } 181 | 182 | return attr, nil 183 | } 184 | 185 | func (bpf *Program) String() string { 186 | if bpf.name != "" { 187 | return fmt.Sprintf("%s(%s)#%s", bpf.abi.Type, bpf.name, bpf.fd) 188 | } 189 | return fmt.Sprintf("%s#%s", bpf.abi.Type, bpf.fd) 190 | } 191 | 192 | // ABI gets the ABI of the Program 193 | func (bpf *Program) ABI() ProgramABI { 194 | return bpf.abi 195 | } 196 | 197 | // FD gets the file descriptor of the Program. 198 | // 199 | // It is invalid to call this function after Close has been called. 200 | func (bpf *Program) FD() int { 201 | fd, err := bpf.fd.value() 202 | if err != nil { 203 | // Best effort: -1 is the number most likely to be an 204 | // invalid file descriptor. 205 | return -1 206 | } 207 | 208 | return int(fd) 209 | } 210 | 211 | // Clone creates a duplicate of the Program. 212 | // 213 | // Closing the duplicate does not affect the original, and vice versa. 214 | // 215 | // Cloning a nil Program returns nil. 216 | func (bpf *Program) Clone() (*Program, error) { 217 | if bpf == nil { 218 | return nil, nil 219 | } 220 | 221 | dup, err := bpf.fd.dup() 222 | if err != nil { 223 | return nil, errors.Wrap(err, "can't clone program") 224 | } 225 | 226 | return newProgram(dup, bpf.name, &bpf.abi), nil 227 | } 228 | 229 | // Pin persists the Program past the lifetime of the process that created it 230 | // 231 | // This requires bpffs to be mounted above fileName. See http://cilium.readthedocs.io/en/doc-1.0/kubernetes/install/#mounting-the-bpf-fs-optional 232 | func (bpf *Program) Pin(fileName string) error { 233 | return errors.Wrap(bpfPinObject(fileName, bpf.fd), "can't pin program") 234 | } 235 | 236 | // Close unloads the program from the kernel. 237 | func (bpf *Program) Close() error { 238 | if bpf == nil { 239 | return nil 240 | } 241 | 242 | return bpf.fd.close() 243 | } 244 | 245 | // Test runs the Program in the kernel with the given input and returns the 246 | // value returned by the eBPF program. outLen may be zero. 247 | // 248 | // Note: the kernel expects at least 14 bytes input for an ethernet header for 249 | // XDP and SKB programs. 250 | // 251 | // This function requires at least Linux 4.12. 252 | func (bpf *Program) Test(in []byte) (uint32, []byte, error) { 253 | ret, out, _, err := bpf.testRun(in, 1) 254 | return ret, out, err 255 | } 256 | 257 | // Benchmark runs the Program with the given input for a number of times 258 | // and returns the time taken per iteration. 259 | // 260 | // The returned value is the return value of the last execution of 261 | // the program. 262 | // 263 | // This function requires at least Linux 4.12. 264 | func (bpf *Program) Benchmark(in []byte, repeat int) (uint32, time.Duration, error) { 265 | ret, _, total, err := bpf.testRun(in, repeat) 266 | return ret, total, err 267 | } 268 | 269 | var noProgTestRun = featureTest{ 270 | Fn: func() bool { 271 | prog, err := NewProgram(&ProgramSpec{ 272 | Type: SocketFilter, 273 | Instructions: asm.Instructions{ 274 | asm.LoadImm(asm.R0, 0, asm.DWord), 275 | asm.Return(), 276 | }, 277 | License: "MIT", 278 | }) 279 | if err != nil { 280 | // This may be because we lack sufficient permissions, etc. 281 | return false 282 | } 283 | defer prog.Close() 284 | 285 | fd, err := prog.fd.value() 286 | if err != nil { 287 | return false 288 | } 289 | 290 | // Programs require at least 14 bytes input 291 | in := make([]byte, 14) 292 | attr := bpfProgTestRunAttr{ 293 | fd: fd, 294 | dataSizeIn: uint32(len(in)), 295 | dataIn: newPtr(unsafe.Pointer(&in[0])), 296 | } 297 | 298 | _, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 299 | return errors.Cause(err) == unix.EINVAL 300 | }, 301 | } 302 | 303 | func (bpf *Program) testRun(in []byte, repeat int) (uint32, []byte, time.Duration, error) { 304 | if uint(repeat) > math.MaxUint32 { 305 | return 0, nil, 0, fmt.Errorf("repeat is too high") 306 | } 307 | 308 | if len(in) == 0 { 309 | return 0, nil, 0, fmt.Errorf("missing input") 310 | } 311 | 312 | if uint(len(in)) > math.MaxUint32 { 313 | return 0, nil, 0, fmt.Errorf("input is too long") 314 | } 315 | 316 | if noProgTestRun.Result() { 317 | return 0, nil, 0, ErrNotSupported 318 | } 319 | 320 | // Older kernels ignore the dataSizeOut argument when copying to user space. 321 | // Combined with things like bpf_xdp_adjust_head() we don't really know what the final 322 | // size will be. Hence we allocate an output buffer which we hope will always be large 323 | // enough, and panic if the kernel wrote past the end of the allocation. 324 | // See https://patchwork.ozlabs.org/cover/1006822/ 325 | out := make([]byte, len(in)+outputPad) 326 | 327 | fd, err := bpf.fd.value() 328 | if err != nil { 329 | return 0, nil, 0, err 330 | } 331 | 332 | attr := bpfProgTestRunAttr{ 333 | fd: fd, 334 | dataSizeIn: uint32(len(in)), 335 | dataSizeOut: uint32(len(out)), 336 | dataIn: newPtr(unsafe.Pointer(&in[0])), 337 | dataOut: newPtr(unsafe.Pointer(&out[0])), 338 | repeat: uint32(repeat), 339 | } 340 | 341 | _, err = bpfCall(_ProgTestRun, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 342 | if err != nil { 343 | return 0, nil, 0, errors.Wrap(err, "can't run test") 344 | } 345 | 346 | if int(attr.dataSizeOut) > cap(out) { 347 | // Houston, we have a problem. The program created more data than we allocated, 348 | // and the kernel wrote past the end of our buffer. 349 | panic("kernel wrote past end of output buffer") 350 | } 351 | out = out[:int(attr.dataSizeOut)] 352 | 353 | total := time.Duration(attr.duration) * time.Nanosecond 354 | return attr.retval, out, total, nil 355 | } 356 | 357 | func unmarshalProgram(buf []byte) (*Program, error) { 358 | if len(buf) != 4 { 359 | return nil, errors.New("program id requires 4 byte value") 360 | } 361 | 362 | // Looking up an entry in a nested map or prog array returns an id, 363 | // not an fd. 364 | id := nativeEndian.Uint32(buf) 365 | fd, err := bpfGetProgramFDByID(id) 366 | if err != nil { 367 | return nil, err 368 | } 369 | 370 | abi, err := newProgramABIFromFd(fd) 371 | if err != nil { 372 | _ = fd.close() 373 | return nil, err 374 | } 375 | 376 | return newProgram(fd, "", abi), nil 377 | } 378 | 379 | // MarshalBinary implements BinaryMarshaler. 380 | func (bpf *Program) MarshalBinary() ([]byte, error) { 381 | value, err := bpf.fd.value() 382 | if err != nil { 383 | return nil, err 384 | } 385 | 386 | buf := make([]byte, 4) 387 | nativeEndian.PutUint32(buf, value) 388 | return buf, nil 389 | } 390 | 391 | // LoadPinnedProgram loads a Program from a BPF file. 392 | // 393 | // Requires at least Linux 4.13, use LoadPinnedProgramExplicit on 394 | // earlier versions. 395 | func LoadPinnedProgram(fileName string) (*Program, error) { 396 | fd, err := bpfGetObject(fileName) 397 | if err != nil { 398 | return nil, err 399 | } 400 | 401 | abi, err := newProgramABIFromFd(fd) 402 | if err != nil { 403 | _ = fd.close() 404 | return nil, err 405 | } 406 | 407 | return newProgram(fd, filepath.Base(fileName), abi), nil 408 | } 409 | 410 | // LoadPinnedProgramExplicit loads a program with explicit parameters. 411 | func LoadPinnedProgramExplicit(fileName string, abi *ProgramABI) (*Program, error) { 412 | fd, err := bpfGetObject(fileName) 413 | if err != nil { 414 | return nil, err 415 | } 416 | 417 | return newProgram(fd, filepath.Base(fileName), abi), nil 418 | } 419 | 420 | // SanitizeName replaces all invalid characters in name. 421 | // 422 | // Use this to automatically generate valid names for maps and 423 | // programs at run time. 424 | // 425 | // Passing a negative value for replacement will delete characters 426 | // instead of replacing them. 427 | func SanitizeName(name string, replacement rune) string { 428 | return strings.Map(func(char rune) rune { 429 | if invalidBPFObjNameChar(char) { 430 | return replacement 431 | } 432 | return char 433 | }, name) 434 | } 435 | 436 | type loadError struct { 437 | cause error 438 | verifierLog string 439 | } 440 | 441 | func (le *loadError) Error() string { 442 | if le.verifierLog == "" { 443 | return fmt.Sprintf("failed to load program: %s", le.cause) 444 | } 445 | return fmt.Sprintf("failed to load program: %s: %s", le.cause, le.verifierLog) 446 | } 447 | 448 | func (le *loadError) Cause() error { 449 | return le.cause 450 | } 451 | -------------------------------------------------------------------------------- /asm/func.go: -------------------------------------------------------------------------------- 1 | package asm 2 | 3 | //go:generate stringer -output func_string.go -type=BuiltinFunc 4 | 5 | // BuiltinFunc is a built-in eBPF function. 6 | type BuiltinFunc int32 7 | 8 | // eBPF built-in functions 9 | const ( 10 | // MapLookupElement - void *map_lookup_elem(&map, &key) 11 | // Return: Map value or NULL 12 | MapLookupElement BuiltinFunc = iota + 1 13 | // MapUpdateElement - int map_update_elem(&map, &key, &value, flags) 14 | // Return: 0 on success or negative error 15 | MapUpdateElement 16 | // MapDeleteElement - int map_delete_elem(&map, &key) 17 | // Return: 0 on success or negative error 18 | MapDeleteElement 19 | // ProbeRead - int bpf_probe_read(void *dst, int size, void *src) 20 | // Return: 0 on success or negative error 21 | ProbeRead 22 | // KtimeGetNS - u64 bpf_ktime_get_ns(void) 23 | // Return: current ktime 24 | KtimeGetNS 25 | // TracePrintk - int bpf_trace_printk(const char *fmt, int fmt_size, ...) 26 | // Return: length of buffer written or negative error 27 | TracePrintk 28 | // GetPRandomu32 - u32 prandom_u32(void) 29 | // Return: random value 30 | GetPRandomu32 31 | // GetSMPProcessorID - u32 raw_smp_processor_id(void) 32 | // Return: SMP processor ID 33 | GetSMPProcessorID 34 | // SKBStoreBytes - skb_store_bytes(skb, offset, from, len, flags) 35 | // store bytes into packet 36 | // @skb: pointer to skb 37 | // @offset: offset within packet from skb->mac_header 38 | // @from: pointer where to copy bytes from 39 | // @len: number of bytes to store into packet 40 | // @flags: bit 0 - if true, recompute skb->csum 41 | // other bits - reserved 42 | // Return: 0 on success 43 | SKBStoreBytes 44 | // CSUMReplaceL3 - l3_csum_replace(skb, offset, from, to, flags) 45 | // recompute IP checksum 46 | // @skb: pointer to skb 47 | // @offset: offset within packet where IP checksum is located 48 | // @from: old value of header field 49 | // @to: new value of header field 50 | // @flags: bits 0-3 - size of header field 51 | // other bits - reserved 52 | // Return: 0 on success 53 | CSUMReplaceL3 54 | // CSUMReplaceL4 - l4_csum_replace(skb, offset, from, to, flags) 55 | // recompute TCP/UDP checksum 56 | // @skb: pointer to skb 57 | // @offset: offset within packet where TCP/UDP checksum is located 58 | // @from: old value of header field 59 | // @to: new value of header field 60 | // @flags: bits 0-3 - size of header field 61 | // bit 4 - is pseudo header 62 | // other bits - reserved 63 | // Return: 0 on success 64 | CSUMReplaceL4 65 | // TailCall - int bpf_tail_call(ctx, prog_array_map, index) 66 | // jump into another BPF program 67 | // @ctx: context pointer passed to next program 68 | // @prog_array_map: pointer to map which type is BPF_MAP_TYPE_PROG_ARRAY 69 | // @index: index inside array that selects specific program to run 70 | // Return: 0 on success or negative error 71 | TailCall 72 | // CloneRedirect - int bpf_clone_redirect(skb, ifindex, flags) 73 | // redirect to another netdev 74 | // @skb: pointer to skb 75 | // @ifindex: ifindex of the net device 76 | // @flags: bit 0 - if set, redirect to ingress instead of egress 77 | // other bits - reserved 78 | // Return: 0 on success or negative error 79 | CloneRedirect 80 | // GetCurrentPIDTGID - u64 bpf_get_current_pid_tgid(void) 81 | // Return: current->tgid << 32 | current->pid 82 | GetCurrentPIDTGID 83 | // GetCurrentUIDGID - u64 bpf_get_current_uid_gid(void) 84 | // Return: current_gid << 32 | current_uid 85 | GetCurrentUIDGID 86 | // GetCurrentComm - int bpf_get_current_comm(char *buf, int size_of_buf) - stores current->comm into buf 87 | // Return: 0 on success or negative error 88 | GetCurrentComm 89 | // GetCGroupClassID - u32 bpf_get_cgroup_classid(skb) 90 | // retrieve a proc's classid 91 | // @skb: pointer to skb 92 | // Return: classid if != 0 93 | GetCGroupClassID 94 | // SKBVlanPush - int bpf_skb_vlan_push(skb, vlan_proto, vlan_tci) 95 | // Return: 0 on success or negative error 96 | SKBVlanPush 97 | // SKBVlanPop - int bpf_skb_vlan_pop(skb) 98 | // Return: 0 on success or negative error 99 | SKBVlanPop 100 | // SKBGetTunnelKey - int bpf_skb_get_tunnel_key(skb, key, size, flags) 101 | // retrieve or populate tunnel metadata 102 | // @skb: pointer to skb 103 | // @key: pointer to 'struct bpf_tunnel_key' 104 | // @size: size of 'struct bpf_tunnel_key' 105 | // @flags: room for future extensions 106 | // Return: 0 on success or negative error 107 | SKBGetTunnelKey 108 | // SKBSetTunnelKey - int bpf_skb_set_tunnel_key(skb, key, size, flags) 109 | // retrieve or populate tunnel metadata 110 | // @skb: pointer to skb 111 | // @key: pointer to 'struct bpf_tunnel_key' 112 | // @size: size of 'struct bpf_tunnel_key' 113 | // @flags: room for future extensions 114 | // Return: 0 on success or negative error 115 | SKBSetTunnelKey 116 | // PerfEventRead - u64 bpf_perf_event_read(map, flags) 117 | // read perf event counter value 118 | // @map: pointer to perf_event_array map 119 | // @flags: index of event in the map or bitmask flags 120 | // Return: value of perf event counter read or error code 121 | PerfEventRead 122 | // Redirect - int bpf_redirect(ifindex, flags) 123 | // redirect to another netdev 124 | // @ifindex: ifindex of the net device 125 | // @flags: bit 0 - if set, redirect to ingress instead of egress 126 | // other bits - reserved 127 | // Return: TC_ACT_REDIRECT 128 | Redirect 129 | // GetRouteRealm - u32 bpf_get_route_realm(skb) 130 | // retrieve a dst's tclassid 131 | // @skb: pointer to skb 132 | // Return: realm if != 0 133 | GetRouteRealm 134 | // PerfEventOutput - int bpf_perf_event_output(ctx, map, flags, data, size) 135 | // output perf raw sample 136 | // @ctx: struct pt_regs* 137 | // @map: pointer to perf_event_array map 138 | // @flags: index of event in the map or bitmask flags 139 | // @data: data on stack to be output as raw data 140 | // @size: size of data 141 | // Return: 0 on success or negative error 142 | PerfEventOutput 143 | // GetStackID - int bpf_get_stackid(ctx, map, flags) 144 | // walk user or kernel stack and return id 145 | // @ctx: struct pt_regs* 146 | // @map: pointer to stack_trace map 147 | // @flags: bits 0-7 - numer of stack frames to skip 148 | // bit 8 - collect user stack instead of kernel 149 | // bit 9 - compare stacks by hash only 150 | // bit 10 - if two different stacks hash into the same stackid 151 | // discard old 152 | // other bits - reserved 153 | // Return: >= 0 stackid on success or negative error 154 | GetStackID 155 | // CsumDiff - s64 bpf_csum_diff(from, from_size, to, to_size, seed) 156 | // calculate csum diff 157 | // @from: raw from buffer 158 | // @from_size: length of from buffer 159 | // @to: raw to buffer 160 | // @to_size: length of to buffer 161 | // @seed: optional seed 162 | // Return: csum result or negative error code 163 | CsumDiff 164 | // SKBGetTunnelOpt - int bpf_skb_get_tunnel_opt(skb, opt, size) 165 | // retrieve tunnel options metadata 166 | // @skb: pointer to skb 167 | // @opt: pointer to raw tunnel option data 168 | // @size: size of @opt 169 | // Return: option size 170 | SKBGetTunnelOpt 171 | // SKBSetTunnelOpt - int bpf_skb_set_tunnel_opt(skb, opt, size) 172 | // populate tunnel options metadata 173 | // @skb: pointer to skb 174 | // @opt: pointer to raw tunnel option data 175 | // @size: size of @opt 176 | // Return: 0 on success or negative error 177 | SKBSetTunnelOpt 178 | // SKBChangeProto - int bpf_skb_change_proto(skb, proto, flags) 179 | // Change protocol of the skb. Currently supported is v4 -> v6, 180 | // v6 -> v4 transitions. The helper will also resize the skb. eBPF 181 | // program is expected to fill the new headers via skb_store_bytes 182 | // and lX_csum_replace. 183 | // @skb: pointer to skb 184 | // @proto: new skb->protocol type 185 | // @flags: reserved 186 | // Return: 0 on success or negative error 187 | SKBChangeProto 188 | // SKBChangeType - int bpf_skb_change_type(skb, type) 189 | // Change packet type of skb. 190 | // @skb: pointer to skb 191 | // @type: new skb->pkt_type type 192 | // Return: 0 on success or negative error 193 | SKBChangeType 194 | // SKBUnderCGroup - int bpf_skb_under_cgroup(skb, map, index) 195 | // Check cgroup2 membership of skb 196 | // @skb: pointer to skb 197 | // @map: pointer to bpf_map in BPF_MAP_TYPE_CGROUP_ARRAY type 198 | // @index: index of the cgroup in the bpf_map 199 | // Return: 200 | // == 0 skb failed the cgroup2 descendant test 201 | // == 1 skb succeeded the cgroup2 descendant test 202 | // < 0 error 203 | SKBUnderCGroup 204 | // GetHashRecalc - u32 bpf_get_hash_recalc(skb) 205 | // Retrieve and possibly recalculate skb->hash. 206 | // @skb: pointer to skb 207 | // Return: hash 208 | GetHashRecalc 209 | // GetCurrentTask - u64 bpf_get_current_task(void) 210 | // Returns current task_struct 211 | // Return: current 212 | GetCurrentTask 213 | // ProbeWriteUser - int bpf_probe_write_user(void *dst, void *src, int len) 214 | // safely attempt to write to a location 215 | // @dst: destination address in userspace 216 | // @src: source address on stack 217 | // @len: number of bytes to copy 218 | // Return: 0 on success or negative error 219 | ProbeWriteUser 220 | // CurrentTaskUnderCGroup - int bpf_current_task_under_cgroup(map, index) 221 | // Check cgroup2 membership of current task 222 | // @map: pointer to bpf_map in BPF_MAP_TYPE_CGROUP_ARRAY type 223 | // @index: index of the cgroup in the bpf_map 224 | // Return: 225 | // == 0 current failed the cgroup2 descendant test 226 | // == 1 current succeeded the cgroup2 descendant test 227 | // < 0 error 228 | CurrentTaskUnderCGroup 229 | // SKBChangeTail - int bpf_skb_change_tail(skb, len, flags) 230 | // The helper will resize the skb to the given new size, to be used f.e. 231 | // with control messages. 232 | // @skb: pointer to skb 233 | // @len: new skb length 234 | // @flags: reserved 235 | // Return: 0 on success or negative error 236 | SKBChangeTail 237 | // SKBPullData - int bpf_skb_pull_data(skb, len) 238 | // The helper will pull in non-linear data in case the skb is non-linear 239 | // and not all of len are part of the linear section. Only needed for 240 | // read/write with direct packet access. 241 | // @skb: pointer to skb 242 | // @Len: len to make read/writeable 243 | // Return: 0 on success or negative error 244 | SKBPullData 245 | // CSUMUpdate - s64 bpf_csum_update(skb, csum) 246 | // Adds csum into skb->csum in case of CHECKSUM_COMPLETE. 247 | // @skb: pointer to skb 248 | // @csum: csum to add 249 | // Return: csum on success or negative error 250 | CSUMUpdate 251 | // SetHashInvalid - void bpf_set_hash_invalid(skb) 252 | // Invalidate current skb->hash. 253 | // @skb: pointer to skb 254 | SetHashInvalid 255 | // GetNUMANodeID - int bpf_get_numa_node_id() 256 | // Return: Id of current NUMA node. 257 | GetNUMANodeID 258 | // SKBChangeHead - int bpf_skb_change_head() 259 | // Grows headroom of skb and adjusts MAC header offset accordingly. 260 | // Will extends/reallocae as required automatically. 261 | // May change skb data pointer and will thus invalidate any check 262 | // performed for direct packet access. 263 | // @skb: pointer to skb 264 | // @len: length of header to be pushed in front 265 | // @flags: Flags (unused for now) 266 | // Return: 0 on success or negative error 267 | SKBChangeHead 268 | // XDPAdjustHead - int bpf_xdp_adjust_head(xdp_md, delta) 269 | // Adjust the xdp_md.data by delta 270 | // @xdp_md: pointer to xdp_md 271 | // @delta: An positive/negative integer to be added to xdp_md.data 272 | // Return: 0 on success or negative on error 273 | XDPAdjustHead 274 | // ProbeReadStr - int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) 275 | // Copy a NUL terminated string from unsafe address. In case the string 276 | // length is smaller than size, the target is not padded with further NUL 277 | // bytes. In case the string length is larger than size, just count-1 278 | // bytes are copied and the last byte is set to NUL. 279 | // @dst: destination address 280 | // @size: maximum number of bytes to copy, including the trailing NUL 281 | // @unsafe_ptr: unsafe address 282 | // Return: 283 | // > 0 length of the string including the trailing NUL on success 284 | // < 0 error 285 | ProbeReadStr 286 | // GetSocketCookie - u64 bpf_get_socket_cookie(skb) 287 | // Get the cookie for the socket stored inside sk_buff. 288 | // @skb: pointer to skb 289 | // Return: 8 Bytes non-decreasing number on success or 0 if the socket 290 | // field is missing inside sk_buff 291 | GetSocketCookie 292 | // GetSocketUID - u32 bpf_get_socket_uid(skb) 293 | // Get the owner uid of the socket stored inside sk_buff. 294 | // @skb: pointer to skb 295 | // Return: uid of the socket owner on success or overflowuid if failed. 296 | GetSocketUID 297 | // SetHash - u32 bpf_set_hash(skb, hash) 298 | // Set full skb->hash. 299 | // @skb: pointer to skb 300 | // @hash: hash to set 301 | SetHash 302 | // SetSockOpt - int bpf_setsockopt(bpf_socket, level, optname, optval, optlen) 303 | // Calls setsockopt. Not all opts are available, only those with 304 | // integer optvals plus TCP_CONGESTION. 305 | // Supported levels: SOL_SOCKET and IPROTO_TCP 306 | // @bpf_socket: pointer to bpf_socket 307 | // @level: SOL_SOCKET or IPROTO_TCP 308 | // @optname: option name 309 | // @optval: pointer to option value 310 | // @optlen: length of optval in byes 311 | // Return: 0 or negative error 312 | SetSockOpt 313 | // SKBAdjustRoom - int bpf_skb_adjust_room(skb, len_diff, mode, flags) 314 | // Grow or shrink room in sk_buff. 315 | // @skb: pointer to skb 316 | // @len_diff: (signed) amount of room to grow/shrink 317 | // @mode: operation mode (enum bpf_adj_room_mode) 318 | // @flags: reserved for future use 319 | // Return: 0 on success or negative error code 320 | SKBAdjustRoom 321 | ) 322 | 323 | // Call emits a function call. 324 | func (fn BuiltinFunc) Call() Instruction { 325 | return Instruction{ 326 | OpCode: OpCode(JumpClass).SetJumpOp(Call), 327 | Constant: int64(fn), 328 | } 329 | } 330 | --------------------------------------------------------------------------------