├── .gitignore ├── .semaphore └── semaphore.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── abi.go ├── abi_test.go ├── asm ├── alu.go ├── alu_string.go ├── doc.go ├── dsl_test.go ├── func.go ├── func_string.go ├── instruction.go ├── instruction_test.go ├── jump.go ├── jump_string.go ├── load_store.go ├── load_store_string.go ├── opcode.go ├── opcode_string.go └── register.go ├── collection.go ├── collection_test.go ├── doc.go ├── elf_reader.go ├── elf_reader_test.go ├── example_program_test.go ├── example_sock_elf_test.go ├── example_sock_extract_dist_test.go ├── go.mod ├── go.sum ├── internal ├── btf │ ├── btf.go │ ├── btf_test.go │ ├── btf_types.go │ ├── doc.go │ ├── ext_info.go │ ├── strings.go │ ├── strings_test.go │ ├── testdata │ │ ├── Makefile │ │ └── vmlinux-btf.gz │ ├── types.go │ └── types_test.go ├── cpu.go ├── cpu_test.go ├── endian.go ├── errors.go ├── fd.go ├── feature.go ├── feature_test.go ├── io.go ├── io_test.go ├── ptr.go ├── ptr_32_be.go ├── ptr_32_le.go ├── ptr_64.go ├── syscall.go ├── testutils │ ├── feature.go │ └── glob.go └── unix │ ├── types_linux.go │ └── types_other.go ├── kernel_version.go ├── kernel_version_test.go ├── kernel_version_unsupported.go ├── linker.go ├── linker_test.go ├── manager ├── editor.go ├── editor_test.go ├── err.go ├── examples │ ├── activated_probes │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ └── main.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── clone_vs_add_hook │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── demo.go │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ └── main.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── map_rewrite_vs_map_router │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── demo.go │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ ├── prog1.c │ │ │ └── prog2.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── mapspec_editor │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ └── main.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── object_pinning │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ └── main.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── program_router │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── demo.go │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ ├── bpf_helpers.h │ │ │ │ └── bpf_map.h │ │ │ ├── prog1.c │ │ │ └── prog2.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go │ ├── programs │ │ ├── cgroup │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ ├── kprobe │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ ├── socket │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ ├── tc │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ ├── tracepoint │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ ├── uprobe │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ │ ├── include │ │ │ │ │ ├── bpf.h │ │ │ │ │ └── bpf_helpers.h │ │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ │ └── xdp │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── ebpf │ │ │ ├── include │ │ │ │ ├── bpf.h │ │ │ │ └── bpf_helpers.h │ │ │ └── main.c │ │ │ ├── main.go │ │ │ ├── probe.go │ │ │ └── utils.go │ └── tests_and_benchmarks │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── ebpf │ │ ├── include │ │ │ ├── bpf.h │ │ │ ├── bpf_helpers.h │ │ │ └── bpf_map.h │ │ └── main.c │ │ ├── main.go │ │ ├── probe.go │ │ └── utils.go ├── manager.go ├── map.go ├── perf.go ├── probe.go ├── selectors.go ├── syscalls.go ├── testdata │ ├── Makefile │ ├── common.h │ ├── rewrite.c │ └── rewrite.elf ├── utils.go └── utils_test.go ├── map.go ├── map_test.go ├── marshaler_example_test.go ├── marshalers.go ├── perf ├── doc.go ├── reader.go ├── reader_test.go ├── ring.go └── ring_test.go ├── prog.go ├── prog_test.go ├── readme.md ├── run-tests.sh ├── syscalls.go ├── syscalls_test.go ├── testdata ├── Makefile ├── common.h ├── invalid_map-eb.elf ├── invalid_map-el.elf ├── invalid_map.c ├── loader-clang-6.0-eb.elf ├── loader-clang-6.0-el.elf ├── loader-clang-7-eb.elf ├── loader-clang-7-el.elf ├── loader-clang-8-eb.elf ├── loader-clang-8-el.elf ├── loader-clang-9-eb.elf ├── loader-clang-9-el.elf ├── loader.c ├── rewrite-eb.elf ├── rewrite-el.elf └── rewrite.c ├── types.go ├── types_string.go ├── utsname_int8.go └── utsname_uint8.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | vendor/ 15 | -------------------------------------------------------------------------------- /.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.14 16 | - go build ./... 17 | - sudo pip3 install https://github.com/amluto/virtme/archive/538f1e756139a6b57a4780e7ceb3ac6bcaa4fe6f.zip 18 | - sudo apt-get install -y qemu-system-x86 19 | env_vars: 20 | - name: TMPDIR 21 | value: /tmp 22 | jobs: 23 | - name: Test building on other OS and arch 24 | commands: 25 | - GOOS=darwin go build ./... && for p in $(go list ./...) ; do GOOS=darwin go test -c $p ; done 26 | - GOARCH=arm GOARM=6 go build ./... && for p in $(go list ./...) ; do GOARCH=arm GOARM=6 go test -c $p ; done 27 | - GOARCH=arm64 go build ./... && for p in $(go list ./...) ; do GOARCH=arm64 go test -c $p ; done 28 | - name: Run unit tests 29 | matrix: 30 | - env_var: GO_VERSION 31 | values: [ "1.13", "1.14" ] 32 | - env_var: KERNEL_VERSION 33 | values: ["5.4.5", "4.19.81", "4.9.198"] 34 | commands: 35 | - sem-version go $GO_VERSION 36 | - timeout -s KILL 90s ./run-tests.sh $KERNEL_VERSION 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nathan Sweet 4 | Copyright (c) 2018, 2019 Cloudflare 5 | Copyright (c) 2019 Authors of Cilium 6 | Copyright (c) 2020 Authors of Datadog 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /abi.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "os" 10 | "syscall" 11 | 12 | "github.com/DataDog/ebpf/internal" 13 | ) 14 | 15 | // MapABI are the attributes of a Map which are available across all supported kernels. 16 | type MapABI struct { 17 | Type MapType 18 | KeySize uint32 19 | ValueSize uint32 20 | MaxEntries uint32 21 | Flags uint32 22 | } 23 | 24 | func newMapABIFromSpec(spec *MapSpec) *MapABI { 25 | return &MapABI{ 26 | spec.Type, 27 | spec.KeySize, 28 | spec.ValueSize, 29 | spec.MaxEntries, 30 | spec.Flags, 31 | } 32 | } 33 | 34 | func newMapABIFromFd(fd *internal.FD) (string, *MapABI, error) { 35 | info, err := bpfGetMapInfoByFD(fd) 36 | if err != nil { 37 | if errors.Is(err, syscall.EINVAL) { 38 | abi, err := newMapABIFromProc(fd) 39 | return "", abi, err 40 | } 41 | return "", nil, err 42 | } 43 | 44 | return "", &MapABI{ 45 | MapType(info.mapType), 46 | info.keySize, 47 | info.valueSize, 48 | info.maxEntries, 49 | info.flags, 50 | }, nil 51 | } 52 | 53 | func newMapABIFromProc(fd *internal.FD) (*MapABI, error) { 54 | var abi MapABI 55 | err := scanFdInfo(fd, map[string]interface{}{ 56 | "map_type": &abi.Type, 57 | "key_size": &abi.KeySize, 58 | "value_size": &abi.ValueSize, 59 | "max_entries": &abi.MaxEntries, 60 | "map_flags": &abi.Flags, 61 | }) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return &abi, nil 66 | } 67 | 68 | // Equal returns true if two ABIs have the same values. 69 | func (abi *MapABI) Equal(other *MapABI) bool { 70 | switch { 71 | case abi.Type != other.Type: 72 | return false 73 | case abi.KeySize != other.KeySize: 74 | return false 75 | case abi.ValueSize != other.ValueSize: 76 | return false 77 | case abi.MaxEntries != other.MaxEntries: 78 | return false 79 | case abi.Flags != other.Flags: 80 | return false 81 | default: 82 | return true 83 | } 84 | } 85 | 86 | // ProgramABI are the attributes of a Program which are available across all supported kernels. 87 | type ProgramABI struct { 88 | Type ProgramType 89 | } 90 | 91 | func newProgramABIFromSpec(spec *ProgramSpec) *ProgramABI { 92 | return &ProgramABI{ 93 | spec.Type, 94 | } 95 | } 96 | 97 | func newProgramABIFromFd(fd *internal.FD) (string, *ProgramABI, error) { 98 | info, err := bpfGetProgInfoByFD(fd) 99 | if err != nil { 100 | if errors.Is(err, syscall.EINVAL) { 101 | return newProgramABIFromProc(fd) 102 | } 103 | 104 | return "", nil, err 105 | } 106 | 107 | var name string 108 | if bpfName := internal.CString(info.name[:]); bpfName != "" { 109 | name = bpfName 110 | } else { 111 | name = internal.CString(info.tag[:]) 112 | } 113 | 114 | return name, &ProgramABI{ 115 | Type: ProgramType(info.progType), 116 | }, nil 117 | } 118 | 119 | func newProgramABIFromProc(fd *internal.FD) (string, *ProgramABI, error) { 120 | var ( 121 | abi ProgramABI 122 | name string 123 | ) 124 | 125 | err := scanFdInfo(fd, map[string]interface{}{ 126 | "prog_type": &abi.Type, 127 | "prog_tag": &name, 128 | }) 129 | if errors.Is(err, errMissingFields) { 130 | return "", nil, &internal.UnsupportedFeatureError{ 131 | Name: "reading ABI from /proc/self/fdinfo", 132 | MinimumVersion: internal.Version{4, 11, 0}, 133 | } 134 | } 135 | if err != nil { 136 | return "", nil, err 137 | } 138 | 139 | return name, &abi, nil 140 | } 141 | 142 | func scanFdInfo(fd *internal.FD, fields map[string]interface{}) error { 143 | raw, err := fd.Value() 144 | if err != nil { 145 | return err 146 | } 147 | 148 | fh, err := os.Open(fmt.Sprintf("/proc/self/fdinfo/%d", raw)) 149 | if err != nil { 150 | return err 151 | } 152 | defer fh.Close() 153 | 154 | if err := scanFdInfoReader(fh, fields); err != nil { 155 | return fmt.Errorf("%s: %w", fh.Name(), err) 156 | } 157 | return nil 158 | } 159 | 160 | var errMissingFields = errors.New("missing fields") 161 | 162 | func scanFdInfoReader(r io.Reader, fields map[string]interface{}) error { 163 | var ( 164 | scanner = bufio.NewScanner(r) 165 | scanned int 166 | ) 167 | 168 | for scanner.Scan() { 169 | parts := bytes.SplitN(scanner.Bytes(), []byte("\t"), 2) 170 | if len(parts) != 2 { 171 | continue 172 | } 173 | 174 | name := bytes.TrimSuffix(parts[0], []byte(":")) 175 | field, ok := fields[string(name)] 176 | if !ok { 177 | continue 178 | } 179 | 180 | if n, err := fmt.Fscanln(bytes.NewReader(parts[1]), field); err != nil || n != 1 { 181 | return fmt.Errorf("can't parse field %s: %v", name, err) 182 | } 183 | 184 | scanned++ 185 | } 186 | 187 | if err := scanner.Err(); err != nil { 188 | return err 189 | } 190 | 191 | if scanned != len(fields) { 192 | return errMissingFields 193 | } 194 | 195 | return nil 196 | } 197 | 198 | // Equal returns true if two ABIs have the same values. 199 | func (abi *ProgramABI) Equal(other *ProgramABI) bool { 200 | switch { 201 | case abi.Type != other.Type: 202 | return false 203 | default: 204 | return true 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /abi_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DataDog/ebpf/internal/testutils" 7 | ) 8 | 9 | func TestMapABIEqual(t *testing.T) { 10 | abi := &MapABI{ 11 | Type: Array, 12 | KeySize: 4, 13 | ValueSize: 2, 14 | MaxEntries: 3, 15 | Flags: 1, 16 | } 17 | 18 | if !abi.Equal(abi) { 19 | t.Error("Equal returns true when comparing an ABI to itself") 20 | } 21 | 22 | if abi.Equal(&MapABI{}) { 23 | t.Error("Equal returns true for different ABIs") 24 | } 25 | } 26 | 27 | func TestMapABIFromProc(t *testing.T) { 28 | hash, err := NewMap(&MapSpec{ 29 | Type: Hash, 30 | KeySize: 4, 31 | ValueSize: 5, 32 | MaxEntries: 2, 33 | Flags: 0x1, // BPF_F_NO_PREALLOC 34 | }) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | defer hash.Close() 39 | 40 | abi, err := newMapABIFromProc(hash.fd) 41 | if err != nil { 42 | t.Fatal("Can't get map ABI:", err) 43 | } 44 | 45 | if abi.Type != Hash { 46 | t.Error("Expected Hash, got", abi.Type) 47 | } 48 | 49 | if abi.KeySize != 4 { 50 | t.Error("Expected KeySize of 4, got", abi.KeySize) 51 | } 52 | 53 | if abi.ValueSize != 5 { 54 | t.Error("Expected ValueSize of 5, got", abi.ValueSize) 55 | } 56 | 57 | if abi.MaxEntries != 2 { 58 | t.Error("Expected MaxEntries of 2, got", abi.MaxEntries) 59 | } 60 | 61 | if abi.Flags != 1 { 62 | t.Error("Expected Flags to be 1, got", abi.Flags) 63 | } 64 | 65 | nested, err := NewMap(&MapSpec{ 66 | Type: ArrayOfMaps, 67 | KeySize: 4, 68 | MaxEntries: 2, 69 | InnerMap: &MapSpec{ 70 | Type: Array, 71 | KeySize: 4, 72 | ValueSize: 4, 73 | MaxEntries: 2, 74 | }, 75 | }) 76 | testutils.SkipIfNotSupported(t, err) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | defer nested.Close() 81 | 82 | _, err = newMapABIFromProc(nested.fd) 83 | if err != nil { 84 | t.Fatal("Can't get nested map ABI from /proc:", err) 85 | } 86 | } 87 | 88 | func TestProgramABI(t *testing.T) { 89 | abi := &ProgramABI{Type: SocketFilter} 90 | 91 | if !abi.Equal(abi) { 92 | t.Error("Equal returns true when comparing an ABI to itself") 93 | } 94 | 95 | if abi.Equal(&ProgramABI{}) { 96 | t.Error("Equal returns true for different ABIs") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[InvalidSource-255] 12 | _ = x[ImmSource-0] 13 | _ = x[RegSource-8] 14 | } 15 | 16 | const ( 17 | _Source_name_0 = "ImmSource" 18 | _Source_name_1 = "RegSource" 19 | _Source_name_2 = "InvalidSource" 20 | ) 21 | 22 | func (i Source) String() string { 23 | switch { 24 | case i == 0: 25 | return _Source_name_0 26 | case i == 8: 27 | return _Source_name_1 28 | case i == 255: 29 | return _Source_name_2 30 | default: 31 | return "Source(" + strconv.FormatInt(int64(i), 10) + ")" 32 | } 33 | } 34 | func _() { 35 | // An "invalid array index" compiler error signifies that the constant values have changed. 36 | // Re-run the stringer command to generate them again. 37 | var x [1]struct{} 38 | _ = x[InvalidEndian-255] 39 | _ = x[LE-0] 40 | _ = x[BE-8] 41 | } 42 | 43 | const ( 44 | _Endianness_name_0 = "LE" 45 | _Endianness_name_1 = "BE" 46 | _Endianness_name_2 = "InvalidEndian" 47 | ) 48 | 49 | func (i Endianness) String() string { 50 | switch { 51 | case i == 0: 52 | return _Endianness_name_0 53 | case i == 8: 54 | return _Endianness_name_1 55 | case i == 255: 56 | return _Endianness_name_2 57 | default: 58 | return "Endianness(" + strconv.FormatInt(int64(i), 10) + ")" 59 | } 60 | } 61 | func _() { 62 | // An "invalid array index" compiler error signifies that the constant values have changed. 63 | // Re-run the stringer command to generate them again. 64 | var x [1]struct{} 65 | _ = x[InvalidALUOp-255] 66 | _ = x[Add-0] 67 | _ = x[Sub-16] 68 | _ = x[Mul-32] 69 | _ = x[Div-48] 70 | _ = x[Or-64] 71 | _ = x[And-80] 72 | _ = x[LSh-96] 73 | _ = x[RSh-112] 74 | _ = x[Neg-128] 75 | _ = x[Mod-144] 76 | _ = x[Xor-160] 77 | _ = x[Mov-176] 78 | _ = x[ArSh-192] 79 | _ = x[Swap-208] 80 | } 81 | 82 | const _ALUOp_name = "AddSubMulDivOrAndLShRShNegModXorMovArShSwapInvalidALUOp" 83 | 84 | var _ALUOp_map = map[ALUOp]string{ 85 | 0: _ALUOp_name[0:3], 86 | 16: _ALUOp_name[3:6], 87 | 32: _ALUOp_name[6:9], 88 | 48: _ALUOp_name[9:12], 89 | 64: _ALUOp_name[12:14], 90 | 80: _ALUOp_name[14:17], 91 | 96: _ALUOp_name[17:20], 92 | 112: _ALUOp_name[20:23], 93 | 128: _ALUOp_name[23:26], 94 | 144: _ALUOp_name[26:29], 95 | 160: _ALUOp_name[29:32], 96 | 176: _ALUOp_name[32:35], 97 | 192: _ALUOp_name[35:39], 98 | 208: _ALUOp_name[39:43], 99 | 255: _ALUOp_name[43:55], 100 | } 101 | 102 | func (i ALUOp) String() string { 103 | if str, ok := _ALUOp_map[i]; ok { 104 | return str 105 | } 106 | return "ALUOp(" + strconv.FormatInt(int64(i), 10) + ")" 107 | } 108 | -------------------------------------------------------------------------------- /asm/doc.go: -------------------------------------------------------------------------------- 1 | // Package asm is an assembler for eBPF bytecode. 2 | package asm 3 | -------------------------------------------------------------------------------- /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", FnMapLookupElem.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 | -------------------------------------------------------------------------------- /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 | // 10 | // You can renegerate this list using the following gawk script: 11 | // 12 | // /FN\(.+\),/ { 13 | // match($1, /\((.+)\)/, r) 14 | // split(r[1], p, "_") 15 | // printf "Fn" 16 | // for (i in p) { 17 | // printf "%s%s", toupper(substr(p[i], 1, 1)), substr(p[i], 2) 18 | // } 19 | // print "" 20 | // } 21 | // 22 | // The script expects include/uapi/linux/bpf.h as it's input. 23 | const ( 24 | FnUnspec BuiltinFunc = iota 25 | FnMapLookupElem 26 | FnMapUpdateElem 27 | FnMapDeleteElem 28 | FnProbeRead 29 | FnKtimeGetNs 30 | FnTracePrintk 31 | FnGetPrandomU32 32 | FnGetSmpProcessorId 33 | FnSkbStoreBytes 34 | FnL3CsumReplace 35 | FnL4CsumReplace 36 | FnTailCall 37 | FnCloneRedirect 38 | FnGetCurrentPidTgid 39 | FnGetCurrentUidGid 40 | FnGetCurrentComm 41 | FnGetCgroupClassid 42 | FnSkbVlanPush 43 | FnSkbVlanPop 44 | FnSkbGetTunnelKey 45 | FnSkbSetTunnelKey 46 | FnPerfEventRead 47 | FnRedirect 48 | FnGetRouteRealm 49 | FnPerfEventOutput 50 | FnSkbLoadBytes 51 | FnGetStackid 52 | FnCsumDiff 53 | FnSkbGetTunnelOpt 54 | FnSkbSetTunnelOpt 55 | FnSkbChangeProto 56 | FnSkbChangeType 57 | FnSkbUnderCgroup 58 | FnGetHashRecalc 59 | FnGetCurrentTask 60 | FnProbeWriteUser 61 | FnCurrentTaskUnderCgroup 62 | FnSkbChangeTail 63 | FnSkbPullData 64 | FnCsumUpdate 65 | FnSetHashInvalid 66 | FnGetNumaNodeId 67 | FnSkbChangeHead 68 | FnXdpAdjustHead 69 | FnProbeReadStr 70 | FnGetSocketCookie 71 | FnGetSocketUid 72 | FnSetHash 73 | FnSetsockopt 74 | FnSkbAdjustRoom 75 | FnRedirectMap 76 | FnSkRedirectMap 77 | FnSockMapUpdate 78 | FnXdpAdjustMeta 79 | FnPerfEventReadValue 80 | FnPerfProgReadValue 81 | FnGetsockopt 82 | FnOverrideReturn 83 | FnSockOpsCbFlagsSet 84 | FnMsgRedirectMap 85 | FnMsgApplyBytes 86 | FnMsgCorkBytes 87 | FnMsgPullData 88 | FnBind 89 | FnXdpAdjustTail 90 | FnSkbGetXfrmState 91 | FnGetStack 92 | FnSkbLoadBytesRelative 93 | FnFibLookup 94 | FnSockHashUpdate 95 | FnMsgRedirectHash 96 | FnSkRedirectHash 97 | FnLwtPushEncap 98 | FnLwtSeg6StoreBytes 99 | FnLwtSeg6AdjustSrh 100 | FnLwtSeg6Action 101 | FnRcRepeat 102 | FnRcKeydown 103 | FnSkbCgroupId 104 | FnGetCurrentCgroupId 105 | FnGetLocalStorage 106 | FnSkSelectReuseport 107 | FnSkbAncestorCgroupId 108 | FnSkLookupTcp 109 | FnSkLookupUdp 110 | FnSkRelease 111 | FnMapPushElem 112 | FnMapPopElem 113 | FnMapPeekElem 114 | FnMsgPushData 115 | FnMsgPopData 116 | FnRcPointerRel 117 | FnSpinLock 118 | FnSpinUnlock 119 | FnSkFullsock 120 | FnTcpSock 121 | FnSkbEcnSetCe 122 | FnGetListenerSock 123 | FnSkcLookupTcp 124 | FnTcpCheckSyncookie 125 | FnSysctlGetName 126 | FnSysctlGetCurrentValue 127 | FnSysctlGetNewValue 128 | FnSysctlSetNewValue 129 | FnStrtol 130 | FnStrtoul 131 | FnSkStorageGet 132 | FnSkStorageDelete 133 | FnSendSignal 134 | FnTcpGenSyncookie 135 | ) 136 | 137 | // Call emits a function call. 138 | func (fn BuiltinFunc) Call() Instruction { 139 | return Instruction{ 140 | OpCode: OpCode(JumpClass).SetJumpOp(Call), 141 | Constant: int64(fn), 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /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: PseudoCall, 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 | -------------------------------------------------------------------------------- /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 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[InvalidJumpOp-255] 12 | _ = x[Ja-0] 13 | _ = x[JEq-16] 14 | _ = x[JGT-32] 15 | _ = x[JGE-48] 16 | _ = x[JSet-64] 17 | _ = x[JNE-80] 18 | _ = x[JSGT-96] 19 | _ = x[JSGE-112] 20 | _ = x[Call-128] 21 | _ = x[Exit-144] 22 | _ = x[JLT-160] 23 | _ = x[JLE-176] 24 | _ = x[JSLT-192] 25 | _ = x[JSLE-208] 26 | } 27 | 28 | const _JumpOp_name = "JaJEqJGTJGEJSetJNEJSGTJSGECallExitJLTJLEJSLTJSLEInvalidJumpOp" 29 | 30 | var _JumpOp_map = map[JumpOp]string{ 31 | 0: _JumpOp_name[0:2], 32 | 16: _JumpOp_name[2:5], 33 | 32: _JumpOp_name[5:8], 34 | 48: _JumpOp_name[8:11], 35 | 64: _JumpOp_name[11:15], 36 | 80: _JumpOp_name[15:18], 37 | 96: _JumpOp_name[18:22], 38 | 112: _JumpOp_name[22:26], 39 | 128: _JumpOp_name[26:30], 40 | 144: _JumpOp_name[30:34], 41 | 160: _JumpOp_name[34:37], 42 | 176: _JumpOp_name[37:40], 43 | 192: _JumpOp_name[40:44], 44 | 208: _JumpOp_name[44:48], 45 | 255: _JumpOp_name[48:61], 46 | } 47 | 48 | func (i JumpOp) String() string { 49 | if str, ok := _JumpOp_map[i]; ok { 50 | return str 51 | } 52 | return "JumpOp(" + strconv.FormatInt(int64(i), 10) + ")" 53 | } 54 | -------------------------------------------------------------------------------- /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 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[InvalidMode-255] 12 | _ = x[ImmMode-0] 13 | _ = x[AbsMode-32] 14 | _ = x[IndMode-64] 15 | _ = x[MemMode-96] 16 | _ = x[XAddMode-192] 17 | } 18 | 19 | const ( 20 | _Mode_name_0 = "ImmMode" 21 | _Mode_name_1 = "AbsMode" 22 | _Mode_name_2 = "IndMode" 23 | _Mode_name_3 = "MemMode" 24 | _Mode_name_4 = "XAddMode" 25 | _Mode_name_5 = "InvalidMode" 26 | ) 27 | 28 | func (i Mode) String() string { 29 | switch { 30 | case i == 0: 31 | return _Mode_name_0 32 | case i == 32: 33 | return _Mode_name_1 34 | case i == 64: 35 | return _Mode_name_2 36 | case i == 96: 37 | return _Mode_name_3 38 | case i == 192: 39 | return _Mode_name_4 40 | case i == 255: 41 | return _Mode_name_5 42 | default: 43 | return "Mode(" + strconv.FormatInt(int64(i), 10) + ")" 44 | } 45 | } 46 | func _() { 47 | // An "invalid array index" compiler error signifies that the constant values have changed. 48 | // Re-run the stringer command to generate them again. 49 | var x [1]struct{} 50 | _ = x[InvalidSize-255] 51 | _ = x[DWord-24] 52 | _ = x[Word-0] 53 | _ = x[Half-8] 54 | _ = x[Byte-16] 55 | } 56 | 57 | const ( 58 | _Size_name_0 = "Word" 59 | _Size_name_1 = "Half" 60 | _Size_name_2 = "Byte" 61 | _Size_name_3 = "DWord" 62 | _Size_name_4 = "InvalidSize" 63 | ) 64 | 65 | func (i Size) String() string { 66 | switch { 67 | case i == 0: 68 | return _Size_name_0 69 | case i == 8: 70 | return _Size_name_1 71 | case i == 16: 72 | return _Size_name_2 73 | case i == 24: 74 | return _Size_name_3 75 | case i == 255: 76 | return _Size_name_4 77 | default: 78 | return "Size(" + strconv.FormatInt(int64(i), 10) + ")" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /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 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[LdClass-0] 12 | _ = x[LdXClass-1] 13 | _ = x[StClass-2] 14 | _ = x[StXClass-3] 15 | _ = x[ALUClass-4] 16 | _ = x[JumpClass-5] 17 | _ = x[ALU64Class-7] 18 | } 19 | 20 | const ( 21 | _Class_name_0 = "LdClassLdXClassStClassStXClassALUClassJumpClass" 22 | _Class_name_1 = "ALU64Class" 23 | ) 24 | 25 | var ( 26 | _Class_index_0 = [...]uint8{0, 7, 15, 22, 30, 38, 47} 27 | ) 28 | 29 | func (i Class) String() string { 30 | switch { 31 | case 0 <= i && i <= 5: 32 | return _Class_name_0[_Class_index_0[i]:_Class_index_0[i+1]] 33 | case i == 7: 34 | return _Class_name_1 35 | default: 36 | return "Class(" + strconv.FormatInt(int64(i), 10) + ")" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | // Pseudo registers used by 64bit loads and jumps 37 | const ( 38 | PseudoMapFD = R1 // BPF_PSEUDO_MAP_FD 39 | PseudoMapValue = R2 // BPF_PSEUDO_MAP_VALUE 40 | PseudoCall = R1 // BPF_PSEUDO_CALL 41 | ) 42 | 43 | func (r Register) String() string { 44 | v := uint8(r) 45 | if v == 10 { 46 | return "rfp" 47 | } 48 | return fmt.Sprintf("r%d", v) 49 | } 50 | -------------------------------------------------------------------------------- /collection_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DataDog/ebpf/asm" 7 | "github.com/DataDog/ebpf/internal/testutils" 8 | ) 9 | 10 | func TestCollectionSpecNotModified(t *testing.T) { 11 | cs := CollectionSpec{ 12 | Maps: map[string]*MapSpec{ 13 | "my-map": { 14 | Type: Array, 15 | KeySize: 4, 16 | ValueSize: 4, 17 | MaxEntries: 1, 18 | }, 19 | }, 20 | Programs: map[string]*ProgramSpec{ 21 | "test": { 22 | Type: SocketFilter, 23 | Instructions: asm.Instructions{ 24 | asm.LoadImm(asm.R1, 0, asm.DWord), 25 | asm.LoadImm(asm.R0, 0, asm.DWord), 26 | asm.Return(), 27 | }, 28 | License: "MIT", 29 | }, 30 | }, 31 | } 32 | 33 | cs.Programs["test"].Instructions[0].Reference = "my-map" 34 | 35 | coll, err := NewCollection(&cs) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | coll.Close() 40 | 41 | if cs.Programs["test"].Instructions[0].Constant != 0 { 42 | t.Error("Creating a collection modifies input spec") 43 | } 44 | } 45 | 46 | func TestCollectionSpecCopy(t *testing.T) { 47 | cs := &CollectionSpec{ 48 | Maps: map[string]*MapSpec{ 49 | "my-map": { 50 | Type: Array, 51 | KeySize: 4, 52 | ValueSize: 4, 53 | MaxEntries: 1, 54 | }, 55 | }, 56 | Programs: map[string]*ProgramSpec{ 57 | "test": { 58 | Type: SocketFilter, 59 | Instructions: asm.Instructions{ 60 | asm.LoadMapPtr(asm.R1, 0), 61 | asm.LoadImm(asm.R0, 0, asm.DWord), 62 | asm.Return(), 63 | }, 64 | License: "MIT", 65 | }, 66 | }, 67 | } 68 | cpy := cs.Copy() 69 | 70 | if cpy == cs { 71 | t.Error("Copy returned the same pointner") 72 | } 73 | 74 | if cpy.Maps["my-map"] == cs.Maps["my-map"] { 75 | t.Error("Copy returned same Maps") 76 | } 77 | 78 | if cpy.Programs["test"] == cs.Programs["test"] { 79 | t.Error("Copy returned same Programs") 80 | } 81 | } 82 | 83 | func TestCollectionSpecRewriteMaps(t *testing.T) { 84 | insns := asm.Instructions{ 85 | // R1 map 86 | asm.LoadMapPtr(asm.R1, 0), 87 | // R2 key 88 | asm.Mov.Reg(asm.R2, asm.R10), 89 | asm.Add.Imm(asm.R2, -4), 90 | asm.StoreImm(asm.R2, 0, 0, asm.Word), 91 | // Lookup map[0] 92 | asm.FnMapLookupElem.Call(), 93 | asm.JEq.Imm(asm.R0, 0, "ret"), 94 | asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 95 | asm.Return().Sym("ret"), 96 | } 97 | insns[0].Reference = "test-map" 98 | 99 | cs := &CollectionSpec{ 100 | Maps: map[string]*MapSpec{ 101 | "test-map": { 102 | Type: Array, 103 | KeySize: 4, 104 | ValueSize: 4, 105 | MaxEntries: 1, 106 | }, 107 | }, 108 | Programs: map[string]*ProgramSpec{ 109 | "test-prog": { 110 | Type: SocketFilter, 111 | Instructions: insns, 112 | License: "MIT", 113 | }, 114 | }, 115 | } 116 | 117 | // Override the map with another one 118 | newMap, err := NewMap(cs.Maps["test-map"]) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | defer newMap.Close() 123 | 124 | err = newMap.Put(uint32(0), uint32(2)) 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | 129 | err = cs.RewriteMaps(map[string]*Map{ 130 | "test-map": newMap, 131 | }) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | if cs.Maps["test-map"] != nil { 137 | t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps") 138 | } 139 | 140 | coll, err := NewCollection(cs) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | defer coll.Close() 145 | 146 | ret, _, err := coll.Programs["test-prog"].Test(make([]byte, 14)) 147 | testutils.SkipIfNotSupported(t, err) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | 152 | if ret != 2 { 153 | t.Fatal("new / override map not used") 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /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 | // This package is designed for long-running processes which 10 | // want to use eBPF to implement part of their application logic. It has no 11 | // run-time dependencies outside of the library and the Linux kernel itself. 12 | // eBPF code should be compiled ahead of time using clang, and shipped with 13 | // your application as any other resource. 14 | // 15 | // This package doesn't include code required to attach eBPF to Linux 16 | // subsystems, since this varies per subsystem. 17 | package ebpf 18 | -------------------------------------------------------------------------------- /example_program_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package ebpf_test 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strconv" 11 | "time" 12 | "strings" 13 | 14 | "github.com/DataDog/ebpf" 15 | "github.com/DataDog/ebpf/asm" 16 | "github.com/DataDog/ebpf/perf" 17 | 18 | "golang.org/x/sys/unix" 19 | ) 20 | 21 | // getTracepointID returns the system specific ID for the tracepoint sys_enter_open. 22 | func getTracepointID() (uint64, error) { 23 | data, err := ioutil.ReadFile("/sys/kernel/debug/tracing/events/syscalls/sys_enter_open/id") 24 | if err != nil { 25 | return 0, fmt.Errorf("failed to read tracepoint ID for 'sys_enter_open': %v", err) 26 | } 27 | tid := strings.TrimSuffix(string(data), "\n") 28 | return strconv.ParseUint(tid, 10, 64) 29 | } 30 | 31 | // Example_program demonstrates how to attach an eBPF program to a tracepoint. 32 | // The program will be attached to the sys_enter_open syscall and print out the integer 33 | // 123 everytime the sycall is used. 34 | func Example_program() { 35 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 36 | defer cancel() 37 | events, err := ebpf.NewMap(&ebpf.MapSpec{ 38 | Type: ebpf.PerfEventArray, 39 | Name: "pureGo", 40 | }) 41 | if err != nil { 42 | panic(fmt.Errorf("could not create event map: %v\n", err)) 43 | } 44 | defer events.Close() 45 | 46 | rd, err := perf.NewReader(events, os.Getpagesize()) 47 | if err != nil { 48 | panic(fmt.Errorf("could not create event reader: %v", err)) 49 | } 50 | defer rd.Close() 51 | 52 | go func() { 53 | for { 54 | select { 55 | case <-ctx.Done(): 56 | return 57 | default: 58 | } 59 | record, err := rd.Read() 60 | if err != nil { 61 | if perf.IsClosed(err) { 62 | return 63 | } 64 | panic(fmt.Errorf("could not read from reader: %v", err)) 65 | } 66 | fmt.Println(record) 67 | } 68 | }() 69 | 70 | ins := asm.Instructions{ 71 | // store the integer 123 at FP[-8] 72 | asm.Mov.Imm(asm.R2, 123), 73 | asm.StoreMem(asm.RFP, -8, asm.R2, asm.Word), 74 | 75 | // load registers with arguments for call of FnPerfEventOutput 76 | asm.LoadMapPtr(asm.R2, events.FD()), 77 | asm.LoadImm(asm.R3, 0xffffffff, asm.DWord), 78 | asm.Mov.Reg(asm.R4, asm.RFP), 79 | asm.Add.Imm(asm.R4, -8), 80 | asm.Mov.Imm(asm.R5, 4), 81 | 82 | // call FnPerfEventOutput 83 | asm.FnPerfEventOutput.Call(), 84 | 85 | // set exit code to 0 86 | asm.Mov.Imm(asm.R0, 0), 87 | asm.Return(), 88 | } 89 | 90 | prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{ 91 | Name: "sys_enter_open", 92 | Type: ebpf.TracePoint, 93 | License: "GPL", 94 | Instructions: ins, 95 | }) 96 | if err != nil { 97 | panic(fmt.Errorf("could not create new ebpf program: %v", err)) 98 | } 99 | defer prog.Close() 100 | 101 | tid, err := getTracepointID() 102 | if err != nil { 103 | panic(fmt.Errorf("could not get tracepoint id: %v", err)) 104 | } 105 | 106 | attr := unix.PerfEventAttr{ 107 | Type: unix.PERF_TYPE_TRACEPOINT, 108 | Config: tid, 109 | Sample_type: unix.PERF_SAMPLE_RAW, 110 | Sample: 1, 111 | Wakeup: 1, 112 | } 113 | pfd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 114 | if err != nil { 115 | panic(fmt.Errorf("unable to open perf events: %v", err)) 116 | } 117 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_ENABLE, 0); errno != 0 { 118 | panic(fmt.Errorf("unable to enable perf events: %v", err)) 119 | } 120 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_SET_BPF, uintptr(prog.FD())); errno != 0 { 121 | panic(fmt.Errorf("unable to attach bpf program to perf events: %v", err)) 122 | } 123 | 124 | <-ctx.Done() 125 | 126 | if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(pfd), unix.PERF_EVENT_IOC_DISABLE, 0); errno != 0 { 127 | panic(fmt.Errorf("unable to disable perf events: %v", err)) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DataDog/ebpf 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 7 | github.com/avast/retry-go v2.7.0+incompatible 8 | github.com/florianl/go-tc v0.2.0 9 | github.com/hashicorp/go-multierror v1.1.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/sirupsen/logrus v1.6.0 12 | github.com/vishvananda/netlink v1.1.0 13 | golang.org/x/sys v0.0.0-20200320181252-af34d8274f85 14 | ) 15 | -------------------------------------------------------------------------------- /internal/btf/btf_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "encoding/binary" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "testing" 11 | 12 | "github.com/DataDog/ebpf/internal" 13 | "github.com/DataDog/ebpf/internal/testutils" 14 | ) 15 | 16 | func TestParseVmlinux(t *testing.T) { 17 | fh, err := os.Open("testdata/vmlinux-btf.gz") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | defer fh.Close() 22 | 23 | rd, err := gzip.NewReader(fh) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | buf, err := ioutil.ReadAll(rd) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | _, _, err = parseBTF(bytes.NewReader(buf), binary.LittleEndian) 34 | if err != nil { 35 | t.Fatal("Can't load BTF:", err) 36 | } 37 | } 38 | 39 | func TestParseCurrentKernelBTF(t *testing.T) { 40 | if _, err := os.Stat("/sys/kernel/btf/vmlinux"); os.IsNotExist(err) { 41 | t.Skip("/sys/kernel/btf/vmlinux is not available") 42 | } 43 | 44 | fh, err := os.Open("/sys/kernel/btf/vmlinux") 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | defer fh.Close() 49 | 50 | _, _, err = parseBTF(fh, binary.LittleEndian) 51 | if err != nil { 52 | t.Fatal("Can't load BTF:", err) 53 | } 54 | } 55 | 56 | func TestLoadSpecFromElf(t *testing.T) { 57 | testutils.TestFiles(t, "../../testdata/loader-clang-9-*.elf", func(t *testing.T, file string) { 58 | fh, err := os.Open(file) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | defer fh.Close() 63 | 64 | spec, err := LoadSpecFromReader(fh) 65 | if err != nil { 66 | t.Fatal("Can't load BTF:", err) 67 | } 68 | 69 | if spec == nil { 70 | t.Error("No BTF found in ELF") 71 | } 72 | 73 | if sec, err := spec.Program("xdp", 1); err != nil { 74 | t.Error("Can't get BTF for the xdp section:", err) 75 | } else if sec == nil { 76 | t.Error("Missing BTF for the xdp section") 77 | } 78 | 79 | if sec, err := spec.Program("socket", 1); err != nil { 80 | t.Error("Can't get BTF for the socket section:", err) 81 | } else if sec == nil { 82 | t.Error("Missing BTF for the socket section") 83 | } 84 | 85 | var bpfMapDef Struct 86 | if err := spec.FindType("bpf_map_def", &bpfMapDef); err != nil { 87 | t.Fatal("Can't find bpf_map_def:", err) 88 | } 89 | 90 | if spec.byteOrder != internal.NativeEndian { 91 | return 92 | } 93 | 94 | t.Run("Handle", func(t *testing.T) { 95 | btf, err := NewHandle(spec) 96 | testutils.SkipIfNotSupported(t, err) 97 | if err != nil { 98 | t.Fatal("Can't load BTF:", err) 99 | } 100 | defer btf.Close() 101 | }) 102 | }) 103 | } 104 | 105 | func TestHaveBTF(t *testing.T) { 106 | testutils.CheckFeatureTest(t, haveBTF) 107 | } 108 | 109 | func ExampleSpec_FindType() { 110 | // Acquire a Spec via one of its constructors. 111 | spec := new(Spec) 112 | 113 | // Declare a variable of the desired type 114 | var foo Struct 115 | 116 | if err := spec.FindType("foo", &foo); err != nil { 117 | // There is no struct with name foo, or there 118 | // are multiple possibilities. 119 | } 120 | 121 | // We've found struct foo 122 | fmt.Println(foo.Name) 123 | } 124 | -------------------------------------------------------------------------------- /internal/btf/doc.go: -------------------------------------------------------------------------------- 1 | // Package btf handles data encoded according to the BPF Type Format. 2 | // 3 | // The canonical documentation lives in the Linux kernel repository and is 4 | // available at https://www.kernel.org/doc/html/latest/bpf/btf.html 5 | // 6 | // The API is very much unstable. You should only use this via the main 7 | // ebpf library. 8 | package btf 9 | -------------------------------------------------------------------------------- /internal/btf/ext_info.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | 11 | "github.com/DataDog/ebpf/asm" 12 | "github.com/DataDog/ebpf/internal" 13 | ) 14 | 15 | type btfExtHeader struct { 16 | Magic uint16 17 | Version uint8 18 | Flags uint8 19 | HdrLen uint32 20 | 21 | FuncInfoOff uint32 22 | FuncInfoLen uint32 23 | LineInfoOff uint32 24 | LineInfoLen uint32 25 | } 26 | 27 | func parseExtInfos(r io.ReadSeeker, bo binary.ByteOrder, strings stringTable) (funcInfo, lineInfo map[string]extInfo, err error) { 28 | var header btfExtHeader 29 | if err := binary.Read(r, bo, &header); err != nil { 30 | return nil, nil, fmt.Errorf("can't read header: %v", err) 31 | } 32 | 33 | if header.Magic != btfMagic { 34 | return nil, nil, fmt.Errorf("incorrect magic value %v", header.Magic) 35 | } 36 | 37 | if header.Version != 1 { 38 | return nil, nil, fmt.Errorf("unexpected version %v", header.Version) 39 | } 40 | 41 | if header.Flags != 0 { 42 | return nil, nil, fmt.Errorf("unsupported flags %v", header.Flags) 43 | } 44 | 45 | remainder := int64(header.HdrLen) - int64(binary.Size(&header)) 46 | if remainder < 0 { 47 | return nil, nil, errors.New("header is too short") 48 | } 49 | 50 | // Of course, the .BTF.ext header has different semantics than the 51 | // .BTF ext header. We need to ignore non-null values. 52 | _, err = io.CopyN(ioutil.Discard, r, remainder) 53 | if err != nil { 54 | return nil, nil, fmt.Errorf("header padding: %v", err) 55 | } 56 | 57 | if _, err := r.Seek(int64(header.HdrLen+header.FuncInfoOff), io.SeekStart); err != nil { 58 | return nil, nil, fmt.Errorf("can't seek to function info section: %v", err) 59 | } 60 | 61 | funcInfo, err = parseExtInfo(io.LimitReader(r, int64(header.FuncInfoLen)), bo, strings) 62 | if err != nil { 63 | return nil, nil, fmt.Errorf("function info: %w", err) 64 | } 65 | 66 | if _, err := r.Seek(int64(header.HdrLen+header.LineInfoOff), io.SeekStart); err != nil { 67 | return nil, nil, fmt.Errorf("can't seek to line info section: %v", err) 68 | } 69 | 70 | lineInfo, err = parseExtInfo(io.LimitReader(r, int64(header.LineInfoLen)), bo, strings) 71 | if err != nil { 72 | return nil, nil, fmt.Errorf("line info: %w", err) 73 | } 74 | 75 | return funcInfo, lineInfo, nil 76 | } 77 | 78 | type btfExtInfoSec struct { 79 | SecNameOff uint32 80 | NumInfo uint32 81 | } 82 | 83 | type extInfoRecord struct { 84 | InsnOff uint64 85 | Opaque []byte 86 | } 87 | 88 | type extInfo struct { 89 | recordSize uint32 90 | records []extInfoRecord 91 | } 92 | 93 | func (ei extInfo) append(other extInfo, offset uint64) (extInfo, error) { 94 | if other.recordSize != ei.recordSize { 95 | return extInfo{}, fmt.Errorf("ext_info record size mismatch, want %d (got %d)", ei.recordSize, other.recordSize) 96 | } 97 | 98 | records := make([]extInfoRecord, 0, len(ei.records)+len(other.records)) 99 | records = append(records, ei.records...) 100 | for _, info := range other.records { 101 | records = append(records, extInfoRecord{ 102 | InsnOff: info.InsnOff + offset, 103 | Opaque: info.Opaque, 104 | }) 105 | } 106 | return extInfo{ei.recordSize, records}, nil 107 | } 108 | 109 | func (ei extInfo) MarshalBinary() ([]byte, error) { 110 | if len(ei.records) == 0 { 111 | return nil, nil 112 | } 113 | 114 | buf := bytes.NewBuffer(make([]byte, 0, int(ei.recordSize)*len(ei.records))) 115 | for _, info := range ei.records { 116 | // The kernel expects offsets in number of raw bpf instructions, 117 | // while the ELF tracks it in bytes. 118 | insnOff := uint32(info.InsnOff / asm.InstructionSize) 119 | if err := binary.Write(buf, internal.NativeEndian, insnOff); err != nil { 120 | return nil, fmt.Errorf("can't write instruction offset: %v", err) 121 | } 122 | 123 | buf.Write(info.Opaque) 124 | } 125 | 126 | return buf.Bytes(), nil 127 | } 128 | 129 | func parseExtInfo(r io.Reader, bo binary.ByteOrder, strings stringTable) (map[string]extInfo, error) { 130 | var recordSize uint32 131 | if err := binary.Read(r, bo, &recordSize); err != nil { 132 | return nil, fmt.Errorf("can't read record size: %v", err) 133 | } 134 | 135 | if recordSize < 4 { 136 | // Need at least insnOff 137 | return nil, errors.New("record size too short") 138 | } 139 | 140 | result := make(map[string]extInfo) 141 | for { 142 | var infoHeader btfExtInfoSec 143 | if err := binary.Read(r, bo, &infoHeader); err == io.EOF { 144 | return result, nil 145 | } else if err != nil { 146 | return nil, fmt.Errorf("can't read ext info header: %v", err) 147 | } 148 | 149 | secName, err := strings.Lookup(infoHeader.SecNameOff) 150 | if err != nil { 151 | return nil, fmt.Errorf("can't get section name: %w", err) 152 | } 153 | 154 | if infoHeader.NumInfo == 0 { 155 | return nil, fmt.Errorf("section %s has invalid number of records", secName) 156 | } 157 | 158 | var records []extInfoRecord 159 | for i := uint32(0); i < infoHeader.NumInfo; i++ { 160 | var byteOff uint32 161 | if err := binary.Read(r, bo, &byteOff); err != nil { 162 | return nil, fmt.Errorf("section %v: can't read extended info offset: %v", secName, err) 163 | } 164 | 165 | buf := make([]byte, int(recordSize-4)) 166 | if _, err := io.ReadFull(r, buf); err != nil { 167 | return nil, fmt.Errorf("section %v: can't read record: %v", secName, err) 168 | } 169 | 170 | if byteOff%asm.InstructionSize != 0 { 171 | return nil, fmt.Errorf("section %v: offset %v is not aligned with instruction size", secName, byteOff) 172 | } 173 | 174 | records = append(records, extInfoRecord{uint64(byteOff), buf}) 175 | } 176 | 177 | result[secName] = extInfo{ 178 | recordSize, 179 | records, 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /internal/btf/strings.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | ) 10 | 11 | type stringTable []byte 12 | 13 | func readStringTable(r io.Reader) (stringTable, error) { 14 | contents, err := ioutil.ReadAll(r) 15 | if err != nil { 16 | return nil, fmt.Errorf("can't read string table: %v", err) 17 | } 18 | 19 | if len(contents) < 1 { 20 | return nil, errors.New("string table is empty") 21 | } 22 | 23 | if contents[0] != '\x00' { 24 | return nil, errors.New("first item in string table is non-empty") 25 | } 26 | 27 | if contents[len(contents)-1] != '\x00' { 28 | return nil, errors.New("string table isn't null terminated") 29 | } 30 | 31 | return stringTable(contents), nil 32 | } 33 | 34 | func (st stringTable) Lookup(offset uint32) (string, error) { 35 | if int64(offset) > int64(^uint(0)>>1) { 36 | return "", fmt.Errorf("offset %d overflows int", offset) 37 | } 38 | 39 | pos := int(offset) 40 | if pos >= len(st) { 41 | return "", fmt.Errorf("offset %d is out of bounds", offset) 42 | } 43 | 44 | if pos > 0 && st[pos-1] != '\x00' { 45 | return "", fmt.Errorf("offset %d isn't start of a string", offset) 46 | } 47 | 48 | str := st[pos:] 49 | end := bytes.IndexByte(str, '\x00') 50 | if end == -1 { 51 | return "", fmt.Errorf("offset %d isn't null terminated", offset) 52 | } 53 | 54 | return string(str[:end]), nil 55 | } 56 | 57 | func (st stringTable) LookupName(offset uint32) (Name, error) { 58 | str, err := st.Lookup(offset) 59 | return Name(str), err 60 | } 61 | -------------------------------------------------------------------------------- /internal/btf/strings_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestStringTable(t *testing.T) { 10 | const in = "\x00one\x00two\x00" 11 | 12 | st, err := readStringTable(strings.NewReader(in)) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | if !bytes.Equal([]byte(in), []byte(st)) { 18 | t.Error("String table doesn't match input") 19 | } 20 | 21 | testcases := []struct { 22 | offset uint32 23 | want string 24 | }{ 25 | {0, ""}, 26 | {1, "one"}, 27 | {5, "two"}, 28 | } 29 | 30 | for _, tc := range testcases { 31 | have, err := st.Lookup(tc.offset) 32 | if err != nil { 33 | t.Errorf("Offset %d: %s", tc.offset, err) 34 | continue 35 | } 36 | 37 | if have != tc.want { 38 | t.Errorf("Offset %d: want %s but have %s", tc.offset, tc.want, have) 39 | } 40 | } 41 | 42 | if _, err := st.Lookup(2); err == nil { 43 | t.Error("No error when using offset pointing into middle of string") 44 | } 45 | 46 | // Make sure we reject bogus tables 47 | _, err = readStringTable(strings.NewReader("\x00one")) 48 | if err == nil { 49 | t.Fatal("Accepted non-terminated string") 50 | } 51 | 52 | _, err = readStringTable(strings.NewReader("one\x00")) 53 | if err == nil { 54 | t.Fatal("Accepted non-empty first item") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/btf/testdata/Makefile: -------------------------------------------------------------------------------- 1 | # Usage: make KDIR=/path/to/foo 2 | 3 | vmlinux-btf.gz: $(KDIR)/vmlinux 4 | objcopy --dump-section .BTF=/dev/stdout "$<" | gzip > "$@" 5 | -------------------------------------------------------------------------------- /internal/btf/testdata/vmlinux-btf.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/internal/btf/testdata/vmlinux-btf.gz -------------------------------------------------------------------------------- /internal/btf/types_test.go: -------------------------------------------------------------------------------- 1 | package btf 2 | 3 | import "testing" 4 | 5 | import "fmt" 6 | 7 | func TestSizeof(t *testing.T) { 8 | testcases := []struct { 9 | size int 10 | typ Type 11 | }{ 12 | {1, &Int{Size: 1}}, 13 | {4, &Enum{}}, 14 | {0, &Array{Type: &Pointer{Target: Void{}}, Nelems: 0}}, 15 | {12, &Array{Type: &Enum{}, Nelems: 3}}, 16 | } 17 | 18 | for _, tc := range testcases { 19 | name := fmt.Sprint(tc.typ) 20 | t.Run(name, func(t *testing.T) { 21 | have, err := Sizeof(tc.typ) 22 | if err != nil { 23 | t.Fatal("Can't calculate size:", err) 24 | } 25 | if have != tc.size { 26 | t.Errorf("Expected size %d, got %d", tc.size, have) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func TestCopyType(t *testing.T) { 33 | _ = copyType(Void{}) 34 | 35 | in := &Int{Size: 4} 36 | out := copyType(in) 37 | 38 | in.Size = 8 39 | if size := out.(*Int).Size; size != 4 { 40 | t.Error("Copy doesn't make a copy, expected size 4, got", size) 41 | } 42 | 43 | t.Run("cyclical", func(t *testing.T) { 44 | ptr := &Pointer{} 45 | foo := &Struct{ 46 | Members: []Member{ 47 | {Type: ptr}, 48 | }, 49 | } 50 | ptr.Target = foo 51 | 52 | _ = copyType(foo) 53 | }) 54 | } 55 | 56 | // The following are valid Types. 57 | // 58 | // There currently is no better way to document which 59 | // types implement an interface. 60 | func ExampleType_validTypes() { 61 | var t Type 62 | t = &Void{} 63 | t = &Int{} 64 | t = &Pointer{} 65 | t = &Array{} 66 | t = &Struct{} 67 | t = &Union{} 68 | t = &Enum{} 69 | t = &Fwd{} 70 | t = &Typedef{} 71 | t = &Volatile{} 72 | t = &Const{} 73 | t = &Restrict{} 74 | t = &Func{} 75 | t = &FuncProto{} 76 | t = &Var{} 77 | t = &Datasec{} 78 | _ = t 79 | } 80 | -------------------------------------------------------------------------------- /internal/cpu.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | "sync" 8 | ) 9 | 10 | var sysCPU struct { 11 | once sync.Once 12 | err error 13 | num int 14 | } 15 | 16 | // PossibleCPUs returns the max number of CPUs a system may possibly have 17 | // Logical CPU numbers must be of the form 0-n 18 | func PossibleCPUs() (int, error) { 19 | sysCPU.once.Do(func() { 20 | sysCPU.num, sysCPU.err = parseCPUsFromFile("/sys/devices/system/cpu/possible") 21 | }) 22 | 23 | return sysCPU.num, sysCPU.err 24 | } 25 | 26 | func parseCPUsFromFile(path string) (int, error) { 27 | spec, err := ioutil.ReadFile(path) 28 | if err != nil { 29 | return 0, err 30 | } 31 | 32 | n, err := parseCPUs(string(spec)) 33 | if err != nil { 34 | return 0, fmt.Errorf("can't parse %s: %v", path, err) 35 | } 36 | 37 | return n, nil 38 | } 39 | 40 | // parseCPUs parses the number of cpus from a string produced 41 | // by bitmap_list_string() in the Linux kernel. 42 | // Multiple ranges are rejected, since they can't be unified 43 | // into a single number. 44 | // This is the format of /sys/devices/system/cpu/possible, it 45 | // is not suitable for /sys/devices/system/cpu/online, etc. 46 | func parseCPUs(spec string) (int, error) { 47 | if strings.Trim(spec, "\n") == "0" { 48 | return 1, nil 49 | } 50 | 51 | var low, high int 52 | n, err := fmt.Sscanf(spec, "%d-%d\n", &low, &high) 53 | if n != 2 || err != nil { 54 | return 0, fmt.Errorf("invalid format: %s", spec) 55 | } 56 | if low != 0 { 57 | return 0, fmt.Errorf("CPU spec doesn't start at zero: %s", spec) 58 | } 59 | 60 | // cpus is 0 indexed 61 | return high + 1, nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/cpu_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseCPUs(t *testing.T) { 8 | for str, result := range map[string]int{ 9 | "0-1": 2, 10 | "0-2\n": 3, 11 | "0": 1, 12 | } { 13 | n, err := parseCPUs(str) 14 | if err != nil { 15 | t.Errorf("Can't parse `%s`: %v", str, err) 16 | } else if n != result { 17 | t.Error("Parsing", str, "returns", n, "instead of", result) 18 | } 19 | } 20 | 21 | for _, str := range []string{ 22 | "0,3-4", 23 | "0-", 24 | "1,", 25 | "", 26 | } { 27 | _, err := parseCPUs(str) 28 | if err == nil { 29 | t.Error("Parsed invalid format:", str) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/endian.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/binary" 5 | "unsafe" 6 | ) 7 | 8 | // NativeEndian is set to either binary.BigEndian or binary.LittleEndian, 9 | // depending on the host's endianness. 10 | var NativeEndian binary.ByteOrder 11 | 12 | func init() { 13 | if isBigEndian() { 14 | NativeEndian = binary.BigEndian 15 | } else { 16 | NativeEndian = binary.LittleEndian 17 | } 18 | } 19 | 20 | func isBigEndian() (ret bool) { 21 | i := int(0x1) 22 | bs := (*[int(unsafe.Sizeof(i))]byte)(unsafe.Pointer(&i)) 23 | return bs[0] == 0 24 | } 25 | -------------------------------------------------------------------------------- /internal/errors.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/DataDog/ebpf/internal/unix" 10 | ) 11 | 12 | // ErrorWithLog returns an error that includes logs from the 13 | // kernel verifier. 14 | // 15 | // logErr should be the error returned by the syscall that generated 16 | // the log. It is used to check for truncation of the output. 17 | func ErrorWithLog(err error, log []byte, logErr error) error { 18 | logStr := strings.Trim(CString(log), "\t\r\n ") 19 | if errors.Is(logErr, unix.ENOSPC) { 20 | logStr += " (truncated...)" 21 | } 22 | 23 | return &VerifierError{err, logStr} 24 | } 25 | 26 | // VerifierError includes information from the eBPF verifier. 27 | type VerifierError struct { 28 | cause error 29 | log string 30 | } 31 | 32 | func (le *VerifierError) Error() string { 33 | if le.log == "" { 34 | return le.cause.Error() 35 | } 36 | 37 | return fmt.Sprintf("%s: %s", le.cause, le.log) 38 | } 39 | 40 | // CString turns a NUL / zero terminated byte buffer into a string. 41 | func CString(in []byte) string { 42 | inLen := bytes.IndexByte(in, 0) 43 | if inLen == -1 { 44 | return "" 45 | } 46 | return string(in[:inLen]) 47 | } 48 | -------------------------------------------------------------------------------- /internal/fd.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "runtime" 7 | "strconv" 8 | 9 | "github.com/DataDog/ebpf/internal/unix" 10 | ) 11 | 12 | var ErrClosedFd = errors.New("use of closed file descriptor") 13 | 14 | type FD struct { 15 | raw int64 16 | } 17 | 18 | func NewFD(value uint32) *FD { 19 | fd := &FD{int64(value)} 20 | runtime.SetFinalizer(fd, (*FD).Close) 21 | return fd 22 | } 23 | 24 | func (fd *FD) String() string { 25 | return strconv.FormatInt(fd.raw, 10) 26 | } 27 | 28 | func (fd *FD) Value() (uint32, error) { 29 | if fd.raw < 0 { 30 | return 0, ErrClosedFd 31 | } 32 | 33 | return uint32(fd.raw), nil 34 | } 35 | 36 | func (fd *FD) Close() error { 37 | if fd.raw < 0 { 38 | return nil 39 | } 40 | 41 | value := int(fd.raw) 42 | fd.raw = -1 43 | 44 | fd.Forget() 45 | return unix.Close(value) 46 | } 47 | 48 | func (fd *FD) Forget() { 49 | runtime.SetFinalizer(fd, nil) 50 | } 51 | 52 | func (fd *FD) Dup() (*FD, error) { 53 | if fd.raw < 0 { 54 | return nil, ErrClosedFd 55 | } 56 | 57 | dup, err := unix.FcntlInt(uintptr(fd.raw), unix.F_DUPFD_CLOEXEC, 0) 58 | if err != nil { 59 | return nil, fmt.Errorf("can't dup fd: %v", err) 60 | } 61 | 62 | return NewFD(uint32(dup)), nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/feature.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | 9 | "github.com/DataDog/ebpf/internal/unix" 10 | ) 11 | 12 | // ErrNotSupported indicates that a feature is not supported by the current kernel. 13 | var ErrNotSupported = errors.New("not supported") 14 | 15 | // UnsupportedFeatureError is returned by FeatureTest() functions. 16 | type UnsupportedFeatureError struct { 17 | // The minimum Linux mainline version required for this feature. 18 | // Used for the error string, and for sanity checking during testing. 19 | MinimumVersion Version 20 | 21 | // The name of the feature that isn't supported. 22 | Name string 23 | } 24 | 25 | func (ufe *UnsupportedFeatureError) Error() string { 26 | return fmt.Sprintf("%s not supported (requires >= %s)", ufe.Name, ufe.MinimumVersion) 27 | } 28 | 29 | // Is indicates that UnsupportedFeatureError is ErrNotSupported. 30 | func (ufe *UnsupportedFeatureError) Is(target error) bool { 31 | return target == ErrNotSupported 32 | } 33 | 34 | // FeatureTest wraps a function so that it is run at most once. 35 | // 36 | // name should identify the tested feature, while version must be in the 37 | // form Major.Minor[.Patch]. 38 | // 39 | // Returns a descriptive UnsupportedFeatureError if the feature is not available. 40 | func FeatureTest(name, version string, fn func() bool) func() error { 41 | v, err := NewVersion(version) 42 | if err != nil { 43 | return func() error { return err } 44 | } 45 | 46 | var ( 47 | once sync.Once 48 | result error 49 | ) 50 | 51 | return func() error { 52 | once.Do(func() { 53 | if !fn() { 54 | result = &UnsupportedFeatureError{ 55 | MinimumVersion: v, 56 | Name: name, 57 | } 58 | } 59 | }) 60 | return result 61 | } 62 | } 63 | 64 | // A Version in the form Major.Minor.Patch. 65 | type Version [3]uint16 66 | 67 | // NewVersion creates a version from a string like "Major.Minor.Patch". 68 | // 69 | // Patch is optional. 70 | func NewVersion(ver string) (Version, error) { 71 | var major, minor, patch uint16 72 | n, _ := fmt.Sscanf(ver, "%d.%d.%d", &major, &minor, &patch) 73 | if n < 2 { 74 | return Version{}, fmt.Errorf("invalid version: %s", ver) 75 | } 76 | return Version{major, minor, patch}, nil 77 | } 78 | 79 | func (v Version) String() string { 80 | if v[2] == 0 { 81 | return fmt.Sprintf("v%d.%d", v[0], v[1]) 82 | } 83 | return fmt.Sprintf("v%d.%d.%d", v[0], v[1], v[2]) 84 | } 85 | 86 | // Less returns true if the version is less than another version. 87 | func (v Version) Less(other Version) bool { 88 | for i, a := range v { 89 | if a == other[i] { 90 | continue 91 | } 92 | return a < other[i] 93 | } 94 | return false 95 | } 96 | 97 | var ( 98 | kernelVersionOnce sync.Once 99 | kernelVersion Version 100 | ) 101 | 102 | // MustKernelVersion() return the current kernel version 103 | func MustKernelVersion() Version { 104 | kernelVersionOnce.Do(func() { 105 | var uname unix.Utsname 106 | err := unix.Uname(&uname) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | end := bytes.IndexByte(uname.Release[:], 0) 112 | release := string(uname.Release[:end]) 113 | 114 | kernelVersion, err = NewVersion(release) 115 | if err != nil { 116 | panic(err) 117 | } 118 | }) 119 | 120 | return kernelVersion 121 | } 122 | -------------------------------------------------------------------------------- /internal/feature_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestFeatureTest(t *testing.T) { 10 | var called bool 11 | 12 | fn := FeatureTest("foo", "1.0", func() bool { 13 | called = true 14 | return true 15 | }) 16 | 17 | if called { 18 | t.Error("Function was called too early") 19 | } 20 | 21 | err := fn() 22 | if !called { 23 | t.Error("Function wasn't called") 24 | } 25 | 26 | if err != nil { 27 | t.Error("Unexpected negative result:", err) 28 | } 29 | 30 | fn = FeatureTest("bar", "2.1.1", func() bool { 31 | return false 32 | }) 33 | 34 | err = fn() 35 | if err == nil { 36 | t.Fatal("Unexpected positive result") 37 | } 38 | 39 | fte, ok := err.(*UnsupportedFeatureError) 40 | if !ok { 41 | t.Fatal("Result is not a *UnsupportedFeatureError") 42 | } 43 | 44 | if !strings.Contains(fte.Error(), "2.1.1") { 45 | t.Error("UnsupportedFeatureError.Error doesn't contain version") 46 | } 47 | 48 | if !errors.Is(err, ErrNotSupported) { 49 | t.Error("UnsupportedFeatureError is not ErrNotSupported") 50 | } 51 | } 52 | 53 | func TestVersion(t *testing.T) { 54 | a, err := NewVersion("1.2") 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | b, err := NewVersion("2.2.1") 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | if !a.Less(b) { 65 | t.Error("A should be less than B") 66 | } 67 | 68 | if b.Less(a) { 69 | t.Error("B shouldn't be less than A") 70 | } 71 | 72 | v200 := Version{2, 0, 0} 73 | if !a.Less(v200) { 74 | t.Error("1.2.1 should not be less than 2.0.0") 75 | } 76 | 77 | if v200.Less(a) { 78 | t.Error("2.0.0 should not be less than 1.2.1") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /internal/io.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "errors" 4 | 5 | // DiscardZeroes makes sure that all written bytes are zero 6 | // before discarding them. 7 | type DiscardZeroes struct{} 8 | 9 | func (DiscardZeroes) Write(p []byte) (int, error) { 10 | for _, b := range p { 11 | if b != 0 { 12 | return 0, errors.New("encountered non-zero byte") 13 | } 14 | } 15 | return len(p), nil 16 | } 17 | -------------------------------------------------------------------------------- /internal/io_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "testing" 7 | ) 8 | 9 | func TestDiscardZero(t *testing.T) { 10 | _, err := io.Copy(DiscardZeroes{}, bytes.NewReader([]byte{0, 0, 0})) 11 | if err != nil { 12 | t.Error("Returned an error even though input was zero:", err) 13 | } 14 | 15 | _, err = io.Copy(DiscardZeroes{}, bytes.NewReader([]byte{1})) 16 | if err == nil { 17 | t.Error("No error even though input is non-zero") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/ptr.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "unsafe" 4 | 5 | // NewPointer creates a 64-bit pointer from an unsafe Pointer. 6 | func NewPointer(ptr unsafe.Pointer) Pointer { 7 | return Pointer{ptr: ptr} 8 | } 9 | 10 | // NewSlicePointer creates a 64-bit pointer from a byte slice. 11 | func NewSlicePointer(buf []byte) Pointer { 12 | if len(buf) == 0 { 13 | return Pointer{} 14 | } 15 | 16 | return Pointer{ptr: unsafe.Pointer(&buf[0])} 17 | } 18 | 19 | // NewStringPointer creates a 64-bit pointer from a string. 20 | func NewStringPointer(str string) Pointer { 21 | if str == "" { 22 | return Pointer{} 23 | } 24 | 25 | // The kernel expects strings to be zero terminated 26 | buf := make([]byte, len(str)+1) 27 | copy(buf, str) 28 | 29 | return Pointer{ptr: unsafe.Pointer(&buf[0])} 30 | } 31 | -------------------------------------------------------------------------------- /internal/ptr_32_be.go: -------------------------------------------------------------------------------- 1 | // +build armbe mips mips64p32 2 | 3 | package internal 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // Pointer wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type Pointer struct { 12 | pad uint32 13 | ptr unsafe.Pointer 14 | } 15 | -------------------------------------------------------------------------------- /internal/ptr_32_le.go: -------------------------------------------------------------------------------- 1 | // +build 386 amd64p32 arm mipsle mips64p32le 2 | 3 | package internal 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // Pointer wraps an unsafe.Pointer to be 64bit to 10 | // conform to the syscall specification. 11 | type Pointer struct { 12 | ptr unsafe.Pointer 13 | pad uint32 14 | } 15 | -------------------------------------------------------------------------------- /internal/ptr_64.go: -------------------------------------------------------------------------------- 1 | // +build !386,!amd64p32,!arm,!mipsle,!mips64p32le 2 | // +build !armbe,!mips,!mips64p32 3 | 4 | package internal 5 | 6 | import ( 7 | "unsafe" 8 | ) 9 | 10 | // Pointer wraps an unsafe.Pointer to be 64bit to 11 | // conform to the syscall specification. 12 | type Pointer struct { 13 | ptr unsafe.Pointer 14 | } 15 | -------------------------------------------------------------------------------- /internal/syscall.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "runtime" 5 | "unsafe" 6 | 7 | "github.com/DataDog/ebpf/internal/unix" 8 | ) 9 | 10 | // BPF wraps SYS_BPF. 11 | // 12 | // Any pointers contained in attr must use the Pointer type from this package. 13 | func BPF(cmd int, attr unsafe.Pointer, size uintptr) (uintptr, error) { 14 | r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size) 15 | runtime.KeepAlive(attr) 16 | 17 | var err error 18 | if errNo != 0 { 19 | err = errNo 20 | } 21 | 22 | return r1, err 23 | } 24 | -------------------------------------------------------------------------------- /internal/testutils/feature.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/DataDog/ebpf/internal" 8 | ) 9 | 10 | func CheckFeatureTest(t *testing.T, fn func() error) { 11 | t.Helper() 12 | 13 | err := fn() 14 | if err == nil { 15 | return 16 | } 17 | 18 | ufe := err.(*internal.UnsupportedFeatureError) 19 | checkKernelVersion(t, ufe) 20 | } 21 | 22 | func SkipIfNotSupported(tb testing.TB, err error) { 23 | var ufe *internal.UnsupportedFeatureError 24 | if errors.As(err, &ufe) { 25 | checkKernelVersion(tb, ufe) 26 | tb.Skip(ufe.Error()) 27 | } 28 | } 29 | 30 | func checkKernelVersion(tb testing.TB, ufe *internal.UnsupportedFeatureError) { 31 | kernelVersion := internal.MustKernelVersion() 32 | if ufe.MinimumVersion.Less(kernelVersion) { 33 | tb.Helper() 34 | tb.Fatalf("Feature '%s' isn't supported even though kernel %s is newer than %s", 35 | ufe.Name, kernelVersion, ufe.MinimumVersion) 36 | } 37 | } 38 | 39 | func SkipOnOldKernel(tb testing.TB, minVersion, feature string) { 40 | tb.Helper() 41 | 42 | minv, err := internal.NewVersion(minVersion) 43 | if err != nil { 44 | tb.Fatalf("Invalid version %s: %s", minVersion, err) 45 | } 46 | 47 | if internal.MustKernelVersion().Less(minv) { 48 | tb.Skipf("Test requires at least kernel %s (due to missing %s)", minv, feature) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/testutils/glob.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | ) 7 | 8 | // TestFiles calls fn for each file matching pattern. 9 | // 10 | // The function errors out if the pattern matches no files. 11 | func TestFiles(t *testing.T, pattern string, fn func(*testing.T, string)) { 12 | t.Helper() 13 | 14 | files, err := filepath.Glob(pattern) 15 | if err != nil { 16 | t.Fatal("Can't glob files:", err) 17 | } 18 | 19 | if len(files) == 0 { 20 | t.Fatalf("Pattern %s matched no files", pattern) 21 | } 22 | 23 | for _, f := range files { 24 | file := f // force copy 25 | name := filepath.Base(file) 26 | t.Run(name, func(t *testing.T) { 27 | fn(t, file) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/unix/types_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package unix 4 | 5 | import ( 6 | "syscall" 7 | 8 | linux "golang.org/x/sys/unix" 9 | ) 10 | 11 | const ( 12 | ENOENT = linux.ENOENT 13 | EEXIST = linux.EEXIST 14 | EAGAIN = linux.EAGAIN 15 | ENOSPC = linux.ENOSPC 16 | EINVAL = linux.EINVAL 17 | EPOLLIN = linux.EPOLLIN 18 | EINTR = linux.EINTR 19 | ESRCH = linux.ESRCH 20 | ENODEV = linux.ENODEV 21 | BPF_F_RDONLY_PROG = linux.BPF_F_RDONLY_PROG 22 | BPF_F_WRONLY_PROG = linux.BPF_F_WRONLY_PROG 23 | BPF_OBJ_NAME_LEN = linux.BPF_OBJ_NAME_LEN 24 | BPF_TAG_SIZE = linux.BPF_TAG_SIZE 25 | SYS_BPF = linux.SYS_BPF 26 | F_DUPFD_CLOEXEC = linux.F_DUPFD_CLOEXEC 27 | EPOLL_CTL_ADD = linux.EPOLL_CTL_ADD 28 | EPOLL_CLOEXEC = linux.EPOLL_CLOEXEC 29 | O_CLOEXEC = linux.O_CLOEXEC 30 | O_NONBLOCK = linux.O_NONBLOCK 31 | PROT_READ = linux.PROT_READ 32 | PROT_WRITE = linux.PROT_WRITE 33 | MAP_SHARED = linux.MAP_SHARED 34 | PERF_TYPE_SOFTWARE = linux.PERF_TYPE_SOFTWARE 35 | PERF_COUNT_SW_BPF_OUTPUT = linux.PERF_COUNT_SW_BPF_OUTPUT 36 | PerfBitWatermark = linux.PerfBitWatermark 37 | PERF_SAMPLE_RAW = linux.PERF_SAMPLE_RAW 38 | PERF_FLAG_FD_CLOEXEC = linux.PERF_FLAG_FD_CLOEXEC 39 | RLIM_INFINITY = linux.RLIM_INFINITY 40 | RLIMIT_MEMLOCK = linux.RLIMIT_MEMLOCK 41 | ) 42 | 43 | // Statfs_t is a wrapper 44 | type Statfs_t = linux.Statfs_t 45 | 46 | // Rlimit is a wrapper 47 | type Rlimit = linux.Rlimit 48 | 49 | // Setrlimit is a wrapper 50 | func Setrlimit(resource int, rlim *Rlimit) (err error) { 51 | return linux.Setrlimit(resource, rlim) 52 | } 53 | 54 | // Syscall is a wrapper 55 | func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 56 | return linux.Syscall(trap, a1, a2, a3) 57 | } 58 | 59 | // FcntlInt is a wrapper 60 | func FcntlInt(fd uintptr, cmd, arg int) (int, error) { 61 | return linux.FcntlInt(fd, cmd, arg) 62 | } 63 | 64 | // Statfs is a wrapper 65 | func Statfs(path string, buf *Statfs_t) (err error) { 66 | return linux.Statfs(path, buf) 67 | } 68 | 69 | // Close is a wrapper 70 | func Close(fd int) (err error) { 71 | return linux.Close(fd) 72 | } 73 | 74 | // EpollEvent is a wrapper 75 | type EpollEvent = linux.EpollEvent 76 | 77 | // EpollWait is a wrapper 78 | func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { 79 | return linux.EpollWait(epfd, events, msec) 80 | } 81 | 82 | // EpollCtl is a wrapper 83 | func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) { 84 | return linux.EpollCtl(epfd, op, fd, event) 85 | } 86 | 87 | // Eventfd is a wrapper 88 | func Eventfd(initval uint, flags int) (fd int, err error) { 89 | return linux.Eventfd(initval, flags) 90 | } 91 | 92 | // Write is a wrapper 93 | func Write(fd int, p []byte) (n int, err error) { 94 | return linux.Write(fd, p) 95 | } 96 | 97 | // EpollCreate1 is a wrapper 98 | func EpollCreate1(flag int) (fd int, err error) { 99 | return linux.EpollCreate1(flag) 100 | } 101 | 102 | // PerfEventMmapPage is a wrapper 103 | type PerfEventMmapPage linux.PerfEventMmapPage 104 | 105 | // SetNonblock is a wrapper 106 | func SetNonblock(fd int, nonblocking bool) (err error) { 107 | return linux.SetNonblock(fd, nonblocking) 108 | } 109 | 110 | // Mmap is a wrapper 111 | func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { 112 | return linux.Mmap(fd, offset, length, prot, flags) 113 | } 114 | 115 | // Munmap is a wrapper 116 | func Munmap(b []byte) (err error) { 117 | return linux.Munmap(b) 118 | } 119 | 120 | // PerfEventAttr is a wrapper 121 | type PerfEventAttr = linux.PerfEventAttr 122 | 123 | // PerfEventOpen is a wrapper 124 | func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) { 125 | return linux.PerfEventOpen(attr, pid, cpu, groupFd, flags) 126 | } 127 | 128 | // Utsname is a wrapper 129 | type Utsname = linux.Utsname 130 | 131 | // Uname is a wrapper 132 | func Uname(buf *Utsname) (err error) { 133 | return linux.Uname(buf) 134 | } 135 | 136 | // Getpid is a wrapper 137 | func Getpid() int { 138 | return linux.Getpid() 139 | } 140 | 141 | // Gettid is a wrapper 142 | func Gettid() int { 143 | return linux.Gettid() 144 | } 145 | 146 | // Tgkill is a wrapper 147 | func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { 148 | return linux.Tgkill(tgid, tid, sig) 149 | } 150 | -------------------------------------------------------------------------------- /internal/unix/types_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package unix 4 | 5 | import ( 6 | "fmt" 7 | "runtime" 8 | "syscall" 9 | ) 10 | 11 | var errNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) 12 | 13 | const ( 14 | ENOENT = syscall.ENOENT 15 | EEXIST = syscall.EEXIST 16 | EAGAIN = syscall.EAGAIN 17 | ENOSPC = syscall.ENOSPC 18 | EINVAL = syscall.EINVAL 19 | EINTR = syscall.EINTR 20 | ESRCH = syscall.ESRCH 21 | ENODEV = syscall.ENODEV 22 | BPF_F_RDONLY_PROG = 0 23 | BPF_F_WRONLY_PROG = 0 24 | BPF_OBJ_NAME_LEN = 0x10 25 | BPF_TAG_SIZE = 0x8 26 | SYS_BPF = 321 27 | F_DUPFD_CLOEXEC = 0x406 28 | EPOLLIN = 0x1 29 | EPOLL_CTL_ADD = 0x1 30 | EPOLL_CLOEXEC = 0x80000 31 | O_CLOEXEC = 0x80000 32 | O_NONBLOCK = 0x800 33 | PROT_READ = 0x1 34 | PROT_WRITE = 0x2 35 | MAP_SHARED = 0x1 36 | PERF_TYPE_SOFTWARE = 0x1 37 | PERF_COUNT_SW_BPF_OUTPUT = 0xa 38 | PerfBitWatermark = 0x4000 39 | PERF_SAMPLE_RAW = 0x400 40 | PERF_FLAG_FD_CLOEXEC = 0x8 41 | RLIM_INFINITY = 0x7fffffffffffffff 42 | RLIMIT_MEMLOCK = 8 43 | ) 44 | 45 | // Statfs_t is a wrapper 46 | type Statfs_t struct { 47 | Type int64 48 | Bsize int64 49 | Blocks uint64 50 | Bfree uint64 51 | Bavail uint64 52 | Files uint64 53 | Ffree uint64 54 | Fsid [2]int32 55 | Namelen int64 56 | Frsize int64 57 | Flags int64 58 | Spare [4]int64 59 | } 60 | 61 | // Rlimit is a wrapper 62 | type Rlimit struct { 63 | Cur uint64 64 | Max uint64 65 | } 66 | 67 | // Setrlimit is a wrapper 68 | func Setrlimit(resource int, rlim *Rlimit) (err error) { 69 | return errNonLinux 70 | } 71 | 72 | // Syscall is a wrapper 73 | func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 74 | return 0, 0, syscall.Errno(1) 75 | } 76 | 77 | // FcntlInt is a wrapper 78 | func FcntlInt(fd uintptr, cmd, arg int) (int, error) { 79 | return -1, errNonLinux 80 | } 81 | 82 | // Statfs is a wrapper 83 | func Statfs(path string, buf *Statfs_t) error { 84 | return errNonLinux 85 | } 86 | 87 | // Close is a wrapper 88 | func Close(fd int) (err error) { 89 | return errNonLinux 90 | } 91 | 92 | // EpollEvent is a wrapper 93 | type EpollEvent struct { 94 | Events uint32 95 | Fd int32 96 | Pad int32 97 | } 98 | 99 | // EpollWait is a wrapper 100 | func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) { 101 | return 0, errNonLinux 102 | } 103 | 104 | // EpollCtl is a wrapper 105 | func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) { 106 | return errNonLinux 107 | } 108 | 109 | // Eventfd is a wrapper 110 | func Eventfd(initval uint, flags int) (fd int, err error) { 111 | return 0, errNonLinux 112 | } 113 | 114 | // Write is a wrapper 115 | func Write(fd int, p []byte) (n int, err error) { 116 | return 0, errNonLinux 117 | } 118 | 119 | // EpollCreate1 is a wrapper 120 | func EpollCreate1(flag int) (fd int, err error) { 121 | return 0, errNonLinux 122 | } 123 | 124 | // PerfEventMmapPage is a wrapper 125 | type PerfEventMmapPage struct { 126 | Version uint32 127 | Compat_version uint32 128 | Lock uint32 129 | Index uint32 130 | Offset int64 131 | Time_enabled uint64 132 | Time_running uint64 133 | Capabilities uint64 134 | Pmc_width uint16 135 | Time_shift uint16 136 | Time_mult uint32 137 | Time_offset uint64 138 | Time_zero uint64 139 | Size uint32 140 | 141 | Data_head uint64 142 | Data_tail uint64 143 | Data_offset uint64 144 | Data_size uint64 145 | Aux_head uint64 146 | Aux_tail uint64 147 | Aux_offset uint64 148 | Aux_size uint64 149 | } 150 | 151 | // SetNonblock is a wrapper 152 | func SetNonblock(fd int, nonblocking bool) (err error) { 153 | return errNonLinux 154 | } 155 | 156 | // Mmap is a wrapper 157 | func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { 158 | return []byte{}, errNonLinux 159 | } 160 | 161 | // Munmap is a wrapper 162 | func Munmap(b []byte) (err error) { 163 | return errNonLinux 164 | } 165 | 166 | // PerfEventAttr is a wrapper 167 | type PerfEventAttr struct { 168 | Type uint32 169 | Size uint32 170 | Config uint64 171 | Sample uint64 172 | Sample_type uint64 173 | Read_format uint64 174 | Bits uint64 175 | Wakeup uint32 176 | Bp_type uint32 177 | Ext1 uint64 178 | Ext2 uint64 179 | Branch_sample_type uint64 180 | Sample_regs_user uint64 181 | Sample_stack_user uint32 182 | Clockid int32 183 | Sample_regs_intr uint64 184 | Aux_watermark uint32 185 | Sample_max_stack uint16 186 | } 187 | 188 | // PerfEventOpen is a wrapper 189 | func PerfEventOpen(attr *PerfEventAttr, pid int, cpu int, groupFd int, flags int) (fd int, err error) { 190 | return 0, errNonLinux 191 | } 192 | 193 | // Utsname is a wrapper 194 | type Utsname struct { 195 | Release [65]byte 196 | } 197 | 198 | // Uname is a wrapper 199 | func Uname(buf *Utsname) (err error) { 200 | return errNonLinux 201 | } 202 | 203 | // Getpid is a wrapper 204 | func Getpid() int { 205 | return -1 206 | } 207 | 208 | // Gettid is a wrapper 209 | func Gettid() int { 210 | return -1 211 | } 212 | 213 | // Tgkill is a wrapper 214 | func Tgkill(tgid int, tid int, sig syscall.Signal) (err error) { 215 | return errNonLinux 216 | } 217 | -------------------------------------------------------------------------------- /kernel_version.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | // Copyright 2016-2017 Kinvolk 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package ebpf 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "regexp" 23 | "strconv" 24 | "strings" 25 | "syscall" 26 | ) 27 | 28 | var versionRegex = regexp.MustCompile(`^(\d+)\.(\d+)(?:.(\d+))?.*$`) 29 | 30 | // KernelVersionFromReleaseString converts a release string with format 31 | // 4.4.2[-1] to a kernel version number in LINUX_VERSION_CODE format. 32 | // That is, for kernel "a.b.c", the version number will be (a<<16 + b<<8 + c) 33 | func KernelVersionFromReleaseString(releaseString string) (uint32, error) { 34 | versionParts := versionRegex.FindStringSubmatch(releaseString) 35 | if len(versionParts) < 3 { 36 | return 0, fmt.Errorf("got invalid release version %q (expected format '4.3.2-1')", releaseString) 37 | } 38 | var major, minor, patch uint64 39 | var err error 40 | major, err = strconv.ParseUint(versionParts[1], 10, 8) 41 | if err != nil { 42 | return 0, err 43 | } 44 | 45 | minor, err = strconv.ParseUint(versionParts[2], 10, 8) 46 | if err != nil { 47 | return 0, err 48 | } 49 | 50 | // patch is optional 51 | if len(versionParts) >= 4 { 52 | patch, _ = strconv.ParseUint(versionParts[3], 10, 8) 53 | } 54 | 55 | // clamp patch/sublevel to 255 EARLY in 4.14.252 because they merged this too early: 56 | // https://github.com/torvalds/linux/commit/e131e0e880f942f138c4b5e6af944c7ddcd7ec96 57 | if major == 4 && minor == 14 && patch >= 252 { 58 | patch = 255 59 | } 60 | 61 | out := major*256*256 + minor*256 + patch 62 | return uint32(out), nil 63 | } 64 | 65 | func currentVersionUname() (uint32, error) { 66 | var buf syscall.Utsname 67 | if err := syscall.Uname(&buf); err != nil { 68 | return 0, err 69 | } 70 | releaseString := strings.Trim(utsnameStr(buf.Release[:]), "\x00") 71 | return KernelVersionFromReleaseString(releaseString) 72 | } 73 | 74 | func currentVersionUbuntu() (uint32, error) { 75 | procVersion, err := ioutil.ReadFile("/proc/version_signature") 76 | if err != nil { 77 | return 0, err 78 | } 79 | return parseUbuntuVersion(string(procVersion)) 80 | } 81 | 82 | func parseUbuntuVersion(procVersion string) (uint32, error) { 83 | var u1, u2, releaseString string 84 | _, err := fmt.Sscanf(procVersion, "%s %s %s", &u1, &u2, &releaseString) 85 | if err != nil { 86 | return 0, err 87 | } 88 | return KernelVersionFromReleaseString(releaseString) 89 | } 90 | 91 | var debianVersionRegex = regexp.MustCompile(`.* SMP Debian (\d+\.\d+.\d+-\d+)(?:\+[[:alnum:]]*)?.*`) 92 | 93 | func parseDebianVersion(str string) (uint32, error) { 94 | match := debianVersionRegex.FindStringSubmatch(str) 95 | if len(match) != 2 { 96 | return 0, fmt.Errorf("failed to parse kernel version from /proc/version: %s", str) 97 | } 98 | return KernelVersionFromReleaseString(match[1]) 99 | } 100 | 101 | func currentVersionDebian() (uint32, error) { 102 | procVersion, err := ioutil.ReadFile("/proc/version") 103 | if err != nil { 104 | return 0, fmt.Errorf("error reading /proc/version: %s", err) 105 | } 106 | 107 | return parseDebianVersion(string(procVersion)) 108 | } 109 | 110 | // CurrentKernelVersion returns the current kernel version in 111 | // LINUX_VERSION_CODE format (see KernelVersionFromReleaseString()) 112 | func CurrentKernelVersion() (uint32, error) { 113 | // We need extra checks for Debian and Ubuntu as they modify 114 | // the kernel version patch number for compatibilty with 115 | // out-of-tree modules. Linux perf tools do the same for Ubuntu 116 | // systems: https://github.com/torvalds/linux/commit/d18acd15c 117 | // 118 | // See also: 119 | // https://kernel-handbook.alioth.debian.org/ch-versions.html 120 | // https://wiki.ubuntu.com/Kernel/FAQ 121 | version, err := currentVersionUbuntu() 122 | if err == nil { 123 | return version, nil 124 | } 125 | version, err = currentVersionDebian() 126 | if err == nil { 127 | return version, nil 128 | } 129 | return currentVersionUname() 130 | } 131 | -------------------------------------------------------------------------------- /kernel_version_test.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | // Copyright 2017 Kinvolk 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package ebpf 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | var testData = []struct { 24 | succeed bool 25 | releaseString string 26 | kernelVersion uint32 27 | }{ 28 | {true, "4.1.2-3", 262402}, 29 | {true, "4.8.14-200.fc24.x86_64", 264206}, 30 | {true, "4.1.2-3foo", 262402}, 31 | {true, "4.1.2foo-1", 262402}, 32 | {true, "4.1.2-rkt-v1", 262402}, 33 | {true, "4.1.2rkt-v1", 262402}, 34 | {true, "4.1.2-3 foo", 262402}, 35 | {false, "foo 4.1.2-3", 0}, 36 | {true, "4.1.2", 262402}, 37 | {false, ".4.1.2", 0}, 38 | {false, "4", 0}, 39 | {false, "4.", 0}, 40 | {true, "4.1.", 262400}, 41 | {true, "4.1", 262400}, 42 | {true, "4.19-ovh", 267008}, 43 | {true, "4.14.252", 265983}, 44 | } 45 | 46 | func TestKernelVersionFromReleaseString(t *testing.T) { 47 | for _, test := range testData { 48 | version, err := KernelVersionFromReleaseString(test.releaseString) 49 | if err != nil && test.succeed { 50 | t.Errorf("expected %q to succeed: %s", test.releaseString, err) 51 | } else if err == nil && !test.succeed { 52 | t.Errorf("expected %q to fail", test.releaseString) 53 | } 54 | if version != test.kernelVersion { 55 | t.Errorf("expected kernel version %d, got %d", test.kernelVersion, version) 56 | } 57 | } 58 | } 59 | 60 | func TestParseDebianVersion(t *testing.T) { 61 | for _, tc := range []struct { 62 | succeed bool 63 | releaseString string 64 | kernelVersion uint32 65 | }{ 66 | // 4.9.168 67 | {true, "Linux version 4.9.0-9-amd64 (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.168-1+deb9u3 (2019-06-16)", 264616}, 68 | // 4.9.88 69 | {true, "Linux ip-10-0-75-49 4.9.0-6-amd64 #1 SMP Debian 4.9.88-1+deb9u1 (2018-05-07) x86_64 GNU/Linux", 264536}, 70 | // 3.0.4 71 | {true, "Linux version 3.16.0-9-amd64 (debian-kernel@lists.debian.org) (gcc version 4.9.2 (Debian 4.9.2-10+deb8u2) ) #1 SMP Debian 3.16.68-1 (2019-05-22)", 200772}, 72 | // Invalid 73 | {false, "Linux version 4.9.125-linuxkit (root@659b6d51c354) (gcc version 6.4.0 (Alpine 6.4.0) ) #1 SMP Fri Sep 7 08:20:28 UTC 2018", 0}, 74 | // 4.9.258-1 overflow of patch version which has max 255 75 | {true, "Linux version 4.9.0-15-amd64 (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.258-1 (2021-03-08)", 264703}, 76 | } { 77 | version, err := parseDebianVersion(tc.releaseString) 78 | if err != nil && tc.succeed { 79 | t.Errorf("expected %q to succeed: %s", tc.releaseString, err) 80 | } else if err == nil && !tc.succeed { 81 | t.Errorf("expected %q to fail", tc.releaseString) 82 | } 83 | if version != tc.kernelVersion { 84 | t.Errorf("expected kernel version %d, got %d", tc.kernelVersion, version) 85 | } 86 | } 87 | } 88 | 89 | func TestParseUbuntuVersion(t *testing.T) { 90 | for _, tc := range []struct { 91 | succeed bool 92 | procVersion string 93 | kernelVersion uint32 94 | }{ 95 | // 5.4.0-52.57 96 | {true, "Ubuntu 5.4.0-52.57-generic 5.4.65", 328769}, 97 | } { 98 | version, err := parseUbuntuVersion(tc.procVersion) 99 | if err != nil && tc.succeed { 100 | t.Errorf("expected %q to succeed: %s", tc.procVersion, err) 101 | } else if err == nil && !tc.succeed { 102 | t.Errorf("expected %q to fail", tc.procVersion) 103 | } 104 | if version != tc.kernelVersion { 105 | t.Errorf("expected kernel version %d, got %d", tc.kernelVersion, version) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /kernel_version_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package ebpf 4 | 5 | import ( 6 | "fmt" 7 | 8 | "runtime" 9 | ) 10 | 11 | var ErrNonLinux = fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH) 12 | 13 | func KernelVersionFromReleaseString(releaseString string) (uint32, error) { 14 | return 0, ErrNonLinux 15 | } 16 | 17 | func CurrentKernelVersion() (uint32, error) { 18 | return 0, ErrNonLinux 19 | } 20 | -------------------------------------------------------------------------------- /linker.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DataDog/ebpf/asm" 7 | "github.com/DataDog/ebpf/internal/btf" 8 | ) 9 | 10 | // link resolves bpf-to-bpf calls. 11 | // 12 | // Each library may contain multiple functions / labels, and is only linked 13 | // if prog references one of these functions. 14 | // 15 | // Libraries also linked. 16 | func link(prog *ProgramSpec, libs []*ProgramSpec) error { 17 | var ( 18 | linked = make(map[*ProgramSpec]bool) 19 | pending = []asm.Instructions{prog.Instructions} 20 | insns asm.Instructions 21 | ) 22 | for len(pending) > 0 { 23 | insns, pending = pending[0], pending[1:] 24 | for _, lib := range libs { 25 | if linked[lib] { 26 | continue 27 | } 28 | 29 | needed, err := needSection(insns, lib.Instructions) 30 | if err != nil { 31 | return fmt.Errorf("linking %s: %w", lib.Name, err) 32 | } 33 | 34 | if !needed { 35 | continue 36 | } 37 | 38 | linked[lib] = true 39 | prog.Instructions = append(prog.Instructions, lib.Instructions...) 40 | pending = append(pending, lib.Instructions) 41 | 42 | if prog.BTF != nil && lib.BTF != nil { 43 | if err := btf.ProgramAppend(prog.BTF, lib.BTF); err != nil { 44 | return fmt.Errorf("linking BTF of %s: %w", lib.Name, err) 45 | } 46 | } 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func needSection(insns, section asm.Instructions) (bool, error) { 54 | // A map of symbols to the libraries which contain them. 55 | symbols, err := section.SymbolOffsets() 56 | if err != nil { 57 | return false, err 58 | } 59 | 60 | for _, ins := range insns { 61 | if ins.Reference == "" { 62 | continue 63 | } 64 | 65 | if ins.OpCode.JumpOp() != asm.Call || ins.Src != asm.PseudoCall { 66 | continue 67 | } 68 | 69 | if ins.Constant != -1 { 70 | // This is already a valid call, no need to link again. 71 | continue 72 | } 73 | 74 | if _, ok := symbols[ins.Reference]; !ok { 75 | // Symbol isn't available in this section 76 | continue 77 | } 78 | 79 | // At this point we know that at least one function in the 80 | // library is called from insns, so we have to link it. 81 | return true, nil 82 | } 83 | 84 | // None of the functions in the section are called. 85 | return false, nil 86 | } 87 | -------------------------------------------------------------------------------- /linker_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DataDog/ebpf/asm" 7 | "github.com/DataDog/ebpf/internal/testutils" 8 | ) 9 | 10 | func TestLink(t *testing.T) { 11 | spec := &ProgramSpec{ 12 | Type: SocketFilter, 13 | Instructions: asm.Instructions{ 14 | // Make sure the call doesn't happen at instruction 0 15 | // to exercise the relative offset calculation. 16 | asm.Mov.Reg(asm.R0, asm.R1), 17 | asm.Call.Label("my_func"), 18 | asm.Return(), 19 | }, 20 | License: "MIT", 21 | } 22 | 23 | libs := []*ProgramSpec{ 24 | { 25 | Instructions: asm.Instructions{ 26 | asm.LoadImm(asm.R0, 1337, asm.DWord).Sym("my_other_func"), 27 | asm.Return(), 28 | }, 29 | }, 30 | { 31 | Instructions: asm.Instructions{ 32 | asm.Call.Label("my_other_func").Sym("my_func"), 33 | asm.Return(), 34 | }, 35 | }, 36 | } 37 | 38 | err := link(spec, libs) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | t.Log(spec.Instructions) 44 | 45 | testutils.SkipOnOldKernel(t, "4.16", "bpf2bpf calls") 46 | 47 | prog, err := NewProgram(spec) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | ret, _, err := prog.Test(make([]byte, 14)) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if ret != 1337 { 58 | t.Errorf("Expected return code 1337, got %d", ret) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /manager/editor.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/DataDog/ebpf/asm" 9 | ) 10 | 11 | // Editor modifies eBPF instructions. 12 | type Editor struct { 13 | instructions *asm.Instructions 14 | ReferenceOffsets map[string][]int 15 | } 16 | 17 | // Edit creates a new Editor. 18 | // 19 | // The editor retains a reference to insns and modifies its 20 | // contents. 21 | func Edit(insns *asm.Instructions) *Editor { 22 | refs := insns.ReferenceOffsets() 23 | return &Editor{insns, refs} 24 | } 25 | 26 | // RewriteConstant rewrites all loads of a symbol to a constant value. 27 | // 28 | // This is a way to parameterize clang-compiled eBPF byte code at load 29 | // time. 30 | // 31 | // The following macro should be used to access the constant: 32 | // 33 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 34 | // 35 | // int xdp() { 36 | // bool my_constant; 37 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 38 | // 39 | // if (my_constant) ... 40 | // 41 | // Caveats: 42 | // - The symbol name you pick must be unique 43 | // 44 | // - Failing to rewrite a symbol will not result in an error, 45 | // 0 will be loaded instead (subject to change) 46 | // 47 | // Use IsUnreferencedSymbol if you want to rewrite potentially 48 | // unused symbols. 49 | func (ed *Editor) RewriteConstant(symbol string, value uint64) error { 50 | indices := ed.ReferenceOffsets[symbol] 51 | if len(indices) == 0 { 52 | return &unreferencedSymbolError{symbol} 53 | } 54 | 55 | ldDWImm := asm.LoadImmOp(asm.DWord) 56 | for _, index := range indices { 57 | load := &(*ed.instructions)[index] 58 | if load.OpCode != ldDWImm { 59 | return errors.Errorf("symbol %v: load: found %v instead of %v", symbol, load.OpCode, ldDWImm) 60 | } 61 | 62 | load.Constant = int64(value) 63 | } 64 | return nil 65 | } 66 | 67 | type unreferencedSymbolError struct { 68 | symbol string 69 | } 70 | 71 | func (use *unreferencedSymbolError) Error() string { 72 | return fmt.Sprintf("unreferenced symbol %s", use.symbol) 73 | } 74 | 75 | // IsUnreferencedSymbol returns true if err was caused by 76 | // an unreferenced symbol. 77 | func IsUnreferencedSymbol(err error) bool { 78 | _, ok := err.(*unreferencedSymbolError) 79 | return ok 80 | } 81 | -------------------------------------------------------------------------------- /manager/editor_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DataDog/ebpf" 9 | "github.com/DataDog/ebpf/asm" 10 | ) 11 | 12 | // ExampleEditor_rewriteConstant shows how to change constants in 13 | // compiled eBPF byte code. 14 | // 15 | // The C should look something like this: 16 | // 17 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 18 | // 19 | // int xdp() { 20 | // bool my_constant; 21 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 22 | // 23 | // if (my_constant) ... 24 | func ExampleEditor_rewriteConstant() { 25 | // This assembly is roughly equivalent to what clang 26 | // would emit for the C above. 27 | insns := asm.Instructions{ 28 | asm.LoadImm(asm.R0, 0, asm.DWord), 29 | asm.Return(), 30 | } 31 | 32 | insns[0].Reference = "my_ret" 33 | 34 | editor := Edit(&insns) 35 | if err := editor.RewriteConstant("my_ret", 42); err != nil { 36 | panic(err) 37 | } 38 | 39 | fmt.Printf("%0.0s", insns) 40 | 41 | // Output: 0: LdImmDW dst: r0 imm: 42 42 | // 2: Exit 43 | } 44 | 45 | func TestEditorRewriteConstant(t *testing.T) { 46 | spec, err := ebpf.LoadCollectionSpec("testdata/rewrite.elf") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | progSpec := spec.Programs["socket"] 52 | editor := Edit(&progSpec.Instructions) 53 | 54 | if err := editor.RewriteConstant("constant", 0x01); err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | if err := editor.RewriteConstant("bogus", 0x01); !IsUnreferencedSymbol(err) { 59 | t.Error("Rewriting unreferenced symbol doesn't return appropriate error") 60 | } 61 | 62 | t.Log(progSpec.Instructions) 63 | 64 | prog, err := ebpf.NewProgram(progSpec) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | defer prog.Close() 69 | 70 | ret, _, err := prog.Test(make([]byte, 14)) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | const N = 1 // number of rewrites 76 | if expected := uint32(1< Stopping the program without cleanup, the pinned map and programs should show up in /sys/fs/bpf/") 68 | logrus.Println("=> Restart without --kill to load the pinned object from the bpf file system and properly remove them") 69 | return 70 | } 71 | 72 | // Close the manager 73 | if err := m.Stop(manager.CleanAll); err != nil { 74 | logrus.Fatal(err) 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /manager/examples/object_pinning/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets() io.ReaderAt { 14 | buf, err := Asset("/probe.o") 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Creates and then removes a tmp folder to trigger the probes 22 | func trigger() error { 23 | logrus.Println("Generating events to trigger the probes ...") 24 | // Creating a tmp directory to trigger the probes 25 | tmpDir := "/tmp/test_folder" 26 | logrus.Printf("creating %v", tmpDir) 27 | err := os.MkdirAll(tmpDir, 0666) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Removing a tmp directory to trigger the probes 33 | logrus.Printf("removing %v", tmpDir) 34 | return os.RemoveAll(tmpDir) 35 | } 36 | -------------------------------------------------------------------------------- /manager/examples/program_router/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/program_router/Makefile: -------------------------------------------------------------------------------- 1 | all: build-prog1 build-prog2 build run 2 | 3 | build-prog1: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/prog1.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe1.o 21 | 22 | build-prog2: 23 | mkdir -p ebpf/bin 24 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 25 | -Wno-unused-value \ 26 | -Wno-pointer-sign \ 27 | -Wno-compare-distinct-pointer-types \ 28 | -Wunused \ 29 | -Wall \ 30 | -Werror \ 31 | -I/lib/modules/$$(uname -r)/build/include \ 32 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 33 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 34 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 35 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 36 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 37 | -O2 -emit-llvm \ 38 | ebpf/prog2.c \ 39 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe2.o 40 | 41 | build: 42 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe1.o" "ebpf/bin/probe2.o" 43 | go build -o bin/main . 44 | 45 | run: 46 | sudo bin/main 47 | -------------------------------------------------------------------------------- /manager/examples/program_router/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/DataDog/ebpf/manager" 9 | ) 10 | 11 | func demoTailCall() error { 12 | logrus.Println("generating some traffic to show what happens when the tail call is not set up ...") 13 | trigger() 14 | time.Sleep(1 * time.Second) 15 | 16 | prog, _, err := m2.GetProgram(manager.ProbeIdentificationPair{Section: "classifier/three"}) 17 | if err != nil { 18 | logrus.Fatal(err) 19 | } 20 | 21 | // prepare tail call 22 | routes := []manager.TailCallRoute{ 23 | { 24 | ProgArrayName: "tc_prog_array", 25 | Key: uint32(1), 26 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 27 | Section: "classifier/two", 28 | }, 29 | }, 30 | { 31 | ProgArrayName: "tc_prog_array", 32 | Key: uint32(2), 33 | Program: prog[0], 34 | }, 35 | } 36 | 37 | // Map programs 38 | if err := m.UpdateTailCallRoutes(routes...); err != nil { 39 | return err 40 | } 41 | logrus.Println("generating some traffic to show what happens when the tail call is set up ...") 42 | trigger() 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /manager/examples/program_router/ebpf/include/bpf_map.h: -------------------------------------------------------------------------------- 1 | #define BUF_SIZE_MAP_NS 256 2 | 3 | typedef struct bpf_map_def { 4 | unsigned int type; 5 | unsigned int key_size; 6 | unsigned int value_size; 7 | unsigned int max_entries; 8 | unsigned int map_flags; 9 | unsigned int inner_map_idx; 10 | unsigned int pinning; 11 | char namespace[BUF_SIZE_MAP_NS]; 12 | } bpf_map_def; 13 | 14 | enum bpf_pin_type { 15 | PIN_NONE = 0, 16 | PIN_OBJECT_NS, 17 | PIN_GLOBAL_NS, 18 | PIN_CUSTOM_NS, 19 | }; 20 | -------------------------------------------------------------------------------- /manager/examples/program_router/ebpf/prog1.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_map.h" 3 | #include "include/bpf_helpers.h" 4 | 5 | #include 6 | 7 | #define TAIL_CALL_KEY 1 8 | #define EXTERNAL_TAIL_CALL_KEY 2 9 | 10 | struct bpf_map_def SEC("maps/tc_prog_array") tc_prog_array = { 11 | .type = BPF_MAP_TYPE_PROG_ARRAY, 12 | .key_size = 4, 13 | .value_size = 4, 14 | .max_entries = 3, 15 | }; 16 | 17 | SEC("classifier/one") 18 | int classifier_one(struct __sk_buff *skb) 19 | { 20 | bpf_printk("(classifier/one) new packet captured (TC)\n"); 21 | 22 | // Tail call 23 | int key = TAIL_CALL_KEY; 24 | bpf_tail_call(skb, &tc_prog_array, key); 25 | 26 | // Tail call failed 27 | bpf_printk("(classifier/one) couldn't tail call (TC)\n"); 28 | return TC_ACT_OK; 29 | }; 30 | 31 | SEC("classifier/two") 32 | int classifier_two(struct __sk_buff *skb) 33 | { 34 | bpf_printk("(classifier/two) tail call triggered (TC)\n"); 35 | 36 | // Tail call 37 | int key = EXTERNAL_TAIL_CALL_KEY; 38 | bpf_tail_call(skb, &tc_prog_array, key); 39 | 40 | // Tail call failed 41 | bpf_printk("(classifier/two) external tail call failed (TC)\n"); 42 | return TC_ACT_OK; 43 | }; 44 | 45 | char _license[] SEC("license") = "GPL"; 46 | __u32 _version SEC("version") = 0xFFFFFFFE; 47 | -------------------------------------------------------------------------------- /manager/examples/program_router/ebpf/prog2.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_map.h" 3 | #include "include/bpf_helpers.h" 4 | 5 | #include 6 | 7 | SEC("classifier/three") 8 | int classifier_three(struct __sk_buff *skb) 9 | { 10 | bpf_printk("(classifier/three) tail call triggered (TC)\n"); 11 | return TC_ACT_OK; 12 | }; 13 | 14 | char _license[] SEC("license") = "GPL"; 15 | __u32 _version SEC("version") = 0xFFFFFFFE; 16 | -------------------------------------------------------------------------------- /manager/examples/program_router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/DataDog/ebpf/manager" 7 | ) 8 | 9 | var m = &manager.Manager{ 10 | Probes: []*manager.Probe{ 11 | &manager.Probe{ 12 | Section: "classifier/one", 13 | Ifname: "enp0s3", // change this to the interface index connected to the internet 14 | NetworkDirection: manager.Egress, 15 | }, 16 | }, 17 | } 18 | 19 | var m2 = &manager.Manager{ 20 | Probes: []*manager.Probe{}, 21 | } 22 | 23 | func main() { 24 | // Initialize the manager 25 | if err := m.Init(recoverAssets("/probe1.o")); err != nil { 26 | logrus.Fatal(err) 27 | } 28 | if err := m2.Init(recoverAssets("/probe2.o")); err != nil { 29 | logrus.Fatal(err) 30 | } 31 | 32 | // Start the manager 33 | if err := m.Start(); err != nil { 34 | logrus.Fatal(err) 35 | } 36 | if err := m2.Start(); err != nil { 37 | logrus.Fatal(err) 38 | } 39 | 40 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 41 | 42 | if err := demoTailCall(); err != nil { 43 | logrus.Error(err) 44 | } 45 | 46 | // Close the manager 47 | if err := m.Stop(manager.CleanAll); err != nil { 48 | logrus.Fatal(err) 49 | } 50 | if err := m2.Stop(manager.CleanAll); err != nil { 51 | logrus.Fatal(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /manager/examples/program_router/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets(probe string) io.ReaderAt { 14 | buf, err := Asset(probe) 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Generate some network traffic to trigger the probe 22 | func trigger() { 23 | _, _ = http.Get("https://www.google.com/") 24 | } 25 | -------------------------------------------------------------------------------- /manager/examples/programs/cgroup/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/cgroup/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/cgroup/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("cgroup_skb/egress") 5 | int cgroup_egress_func(struct __sk_buff *skb) 6 | { 7 | bpf_printk("new packet captured on cgroup egress\n"); 8 | return 1; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | __u32 _version SEC("version") = 0xFFFFFFFE; 13 | -------------------------------------------------------------------------------- /manager/examples/programs/cgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/DataDog/ebpf/manager" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | var m = &manager.Manager{ 9 | Probes: []*manager.Probe{ 10 | &manager.Probe{ 11 | Section: "cgroup_skb/egress", 12 | CGroupPath: "/sys/fs/cgroup/unified", 13 | }, 14 | }, 15 | } 16 | 17 | func main() { 18 | // Initialize the manager 19 | if err := m.Init(recoverAssets()); err != nil { 20 | logrus.Fatal(err) 21 | } 22 | 23 | // Start the manager 24 | if err := m.Start(); err != nil { 25 | logrus.Fatal(err) 26 | } 27 | 28 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 29 | 30 | // Generate some network traffic to trigger the probe 31 | trigger() 32 | 33 | // Close the manager 34 | if err := m.Stop(manager.CleanAll); err != nil { 35 | logrus.Fatal(err) 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /manager/examples/programs/cgroup/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets() io.ReaderAt { 14 | buf, err := Asset("/probe.o") 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Generate some network traffic to trigger the probe 22 | func trigger() { 23 | logrus.Println("Generating some network traffic to trigger the probes ...") 24 | _, _ = http.Get("https://www.google.com/") 25 | } 26 | -------------------------------------------------------------------------------- /manager/examples/programs/kprobe/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/kprobe/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/kprobe/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("kprobe/vfs_mkdir") 5 | int kprobe_vfs_mkdir(void *ctx) 6 | { 7 | bpf_printk("mkdir (vfs hook point)\n"); 8 | return 0; 9 | }; 10 | 11 | SEC("kprobe/utimes_common") 12 | int kprobe_utimes_common(void *ctx) 13 | { 14 | bpf_printk("utimes_common\n"); 15 | return 0; 16 | }; 17 | 18 | SEC("kretprobe/mkdirat") 19 | int kretpobe_unlinkat(void *ctx) 20 | { 21 | bpf_printk("mkdirat return (syscall hook point)\n"); 22 | return 0; 23 | } 24 | 25 | char _license[] SEC("license") = "GPL"; 26 | __u32 _version SEC("version") = 0xFFFFFFFE; 27 | -------------------------------------------------------------------------------- /manager/examples/programs/kprobe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/DataDog/ebpf/manager" 6 | "github.com/sirupsen/logrus" 7 | "os" 8 | "os/signal" 9 | "time" 10 | ) 11 | 12 | var m = &manager.Manager{ 13 | Probes: []*manager.Probe{ 14 | &manager.Probe{ 15 | UID: "MyVFSMkdir", 16 | Section: "kprobe/vfs_mkdir", 17 | }, 18 | &manager.Probe{ 19 | UID: "UtimesCommon", 20 | Section: "kprobe/utimes_common", 21 | MatchFuncName: "utimes_common", 22 | }, 23 | &manager.Probe{ 24 | UID: "", // UID is needed only if there are multiple instances of your program (after using 25 | // m.CloneProgram for example), or if multiple programs with the exact same section are attaching 26 | // at the exact same hook point (using m.AddHook for example, or simply because another manager 27 | // on the system is planning on hooking there). 28 | Section: "kretprobe/mkdirat", 29 | SyscallFuncName: "mkdirat", 30 | KProbeMaxActive: 100, 31 | }, 32 | }, 33 | } 34 | 35 | func main() { 36 | options := manager.Options{ 37 | DefaultProbeRetry: 2, 38 | DefaultProbeRetryDelay: time.Second, 39 | } 40 | 41 | // Initialize the manager 42 | if err := m.InitWithOptions(recoverAssets(), options); err != nil { 43 | logrus.Fatal(err) 44 | } 45 | 46 | // Start the manager 47 | if err := m.Start(); err != nil { 48 | logrus.Fatal(err) 49 | } 50 | 51 | logrus.Println("successfully started") 52 | logrus.Println("=> head over to /sys/kernel/debug/tracing/trace_pipe") 53 | logrus.Println("=> checkout /sys/kernel/debug/tracing/kprobe_events, utimes_common might have become utimes_common.isra.0") 54 | logrus.Println("=> Cmd+C to exit") 55 | 56 | // Create a folder to trigger the probes 57 | if err := trigger(); err != nil { 58 | logrus.Error(err) 59 | } 60 | 61 | wait() 62 | 63 | // Close the manager 64 | if err := m.Stop(manager.CleanAll); err != nil { 65 | logrus.Fatal(err) 66 | } 67 | } 68 | 69 | // wait - Waits until an interrupt or kill signal is sent 70 | func wait() { 71 | sig := make(chan os.Signal, 1) 72 | signal.Notify(sig, os.Interrupt, os.Kill) 73 | <-sig 74 | fmt.Println() 75 | } 76 | -------------------------------------------------------------------------------- /manager/examples/programs/kprobe/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets() io.ReaderAt { 14 | buf, err := Asset("/probe.o") 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Creates and then removes a tmp folder to trigger the probes 22 | func trigger() error { 23 | logrus.Println("Generating events to trigger the probes ...") 24 | // Creating a tmp directory to trigger the probes 25 | tmpDir := "/tmp/test_folder" 26 | logrus.Printf("creating %v", tmpDir) 27 | err := os.MkdirAll(tmpDir, 0666) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Removing a tmp directory to trigger the probes 33 | logrus.Printf("removing %v", tmpDir) 34 | return os.RemoveAll(tmpDir) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /manager/examples/programs/socket/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/socket/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/socket/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("socket/sock_filter") 5 | int socket_sock_filter(void *ctx) 6 | { 7 | bpf_printk("new packet received\n"); 8 | return 0; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | __u32 _version SEC("version") = 0xFFFFFFFE; 13 | -------------------------------------------------------------------------------- /manager/examples/programs/socket/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/DataDog/ebpf/manager" 7 | ) 8 | 9 | var m = &manager.Manager{ 10 | Probes: []*manager.Probe{ 11 | &manager.Probe{ 12 | Section: "socket/sock_filter", 13 | }, 14 | }, 15 | } 16 | 17 | func main() { 18 | // Create a socket pair that will be used to trigger the socket filter 19 | sockPair, err := newSocketPair() 20 | if err != nil { 21 | logrus.Fatal(err) 22 | } 23 | 24 | // Set the socket file descriptor on which the socket filter should trigger 25 | m.Probes[0].SocketFD = sockPair[0] 26 | 27 | // Initialize the manager 28 | if err := m.Init(recoverAssets()); err != nil { 29 | logrus.Fatal(err) 30 | } 31 | 32 | // Start the manager 33 | if err := m.Start(); err != nil { 34 | logrus.Fatal(err) 35 | } 36 | 37 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 38 | 39 | // Send a message through the socket pair to trigger the probe 40 | if err := trigger(sockPair); err != nil { 41 | logrus.Error(err) 42 | } 43 | 44 | // Close the manager 45 | if err := m.Stop(manager.CleanAll); err != nil { 46 | logrus.Fatal(err) 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /manager/examples/programs/socket/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "syscall" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets() io.ReaderAt { 14 | buf, err := Asset("/probe.o") 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Send a message through the socket pair to trigger the probe 22 | func trigger(sockPair SocketPair) error { 23 | logrus.Println("Sending a message through the socket pair to trigger the probes ...") 24 | _, err := syscall.Write(sockPair[1], nil) 25 | if err != nil { 26 | return err 27 | } 28 | _, err = syscall.Read(sockPair[0], nil) 29 | return err 30 | } 31 | 32 | type SocketPair [2]int 33 | 34 | func (p SocketPair) Close() error { 35 | err1 := syscall.Close(p[0]) 36 | err2 := syscall.Close(p[1]) 37 | 38 | if err1 != nil { 39 | return err1 40 | } 41 | return err2 42 | } 43 | 44 | // newSocketPair - Create a socket pair 45 | func newSocketPair() (SocketPair, error) { 46 | return syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) 47 | } 48 | -------------------------------------------------------------------------------- /manager/examples/programs/tc/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/tc/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/tc/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | #include 5 | 6 | SEC("classifier/egress") 7 | int egress_cls_func(struct __sk_buff *skb) 8 | { 9 | bpf_printk("new packet captured on egress (TC)\n"); 10 | return TC_ACT_OK; 11 | }; 12 | 13 | SEC("classifier/ingress") 14 | int ingress_cls_func(struct __sk_buff *skb) 15 | { 16 | bpf_printk("new packet captured on ingress (TC)\n"); 17 | return TC_ACT_OK; 18 | }; 19 | 20 | char _license[] SEC("license") = "GPL"; 21 | __u32 _version SEC("version") = 0xFFFFFFFE; 22 | -------------------------------------------------------------------------------- /manager/examples/programs/tc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/DataDog/ebpf/manager" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | var m = &manager.Manager{ 9 | Probes: []*manager.Probe{ 10 | &manager.Probe{ 11 | Section: "classifier/egress", 12 | Ifname: "enp0s3", // change this to the interface connected to the internet 13 | NetworkDirection: manager.Egress, 14 | }, 15 | &manager.Probe{ 16 | Section: "classifier/ingress", 17 | Ifname: "enp0s3", // change this to the interface connected to the internet 18 | NetworkDirection: manager.Ingress, 19 | }, 20 | }, 21 | } 22 | 23 | func main() { 24 | // Initialize the manager 25 | if err := m.Init(recoverAssets()); err != nil { 26 | logrus.Fatal(err) 27 | } 28 | 29 | // Start the manager 30 | if err := m.Start(); err != nil { 31 | logrus.Fatal(err) 32 | } 33 | 34 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 35 | 36 | // Generate some network traffic to trigger the probe 37 | trigger() 38 | 39 | // Close the manager 40 | if err := m.Stop(manager.CleanAll); err != nil { 41 | logrus.Fatal(err) 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /manager/examples/programs/tc/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/pkg/errors" 6 | "github.com/sirupsen/logrus" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | // recoverAssets - Recover ebpf asset 12 | func recoverAssets() io.ReaderAt { 13 | buf, err := Asset("/probe.o") 14 | if err != nil { 15 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 16 | } 17 | return bytes.NewReader(buf) 18 | } 19 | 20 | // trigger - Generate some network traffic to trigger the probe 21 | func trigger() { 22 | logrus.Println("Generating some network traffic to trigger the probes ...") 23 | _, _ = http.Get("https://www.google.com/") 24 | } 25 | -------------------------------------------------------------------------------- /manager/examples/programs/tracepoint/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/tracepoint/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/tracepoint/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("tracepoint/syscalls/sys_enter_mkdirat") 5 | int tracepoint_sys_enter_mkdirat(void *ctx) 6 | { 7 | bpf_printk("mkdirat enter (tracepoint)\n"); 8 | return 0; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | __u32 _version SEC("version") = 0xFFFFFFFE; 13 | -------------------------------------------------------------------------------- /manager/examples/programs/tracepoint/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/DataDog/ebpf/manager" 7 | ) 8 | 9 | var m = &manager.Manager{ 10 | Probes: []*manager.Probe{ 11 | &manager.Probe{ 12 | Section: "tracepoint/syscalls/sys_enter_mkdirat", 13 | }, 14 | }, 15 | } 16 | 17 | func main() { 18 | // Initialize the manager 19 | if err := m.Init(recoverAssets()); err != nil { 20 | logrus.Fatal(err) 21 | } 22 | 23 | // Start the manager 24 | if err := m.Start(); err != nil { 25 | logrus.Fatal(err) 26 | } 27 | 28 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 29 | 30 | // Create a folder to trigger the probes 31 | if err := trigger(); err != nil { 32 | logrus.Error(err) 33 | } 34 | 35 | // Close the manager 36 | if err := m.Stop(manager.CleanAll); err != nil { 37 | logrus.Fatal(err) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /manager/examples/programs/tracepoint/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // recoverAssets - Recover ebpf asset 13 | func recoverAssets() io.ReaderAt { 14 | buf, err := Asset("/probe.o") 15 | if err != nil { 16 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 17 | } 18 | return bytes.NewReader(buf) 19 | } 20 | 21 | // trigger - Creates and then removes a tmp folder to trigger the probes 22 | func trigger() error { 23 | logrus.Println("Generating events to trigger the probes ...") 24 | // Creating a tmp directory to trigger the probes 25 | tmpDir := "/tmp/test_folder" 26 | logrus.Printf("creating %v", tmpDir) 27 | err := os.MkdirAll(tmpDir, 0666) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Removing a tmp directory to trigger the probes 33 | logrus.Printf("removing %v", tmpDir) 34 | return os.RemoveAll(tmpDir) 35 | } 36 | 37 | -------------------------------------------------------------------------------- /manager/examples/programs/uprobe/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/uprobe/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/uprobe/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("uprobe/readline") 5 | int uprobe_readline(void *ctx) 6 | { 7 | bpf_printk("new bash command detected\n"); 8 | return 0; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | __u32 _version SEC("version") = 0xFFFFFFFE; 13 | -------------------------------------------------------------------------------- /manager/examples/programs/uprobe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/DataDog/ebpf/manager" 7 | ) 8 | 9 | var m = &manager.Manager{ 10 | Probes: []*manager.Probe{ 11 | &manager.Probe{ 12 | Section: "uprobe/readline", 13 | BinaryPath: "/usr/bin/bash", 14 | }, 15 | }, 16 | } 17 | 18 | func main() { 19 | // Initialize the manager 20 | if err := m.Init(recoverAssets()); err != nil { 21 | logrus.Fatal(err) 22 | } 23 | 24 | // Start the manager 25 | if err := m.Start(); err != nil { 26 | logrus.Fatal(err) 27 | } 28 | 29 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 30 | 31 | // Spawn a bash and right a command to trigger the probe 32 | if err := trigger(); err != nil { 33 | logrus.Error(err) 34 | } 35 | 36 | // Close the manager 37 | if err := m.Stop(manager.CleanAll); err != nil { 38 | logrus.Fatal(err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /manager/examples/programs/uprobe/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os/exec" 7 | "time" 8 | 9 | "github.com/pkg/errors" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // recoverAssets - Recover ebpf asset 14 | func recoverAssets() io.ReaderAt { 15 | buf, err := Asset("/probe.o") 16 | if err != nil { 17 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 18 | } 19 | return bytes.NewReader(buf) 20 | } 21 | 22 | // trigger - Spawn a bash and execute a command to trigger the probe 23 | func trigger() error { 24 | logrus.Println("Spawning a shell and executing `id` to trigger the probe ...") 25 | cmd := exec.Command("/usr/bin/bash", "-i") 26 | stdinPipe, _ := cmd.StdinPipe() 27 | go func() { 28 | io.WriteString(stdinPipe, "id") 29 | time.Sleep(100*time.Millisecond) 30 | stdinPipe.Close() 31 | }() 32 | b, err := cmd.Output() 33 | if err != nil { 34 | return err 35 | } 36 | logrus.Printf("from bash: %v", string(b)) 37 | return nil 38 | } 39 | 40 | -------------------------------------------------------------------------------- /manager/examples/programs/xdp/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/programs/xdp/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/programs/xdp/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_helpers.h" 3 | 4 | SEC("xdp/ingress") 5 | int egress_cls_func(struct __sk_buff *skb) 6 | { 7 | bpf_printk("new packet captured (XDP)\n"); 8 | return XDP_PASS; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | __u32 _version SEC("version") = 0xFFFFFFFE; 13 | -------------------------------------------------------------------------------- /manager/examples/programs/xdp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | 6 | "github.com/DataDog/ebpf/manager" 7 | ) 8 | 9 | var m = &manager.Manager{ 10 | Probes: []*manager.Probe{ 11 | &manager.Probe{ 12 | Section: "xdp/ingress", 13 | Ifindex: 2, // change this to the interface index connected to the internet 14 | XDPAttachMode: manager.XdpAttachModeSkb, 15 | }, 16 | }, 17 | } 18 | 19 | func main() { 20 | // Initialize the manager 21 | if err := m.Init(recoverAssets()); err != nil { 22 | logrus.Fatal(err) 23 | } 24 | 25 | // Start the manager 26 | if err := m.Start(); err != nil { 27 | logrus.Fatal(err) 28 | } 29 | 30 | logrus.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 31 | 32 | // Generate some network traffic to trigger the probe 33 | trigger() 34 | 35 | // Close the manager 36 | if err := m.Stop(manager.CleanAll); err != nil { 37 | logrus.Fatal(err) 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /manager/examples/programs/xdp/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/pkg/errors" 6 | "github.com/sirupsen/logrus" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | // recoverAssets - Recover ebpf asset 12 | func recoverAssets() io.ReaderAt { 13 | buf, err := Asset("/probe.o") 14 | if err != nil { 15 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 16 | } 17 | return bytes.NewReader(buf) 18 | } 19 | 20 | // trigger - Generate some network traffic to trigger the probe 21 | func trigger() { 22 | logrus.Println("Generating some network traffic to trigger the probes ...") 23 | _, _ = http.Get("https://www.google.com/") 24 | } 25 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | ebpf/bin/ 3 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go-bindata -pkg main -prefix "ebpf/bin" -o "probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | go build -o bin/main . 25 | 26 | run: 27 | sudo bin/main 28 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/ebpf/include/bpf_map.h: -------------------------------------------------------------------------------- 1 | #define BUF_SIZE_MAP_NS 256 2 | 3 | typedef struct bpf_map_def { 4 | unsigned int type; 5 | unsigned int key_size; 6 | unsigned int value_size; 7 | unsigned int max_entries; 8 | unsigned int map_flags; 9 | unsigned int inner_map_idx; 10 | unsigned int pinning; 11 | char namespace[BUF_SIZE_MAP_NS]; 12 | } bpf_map_def; 13 | 14 | enum bpf_pin_type { 15 | PIN_NONE = 0, 16 | PIN_OBJECT_NS, 17 | PIN_GLOBAL_NS, 18 | PIN_CUSTOM_NS, 19 | }; 20 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "include/bpf.h" 2 | #include "include/bpf_map.h" 3 | #include "include/bpf_helpers.h" 4 | 5 | __attribute__((always_inline)) static int my_func(u32 input) 6 | { 7 | return 2*input; 8 | } 9 | 10 | #define TEST_DATA_KEY 1 11 | 12 | struct my_func_test_data_t { 13 | u32 input; 14 | u32 output; 15 | }; 16 | 17 | struct bpf_map_def SEC("maps/my_func_test_data") my_func_test_data = { 18 | .type = BPF_MAP_TYPE_ARRAY, 19 | .key_size = sizeof(u32), 20 | .value_size = sizeof(struct my_func_test_data_t), 21 | .max_entries = 2, 22 | }; 23 | 24 | SEC("xdp/my_func_test") 25 | int my_func_test(struct __sk_buff *skb) 26 | { 27 | // Retrieve test data 28 | u32 key = TEST_DATA_KEY; 29 | struct my_func_test_data_t *data = bpf_map_lookup_elem(&my_func_test_data, &key); 30 | if (data == NULL) { 31 | bpf_printk("no test data\n"); 32 | return -1; 33 | } 34 | u32 ret = my_func(data->input); 35 | if (ret != data->output) { 36 | bpf_printk("expected %d for input %d, got %d\n", data->output, data->input, ret); 37 | return -1; 38 | } 39 | return 0; 40 | }; 41 | 42 | char _license[] SEC("license") = "GPL"; 43 | __u32 _version SEC("version") = 0xFFFFFFFE; 44 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/DataDog/ebpf" 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/DataDog/ebpf/manager" 9 | ) 10 | 11 | type TestData struct { 12 | Input uint32 13 | Output uint32 14 | } 15 | 16 | func (td TestData) String() string { 17 | return fmt.Sprintf("{ Input:%v Output:%v }", td.Input, td.Output) 18 | } 19 | 20 | var testDataKey = uint32(1) 21 | 22 | var testData = []TestData{ 23 | {2, 4}, 24 | {10, 20}, 25 | {42, 128}, 26 | {42, 84}, 27 | } 28 | 29 | func main() { 30 | // Initialize the manager 31 | var m = &manager.Manager{} 32 | if err := m.Init(recoverAssets()); err != nil { 33 | logrus.Fatal(err) 34 | } 35 | 36 | // Get map used to send tests 37 | testMap, found, err := m.GetMap("my_func_test_data") 38 | if !found || err != nil { 39 | logrus.Fatalf("couldn't retrieve my_func_test_data %v", err) 40 | } 41 | 42 | // Get xdp program used to trigger the tests 43 | testProgs, found, err := m.GetProgram( 44 | manager.ProbeIdentificationPair{ 45 | Section: "xdp/my_func_test", 46 | }, 47 | ) 48 | if !found || err != nil { 49 | logrus.Fatalf("couldn't retrieve my_func_test %v", err) 50 | } 51 | testProg := testProgs[0] 52 | 53 | // Run test 54 | runtTest(testMap, testProg) 55 | 56 | // Run benchmark 57 | runtBenchmark(testMap, testProg) 58 | } 59 | 60 | func runtTest(testMap *ebpf.Map, testProg *ebpf.Program) { 61 | logrus.Println("Running tests ...") 62 | for _, data := range testData { 63 | // insert data 64 | testMap.Put(testDataKey, data) 65 | 66 | // Trigger test - (the 14 bytes is for the minimum packet size required to test an XDP program) 67 | outLen, _, err := testProg.Test(make([]byte, 14)) 68 | if err != nil { 69 | logrus.Fatal(err) 70 | } 71 | if outLen == 0 { 72 | logrus.Printf("%v - PASS", data) 73 | } else { 74 | logrus.Printf("%v - FAIL (checkout /sys/kernel/debug/tracing/trace_pipe to see the logs)", data) 75 | } 76 | } 77 | } 78 | 79 | func runtBenchmark(testMap *ebpf.Map, testProg *ebpf.Program) { 80 | logrus.Println("Running benchmark ...") 81 | for _, data := range testData { 82 | // insert data 83 | testMap.Put(testDataKey, data) 84 | 85 | // Trigger test 86 | outLen, duration, err := testProg.Benchmark(make([]byte, 14), 1000, nil) 87 | if err != nil { 88 | logrus.Fatal(err) 89 | } 90 | if outLen == 0 { 91 | logrus.Printf("%v - PASS (duration: %v)", data, duration) 92 | } else { 93 | logrus.Printf("%v - benchmark FAILED (checkout /sys/kernel/debug/tracing/trace_pipe to see the logs)", data) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /manager/examples/tests_and_benchmarks/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/pkg/errors" 6 | "github.com/sirupsen/logrus" 7 | "io" 8 | ) 9 | 10 | // recoverAssets - Recover ebpf asset 11 | func recoverAssets() io.ReaderAt { 12 | buf, err := Asset("/probe.o") 13 | if err != nil { 14 | logrus.Fatal(errors.Wrap(err, "couldn't find asset")) 15 | } 16 | return bytes.NewReader(buf) 17 | } 18 | -------------------------------------------------------------------------------- /manager/selectors.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // OneOf - This selector is used to ensure that at least of a list of probe selectors is valid. In other words, this 11 | // can be used to ensure that at least one of a list of optional probes is activated. 12 | type OneOf struct { 13 | Selectors []ProbesSelector 14 | } 15 | 16 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 17 | func (oo *OneOf) GetProbesIdentificationPairList() []ProbeIdentificationPair { 18 | var l []ProbeIdentificationPair 19 | for _, selector := range oo.Selectors { 20 | l = append(l, selector.GetProbesIdentificationPairList()...) 21 | } 22 | return l 23 | } 24 | 25 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 26 | // For example, see OneOf. 27 | func (oo *OneOf) RunValidator(manager *Manager) error { 28 | var errs []string 29 | for _, selector := range oo.Selectors { 30 | if err := selector.RunValidator(manager); err != nil { 31 | errs = append(errs, err.Error()) 32 | } 33 | } 34 | if len(errs) == len(oo.Selectors) { 35 | return errors.Errorf( 36 | "OneOf requirement failed, none of the following probes are running [%s]", 37 | strings.Join(errs, " | ")) 38 | } 39 | // at least one selector was successful 40 | return nil 41 | } 42 | 43 | func (oo *OneOf) String() string { 44 | var strs []string 45 | for _, id := range oo.GetProbesIdentificationPairList() { 46 | str := fmt.Sprintf("%s", id) 47 | strs = append(strs, str) 48 | } 49 | return strings.Join(strs, ", ") 50 | } 51 | 52 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 53 | // now select the new one 54 | func (oo *OneOf) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 55 | for _, selector := range oo.Selectors { 56 | selector.EditProbeIdentificationPair(old, new) 57 | } 58 | } 59 | 60 | // AllOf - This selector is used to ensure that all the proves in the provided list are running. 61 | type AllOf struct { 62 | Selectors []ProbesSelector 63 | } 64 | 65 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 66 | func (ao *AllOf) GetProbesIdentificationPairList() []ProbeIdentificationPair { 67 | var l []ProbeIdentificationPair 68 | for _, selector := range ao.Selectors { 69 | l = append(l, selector.GetProbesIdentificationPairList()...) 70 | } 71 | return l 72 | } 73 | 74 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 75 | // For example, see OneOf. 76 | func (ao *AllOf) RunValidator(manager *Manager) error { 77 | var errMsg []string 78 | for _, selector := range ao.Selectors { 79 | if err := selector.RunValidator(manager); err != nil { 80 | errMsg = append(errMsg, err.Error()) 81 | } 82 | } 83 | if len(errMsg) > 0 { 84 | return errors.Errorf( 85 | "AllOf requirement failed, the following probes are not running [%s]", 86 | strings.Join(errMsg, " | ")) 87 | } 88 | // no error means that all the selectors were successful 89 | return nil 90 | } 91 | 92 | func (ao *AllOf) String() string { 93 | var strs []string 94 | for _, id := range ao.GetProbesIdentificationPairList() { 95 | str := fmt.Sprintf("%s", id) 96 | strs = append(strs, str) 97 | } 98 | return strings.Join(strs, ", ") 99 | } 100 | 101 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 102 | // now select the new one 103 | func (ao *AllOf) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 104 | for _, selector := range ao.Selectors { 105 | selector.EditProbeIdentificationPair(old, new) 106 | } 107 | } 108 | 109 | // BestEffort - This selector is used to load probes in best effort mode 110 | type BestEffort struct { 111 | Selectors []ProbesSelector 112 | } 113 | 114 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 115 | func (be *BestEffort) GetProbesIdentificationPairList() []ProbeIdentificationPair { 116 | var l []ProbeIdentificationPair 117 | for _, selector := range be.Selectors { 118 | l = append(l, selector.GetProbesIdentificationPairList()...) 119 | } 120 | return l 121 | } 122 | 123 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 124 | // For example, see OneOf. 125 | func (be *BestEffort) RunValidator(manager *Manager) error { 126 | return nil 127 | } 128 | 129 | func (be *BestEffort) String() string { 130 | var strs []string 131 | for _, id := range be.GetProbesIdentificationPairList() { 132 | str := fmt.Sprintf("%s", id) 133 | strs = append(strs, str) 134 | } 135 | return strings.Join(strs, ", ") 136 | } 137 | 138 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 139 | // now select the new one 140 | func (be *BestEffort) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 141 | for _, selector := range be.Selectors { 142 | selector.EditProbeIdentificationPair(old, new) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /manager/syscalls.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "github.com/DataDog/ebpf" 5 | "golang.org/x/sys/unix" 6 | "syscall" 7 | "unsafe" 8 | 9 | "github.com/pkg/errors" 10 | 11 | "github.com/DataDog/ebpf/internal" 12 | ) 13 | 14 | func perfEventOpenTracepoint(id int, progFd int) (*internal.FD, error) { 15 | attr := unix.PerfEventAttr{ 16 | Type: unix.PERF_TYPE_TRACEPOINT, 17 | Sample_type: unix.PERF_SAMPLE_RAW, 18 | Sample: 1, 19 | Wakeup: 1, 20 | Config: uint64(id), 21 | } 22 | attr.Size = uint32(unsafe.Sizeof(attr)) 23 | 24 | efd, err := unix.PerfEventOpen(&attr, -1, 0, -1, unix.PERF_FLAG_FD_CLOEXEC) 25 | if efd < 0 { 26 | return nil, errors.Wrap(err, "perf_event_open error") 27 | } 28 | 29 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(efd), unix.PERF_EVENT_IOC_ENABLE, 0); err != 0 { 30 | return nil, errors.Wrap(err, "error enabling perf event") 31 | } 32 | 33 | if _, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(efd), unix.PERF_EVENT_IOC_SET_BPF, uintptr(progFd)); err != 0 { 34 | return nil, errors.Wrap(err, "error attaching bpf program to perf event") 35 | } 36 | return internal.NewFD(uint32(efd)), nil 37 | } 38 | 39 | type bpfProgAttachAttr struct { 40 | targetFD uint32 41 | attachBpfFD uint32 42 | attachType uint32 43 | attachFlags uint32 44 | } 45 | 46 | const ( 47 | _ProgAttach = 8 48 | _ProgDetach = 9 49 | ) 50 | 51 | func bpfProgAttach(progFd int, targetFd int, attachType ebpf.AttachType) (int, error) { 52 | attr := bpfProgAttachAttr{ 53 | targetFD: uint32(targetFd), 54 | attachBpfFD: uint32(progFd), 55 | attachType: uint32(attachType), 56 | } 57 | ptr, err := internal.BPF(_ProgAttach, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 58 | if err != nil { 59 | return -1, errors.Wrapf(err, "can't attach program id %d to target fd %d", progFd, targetFd) 60 | } 61 | return int(ptr), nil 62 | } 63 | 64 | func bpfProgDetach(progFd int, targetFd int, attachType ebpf.AttachType) (int, error) { 65 | attr := bpfProgAttachAttr{ 66 | targetFD: uint32(targetFd), 67 | attachBpfFD: uint32(progFd), 68 | attachType: uint32(attachType), 69 | } 70 | ptr, err := internal.BPF(_ProgDetach, unsafe.Pointer(&attr), unsafe.Sizeof(attr)) 71 | if err != nil { 72 | return -1, errors.Wrapf(err, "can't detach program id %d to target fd %d", progFd, targetFd) 73 | } 74 | return int(ptr), nil 75 | } 76 | 77 | func sockAttach(sockFd int, progFd int) error { 78 | return syscall.SetsockoptInt(sockFd, syscall.SOL_SOCKET, unix.SO_ATTACH_BPF, progFd) 79 | } 80 | 81 | func sockDetach(sockFd int, progFd int) error { 82 | return syscall.SetsockoptInt(sockFd, syscall.SOL_SOCKET, unix.SO_DETACH_BPF, progFd) 83 | } 84 | -------------------------------------------------------------------------------- /manager/testdata/Makefile: -------------------------------------------------------------------------------- 1 | LLVM_PREFIX ?= /usr/bin 2 | CLANG ?= $(LLVM_PREFIX)/clang 3 | 4 | all: rewrite.elf 5 | 6 | clean: 7 | -$(RM) *.elf 8 | 9 | %.elf : %.c 10 | $(CLANG) -target bpf -O2 -g \ 11 | -Wall -Werror \ 12 | -c $< -o $@ 13 | -------------------------------------------------------------------------------- /manager/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 | -------------------------------------------------------------------------------- /manager/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 | -------------------------------------------------------------------------------- /manager/testdata/rewrite.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/manager/testdata/rewrite.elf -------------------------------------------------------------------------------- /manager/utils_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGenerateEventName(t *testing.T) { 8 | probeType := "p" 9 | funcName := "func" 10 | UID := "UID" 11 | kprobeAttachPID := 1234 12 | 13 | eventName, err := GenerateEventName(probeType, funcName, UID, kprobeAttachPID) 14 | if err != nil { 15 | t.Error(err) 16 | } 17 | if len(eventName) > MaxEventNameLen { 18 | t.Errorf("Event name too long, kernel limit is %d : MaxEventNameLen", MaxEventNameLen) 19 | } 20 | 21 | // should be truncated 22 | funcName = "01234567890123456790123456789012345678901234567890123456789" 23 | eventName, err = GenerateEventName(probeType, funcName, UID, kprobeAttachPID) 24 | if (err != nil) || (len(eventName) != MaxEventNameLen) || (eventName != "p_01234567890123456790123456789012345678901234567890123_UID_1234") { 25 | t.Errorf("Should not failed and truncate the function name (len %d)", len(eventName)) 26 | } 27 | 28 | UID = "12345678901234567890123456789012345678901234567890" 29 | _, err = GenerateEventName(probeType, funcName, UID, kprobeAttachPID) 30 | if err == nil { 31 | t.Errorf("Test should failed as event name length is too big for the kernel and free space for function Name is < %d", MinFunctionNameLen) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /marshaler_example_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Assert that customEncoding implements the correct interfaces. 10 | var ( 11 | _ encoding.BinaryMarshaler = (*customEncoding)(nil) 12 | _ encoding.BinaryUnmarshaler = (*customEncoding)(nil) 13 | ) 14 | 15 | type customEncoding struct { 16 | data string 17 | } 18 | 19 | func (ce *customEncoding) MarshalBinary() ([]byte, error) { 20 | return []byte(strings.ToUpper(ce.data)), nil 21 | } 22 | 23 | func (ce *customEncoding) UnmarshalBinary(buf []byte) error { 24 | ce.data = string(buf) 25 | return nil 26 | } 27 | 28 | // ExampleMarshaler shows how to use custom encoding with map methods. 29 | func Example_customMarshaler() { 30 | hash := createHash() 31 | defer hash.Close() 32 | 33 | if err := hash.Put(&customEncoding{"hello"}, uint32(111)); err != nil { 34 | panic(err) 35 | } 36 | 37 | var ( 38 | key customEncoding 39 | value uint32 40 | entries = hash.Iterate() 41 | ) 42 | 43 | for entries.Next(&key, &value) { 44 | fmt.Printf("key: %s, value: %d\n", key.data, value) 45 | } 46 | 47 | if err := entries.Err(); err != nil { 48 | panic(err) 49 | } 50 | 51 | // Output: key: HELLO, value: 111 52 | } 53 | -------------------------------------------------------------------------------- /perf/doc.go: -------------------------------------------------------------------------------- 1 | // Package perf allows interacting with Linux perf_events. 2 | // 3 | // BPF allows submitting custom perf_events to a ring-buffer set up 4 | // by userspace. This is very useful to push things like packet samples 5 | // from BPF to a daemon running in user space. 6 | package perf 7 | -------------------------------------------------------------------------------- /perf/ring.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "math" 8 | "os" 9 | "runtime" 10 | "sync/atomic" 11 | "unsafe" 12 | 13 | "github.com/DataDog/ebpf/internal/unix" 14 | ) 15 | 16 | // perfEventRing is a page of metadata followed by 17 | // a variable number of pages which form a ring buffer. 18 | type perfEventRing struct { 19 | fd int 20 | cpu int 21 | mmap []byte 22 | *ringReader 23 | } 24 | 25 | func newPerfEventRing(cpu, perCPUBuffer, watermark int) (*perfEventRing, error) { 26 | if watermark >= perCPUBuffer { 27 | return nil, errors.New("watermark must be smaller than perCPUBuffer") 28 | } 29 | 30 | fd, err := createPerfEvent(cpu, watermark) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | if err := unix.SetNonblock(fd, true); err != nil { 36 | unix.Close(fd) 37 | return nil, err 38 | } 39 | 40 | mmap, err := unix.Mmap(fd, 0, perfBufferSize(perCPUBuffer), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) 41 | if err != nil { 42 | unix.Close(fd) 43 | return nil, fmt.Errorf("can't mmap: %v", err) 44 | } 45 | 46 | // This relies on the fact that we allocate an extra metadata page, 47 | // and that the struct is smaller than an OS page. 48 | // This use of unsafe.Pointer isn't explicitly sanctioned by the 49 | // documentation, since a byte is smaller than sampledPerfEvent. 50 | meta := (*unix.PerfEventMmapPage)(unsafe.Pointer(&mmap[0])) 51 | 52 | ring := &perfEventRing{ 53 | fd: fd, 54 | cpu: cpu, 55 | mmap: mmap, 56 | ringReader: newRingReader(meta, mmap[meta.Data_offset:meta.Data_offset+meta.Data_size]), 57 | } 58 | runtime.SetFinalizer(ring, (*perfEventRing).Close) 59 | 60 | return ring, nil 61 | } 62 | 63 | // mmapBufferSize returns a valid mmap buffer size for use with perf_event_open (1+2^n pages) 64 | func perfBufferSize(perCPUBuffer int) int { 65 | pageSize := os.Getpagesize() 66 | 67 | // Smallest whole number of pages 68 | nPages := (perCPUBuffer + pageSize - 1) / pageSize 69 | 70 | // Round up to nearest power of two number of pages 71 | nPages = int(math.Pow(2, math.Ceil(math.Log2(float64(nPages))))) 72 | 73 | // Add one for metadata 74 | nPages += 1 75 | 76 | return nPages * pageSize 77 | } 78 | 79 | func (ring *perfEventRing) Close() { 80 | runtime.SetFinalizer(ring, nil) 81 | unix.Close(ring.fd) 82 | unix.Munmap(ring.mmap) 83 | 84 | ring.fd = -1 85 | ring.mmap = nil 86 | } 87 | 88 | func createPerfEvent(cpu, watermark int) (int, error) { 89 | if watermark == 0 { 90 | watermark = 1 91 | } 92 | 93 | attr := unix.PerfEventAttr{ 94 | Type: unix.PERF_TYPE_SOFTWARE, 95 | Config: unix.PERF_COUNT_SW_BPF_OUTPUT, 96 | Bits: unix.PerfBitWatermark, 97 | Sample_type: unix.PERF_SAMPLE_RAW, 98 | Wakeup: uint32(watermark), 99 | } 100 | 101 | attr.Size = uint32(unsafe.Sizeof(attr)) 102 | fd, err := unix.PerfEventOpen(&attr, -1, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) 103 | if err != nil { 104 | return -1, fmt.Errorf("can't create perf event: %w", err) 105 | } 106 | return fd, nil 107 | } 108 | 109 | type ringReader struct { 110 | meta *unix.PerfEventMmapPage 111 | head, tail uint64 112 | mask uint64 113 | ring []byte 114 | } 115 | 116 | func newRingReader(meta *unix.PerfEventMmapPage, ring []byte) *ringReader { 117 | return &ringReader{ 118 | meta: meta, 119 | head: atomic.LoadUint64(&meta.Data_head), 120 | tail: atomic.LoadUint64(&meta.Data_tail), 121 | // cap is always a power of two 122 | mask: uint64(cap(ring) - 1), 123 | ring: ring, 124 | } 125 | } 126 | 127 | func (rr *ringReader) loadHead() { 128 | rr.head = atomic.LoadUint64(&rr.meta.Data_head) 129 | } 130 | 131 | func (rr *ringReader) writeTail() { 132 | // Commit the new tail. This lets the kernel know that 133 | // the ring buffer has been consumed. 134 | atomic.StoreUint64(&rr.meta.Data_tail, rr.tail) 135 | } 136 | 137 | func (rr *ringReader) Read(p []byte) (int, error) { 138 | start := int(rr.tail & rr.mask) 139 | 140 | n := len(p) 141 | // Truncate if the read wraps in the ring buffer 142 | if remainder := cap(rr.ring) - start; n > remainder { 143 | n = remainder 144 | } 145 | 146 | // Truncate if there isn't enough data 147 | if remainder := int(rr.head - rr.tail); n > remainder { 148 | n = remainder 149 | } 150 | 151 | copy(p, rr.ring[start:start+n]) 152 | rr.tail += uint64(n) 153 | 154 | if rr.tail == rr.head { 155 | return n, io.EOF 156 | } 157 | 158 | return n, nil 159 | } 160 | -------------------------------------------------------------------------------- /perf/ring_test.go: -------------------------------------------------------------------------------- 1 | package perf 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "testing" 8 | 9 | "github.com/DataDog/ebpf/internal/unix" 10 | ) 11 | 12 | func TestRingBufferReader(t *testing.T) { 13 | buf := make([]byte, 2) 14 | 15 | ring := makeRing(2, 0) 16 | n, err := ring.Read(buf) 17 | if err != io.EOF { 18 | t.Error("Expected io.EOF, got", err) 19 | } 20 | if n != 2 { 21 | t.Errorf("Expected to read 2 bytes, got %d", n) 22 | } 23 | if !bytes.Equal(buf, []byte{0, 1}) { 24 | t.Error("Expected [0, 1], got", buf) 25 | } 26 | n, err = ring.Read(buf) 27 | if err != io.EOF { 28 | t.Error("Expected io.EOF, got", err) 29 | } 30 | if n != 0 { 31 | t.Error("Expected to read 0 bytes, got", n) 32 | } 33 | 34 | // Wrapping read 35 | ring = makeRing(2, 1) 36 | n, err = io.ReadFull(ring, buf) 37 | if err != nil { 38 | t.Error("Error while reading:", err) 39 | } 40 | if n != 2 { 41 | t.Errorf("Expected to read 2 byte, got %d", n) 42 | } 43 | if !bytes.Equal(buf, []byte{1, 0}) { 44 | t.Error("Expected [1, 0], got", buf) 45 | } 46 | } 47 | 48 | func makeRing(size, offset int) *ringReader { 49 | if size%2 != 0 { 50 | panic("size must be power of two") 51 | } 52 | 53 | ring := make([]byte, size) 54 | for i := range ring { 55 | ring[i] = byte(i) 56 | } 57 | 58 | meta := unix.PerfEventMmapPage{ 59 | Data_head: uint64(len(ring) + offset), 60 | Data_tail: uint64(offset), 61 | Data_size: uint64(len(ring)), 62 | } 63 | 64 | return newRingReader(&meta, ring) 65 | } 66 | 67 | func TestPerfEventRing(t *testing.T) { 68 | check := func(buffer, watermark int) { 69 | ring, err := newPerfEventRing(0, buffer, watermark) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | size := len(ring.ringReader.ring) 75 | 76 | // Ring size should be at least as big as buffer 77 | if size < buffer { 78 | t.Fatalf("ring size %d smaller than buffer %d", size, buffer) 79 | } 80 | 81 | // Ring size should be of the form 2^n pages (meta page has already been removed) 82 | if size%os.Getpagesize() != 0 { 83 | t.Fatalf("ring size %d not whole number of pages (pageSize %d)", size, os.Getpagesize()) 84 | } 85 | nPages := size / os.Getpagesize() 86 | if nPages&(nPages-1) != 0 { 87 | t.Fatalf("ring size %d (%d pages) not a power of two pages (pageSize %d)", size, nPages, os.Getpagesize()) 88 | } 89 | } 90 | 91 | // watermark > buffer 92 | _, err := newPerfEventRing(0, 8192, 8193) 93 | if err == nil { 94 | t.Fatal("watermark > buffer allowed") 95 | } 96 | 97 | // watermark == buffer 98 | _, err = newPerfEventRing(0, 8192, 8192) 99 | if err == nil { 100 | t.Fatal("watermark == buffer allowed") 101 | } 102 | 103 | // buffer not a power of two, watermark < buffer 104 | check(8193, 8192) 105 | 106 | // large buffer not a multiple of page size at all (prime) 107 | check(65537, 8192) 108 | } 109 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | eBPF 2 | ------- 3 | [![](https://godoc.org/github.com/DataDog/ebpf?status.svg)](https://godoc.org/github.com/DataDog/ebpf) 4 | 5 | NOTE: This is a fork from [cilium/ebpf](https://github.com/cilium/ebpf) that adds a declarative manager on top to manage the lifecycle of eBPF objects. 6 | 7 | ## Current status 8 | 9 | Work is underway to convert this library to wrap the upstream library, rather than forking. 10 | 11 | ## Requirements 12 | 13 | * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) 14 | * Linux 4.4+ 15 | 16 | ## Useful resources 17 | 18 | * [Upstream library](https://github.com/cilium/ebpf) 19 | * [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) (recommended) 20 | * [Linux documentation on BPF](http://elixir.free-electrons.com/linux/latest/source/Documentation/networking/filter.txt) 21 | * [eBPF features by Linux version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md) 22 | -------------------------------------------------------------------------------- /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 | mount -t bpf bpf /sys/fs/bpf 12 | export CGO_ENABLED=0 13 | export GOFLAGS=-mod=readonly 14 | export GOPATH=/run/go-path 15 | export GOPROXY=file:///run/go-root/pkg/mod/cache/download 16 | export GOCACHE=/run/go-cache 17 | 18 | echo Running tests... 19 | /usr/local/bin/go test -coverprofile="$1/coverage.txt" -covermode=atomic -v ./... 20 | touch "$1/success" 21 | exit 0 22 | fi 23 | 24 | # Pull all dependencies, so that we can run tests without the 25 | # vm having network access. 26 | go mod download 27 | 28 | # Use sudo if /dev/kvm isn't accessible by the current user. 29 | sudo="" 30 | if [[ ! -r /dev/kvm || ! -w /dev/kvm ]]; then 31 | sudo="sudo" 32 | fi 33 | readonly sudo 34 | 35 | readonly kernel_version="${1:-}" 36 | if [[ -z "${kernel_version}" ]]; then 37 | echo "Expecting kernel version as first argument" 38 | exit 1 39 | fi 40 | 41 | readonly kernel="linux-${kernel_version}.bz" 42 | readonly output="$(mktemp -d)" 43 | readonly tmp_dir="${TMPDIR:-$(mktemp -d)}" 44 | 45 | test -e "${tmp_dir}/${kernel}" || { 46 | echo Fetching "${kernel}" 47 | curl --fail -L "https://github.com/cilium/ci-kernels/blob/master/${kernel}?raw=true" -o "${tmp_dir}/${kernel}" 48 | } 49 | 50 | echo Testing on "${kernel_version}" 51 | $sudo virtme-run --kimg "${tmp_dir}/${kernel}" --memory 512M --pwd \ 52 | --rwdir=/run/output="${output}" \ 53 | --rodir=/run/go-path="$(go env GOPATH)" \ 54 | --rwdir=/run/go-cache="$(go env GOCACHE)" \ 55 | --script-sh "$(realpath "$0") --in-vm /run/output" 56 | 57 | if [[ ! -e "${output}/success" ]]; then 58 | echo "Test failed on ${kernel_version}" 59 | exit 1 60 | else 61 | echo "Test successful on ${kernel_version}" 62 | if [[ -v CODECOV_TOKEN ]]; then 63 | curl --fail -s https://codecov.io/bash > "${tmp_dir}/codecov.sh" 64 | chmod +x "${tmp_dir}/codecov.sh" 65 | "${tmp_dir}/codecov.sh" -f "${output}/coverage.txt" 66 | fi 67 | fi 68 | 69 | $sudo rm -r "${output}" 70 | -------------------------------------------------------------------------------- /syscalls_test.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/DataDog/ebpf/internal/testutils" 8 | "github.com/DataDog/ebpf/internal/unix" 9 | ) 10 | 11 | func TestObjNameCharacters(t *testing.T) { 12 | for in, valid := range map[string]bool{ 13 | "test": true, 14 | "": true, 15 | "a-b": false, 16 | "yeah so": false, 17 | "dot.": objNameAllowsDot() == nil, 18 | } { 19 | result := strings.IndexFunc(in, invalidBPFObjNameChar) == -1 20 | if result != valid { 21 | t.Errorf("Name '%s' classified incorrectly", in) 22 | } 23 | } 24 | } 25 | 26 | func TestObjName(t *testing.T) { 27 | name := newBPFObjName("more_than_16_characters_long") 28 | if name[len(name)-1] != 0 { 29 | t.Error("newBPFObjName doesn't null terminate") 30 | } 31 | if len(name) != unix.BPF_OBJ_NAME_LEN { 32 | t.Errorf("Name is %d instead of %d bytes long", len(name), unix.BPF_OBJ_NAME_LEN) 33 | } 34 | } 35 | 36 | func TestHaveObjName(t *testing.T) { 37 | testutils.CheckFeatureTest(t, haveObjName) 38 | } 39 | 40 | func TestObjNameAllowsDot(t *testing.T) { 41 | testutils.CheckFeatureTest(t, objNameAllowsDot) 42 | } 43 | 44 | func TestHaveNestedMaps(t *testing.T) { 45 | testutils.CheckFeatureTest(t, haveNestedMaps) 46 | } 47 | 48 | func TestHaveMapMutabilityModifiers(t *testing.T) { 49 | testutils.CheckFeatureTest(t, haveMapMutabilityModifiers) 50 | } 51 | -------------------------------------------------------------------------------- /testdata/Makefile: -------------------------------------------------------------------------------- 1 | LLVM_PREFIX ?= /usr/bin 2 | CLANG ?= $(LLVM_PREFIX)/clang 3 | CFLAGS := -target bpf -O2 -g -Wall -Werror $(CFLAGS) 4 | 5 | .PHONY: all clean 6 | all: loader-clang-6.0-el.elf loader-clang-7-el.elf loader-clang-8-el.elf loader-clang-9-el.elf rewrite-el.elf invalid_map-el.elf \ 7 | loader-clang-6.0-eb.elf loader-clang-7-eb.elf loader-clang-8-eb.elf loader-clang-9-eb.elf rewrite-eb.elf invalid_map-eb.elf 8 | 9 | clean: 10 | -$(RM) *.elf 11 | 12 | loader-%-el.elf: loader.c 13 | $* $(CFLAGS) -mlittle-endian -c $< -o $@ 14 | 15 | loader-%-eb.elf: loader.c 16 | $* $(CFLAGS) -mbig-endian -c $< -o $@ 17 | 18 | %-el.elf: %.c 19 | $(CLANG) $(CFLAGS) -mlittle-endian -c $< -o $@ 20 | 21 | %-eb.elf : %.c 22 | $(CLANG) $(CFLAGS) -mbig-endian -c $< -o $@ 23 | -------------------------------------------------------------------------------- /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 | #define __uint(name, val) int (*name)[val] 8 | #define __type(name, val) typeof(val) *name 9 | 10 | #define BPF_MAP_TYPE_ARRAY (1) 11 | #define BPF_MAP_TYPE_PERF_EVENT_ARRAY (4) 12 | #define BPF_MAP_TYPE_ARRAY_OF_MAPS (12) 13 | #define BPF_MAP_TYPE_HASH_OF_MAPS (13) 14 | 15 | #define BPF_F_NO_PREALLOC (1U << 0) 16 | #define BPF_F_CURRENT_CPU (0xffffffffULL) 17 | 18 | /* From tools/lib/bpf/libbpf.h */ 19 | struct bpf_map_def { 20 | unsigned int type; 21 | unsigned int key_size; 22 | unsigned int value_size; 23 | unsigned int max_entries; 24 | unsigned int map_flags; 25 | }; 26 | 27 | static void* (*map_lookup_elem)(const void *map, const void *key) = (void*)1; 28 | static int (*perf_event_output)(const void *ctx, const void *map, uint64_t index, const void *data, uint64_t size) = (void*)25; 29 | static uint32_t (*get_smp_processor_id)(void) = (void*)8; 30 | -------------------------------------------------------------------------------- /testdata/invalid_map-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/invalid_map-eb.elf -------------------------------------------------------------------------------- /testdata/invalid_map-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/invalid_map-el.elf -------------------------------------------------------------------------------- /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 { 9 | struct bpf_map_def def; 10 | uint32_t dummy; 11 | } invalid_map __section("maps") = { 12 | .def = { 13 | .type = BPF_MAP_TYPE_ARRAY, 14 | .key_size = 4, 15 | .value_size = 2, 16 | .max_entries = 1, 17 | }, 18 | .dummy = 1, 19 | }; 20 | -------------------------------------------------------------------------------- /testdata/loader-clang-6.0-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-6.0-eb.elf -------------------------------------------------------------------------------- /testdata/loader-clang-6.0-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-6.0-el.elf -------------------------------------------------------------------------------- /testdata/loader-clang-7-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-7-eb.elf -------------------------------------------------------------------------------- /testdata/loader-clang-7-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-7-el.elf -------------------------------------------------------------------------------- /testdata/loader-clang-8-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-8-eb.elf -------------------------------------------------------------------------------- /testdata/loader-clang-8-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-8-el.elf -------------------------------------------------------------------------------- /testdata/loader-clang-9-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-9-eb.elf -------------------------------------------------------------------------------- /testdata/loader-clang-9-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/loader-clang-9-el.elf -------------------------------------------------------------------------------- /testdata/loader.c: -------------------------------------------------------------------------------- 1 | /* This file excercises the ELF loader. 2 | */ 3 | 4 | #include "common.h" 5 | 6 | char __license[] __section("license") = "MIT"; 7 | 8 | struct bpf_map_def hash_map __section("maps") = { 9 | .type = BPF_MAP_TYPE_ARRAY, 10 | .key_size = 4, 11 | .value_size = 2, 12 | .max_entries = 1, 13 | .map_flags = 0, 14 | }; 15 | 16 | struct bpf_map_def hash_map2 __section("maps") = { 17 | .type = BPF_MAP_TYPE_ARRAY, 18 | .key_size = 4, 19 | .value_size = 1, 20 | .max_entries = 2, 21 | .map_flags = BPF_F_NO_PREALLOC, 22 | }; 23 | 24 | struct bpf_map_def 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 | }; 29 | 30 | struct bpf_map_def hash_of_hash_map __section("maps") = { 31 | .type = BPF_MAP_TYPE_HASH_OF_MAPS, 32 | .key_size = sizeof(uint32_t), 33 | .max_entries = 2, 34 | }; 35 | 36 | #if __clang_major__ >= 9 37 | // Clang < 9 doesn't emit the necessary BTF for this to work. 38 | struct { 39 | __uint(type, BPF_MAP_TYPE_ARRAY); 40 | __type(key, uint32_t); 41 | __type(value, uint32_t); 42 | __uint(max_entries, 1); 43 | __uint(map_flags, BPF_F_NO_PREALLOC); 44 | } btf_map __section(".maps"); 45 | #endif 46 | 47 | static int __attribute__((noinline)) static_fn(uint32_t arg) { 48 | return arg; 49 | } 50 | 51 | int __attribute__((noinline)) global_fn2(uint32_t arg) { 52 | return arg++; 53 | } 54 | 55 | int __attribute__((noinline)) __section("other") global_fn3(uint32_t arg) { 56 | return arg+1; 57 | } 58 | 59 | int __attribute__((noinline)) global_fn(uint32_t arg) { 60 | return static_fn(arg) + global_fn2(arg) + global_fn3(arg); 61 | } 62 | 63 | #if __clang_major__ >= 9 64 | static volatile unsigned int key1 = 0; // .bss 65 | static volatile unsigned int key2 = 1; // .data 66 | static volatile const unsigned int key3 = 2; // .rodata 67 | static volatile const uint32_t arg; // .rodata, rewritten by loader 68 | #endif 69 | 70 | __section("xdp") int xdp_prog() { 71 | #if __clang_major__ < 9 72 | unsigned int key1 = 0; 73 | unsigned int key2 = 1; 74 | unsigned int key3 = 2; 75 | uint32_t arg = 1; 76 | #endif 77 | map_lookup_elem(&hash_map, (void*)&key1); 78 | map_lookup_elem(&hash_map2, (void*)&key2); 79 | map_lookup_elem(&hash_map2, (void*)&key3); 80 | return static_fn(arg) + global_fn(arg); 81 | } 82 | 83 | // This function has no relocations, and is thus parsed differently. 84 | __section("socket") int no_relocation() { 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /testdata/rewrite-eb.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/rewrite-eb.elf -------------------------------------------------------------------------------- /testdata/rewrite-el.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf/3fc9ab3b8dafdb4652639523e9b44b68f6c929ae/testdata/rewrite-el.elf -------------------------------------------------------------------------------- /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 bpf_map_def 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 | -------------------------------------------------------------------------------- /types_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output types_string.go -type=MapType,ProgramType"; DO NOT EDIT. 2 | 3 | package ebpf 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[UnspecifiedMap-0] 12 | _ = x[Hash-1] 13 | _ = x[Array-2] 14 | _ = x[ProgramArray-3] 15 | _ = x[PerfEventArray-4] 16 | _ = x[PerCPUHash-5] 17 | _ = x[PerCPUArray-6] 18 | _ = x[StackTrace-7] 19 | _ = x[CGroupArray-8] 20 | _ = x[LRUHash-9] 21 | _ = x[LRUCPUHash-10] 22 | _ = x[LPMTrie-11] 23 | _ = x[ArrayOfMaps-12] 24 | _ = x[HashOfMaps-13] 25 | _ = x[DevMap-14] 26 | _ = x[SockMap-15] 27 | _ = x[CPUMap-16] 28 | _ = x[XSKMap-17] 29 | _ = x[SockHash-18] 30 | _ = x[CGroupStorage-19] 31 | _ = x[ReusePortSockArray-20] 32 | _ = x[PerCPUCGroupStorage-21] 33 | _ = x[Queue-22] 34 | _ = x[Stack-23] 35 | _ = x[SkStorage-24] 36 | _ = x[DevMapHash-25] 37 | } 38 | 39 | const _MapType_name = "UnspecifiedMapHashArrayProgramArrayPerfEventArrayPerCPUHashPerCPUArrayStackTraceCGroupArrayLRUHashLRUCPUHashLPMTrieArrayOfMapsHashOfMapsDevMapSockMapCPUMapXSKMapSockHashCGroupStorageReusePortSockArrayPerCPUCGroupStorageQueueStackSkStorageDevMapHash" 40 | 41 | var _MapType_index = [...]uint8{0, 14, 18, 23, 35, 49, 59, 70, 80, 91, 98, 108, 115, 126, 136, 142, 149, 155, 161, 169, 182, 200, 219, 224, 229, 238, 248} 42 | 43 | func (i MapType) String() string { 44 | if i >= MapType(len(_MapType_index)-1) { 45 | return "MapType(" + strconv.FormatInt(int64(i), 10) + ")" 46 | } 47 | return _MapType_name[_MapType_index[i]:_MapType_index[i+1]] 48 | } 49 | func _() { 50 | // An "invalid array index" compiler error signifies that the constant values have changed. 51 | // Re-run the stringer command to generate them again. 52 | var x [1]struct{} 53 | _ = x[UnspecifiedProgram-0] 54 | _ = x[SocketFilter-1] 55 | _ = x[Kprobe-2] 56 | _ = x[SchedCLS-3] 57 | _ = x[SchedACT-4] 58 | _ = x[TracePoint-5] 59 | _ = x[XDP-6] 60 | _ = x[PerfEvent-7] 61 | _ = x[CGroupSKB-8] 62 | _ = x[CGroupSock-9] 63 | _ = x[LWTIn-10] 64 | _ = x[LWTOut-11] 65 | _ = x[LWTXmit-12] 66 | _ = x[SockOps-13] 67 | _ = x[SkSKB-14] 68 | _ = x[CGroupDevice-15] 69 | _ = x[SkMsg-16] 70 | _ = x[RawTracepoint-17] 71 | _ = x[CGroupSockAddr-18] 72 | _ = x[LWTSeg6Local-19] 73 | _ = x[LircMode2-20] 74 | _ = x[SkReuseport-21] 75 | _ = x[FlowDissector-22] 76 | _ = x[CGroupSysctl-23] 77 | _ = x[RawTracepointWritable-24] 78 | _ = x[CGroupSockopt-25] 79 | _ = x[Tracing-26] 80 | } 81 | 82 | const _ProgramType_name = "UnspecifiedProgramSocketFilterKprobeSchedCLSSchedACTTracePointXDPPerfEventCGroupSKBCGroupSockLWTInLWTOutLWTXmitSockOpsSkSKBCGroupDeviceSkMsgRawTracepointCGroupSockAddrLWTSeg6LocalLircMode2SkReuseportFlowDissectorCGroupSysctlRawTracepointWritableCGroupSockoptTracing" 83 | 84 | var _ProgramType_index = [...]uint16{0, 18, 30, 36, 44, 52, 62, 65, 74, 83, 93, 98, 104, 111, 118, 123, 135, 140, 153, 167, 179, 188, 199, 212, 224, 245, 258, 265} 85 | 86 | func (i ProgramType) String() string { 87 | if i >= ProgramType(len(_ProgramType_index)-1) { 88 | return "ProgramType(" + strconv.FormatInt(int64(i), 10) + ")" 89 | } 90 | return _ProgramType_name[_ProgramType_index[i]:_ProgramType_index[i+1]] 91 | } 92 | -------------------------------------------------------------------------------- /utsname_int8.go: -------------------------------------------------------------------------------- 1 | // +build linux,amd64 linux,arm64 linux,386 2 | 3 | package ebpf 4 | 5 | func utsnameStr(in []int8) string { 6 | out := make([]byte, len(in)) 7 | for i := 0; i < len(in); i++ { 8 | if in[i] == 0 { 9 | break 10 | } 11 | out = append(out, byte(in[i])) 12 | } 13 | return string(out) 14 | } 15 | -------------------------------------------------------------------------------- /utsname_uint8.go: -------------------------------------------------------------------------------- 1 | // +build linux,arm linux,ppc64 linux,ppc64le s390x 2 | 3 | package ebpf 4 | 5 | func utsnameStr(in []uint8) string { 6 | out := make([]byte, len(in)) 7 | for i := 0; i < len(in); i++ { 8 | if in[i] == 0 { 9 | break 10 | } 11 | out = append(out, byte(in[i])) 12 | } 13 | return string(out) 14 | } 15 | --------------------------------------------------------------------------------