├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cgroup.go ├── constant_editor.go ├── editor.go ├── editor_test.go ├── err.go ├── examples ├── .gitignore ├── Makefile ├── Makefile.arch ├── Makefile.clang ├── Makefile.ebpf ├── activated_probes │ ├── Makefile │ ├── ebpf │ │ └── main.c │ └── main.go ├── clone_vs_add_hook │ ├── Makefile │ ├── demo.go │ ├── ebpf │ │ └── main.c │ └── main.go ├── constant_editor │ ├── Makefile │ ├── ebpf │ │ └── main.c │ └── main.go ├── include │ ├── all.h │ ├── asm_goto_workaround.h │ ├── bpf_endian.h │ ├── bpf_helper_defs.h │ ├── bpf_helpers.h │ ├── bpf_tracing.h │ └── kernel.h ├── instruction_patching │ ├── Makefile │ ├── ebpf │ │ └── main.c │ └── main.go ├── map_rewrite_vs_map_router │ ├── Makefile │ ├── demo.go │ ├── ebpf │ │ ├── prog1.c │ │ └── prog2.c │ └── main.go ├── mapspec_editor │ ├── Makefile │ ├── ebpf │ │ └── main.c │ └── main.go ├── object_pinning │ ├── Makefile │ ├── ebpf │ │ └── main.c │ └── main.go ├── program_router │ ├── Makefile │ ├── demo.go │ ├── ebpf │ │ ├── prog1.c │ │ └── prog2.c │ └── main.go ├── programs │ ├── cgroup │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── fentry │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── kprobe │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── lsm │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── perf_event │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── socket │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── tc │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── tracepoint │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ ├── uprobe │ │ ├── Makefile │ │ ├── ebpf │ │ │ └── main.c │ │ └── main.go │ └── xdp │ │ ├── Makefile │ │ ├── ebpf │ │ └── main.c │ │ └── main.go └── tests_and_benchmarks │ ├── Makefile │ ├── ebpf │ └── main.c │ └── main.go ├── fd.go ├── go.mod ├── go.sum ├── innerouter_map_spec.go ├── internal ├── env.go ├── procfs.go └── retry.go ├── kprobe.go ├── lsm.go ├── manager.go ├── manager_perfmap.go ├── manager_ringbuffer.go ├── manager_test.go ├── map.go ├── map_route.go ├── mapspec_editor.go ├── netlink.go ├── pauser.go ├── perf_event.go ├── perfmap.go ├── pip.go ├── probe.go ├── raw_tp.go ├── ringbuffer.go ├── selectors.go ├── socket.go ├── state.go ├── syscalls.go ├── syscalls_test.go ├── sysfs.go ├── tailcall_route.go ├── tc.go ├── testdata ├── Makefile ├── common.h ├── exclude.c ├── exclude.elf ├── patching.c ├── patching.elf ├── rewrite.c └── rewrite.elf ├── tracefs.go ├── tracefs └── tracefs.go ├── tracefs_test.go ├── tracepoint.go ├── tracing.go ├── uprobe.go ├── utils.go └── xdp.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @DataDog/ebpf-platform 2 | 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > If this issue is reporting a bug, answer the following questions 2 | 3 | ### What happened ? 4 | 5 | ### What you expected to see ? 6 | 7 | ### Steps to reproduce the issue 8 | 9 | ### Additional environment details (Operating System, Cloud provider, etc) 10 | 11 | > If this issue is a feature request, answer the following questions 12 | 13 | ### What would you like the manager to do ? 14 | 15 | ### Is this an improvement of an existing feature ? If so, explain the limitation of the current feature. 16 | 17 | ### Is there a minimum kernel version required for this feature to work ? -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What does this PR do? 2 | 3 | A brief description of the change being made with this pull request. 4 | 5 | ### Motivation 6 | 7 | What inspired you to submit this pull request? 8 | 9 | ### Additional Notes 10 | 11 | Anything else we should know when reviewing? 12 | 13 | ### Describe how to test your changes 14 | 15 | Write here in detail how you have tested your changes and instructions on how this should be tested. 16 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 100 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push] 3 | jobs: 4 | test: 5 | name: "run tests" 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | go: [ "1.24", "1.25" ] 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-go@v5 14 | with: 15 | go-version: ${{ matrix.go }} 16 | - name: install dependencies 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y llvm 20 | clang --version 21 | llc --version 22 | 23 | - name: build examples 24 | working-directory: examples 25 | run: | 26 | TARGET=bin/main make all 27 | 28 | - name: golangci-lint 29 | uses: golangci/golangci-lint-action@v3 30 | with: 31 | skip-pkg-cache: true 32 | - name: staticcheck 33 | uses: dominikh/staticcheck-action@fe1dd0c3658873b46f8c9bb3291096a617310ca6 #v1.3.1 34 | with: 35 | version: "5af2e5fc3b08ba46027eb48ebddeba34dc0bd02c" #2025.1 36 | install-go: false 37 | cache-key: ${{ matrix.go }} 38 | 39 | - run: "go test -exec sudo ./..." 40 | -------------------------------------------------------------------------------- /.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 | # IDE directories 15 | .idea/ 16 | .vscode/ 17 | 18 | vendor/ 19 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - errcheck # errcheck is a program for checking for unchecked errors in go programs. 5 | - gosimple # simplify code 6 | - govet # report suspicious things 7 | - ineffassign # Detects when assignments to existing variables are not used 8 | - staticcheck 9 | - typecheck 10 | - unused # Checks Go code for unused constants, variables, functions and types 11 | - unconvert # Remove unnecessary type conversions 12 | - misspell # Finds commonly misspelled English words in comments 13 | - gofmt # Gofmt checks whether code was gofmt-ed 14 | - revive # Revive is a replacement for golint, a coding style checker 15 | 16 | linters-settings: 17 | revive: 18 | rules: 19 | - name: package-comments 20 | disabled: true -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Development is on [GitHub](https://github.com/DataDog/ebpf-manager) and contributions in 4 | the form of pull requests and issues reporting bugs or suggesting new features 5 | are welcome. 6 | 7 | New features must be accompanied by tests. Before starting work on any large 8 | feature, please submit an issue to discuss the design first. 9 | 10 | When submitting pull requests, consider writing details about what problem you 11 | are solving and why the proposed approach solves that problem in commit messages 12 | and/or pull request description to help future library users and maintainers to 13 | reason about the proposed changes. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Authors of Datadog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## eBPF Manager 2 | [![](https://godoc.org/github.com/DataDog/ebpf-manager?status.svg)](https://godoc.org/github.com/DataDog/ebpf-manager) 3 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/mit) 4 | 5 | This repository implements a manager on top of [Cilium's eBPF library](https://github.com/cilium/ebpf). This declarative manager simplifies attaching and detaching eBPF programs by controlling their entire life cycle. It was built with the intention of unifying how eBPF is used in large scale projects such as the [Datadog Agent](https://github.com/DataDog/datadog-agent). By using the same declarative conventions, multiple teams can quickly collaborate on complex eBPF programs by sharing maps, programs or even hook points without having to worry about the setup of complex program types. 6 | 7 | ### Requirements 8 | 9 | * A version of Go that is [supported by upstream](https://golang.org/doc/devel/release.html#policy) 10 | * Linux 4.4+ (some eBPF features are only available on newer kernel versions, see [eBPF features by Linux version](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)) 11 | 12 | ### Getting started 13 | 14 | You can find many examples using the manager in [examples/](https://github.com/DataDog/ebpf-manager/tree/main/examples). For a real world use case, check out the [Datadog Agent](https://github.com/DataDog/datadog-agent). 15 | 16 | ### Useful resources 17 | 18 | * [Cilium eBPF library](https://github.com/cilium/ebpf) 19 | * [Cilium eBPF documentation](https://cilium.readthedocs.io/en/latest/bpf/#bpf-guide) 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 | 23 | ## License 24 | 25 | - Unless explicitly specified otherwise, the golang code in this repository is under the MIT License. 26 | - The eBPF programs are under the GPL v2 License. -------------------------------------------------------------------------------- /cgroup.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf/link" 7 | ) 8 | 9 | // attachCGroup - Attaches the probe to a cgroup hook point 10 | func (p *Probe) attachCGroup() error { 11 | var err error 12 | p.progLink, err = link.AttachCgroup(link.CgroupOptions{ 13 | Path: p.CGroupPath, 14 | Attach: p.programSpec.AttachType, 15 | Program: p.program, 16 | }) 17 | if err != nil { 18 | return fmt.Errorf("cgroup link %s: %w", p.CGroupPath, err) 19 | } 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /constant_editor.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | ) 8 | 9 | // ConstantEditor - A constant editor tries to rewrite the value of a constant in a compiled eBPF program. 10 | // 11 | // Constant edition only works before the eBPF programs are loaded in the kernel, and therefore before the 12 | // Manager is started. If no program sections are provided, the manager will try to edit the constant in all eBPF programs. 13 | type ConstantEditor struct { 14 | // Name - Name of the constant to rewrite 15 | Name string 16 | 17 | // Value - Value to write in the eBPF bytecode. When using the asm load method, the Value has to be a `uint64`. 18 | Value interface{} 19 | 20 | // ValueCallback - Called to get the value to write in the eBPF bytecode 21 | ValueCallback func(prog *ebpf.ProgramSpec) interface{} 22 | 23 | // FailOnMissing - If FailOMissing is set to true, the constant edition process will return an error if the constant 24 | // was missing in at least one program 25 | FailOnMissing bool 26 | 27 | // BTFGlobalConstant - Indicates if the constant is a BTF global constant. 28 | BTFGlobalConstant bool 29 | 30 | // ProbeIdentificationPairs - Identifies the list of programs to edit. If empty, it will apply to all the programs 31 | // of the manager. Will return an error if at least one edition failed. 32 | ProbeIdentificationPairs []ProbeIdentificationPair 33 | } 34 | 35 | func (ce *ConstantEditor) GetValue(prog *ebpf.ProgramSpec) interface{} { 36 | if ce.ValueCallback != nil { 37 | return ce.ValueCallback(prog) 38 | } 39 | return ce.Value 40 | } 41 | 42 | // editConstants - newEditor the programs in the CollectionSpec with the provided constant editors. Tries with the BTF global 43 | // variable first, and fall back to the asm method if BTF is not available. 44 | func (m *Manager) editConstants() error { 45 | // Start with the BTF based solution 46 | rodata := m.collectionSpec.Maps[".rodata"] 47 | if rodata != nil && rodata.Key != nil { 48 | for _, editor := range m.options.ConstantEditors { 49 | if !editor.BTFGlobalConstant { 50 | continue 51 | } 52 | vs, ok := m.collectionSpec.Variables[editor.Name] 53 | if !ok { 54 | if editor.FailOnMissing { 55 | return fmt.Errorf("variable %s not found", editor.Name) 56 | } 57 | continue 58 | } 59 | if !vs.Constant() { 60 | return fmt.Errorf("variable %s is not a constant", editor.Name) 61 | } 62 | 63 | if err := vs.Set(editor.GetValue(nil)); err != nil { 64 | return fmt.Errorf("edit constant %s: %s", editor.Name, err) 65 | } 66 | } 67 | } 68 | 69 | // Fall back to the old school constant edition 70 | for _, constantEditor := range m.options.ConstantEditors { 71 | if constantEditor.BTFGlobalConstant { 72 | continue 73 | } 74 | 75 | // newEditor the constant of the provided programs 76 | for _, id := range constantEditor.ProbeIdentificationPairs { 77 | programs, found, err := m.GetProgramSpec(id) 78 | if err != nil { 79 | return err 80 | } 81 | if !found || len(programs) == 0 { 82 | return fmt.Errorf("couldn't find programSpec %v: %w", id, ErrUnknownSectionOrFuncName) 83 | } 84 | prog := programs[0] 85 | 86 | // newEditor program 87 | if err := m.editConstant(prog, constantEditor); err != nil { 88 | return fmt.Errorf("couldn't edit %s in %v: %w", constantEditor.Name, id, err) 89 | } 90 | } 91 | } 92 | 93 | // Apply to all programs if no section was provided 94 | for section, prog := range m.collectionSpec.Programs { 95 | var edit *editor 96 | for _, constantEditor := range m.options.ConstantEditors { 97 | if constantEditor.BTFGlobalConstant { 98 | continue 99 | } 100 | 101 | if len(constantEditor.ProbeIdentificationPairs) != 0 { 102 | continue 103 | } 104 | 105 | if edit == nil { 106 | edit = newEditor(&prog.Instructions) 107 | } 108 | 109 | if err := m.editConstantWithEditor(prog, edit, constantEditor); err != nil { 110 | return fmt.Errorf("couldn't edit %s in %s: %w", constantEditor.Name, section, err) 111 | } 112 | } 113 | } 114 | 115 | return nil 116 | } 117 | 118 | // editConstant - newEditor the provided program with the provided constant using the asm method. 119 | func (m *Manager) editConstant(prog *ebpf.ProgramSpec, editor ConstantEditor) error { 120 | edit := newEditor(&prog.Instructions) 121 | return m.editConstantWithEditor(prog, edit, editor) 122 | } 123 | 124 | func (m *Manager) editConstantWithEditor(prog *ebpf.ProgramSpec, edit *editor, editor ConstantEditor) error { 125 | data, ok := (editor.GetValue(prog)).(uint64) 126 | if !ok { 127 | return fmt.Errorf("with the asm method, the constant value has to be of type uint64") 128 | } 129 | if err := edit.RewriteConstant(editor.Name, data); err != nil { 130 | if editor.FailOnMissing && isUnreferencedSymbol(err) { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /editor.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/cilium/ebpf/asm" 8 | ) 9 | 10 | // editor modifies eBPF instructions. 11 | type editor struct { 12 | instructions *asm.Instructions 13 | ReferenceOffsets map[string][]int 14 | } 15 | 16 | // newEditor creates a new editor. 17 | // 18 | // The editor retains a reference to insns and modifies its 19 | // contents. 20 | func newEditor(insns *asm.Instructions) *editor { 21 | refs := insns.ReferenceOffsets() 22 | return &editor{insns, refs} 23 | } 24 | 25 | // RewriteConstant rewrites all loads of a symbol to a constant value. 26 | // 27 | // This is a way to parameterize clang-compiled eBPF byte code at load 28 | // time. 29 | // 30 | // The following macro should be used to access the constant: 31 | // 32 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 33 | // 34 | // int xdp() { 35 | // bool my_constant; 36 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 37 | // 38 | // if (my_constant) ... 39 | // 40 | // Caveats: 41 | // 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 fmt.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 | var ue *unreferencedSymbolError 79 | return errors.As(err, &ue) 80 | } 81 | -------------------------------------------------------------------------------- /editor_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/cilium/ebpf" 8 | "github.com/cilium/ebpf/asm" 9 | ) 10 | 11 | // ExampleEditor_rewriteConstant shows how to change constants in 12 | // compiled eBPF byte code. 13 | // 14 | // The C should look something like this: 15 | // 16 | // #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 17 | // 18 | // int xdp() { 19 | // bool my_constant; 20 | // LOAD_CONSTANT("SYMBOL_NAME", my_constant); 21 | // 22 | // if (my_constant) ... 23 | //func ExampleEditor_rewriteConstant() { 24 | // // This assembly is roughly equivalent to what clang 25 | // // would emit for the C above. 26 | // insns := asm.Instructions{ 27 | // asm.LoadImm(asm.R0, 0, asm.DWord).WithReference("my_ret"), 28 | // asm.Return(), 29 | // } 30 | // 31 | // editor := newEditor(&insns) 32 | // if err := editor.RewriteConstant("my_ret", 42); err != nil { 33 | // panic(err) 34 | // } 35 | // 36 | // fmt.Printf("%0.0s", insns) 37 | // 38 | // // Output: 0: LdImmDW dst: r0 imm: 42 39 | // // 2: Exit 40 | //} 41 | 42 | func TestEditorRewriteConstant(t *testing.T) { 43 | spec, err := ebpf.LoadCollectionSpec("testdata/rewrite.elf") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | progSpec := spec.Programs["rewrite"] 49 | editor := newEditor(&progSpec.Instructions) 50 | 51 | if err := editor.RewriteConstant("constant", 0x01); err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | if err := editor.RewriteConstant("bogus", 0x01); !isUnreferencedSymbol(err) { 56 | t.Error("Rewriting unreferenced symbol doesn't return appropriate error") 57 | } 58 | 59 | t.Log(progSpec.Instructions) 60 | 61 | prog, err := ebpf.NewProgram(progSpec) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | defer prog.Close() 66 | 67 | ret, _, err := prog.Test(make([]byte, 14)) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | const N = 1 // number of rewrites 73 | if expected := uint32(1< Enter to continue") 207 | _, _ = fmt.Scanln() 208 | 209 | if err := run2(); err != nil { 210 | return err 211 | } 212 | 213 | log.Println("=> Enter to continue") 214 | _, _ = fmt.Scanln() 215 | 216 | if err := run3(); err != nil { 217 | return err 218 | } 219 | return nil 220 | } 221 | 222 | func run1() error { 223 | if err := m1.InitWithOptions(bytes.NewReader(Probe), options1); err != nil { 224 | return err 225 | } 226 | defer func() { 227 | if err := m1.Stop(manager.CleanAll); err != nil { 228 | log.Print(err) 229 | } 230 | }() 231 | 232 | oldID := manager.ProbeIdentificationPair{ 233 | EBPFFuncName: "kprobe_exclude", 234 | UID: "", 235 | } 236 | newID := manager.ProbeIdentificationPair{ 237 | EBPFFuncName: "kprobe_exclude", 238 | UID: "new", 239 | } 240 | if err := m1.RenameProbeIdentificationPair(oldID, newID); err != nil { 241 | return err 242 | } 243 | _, ok := m1.GetProbe(newID) 244 | if !ok { 245 | return fmt.Errorf("RenameProbeIdentificationPair failed") 246 | } 247 | 248 | if err := m1.Start(); err != nil { 249 | return err 250 | } 251 | log.Println("m1 successfully started") 252 | 253 | // Create a folder to trigger the probes 254 | if err := trigger(); err != nil { 255 | log.Print(err) 256 | } 257 | return nil 258 | } 259 | 260 | func run2() error { 261 | log.Println("moving on to m2 (an error is expected)") 262 | if err := m2.InitWithOptions(bytes.NewReader(Probe), options2); err != nil { 263 | return err 264 | } 265 | defer func() { 266 | if err := m2.Stop(manager.CleanAll); err != nil { 267 | log.Print(err) 268 | } 269 | }() 270 | 271 | if err := m2.Start(); err != nil { 272 | log.Print(err) 273 | } 274 | return nil 275 | } 276 | 277 | func run3() error { 278 | log.Println("moving on to m3 (an error is expected)") 279 | if err := m3.Init(bytes.NewReader(Probe)); err != nil { 280 | return err 281 | } 282 | defer func() { 283 | if err := m3.Stop(manager.CleanAll); err != nil { 284 | log.Print(err) 285 | } 286 | }() 287 | 288 | if err := m3.Start(); err != nil { 289 | log.Print(err) 290 | } 291 | 292 | log.Println("updating activated probes of m3 (no error is expected)") 293 | if err := m3.Init(bytes.NewReader(Probe)); err != nil { 294 | return err 295 | } 296 | mkdirID := manager.ProbeIdentificationPair{UID: "MyVFSMkdir2", EBPFFuncName: "kprobe_vfs_mkdir"} 297 | if err := m3.UpdateActivatedProbes([]manager.ProbesSelector{ 298 | &manager.ProbeSelector{ 299 | ProbeIdentificationPair: mkdirID, 300 | }, 301 | }); err != nil { 302 | return err 303 | } 304 | 305 | vfsOpenID := manager.ProbeIdentificationPair{EBPFFuncName: "kprobe_vfs_opennnnnn"} 306 | vfsOpenProbe, ok := m3.GetProbe(vfsOpenID) 307 | if !ok { 308 | return fmt.Errorf("failed to find kprobe_vfs_opennnnnn") 309 | } 310 | 311 | if vfsOpenProbe.Enabled { 312 | return fmt.Errorf("kprobe_vfs_opennnnnn should not be enabled") 313 | } 314 | return nil 315 | } 316 | 317 | // trigger - Creates and then removes a tmp folder to trigger the probes 318 | func trigger() error { 319 | log.Println("Generating events to trigger the probes ...") 320 | tmpDir, err := os.MkdirTemp("", "example") 321 | if err != nil { 322 | return fmt.Errorf("mkdirtmp: %s", err) 323 | } 324 | log.Printf("removing %v", tmpDir) 325 | return os.RemoveAll(tmpDir) 326 | } 327 | -------------------------------------------------------------------------------- /examples/clone_vs_add_hook/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/clone_vs_add_hook/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | manager "github.com/DataDog/ebpf-manager" 7 | ) 8 | 9 | func demoClone() error { 10 | log.Println("CLONE DEMO") 11 | // Clone kprobe/vfs_open program, edit its constant and load a new probe. 12 | // This will essentially create a new program, and you should see a new line in /sys/kernel/debug/tracing/kprobe_events. 13 | newProbe := manager.Probe{ 14 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 15 | UID: "MySecondHook", 16 | EBPFFuncName: "kprobe_vfs_mkdir", 17 | }, 18 | } 19 | 20 | mkdirCloneEditors := []manager.ConstantEditor{ 21 | { 22 | Name: "my_constant", 23 | Value: uint64(42), 24 | ProbeIdentificationPairs: []manager.ProbeIdentificationPair{ 25 | newProbe.ProbeIdentificationPair, 26 | }, 27 | }, 28 | } 29 | 30 | err := m.CloneProgram("MyFirstHook", &newProbe, mkdirCloneEditors, nil) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return trigger() 36 | } 37 | 38 | func demoAddHook() error { 39 | log.Println("ADD HOOK DEMO") 40 | // Add a new hook point to the kprobe/vfs_mkdir program. The program was initially loaded but not attached. This will 41 | // not create a copy of the program, it will just add a new hook point. This can be donne multiple times. 42 | firstRmdir := manager.Probe{ 43 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 44 | UID: "FirstRmdir", 45 | EBPFFuncName: "kprobe_vfs_rmdir", 46 | }, 47 | } 48 | err := m.AddHook("", &firstRmdir) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | secondRmdir := manager.Probe{ 54 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 55 | UID: "SecondRmdir", 56 | EBPFFuncName: "kprobe_vfs_rmdir", 57 | }, 58 | } 59 | err = m.AddHook("", &secondRmdir) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | if err = trigger(); err != nil { 65 | return err 66 | } 67 | 68 | log.Println("DETACH HOOK DEMO") 69 | 70 | // Detaching a hook point does not close the underlying eBPF program, which means that the other hook points are 71 | // still working 72 | err = m.DetachHook(secondRmdir.ProbeIdentificationPair) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return trigger() 78 | } 79 | -------------------------------------------------------------------------------- /examples/clone_vs_add_hook/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 5 | 6 | __attribute__((always_inline)) static u64 load_my_constant() { 7 | u64 my_constant = 0; 8 | LOAD_CONSTANT("my_constant", my_constant); 9 | return my_constant; 10 | } 11 | 12 | struct bpf_map_def SEC("maps/my_constants") my_constants = { 13 | .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 14 | .key_size = 0, 15 | .value_size = 0, 16 | .max_entries = 0, 17 | }; 18 | 19 | SEC("kprobe/vfs_mkdir") 20 | int kprobe_vfs_mkdir(void *ctx) 21 | { 22 | u64 my_constant = load_my_constant(); 23 | bpf_printk("mkdir (vfs hook point) | my_constant = %d\n", load_my_constant()); 24 | 25 | // Send my constant to user space 26 | u32 cpu = bpf_get_smp_processor_id(); 27 | bpf_perf_event_output(ctx, &my_constants, cpu, &my_constant, sizeof(my_constant)); 28 | return 0; 29 | }; 30 | 31 | SEC("kprobe/vfs_rmdir") 32 | int kprobe_vfs_rmdir(void *ctx) 33 | { 34 | u64 my_constant = load_my_constant(); 35 | bpf_printk("rmdir (vfs hook point) | my_constant = %d\n", load_my_constant()); 36 | 37 | // Send my constant to user space 38 | u32 cpu = bpf_get_smp_processor_id(); 39 | bpf_perf_event_output(ctx, &my_constants, cpu, &my_constant, sizeof(my_constant)); 40 | return 0; 41 | }; 42 | 43 | SEC("kretprobe/mkdir") 44 | int kretprobe_mkdir(void *ctx) 45 | { 46 | bpf_printk("mkdir return (syscall hook point)\n"); 47 | return 0; 48 | } 49 | 50 | char _license[] SEC("license") = "GPL"; 51 | -------------------------------------------------------------------------------- /examples/clone_vs_add_hook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "encoding/binary" 7 | "fmt" 8 | "log" 9 | "os" 10 | "time" 11 | "unsafe" 12 | 13 | manager "github.com/DataDog/ebpf-manager" 14 | ) 15 | 16 | // ByteOrder - host byte order 17 | var ByteOrder binary.ByteOrder 18 | 19 | func init() { 20 | ByteOrder = getHostByteOrder() 21 | } 22 | 23 | // getHostByteOrder - Returns the host byte order 24 | func getHostByteOrder() binary.ByteOrder { 25 | var i int32 = 0x01020304 26 | u := unsafe.Pointer(&i) 27 | pb := (*byte)(u) 28 | b := *pb 29 | if b == 0x04 { 30 | return binary.LittleEndian 31 | } 32 | 33 | return binary.BigEndian 34 | } 35 | 36 | //go:embed ebpf/bin/main.o 37 | var Probe []byte 38 | 39 | var m = &manager.Manager{ 40 | Probes: []*manager.Probe{ 41 | { 42 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 43 | UID: "MyFirstHook", 44 | EBPFFuncName: "kprobe_vfs_mkdir", 45 | }, 46 | KeepProgramSpec: true, 47 | }, 48 | { 49 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 50 | UID: "", 51 | EBPFFuncName: "kretprobe_mkdir", 52 | }, 53 | SyscallFuncName: "mkdir", 54 | KProbeMaxActive: 100, 55 | }, 56 | }, 57 | PerfMaps: []*manager.PerfMap{ 58 | { 59 | Map: manager.Map{ 60 | Name: "my_constants", 61 | }, 62 | PerfMapOptions: manager.PerfMapOptions{ 63 | DataHandler: myDataHandler, 64 | }, 65 | }, 66 | }, 67 | } 68 | 69 | // myDataHandler - Perf event data handler 70 | func myDataHandler(cpu int, data []byte, _ *manager.PerfMap, _ *manager.Manager) { 71 | myConstant := ByteOrder.Uint64(data[0:8]) 72 | log.Printf("received: CPU:%d my_constant:%d", cpu, myConstant) 73 | } 74 | 75 | var editors = []manager.ConstantEditor{ 76 | { 77 | Name: "my_constant", 78 | Value: uint64(100), 79 | FailOnMissing: true, 80 | ProbeIdentificationPairs: []manager.ProbeIdentificationPair{ 81 | {UID: "MyFirstHook", EBPFFuncName: "kprobe_vfs_mkdir"}, 82 | }, 83 | }, 84 | { 85 | Name: "my_constant", 86 | Value: uint64(555), 87 | FailOnMissing: true, 88 | ProbeIdentificationPairs: []manager.ProbeIdentificationPair{ 89 | {UID: "", EBPFFuncName: "kprobe_vfs_mkdir"}, 90 | }, 91 | }, 92 | { 93 | Name: "unused_constant", 94 | Value: uint64(555), 95 | ProbeIdentificationPairs: []manager.ProbeIdentificationPair{}, 96 | }, 97 | } 98 | 99 | func main() { 100 | if err := run(); err != nil { 101 | log.Fatal(err) 102 | } 103 | } 104 | 105 | func run() error { 106 | options := manager.Options{ 107 | ConstantEditors: editors, 108 | KeepUnmappedProgramSpecs: true, 109 | } 110 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 111 | return err 112 | } 113 | defer func() { 114 | if err := m.Stop(manager.CleanAll); err != nil { 115 | log.Print(err) 116 | } 117 | }() 118 | 119 | if err := m.Start(); err != nil { 120 | return err 121 | } 122 | log.Println("eBPF programs running, head over to /sys/kernel/debug/tracing/trace_pipe to see them in action.") 123 | 124 | // Demo 125 | log.Println("INITIAL PROGRAMS") 126 | if err := trigger(); err != nil { 127 | return err 128 | } 129 | if err := demoClone(); err != nil { 130 | return err 131 | } 132 | if err := demoAddHook(); err != nil { 133 | return err 134 | } 135 | return nil 136 | } 137 | 138 | // trigger - Creates and then removes a tmp folder to trigger the probes 139 | func trigger() error { 140 | log.Println("Generating events to trigger the probes ...") 141 | tmpDir, err := os.MkdirTemp("", "example") 142 | if err != nil { 143 | return fmt.Errorf("mkdirtmp: %s", err) 144 | } 145 | // Sleep a bit to give time to the perf event 146 | time.Sleep(500 * time.Millisecond) 147 | 148 | log.Printf("removing %v", tmpDir) 149 | err = os.RemoveAll(tmpDir) 150 | if err != nil { 151 | return fmt.Errorf("rmdir: %s", err) 152 | } 153 | 154 | // Sleep a bit to give time to the perf event 155 | time.Sleep(500 * time.Millisecond) 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /examples/constant_editor/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/constant_editor/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | 5 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 6 | 7 | SEC("kprobe/vfs_mkdir") 8 | int BPF_KPROBE(kprobe_vfs_mkdir, struct user_namespace *mnt_userns) 9 | { 10 | u64 my_constant_var = 0; 11 | LOAD_CONSTANT("my_constant", my_constant_var); 12 | bpf_printk("my_constant: %d\n", my_constant_var); 13 | return 0; 14 | }; 15 | 16 | char _license[] SEC("license") = "GPL"; 17 | -------------------------------------------------------------------------------- /examples/constant_editor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | manager "github.com/DataDog/ebpf-manager" 11 | ) 12 | 13 | //go:embed ebpf/bin/main.o 14 | var Probe []byte 15 | 16 | var m = &manager.Manager{ 17 | Probes: []*manager.Probe{ 18 | { 19 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 20 | UID: "MyVFSMkdir", 21 | EBPFFuncName: "kprobe_vfs_mkdir", 22 | }, 23 | }, 24 | }, 25 | } 26 | 27 | func main() { 28 | if err := run(); err != nil { 29 | log.Fatal(err) 30 | } 31 | } 32 | 33 | func run() error { 34 | options := manager.Options{ 35 | ConstantEditors: []manager.ConstantEditor{ 36 | { 37 | Name: "my_constant", 38 | Value: uint64(123), 39 | }, 40 | }, 41 | } 42 | 43 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 44 | return err 45 | } 46 | defer func() { 47 | if err := m.Stop(manager.CleanAll); err != nil { 48 | log.Print(err) 49 | } 50 | }() 51 | if err := m.Start(); err != nil { 52 | return err 53 | } 54 | 55 | log.Println("successfully started, check out the value of the edited constant in /sys/kernel/debug/tracing/trace_pipe") 56 | 57 | // Create a folder to trigger the probes 58 | if err := trigger(); err != nil { 59 | log.Print(err) 60 | } 61 | 62 | log.Println("=> Enter to continue") 63 | _, _ = fmt.Scanln() 64 | return nil 65 | } 66 | 67 | // trigger - Creates and then removes a tmp folder to trigger the probes 68 | func trigger() error { 69 | log.Println("Generating events to trigger the probes ...") 70 | tmpDir, err := os.MkdirTemp("", "example") 71 | if err != nil { 72 | return fmt.Errorf("mkdirtmp: %s", err) 73 | } 74 | log.Printf("removing %v", tmpDir) 75 | return os.RemoveAll(tmpDir) 76 | } 77 | -------------------------------------------------------------------------------- /examples/include/all.h: -------------------------------------------------------------------------------- 1 | #ifndef _ALL_H__ 2 | #define _ALL_H__ 3 | 4 | #include "kernel.h" 5 | #include "bpf_endian.h" 6 | #include "bpf_helpers.h" 7 | #include "bpf_tracing.h" 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /examples/include/asm_goto_workaround.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | /* Copyright (c) 2019 Facebook */ 3 | #ifndef __ASM_GOTO_WORKAROUND_H 4 | #define __ASM_GOTO_WORKAROUND_H 5 | 6 | /* 7 | * This will bring in asm_volatile_goto and asm_inline macro definitions 8 | * if enabled by compiler and config options. 9 | */ 10 | #include 11 | 12 | #ifdef asm_volatile_goto 13 | #undef asm_volatile_goto 14 | #define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") 15 | #endif 16 | 17 | /* 18 | * asm_inline is defined as asm __inline in "include/linux/compiler_types.h" 19 | * if supported by the kernel's CC (i.e CONFIG_CC_HAS_ASM_INLINE) which is not 20 | * supported by CLANG. 21 | */ 22 | #ifdef asm_inline 23 | #undef asm_inline 24 | #define asm_inline asm 25 | #endif 26 | 27 | #define volatile(x...) volatile("") 28 | #endif 29 | -------------------------------------------------------------------------------- /examples/include/bpf_endian.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | #ifndef __BPF_ENDIAN__ 3 | #define __BPF_ENDIAN__ 4 | 5 | /* 6 | * Isolate byte #n and put it into byte #m, for __u##b type. 7 | * E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64: 8 | * 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 9 | * 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000 10 | * 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 11 | * 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000 12 | */ 13 | #define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8)) 14 | 15 | #define ___bpf_swab16(x) ((__u16)( \ 16 | ___bpf_mvb(x, 16, 0, 1) | \ 17 | ___bpf_mvb(x, 16, 1, 0))) 18 | 19 | #define ___bpf_swab32(x) ((__u32)( \ 20 | ___bpf_mvb(x, 32, 0, 3) | \ 21 | ___bpf_mvb(x, 32, 1, 2) | \ 22 | ___bpf_mvb(x, 32, 2, 1) | \ 23 | ___bpf_mvb(x, 32, 3, 0))) 24 | 25 | #define ___bpf_swab64(x) ((__u64)( \ 26 | ___bpf_mvb(x, 64, 0, 7) | \ 27 | ___bpf_mvb(x, 64, 1, 6) | \ 28 | ___bpf_mvb(x, 64, 2, 5) | \ 29 | ___bpf_mvb(x, 64, 3, 4) | \ 30 | ___bpf_mvb(x, 64, 4, 3) | \ 31 | ___bpf_mvb(x, 64, 5, 2) | \ 32 | ___bpf_mvb(x, 64, 6, 1) | \ 33 | ___bpf_mvb(x, 64, 7, 0))) 34 | 35 | /* LLVM's BPF target selects the endianness of the CPU 36 | * it compiles on, or the user specifies (bpfel/bpfeb), 37 | * respectively. The used __BYTE_ORDER__ is defined by 38 | * the compiler, we cannot rely on __BYTE_ORDER from 39 | * libc headers, since it doesn't reflect the actual 40 | * requested byte order. 41 | * 42 | * Note, LLVM's BPF target has different __builtin_bswapX() 43 | * semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE 44 | * in bpfel and bpfeb case, which means below, that we map 45 | * to cpu_to_be16(). We could use it unconditionally in BPF 46 | * case, but better not rely on it, so that this header here 47 | * can be used from application and BPF program side, which 48 | * use different targets. 49 | */ 50 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 51 | # define __bpf_ntohs(x) __builtin_bswap16(x) 52 | # define __bpf_htons(x) __builtin_bswap16(x) 53 | # define __bpf_constant_ntohs(x) ___bpf_swab16(x) 54 | # define __bpf_constant_htons(x) ___bpf_swab16(x) 55 | # define __bpf_ntohl(x) __builtin_bswap32(x) 56 | # define __bpf_htonl(x) __builtin_bswap32(x) 57 | # define __bpf_constant_ntohl(x) ___bpf_swab32(x) 58 | # define __bpf_constant_htonl(x) ___bpf_swab32(x) 59 | # define __bpf_be64_to_cpu(x) __builtin_bswap64(x) 60 | # define __bpf_cpu_to_be64(x) __builtin_bswap64(x) 61 | # define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x) 62 | # define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x) 63 | #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 64 | # define __bpf_ntohs(x) (x) 65 | # define __bpf_htons(x) (x) 66 | # define __bpf_constant_ntohs(x) (x) 67 | # define __bpf_constant_htons(x) (x) 68 | # define __bpf_ntohl(x) (x) 69 | # define __bpf_htonl(x) (x) 70 | # define __bpf_constant_ntohl(x) (x) 71 | # define __bpf_constant_htonl(x) (x) 72 | # define __bpf_be64_to_cpu(x) (x) 73 | # define __bpf_cpu_to_be64(x) (x) 74 | # define __bpf_constant_be64_to_cpu(x) (x) 75 | # define __bpf_constant_cpu_to_be64(x) (x) 76 | #else 77 | # error "Fix your compiler's __BYTE_ORDER__?!" 78 | #endif 79 | 80 | #define bpf_htons(x) \ 81 | (__builtin_constant_p(x) ? \ 82 | __bpf_constant_htons(x) : __bpf_htons(x)) 83 | #define bpf_ntohs(x) \ 84 | (__builtin_constant_p(x) ? \ 85 | __bpf_constant_ntohs(x) : __bpf_ntohs(x)) 86 | #define bpf_htonl(x) \ 87 | (__builtin_constant_p(x) ? \ 88 | __bpf_constant_htonl(x) : __bpf_htonl(x)) 89 | #define bpf_ntohl(x) \ 90 | (__builtin_constant_p(x) ? \ 91 | __bpf_constant_ntohl(x) : __bpf_ntohl(x)) 92 | #define bpf_cpu_to_be64(x) \ 93 | (__builtin_constant_p(x) ? \ 94 | __bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x)) 95 | #define bpf_be64_to_cpu(x) \ 96 | (__builtin_constant_p(x) ? \ 97 | __bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x)) 98 | 99 | #endif /* __BPF_ENDIAN__ */ 100 | -------------------------------------------------------------------------------- /examples/include/bpf_helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ 2 | #ifndef __BPF_HELPERS__ 3 | #define __BPF_HELPERS__ 4 | 5 | /* 6 | * Note that bpf programs need to include either 7 | * vmlinux.h (auto-generated from BTF) or linux/types.h 8 | * in advance since bpf_helper_defs.h uses such types 9 | * as __u64. 10 | */ 11 | #include "bpf_helper_defs.h" 12 | 13 | #define __uint(name, val) int (*name)[val] 14 | #define __type(name, val) typeof(val) *name 15 | #define __array(name, val) typeof(val) *name[] 16 | 17 | /* 18 | * Helper macro to place programs, maps, license in 19 | * different sections in elf_bpf file. Section names 20 | * are interpreted by libbpf depending on the context (BPF programs, BPF maps, 21 | * extern variables, etc). 22 | * To allow use of SEC() with externs (e.g., for extern .maps declarations), 23 | * make sure __attribute__((unused)) doesn't trigger compilation warning. 24 | */ 25 | #define SEC(name) \ 26 | _Pragma("GCC diagnostic push") \ 27 | _Pragma("GCC diagnostic ignored \"-Wignored-attributes\"") \ 28 | __attribute__((section(name), used)) \ 29 | _Pragma("GCC diagnostic pop") \ 30 | 31 | /* Avoid 'linux/stddef.h' definition of '__always_inline'. */ 32 | #undef __always_inline 33 | #define __always_inline inline __attribute__((always_inline)) 34 | 35 | #ifndef __noinline 36 | #define __noinline __attribute__((noinline)) 37 | #endif 38 | #ifndef __weak 39 | #define __weak __attribute__((weak)) 40 | #endif 41 | 42 | /* 43 | * Use __hidden attribute to mark a non-static BPF subprogram effectively 44 | * static for BPF verifier's verification algorithm purposes, allowing more 45 | * extensive and permissive BPF verification process, taking into account 46 | * subprogram's caller context. 47 | */ 48 | #define __hidden __attribute__((visibility("hidden"))) 49 | 50 | /* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include 51 | * any system-level headers (such as stddef.h, linux/version.h, etc), and 52 | * commonly-used macros like NULL and KERNEL_VERSION aren't available through 53 | * vmlinux.h. This just adds unnecessary hurdles and forces users to re-define 54 | * them on their own. So as a convenience, provide such definitions here. 55 | */ 56 | #ifndef NULL 57 | #define NULL ((void *)0) 58 | #endif 59 | 60 | #ifndef KERNEL_VERSION 61 | #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c))) 62 | #endif 63 | 64 | /* 65 | * Helper macros to manipulate data structures 66 | */ 67 | #ifndef offsetof 68 | #define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER) 69 | #endif 70 | #ifndef container_of 71 | #define container_of(ptr, type, member) \ 72 | ({ \ 73 | void *__mptr = (void *)(ptr); \ 74 | ((type *)(__mptr - offsetof(type, member))); \ 75 | }) 76 | #endif 77 | 78 | /* 79 | * Helper macro to throw a compilation error if __bpf_unreachable() gets 80 | * built into the resulting code. This works given BPF back end does not 81 | * implement __builtin_trap(). This is useful to assert that certain paths 82 | * of the program code are never used and hence eliminated by the compiler. 83 | * 84 | * For example, consider a switch statement that covers known cases used by 85 | * the program. __bpf_unreachable() can then reside in the default case. If 86 | * the program gets extended such that a case is not covered in the switch 87 | * statement, then it will throw a build error due to the default case not 88 | * being compiled out. 89 | */ 90 | #ifndef __bpf_unreachable 91 | # define __bpf_unreachable() __builtin_trap() 92 | #endif 93 | 94 | /* 95 | * Helper function to perform a tail call with a constant/immediate map slot. 96 | */ 97 | #if __clang_major__ >= 8 && defined(__bpf__) 98 | static __always_inline void 99 | bpf_tail_call_static(void *ctx, const void *map, const __u32 slot) 100 | { 101 | if (!__builtin_constant_p(slot)) 102 | __bpf_unreachable(); 103 | 104 | /* 105 | * Provide a hard guarantee that LLVM won't optimize setting r2 (map 106 | * pointer) and r3 (constant map index) from _different paths_ ending 107 | * up at the _same_ call insn as otherwise we won't be able to use the 108 | * jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel 109 | * given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key 110 | * tracking for prog array pokes") for details on verifier tracking. 111 | * 112 | * Note on clobber list: we need to stay in-line with BPF calling 113 | * convention, so even if we don't end up using r0, r4, r5, we need 114 | * to mark them as clobber so that LLVM doesn't end up using them 115 | * before / after the call. 116 | */ 117 | asm volatile("r1 = %[ctx]\n\t" 118 | "r2 = %[map]\n\t" 119 | "r3 = %[slot]\n\t" 120 | "call 12" 121 | :: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot) 122 | : "r0", "r1", "r2", "r3", "r4", "r5"); 123 | } 124 | #endif 125 | 126 | /* 127 | * Helper structure used by eBPF C program 128 | * to describe BPF map attributes to libbpf loader 129 | */ 130 | struct bpf_map_def { 131 | unsigned int type; 132 | unsigned int key_size; 133 | unsigned int value_size; 134 | unsigned int max_entries; 135 | unsigned int map_flags; 136 | }; 137 | 138 | enum libbpf_pin_type { 139 | LIBBPF_PIN_NONE, 140 | /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */ 141 | LIBBPF_PIN_BY_NAME, 142 | }; 143 | 144 | enum libbpf_tristate { 145 | TRI_NO = 0, 146 | TRI_YES = 1, 147 | TRI_MODULE = 2, 148 | }; 149 | 150 | #define __kconfig __attribute__((section(".kconfig"))) 151 | #define __ksym __attribute__((section(".ksyms"))) 152 | 153 | #ifndef ___bpf_concat 154 | #define ___bpf_concat(a, b) a ## b 155 | #endif 156 | #ifndef ___bpf_apply 157 | #define ___bpf_apply(fn, n) ___bpf_concat(fn, n) 158 | #endif 159 | #ifndef ___bpf_nth 160 | #define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N 161 | #endif 162 | #ifndef ___bpf_narg 163 | #define ___bpf_narg(...) \ 164 | ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 165 | #endif 166 | 167 | #define ___bpf_fill0(arr, p, x) do {} while (0) 168 | #define ___bpf_fill1(arr, p, x) arr[p] = x 169 | #define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args) 170 | #define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args) 171 | #define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args) 172 | #define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args) 173 | #define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args) 174 | #define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args) 175 | #define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args) 176 | #define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args) 177 | #define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args) 178 | #define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args) 179 | #define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args) 180 | #define ___bpf_fill(arr, args...) \ 181 | ___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args) 182 | 183 | /* 184 | * BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values 185 | * in a structure. 186 | */ 187 | #define BPF_SEQ_PRINTF(seq, fmt, args...) \ 188 | ({ \ 189 | static const char ___fmt[] = fmt; \ 190 | unsigned long long ___param[___bpf_narg(args)]; \ 191 | \ 192 | _Pragma("GCC diagnostic push") \ 193 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ 194 | ___bpf_fill(___param, args); \ 195 | _Pragma("GCC diagnostic pop") \ 196 | \ 197 | bpf_seq_printf(seq, ___fmt, sizeof(___fmt), \ 198 | ___param, sizeof(___param)); \ 199 | }) 200 | 201 | /* 202 | * BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of 203 | * an array of u64. 204 | */ 205 | #define BPF_SNPRINTF(out, out_size, fmt, args...) \ 206 | ({ \ 207 | static const char ___fmt[] = fmt; \ 208 | unsigned long long ___param[___bpf_narg(args)]; \ 209 | \ 210 | _Pragma("GCC diagnostic push") \ 211 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ 212 | ___bpf_fill(___param, args); \ 213 | _Pragma("GCC diagnostic pop") \ 214 | \ 215 | bpf_snprintf(out, out_size, ___fmt, \ 216 | ___param, sizeof(___param)); \ 217 | }) 218 | 219 | #ifdef BPF_NO_GLOBAL_DATA 220 | #define BPF_PRINTK_FMT_MOD 221 | #else 222 | #define BPF_PRINTK_FMT_MOD static const 223 | #endif 224 | 225 | #define __bpf_printk(fmt, ...) \ 226 | ({ \ 227 | BPF_PRINTK_FMT_MOD char ____fmt[] = fmt; \ 228 | bpf_trace_printk(____fmt, sizeof(____fmt), \ 229 | ##__VA_ARGS__); \ 230 | }) 231 | 232 | /* 233 | * __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments 234 | * instead of an array of u64. 235 | */ 236 | #define __bpf_vprintk(fmt, args...) \ 237 | ({ \ 238 | static const char ___fmt[] = fmt; \ 239 | unsigned long long ___param[___bpf_narg(args)]; \ 240 | \ 241 | _Pragma("GCC diagnostic push") \ 242 | _Pragma("GCC diagnostic ignored \"-Wint-conversion\"") \ 243 | ___bpf_fill(___param, args); \ 244 | _Pragma("GCC diagnostic pop") \ 245 | \ 246 | bpf_trace_vprintk(___fmt, sizeof(___fmt), \ 247 | ___param, sizeof(___param)); \ 248 | }) 249 | 250 | /* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args 251 | * Otherwise use __bpf_vprintk 252 | */ 253 | #define ___bpf_pick_printk(...) \ 254 | ___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \ 255 | __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, \ 256 | __bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\ 257 | __bpf_printk /*1*/, __bpf_printk /*0*/) 258 | 259 | /* Helper macro to print out debug messages */ 260 | #define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args) 261 | 262 | #endif 263 | -------------------------------------------------------------------------------- /examples/include/kernel.h: -------------------------------------------------------------------------------- 1 | #ifndef _KERNEL_H__ 2 | #define _KERNEL_H__ 3 | 4 | #pragma clang diagnostic push 5 | #pragma clang diagnostic ignored "-Waddress-of-packed-member" 6 | #pragma clang diagnostic ignored "-Warray-bounds" 7 | #pragma clang diagnostic ignored "-Wunused-label" 8 | #pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" 9 | #pragma clang diagnostic ignored "-Wframe-address" 10 | 11 | #include 12 | // include asm/compiler.h to fix `error: expected string literal in 'asm'` compilation error coming from mte-kasan.h 13 | // this was fixed in https://github.com/torvalds/linux/commit/b859ebedd1e730bbda69142fca87af4e712649a1 14 | #ifdef CONFIG_HAVE_ARCH_COMPILER_H 15 | #include 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #pragma clang diagnostic pop 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /examples/instruction_patching/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/instruction_patching/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | char _license[] SEC("license") = "GPL"; 4 | 5 | 6 | static void *(*bpf_patch)(unsigned long,...) = (void *)-1; 7 | 8 | SEC("kprobe/security_socket_create") 9 | int kprobe__security_socket_create(struct pt_regs* ctx) { 10 | int ret = 0; 11 | bpf_patch(ret); 12 | return 1; 13 | } 14 | -------------------------------------------------------------------------------- /examples/instruction_patching/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | _ "embed" 7 | "fmt" 8 | "log" 9 | "os" 10 | 11 | "github.com/cilium/ebpf" 12 | "github.com/cilium/ebpf/asm" 13 | 14 | manager "github.com/DataDog/ebpf-manager" 15 | ) 16 | 17 | //go:embed ebpf/bin/main.o 18 | var Probe []byte 19 | 20 | var m1 = &manager.Manager{ 21 | Probes: []*manager.Probe{ 22 | { 23 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 24 | EBPFFuncName: "kprobe__security_socket_create", 25 | }, 26 | }, 27 | }, 28 | InstructionPatchers: []manager.InstructionPatcherFunc{patchBPFTelemetry}, 29 | } 30 | 31 | const BPFTelemetryPatchCall = -1 32 | 33 | func getAllProgramSpecs(m *manager.Manager) ([]*ebpf.ProgramSpec, error) { 34 | var specs []*ebpf.ProgramSpec 35 | for _, p := range m.Probes { 36 | s, present, err := m.GetProgramSpec(p.ProbeIdentificationPair) 37 | if err != nil { 38 | return nil, err 39 | } 40 | if !present { 41 | return nil, fmt.Errorf("could not find ProgramSpec for probe %v", p.ProbeIdentificationPair) 42 | } 43 | 44 | specs = append(specs, s...) 45 | } 46 | 47 | return specs, nil 48 | } 49 | 50 | func patchBPFTelemetry(m *manager.Manager) error { 51 | specs, err := getAllProgramSpecs(m) 52 | if err != nil { 53 | return err 54 | } 55 | for _, spec := range specs { 56 | if spec == nil { 57 | continue 58 | } 59 | iter := spec.Instructions.Iterate() 60 | for iter.Next() { 61 | ins := iter.Ins 62 | 63 | if !ins.IsBuiltinCall() { 64 | continue 65 | } 66 | 67 | if ins.Constant != BPFTelemetryPatchCall { 68 | continue 69 | } 70 | *ins = asm.Mov.Imm(asm.R1, int32(0xff)).WithMetadata(ins.Metadata) 71 | } 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func main() { 78 | if err := run(); err != nil { 79 | log.Fatal(err) 80 | } 81 | } 82 | 83 | func run() error { 84 | if err := m1.Init(bytes.NewReader(Probe)); err != nil { 85 | return err 86 | } 87 | defer func() { 88 | if err := m1.Stop(manager.CleanAll); err != nil { 89 | log.Print(err) 90 | } 91 | }() 92 | 93 | if err := m1.Start(); err != nil { 94 | return err 95 | } 96 | 97 | log.Println("=> Use 'bpftool prog dump xlated id ' to verify that the instruction has been patched") 98 | log.Println("=> Enter to exit") 99 | _, _ = bufio.NewReader(os.Stdin).ReadBytes('\n') 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /examples/map_rewrite_vs_map_router/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/map_rewrite_vs_map_router/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cilium/ebpf" 9 | 10 | manager "github.com/DataDog/ebpf-manager" 11 | ) 12 | 13 | func demoMapEditor() error { 14 | log.Println("MAP EDITOR DEMO") 15 | // Select the shared map to give it to m2 16 | sharedCache1, found, err := m1.GetMap("shared_cache1") 17 | if err != nil || !found { 18 | return fmt.Errorf("couldn't find shared_cache1 in m1: %w", err) 19 | } 20 | if err = dumpSharedMap(sharedCache1); err != nil { 21 | return err 22 | } 23 | 24 | // Give shared_cache1 to m2 through a map editor 25 | options := manager.Options{ 26 | MapEditors: map[string]*ebpf.Map{ 27 | "shared_cache1": sharedCache1, 28 | }, 29 | // The following parameter is NOT used by the MapEditor demo, but is required to load m2 since m2 has a 30 | // BPF_MAP_TYPE_HASH_OF_MAPS map. 31 | InnerOuterMapSpecs: []manager.InnerOuterMapSpec{ 32 | { 33 | OuterMapName: "maps_router", 34 | InnerMapName: "routed_cache", 35 | }, 36 | }, 37 | } 38 | // Initialize m2, edit shared_cache1 and start it 39 | if err = m2.InitWithOptions(bytes.NewReader(Probe2), options); err != nil { 40 | return fmt.Errorf("couldn't init m2: %w", err) 41 | } 42 | if err = m2.Start(); err != nil { 43 | return err 44 | } 45 | if err = trigger(); err != nil { 46 | return err 47 | } 48 | return dumpSharedMap(sharedCache1) 49 | } 50 | 51 | func demoMapRouter() error { 52 | log.Println("MAP ROUTER DEMO") 53 | // Select the shared map to give it to m2 54 | sharedCache2, found, err := m1.GetMap("shared_cache2") 55 | if err != nil || !found { 56 | return fmt.Errorf("couldn't find shared_cache2 in m1: %w", err) 57 | } 58 | if err = dumpSharedMap(sharedCache2); err != nil { 59 | return err 60 | } 61 | 62 | // Give shared_cache2 to m2 through a map router 63 | router := manager.MapRoute{ 64 | RoutingMapName: "maps_router", 65 | Key: uint32(1), 66 | Map: sharedCache2, 67 | } 68 | if err := m2.UpdateMapRoutes(router); err != nil { 69 | return err 70 | } 71 | 72 | if err = trigger(); err != nil { 73 | return err 74 | } 75 | return dumpSharedMap(sharedCache2) 76 | } 77 | -------------------------------------------------------------------------------- /examples/map_rewrite_vs_map_router/ebpf/prog1.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | // shared_cache - This map will be shared with other Manager 5 | struct bpf_map_def SEC("maps/shared_cache1") shared_cache1 = { 6 | .type = BPF_MAP_TYPE_HASH, 7 | .key_size = sizeof(u32), 8 | .value_size = sizeof(u32), 9 | .max_entries = 10, 10 | }; 11 | 12 | // shared_cache2 - This map will be shared with other Manager 13 | struct bpf_map_def SEC("maps/shared_cache2") shared_cache2 = { 14 | .type = BPF_MAP_TYPE_HASH, 15 | .key_size = sizeof(u32), 16 | .value_size = sizeof(u32), 17 | .max_entries = 10, 18 | }; 19 | 20 | SEC("kretprobe/vfs_mkdir") 21 | int kretprobe_vfs_mkdir(void *ctx) 22 | { 23 | // retrieve the value saved in the cache at key 1 24 | u32 key = 1; 25 | u32 *value = bpf_map_lookup_elem(&shared_cache1, &key); 26 | if (!value) { 27 | bpf_printk("(prog1) shared_cache1 is empty\n"); 28 | } else { 29 | bpf_printk("(prog1) shared_cache1 contains %u\n", *value); 30 | } 31 | 32 | value = bpf_map_lookup_elem(&shared_cache2, &key); 33 | if (!value) { 34 | bpf_printk("(prog1) shared_cache2 is empty\n"); 35 | } else { 36 | bpf_printk("(prog1) shared_cache2 contains %u\n", *value); 37 | } 38 | return 0; 39 | }; 40 | 41 | char _license[] SEC("license") = "GPL"; 42 | -------------------------------------------------------------------------------- /examples/map_rewrite_vs_map_router/ebpf/prog2.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | // shared_cache - This map will be shared with other Manager 5 | struct bpf_map_def SEC("maps/shared_cache1") shared_cache1 = { 6 | .type = BPF_MAP_TYPE_HASH, 7 | .key_size = sizeof(u32), 8 | .value_size = sizeof(u32), 9 | .max_entries = 10, 10 | }; 11 | 12 | /** 13 | * routed_cache is used to define the types of maps that are expected in maps_router 14 | * WARNING: it has to be the first map defined in the `maps/maps_router` 15 | * section since it is referred to as map #0 in maps_router. 16 | */ 17 | struct bpf_map_def SEC("maps/routed_cache") routed_cache = { 18 | .type = BPF_MAP_TYPE_HASH, 19 | .key_size = sizeof(u32), 20 | .value_size = sizeof(u32), 21 | .max_entries = 10, 22 | }; 23 | 24 | struct bpf_map_def SEC("maps/maps_router") maps_router = { 25 | .type = BPF_MAP_TYPE_HASH_OF_MAPS, 26 | .key_size = sizeof(u32), 27 | .max_entries = 10, 28 | }; 29 | 30 | SEC("kprobe/vfs_mkdir") 31 | int kprobe_vfs_mkdir(void *ctx) 32 | { 33 | bpf_printk("(prog2) writing 42 in shared_cache1 at key 1 ...\n"); 34 | // Update the shared cache 35 | u32 key = 1; 36 | u32 val = 42; 37 | bpf_map_update_elem(&shared_cache1, &key, &val, BPF_ANY); 38 | 39 | // Update the routed map 40 | val = 500; 41 | void *routed_map = bpf_map_lookup_elem(&maps_router, &key); 42 | if (routed_map == NULL) 43 | { 44 | return 0; 45 | } 46 | bpf_printk("(prog2) writing 500 in router_map at key 1 ...\n"); 47 | bpf_map_update_elem(routed_map, &key, &val, BPF_ANY); 48 | return 0; 49 | }; 50 | 51 | char _license[] SEC("license") = "GPL"; 52 | -------------------------------------------------------------------------------- /examples/map_rewrite_vs_map_router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | "github.com/cilium/ebpf" 11 | 12 | manager "github.com/DataDog/ebpf-manager" 13 | ) 14 | 15 | //go:embed ebpf/bin/prog1.o 16 | var Probe1 []byte 17 | 18 | //go:embed ebpf/bin/prog2.o 19 | var Probe2 []byte 20 | 21 | var m1 = &manager.Manager{ 22 | Probes: []*manager.Probe{ 23 | { 24 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 25 | EBPFFuncName: "kretprobe_vfs_mkdir", 26 | }, 27 | }, 28 | }, 29 | } 30 | 31 | var m2 = &manager.Manager{ 32 | Probes: []*manager.Probe{ 33 | { 34 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 35 | EBPFFuncName: "kprobe_vfs_mkdir", 36 | }, 37 | }, 38 | }, 39 | } 40 | 41 | func main() { 42 | if err := run(); err != nil { 43 | log.Fatal(err) 44 | } 45 | } 46 | 47 | func run() error { 48 | if err := m1.Init(bytes.NewReader(Probe1)); err != nil { 49 | return err 50 | } 51 | defer func() { 52 | if err := m1.Stop(manager.CleanAll); err != nil { 53 | log.Print(err) 54 | } 55 | if err := m2.Stop(manager.CleanAll); err != nil { 56 | log.Print(err) 57 | } 58 | }() 59 | if err := m1.Start(); err != nil { 60 | return err 61 | } 62 | log.Println("Head over to /sys/kernel/debug/tracing/trace_pipe to see the eBPF programs in action") 63 | 64 | // Start demos 65 | if err := demoMapEditor(); err != nil { 66 | return err 67 | } 68 | if err := demoMapRouter(); err != nil { 69 | return err 70 | } 71 | return nil 72 | } 73 | 74 | // trigger - Creates and then removes a tmp folder to trigger the probes 75 | func trigger() error { 76 | log.Println("Generating events to trigger the probes ...") 77 | tmpDir, err := os.MkdirTemp("", "example") 78 | if err != nil { 79 | return fmt.Errorf("mkdirtmp: %s", err) 80 | } 81 | log.Printf("removing %v", tmpDir) 82 | return os.RemoveAll(tmpDir) 83 | } 84 | 85 | // dumpSharedMap - Dumps the content of the provided map at the provided key 86 | func dumpSharedMap(sharedMap *ebpf.Map) error { 87 | var key, val uint32 88 | entries := sharedMap.Iterate() 89 | for entries.Next(&key, &val) { 90 | // Order of keys is non-deterministic due to randomized map seed 91 | log.Printf("%v contains %v at key %v", sharedMap, val, key) 92 | } 93 | return entries.Err() 94 | } 95 | -------------------------------------------------------------------------------- /examples/mapspec_editor/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/mapspec_editor/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | struct bpf_map_def SEC("maps/cache") cache = { 5 | .type = BPF_MAP_TYPE_HASH, 6 | .key_size = sizeof(u32), 7 | .value_size = sizeof(u32), 8 | .max_entries = 10, 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | -------------------------------------------------------------------------------- /examples/mapspec_editor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/cilium/ebpf" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{} 18 | 19 | func main() { 20 | if err := run(); err != nil { 21 | log.Fatal(err) 22 | } 23 | } 24 | 25 | func run() error { 26 | options := manager.Options{ 27 | MapSpecEditors: map[string]manager.MapSpecEditor{ 28 | "cache": { 29 | Type: ebpf.LRUHash, 30 | MaxEntries: 1000000, 31 | EditorFlag: manager.EditMaxEntries | manager.EditType, 32 | }, 33 | }, 34 | RemoveRlimit: true, 35 | } 36 | 37 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 38 | return err 39 | } 40 | defer func() { 41 | if err := m.Stop(manager.CleanAll); err != nil { 42 | log.Print(err) 43 | } 44 | }() 45 | 46 | log.Println("successfully loaded, checkout the parameters of the map \"cache\" using bpftool") 47 | log.Println("=> You should see MaxEntries 1000000 instead of 10") 48 | log.Println("=> Enter to continue") 49 | _, _ = fmt.Scanln() 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /examples/object_pinning/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/object_pinning/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | struct bpf_map_def SEC("maps/map1") map1 = { 5 | .type = BPF_MAP_TYPE_HASH, 6 | .key_size = sizeof(u32), 7 | .value_size = sizeof(u32), 8 | .max_entries = 10, 9 | }; 10 | 11 | struct bpf_map_def SEC("maps/map2") map2 = { 12 | .type = BPF_MAP_TYPE_HASH, 13 | .key_size = sizeof(u32), 14 | .value_size = sizeof(u32), 15 | .max_entries = 10, 16 | }; 17 | 18 | __attribute__((always_inline)) static int mkdir(int t) { 19 | // Check if map has some data 20 | u32 key = 1; 21 | u32 *data = bpf_map_lookup_elem(&map1, &key); 22 | if (data == NULL) { 23 | bpf_printk("(mkdir %d) map1 is empty\n", t); 24 | } else { 25 | bpf_printk("(mkdir %d) map1 contains %d at %d\n", t, *data, key); 26 | } 27 | return 0; 28 | }; 29 | 30 | __attribute__((always_inline)) static int mkdir_ret(int t) { 31 | u32 key = 1; 32 | u32 value = 42; 33 | u32 *data = bpf_map_lookup_elem(&map1, &key); 34 | if (data == NULL) { 35 | bpf_printk("(mkdirat ret %d) inserting %d at %d in map1\n", t, value, key); 36 | bpf_map_update_elem(&map1, &key, &value, BPF_ANY); 37 | } else { 38 | bpf_printk("(mkdirat ret %d) data already there, nothing to do\n", t); 39 | } 40 | return 0; 41 | }; 42 | 43 | SEC("kprobe/mkdir") 44 | int kprobe_mkdir(void *ctx) { 45 | return mkdir(1); 46 | } 47 | 48 | SEC("kprobe/mkdirat") 49 | int kprobe_mkdirat(void *ctx) 50 | { 51 | return mkdir(2); 52 | } 53 | 54 | SEC("kretprobe/mkdir") 55 | int kretprobe_mkdir(void *ctx) 56 | { 57 | return mkdir_ret(1); 58 | } 59 | 60 | SEC("kretprobe/mkdirat") 61 | int kretprobe_mkdirat(void *ctx) 62 | { 63 | return mkdir_ret(2); 64 | } 65 | 66 | char _license[] SEC("license") = "GPL"; 67 | -------------------------------------------------------------------------------- /examples/object_pinning/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | EBPFFuncName: "kprobe_mkdirat", 22 | }, 23 | SyscallFuncName: "mkdirat", 24 | }, 25 | { 26 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 27 | EBPFFuncName: "kretprobe_mkdirat", 28 | }, 29 | SyscallFuncName: "mkdirat", 30 | }, 31 | { 32 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 33 | EBPFFuncName: "kprobe_mkdir", 34 | }, 35 | SyscallFuncName: "mkdir", 36 | }, 37 | { 38 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 39 | EBPFFuncName: "kretprobe_mkdir", 40 | }, 41 | SyscallFuncName: "mkdir", 42 | }, 43 | }, 44 | Maps: []*manager.Map{ 45 | { 46 | Name: "map1", 47 | MapOptions: manager.MapOptions{ 48 | PinPath: "/sys/fs/bpf/map1", 49 | }, 50 | }, 51 | }, 52 | } 53 | 54 | func main() { 55 | if err := run(); err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | 60 | func run() error { 61 | // Parse CLI arguments 62 | var kill bool 63 | flag.BoolVar(&kill, "kill", false, "kills the programs suddenly before doing any cleanup") 64 | flag.Parse() 65 | 66 | log.Println("if they exist, pinned object will be automatically loaded") 67 | 68 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 69 | return err 70 | } 71 | if err := m.Start(); err != nil { 72 | _ = m.Stop(manager.CleanAll) 73 | return err 74 | } 75 | 76 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 77 | 78 | // Create a folder to trigger the probes 79 | if err := trigger(); err != nil { 80 | log.Print(err) 81 | } 82 | 83 | if kill { 84 | log.Println("=> Stopping the program without cleanup, the pinned map should show up in /sys/fs/bpf/") 85 | log.Println("=> Restart without --kill to load the pinned object from the bpf file system and properly remove them") 86 | return m.Stop(manager.CleanInternalNotPinned) 87 | } 88 | return m.Stop(manager.CleanAll) 89 | } 90 | 91 | // trigger - Creates and then removes a tmp folder to trigger the probes 92 | func trigger() error { 93 | log.Println("Generating events to trigger the probes ...") 94 | tmpDir, err := os.MkdirTemp("", "example") 95 | if err != nil { 96 | return fmt.Errorf("mkdirtmp: %s", err) 97 | } 98 | log.Printf("removing %v", tmpDir) 99 | return os.RemoveAll(tmpDir) 100 | } 101 | -------------------------------------------------------------------------------- /examples/program_router/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/program_router/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | manager "github.com/DataDog/ebpf-manager" 9 | ) 10 | 11 | func demoTailCall() error { 12 | log.Println("generating some traffic to show what happens when the tail call is not set up ...") 13 | trigger1() 14 | time.Sleep(1 * time.Second) 15 | 16 | prog, _, err := m2.GetProgram(manager.ProbeIdentificationPair{EBPFFuncName: "three"}) 17 | if err != nil { 18 | log.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 | EBPFFuncName: "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 | log.Println("generating some traffic to show what happens when the tail call is set up ...") 42 | trigger2() 43 | return nil 44 | } 45 | 46 | // trigger1 - Generate some network traffic to trigger the probe 47 | func trigger1() { 48 | _, _ = http.Get("https://www.google.com/") 49 | } 50 | 51 | // trigger2 - Generate some network traffic to trigger the probe 52 | func trigger2() { 53 | _, _ = http.Get("https://www.google.fr/") 54 | } 55 | -------------------------------------------------------------------------------- /examples/program_router/ebpf/prog1.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | 5 | #define TAIL_CALL_KEY 1 6 | #define EXTERNAL_TAIL_CALL_KEY 2 7 | 8 | struct bpf_map_def SEC("maps/tc_prog_array") tc_prog_array = { 9 | .type = BPF_MAP_TYPE_PROG_ARRAY, 10 | .key_size = 4, 11 | .value_size = 4, 12 | .max_entries = 3, 13 | }; 14 | 15 | SEC("classifier/one") 16 | int one(struct __sk_buff *skb) 17 | { 18 | bpf_printk("(classifier/one) new packet captured (TC)\n"); 19 | 20 | // Tail call 21 | int key = TAIL_CALL_KEY; 22 | bpf_tail_call(skb, &tc_prog_array, key); 23 | 24 | // Tail call failed 25 | bpf_printk("(classifier/one) couldn't tail call (TC)\n"); 26 | return TC_ACT_OK; 27 | }; 28 | 29 | SEC("classifier/two") 30 | int two(struct __sk_buff *skb) 31 | { 32 | bpf_printk("(classifier/two) tail call triggered (TC)\n"); 33 | 34 | // Tail call 35 | int key = EXTERNAL_TAIL_CALL_KEY; 36 | bpf_tail_call(skb, &tc_prog_array, key); 37 | 38 | // Tail call failed 39 | bpf_printk("(classifier/two) external tail call failed (TC)\n"); 40 | return TC_ACT_OK; 41 | }; 42 | 43 | char _license[] SEC("license") = "GPL"; 44 | -------------------------------------------------------------------------------- /examples/program_router/ebpf/prog2.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | SEC("classifier/three") 5 | int three(struct __sk_buff *skb) 6 | { 7 | bpf_printk("(classifier/three) tail call triggered (TC)\n"); 8 | return TC_ACT_OK; 9 | }; 10 | 11 | char _license[] SEC("license") = "GPL"; 12 | -------------------------------------------------------------------------------- /examples/program_router/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "log" 7 | 8 | manager "github.com/DataDog/ebpf-manager" 9 | ) 10 | 11 | //go:embed ebpf/bin/prog1.o 12 | var Probe1 []byte 13 | 14 | //go:embed ebpf/bin/prog2.o 15 | var Probe2 []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | EBPFFuncName: "one", 22 | }, 23 | IfName: "lo", // change this to the interface index connected to the internet 24 | NetworkDirection: manager.Egress, 25 | }, 26 | }, 27 | } 28 | 29 | var m2 = &manager.Manager{ 30 | Probes: []*manager.Probe{}, 31 | } 32 | 33 | func main() { 34 | if err := run(); err != nil { 35 | log.Fatal(err) 36 | } 37 | } 38 | 39 | func run() error { 40 | if err := m.Init(bytes.NewReader(Probe1)); err != nil { 41 | return err 42 | } 43 | defer func() { 44 | if err := m.Stop(manager.CleanAll); err != nil { 45 | log.Print(err) 46 | } 47 | }() 48 | if err := m2.Init(bytes.NewReader(Probe2)); err != nil { 49 | return err 50 | } 51 | defer func() { 52 | if err := m2.Stop(manager.CleanAll); err != nil { 53 | log.Print(err) 54 | } 55 | }() 56 | 57 | if err := m.Start(); err != nil { 58 | return err 59 | } 60 | if err := m2.Start(); err != nil { 61 | return err 62 | } 63 | 64 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 65 | 66 | if err := demoTailCall(); err != nil { 67 | log.Print(err) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /examples/programs/cgroup/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/cgroup/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | SEC("cgroup_skb/egress") 4 | int egress(struct __sk_buff *skb) 5 | { 6 | bpf_printk("new packet captured on cgroup egress\n"); 7 | return 1; 8 | }; 9 | 10 | char _license[] SEC("license") = "GPL"; 11 | -------------------------------------------------------------------------------- /examples/programs/cgroup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | _ "embed" 7 | "errors" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strings" 12 | 13 | manager "github.com/DataDog/ebpf-manager" 14 | ) 15 | 16 | //go:embed ebpf/bin/main.o 17 | var Probe []byte 18 | 19 | var m = &manager.Manager{ 20 | Probes: []*manager.Probe{ 21 | { 22 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 23 | EBPFFuncName: "egress", 24 | }, 25 | }, 26 | }, 27 | } 28 | 29 | func main() { 30 | if err := run(); err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | 35 | func run() error { 36 | cp, err := detectCgroupPath() 37 | if err != nil { 38 | return err 39 | } 40 | m.Probes[0].CGroupPath = cp 41 | 42 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 43 | return err 44 | } 45 | defer func() { 46 | if err := m.Stop(manager.CleanAll); err != nil { 47 | log.Print(err) 48 | } 49 | }() 50 | if err := m.Start(); err != nil { 51 | return err 52 | } 53 | 54 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 55 | 56 | // Generate some network traffic to trigger the probe 57 | trigger() 58 | return nil 59 | } 60 | 61 | // trigger - Generate some network traffic to trigger the probe 62 | func trigger() { 63 | log.Println("Generating some network traffic to trigger the probes ...") 64 | _, _ = http.Get("https://www.google.com/") 65 | } 66 | 67 | func detectCgroupPath() (string, error) { 68 | f, err := os.Open("/proc/mounts") 69 | if err != nil { 70 | return "", err 71 | } 72 | defer f.Close() 73 | 74 | scanner := bufio.NewScanner(f) 75 | for scanner.Scan() { 76 | fields := strings.Split(scanner.Text(), " ") 77 | if len(fields) >= 3 && fields[2] == "cgroup2" { 78 | return fields[1], nil 79 | } 80 | } 81 | 82 | return "", errors.New("cgroup2 is not mounted") 83 | } 84 | -------------------------------------------------------------------------------- /examples/programs/fentry/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/fentry/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | #include 5 | 6 | SEC("fentry/vfs_mkdir") 7 | int BPF_PROG(vfs_mkdir_enter, struct user_namespace *mnt_userns, struct inode *dir, 8 | struct dentry *dentry, umode_t mode) 9 | { 10 | bpf_printk("mkdir (vfs hook point) user_ns_ptr:%p\n", mnt_userns); 11 | return 0; 12 | }; 13 | 14 | SEC("fexit/do_mkdirat") 15 | int BPF_PROG(do_mkdirat_exit, int dfd, struct filename *fname, umode_t mode, int ret) 16 | { 17 | bpf_printk("do_mkdirat return (syscall hook point) ret:%d\n", ret); 18 | return 0; 19 | } 20 | 21 | char _license[] SEC("license") = "GPL"; 22 | -------------------------------------------------------------------------------- /examples/programs/fentry/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "os" 9 | "runtime" 10 | "time" 11 | 12 | "github.com/cilium/ebpf/features" 13 | 14 | manager "github.com/DataDog/ebpf-manager" 15 | ) 16 | 17 | //go:embed ebpf/bin/main.o 18 | var Probe []byte 19 | 20 | var m = &manager.Manager{ 21 | Probes: []*manager.Probe{ 22 | { 23 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 24 | UID: "MyVFSMkdir", 25 | EBPFFuncName: "vfs_mkdir_enter", 26 | }, 27 | }, 28 | { 29 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 30 | UID: "", // UID is needed only if there are multiple instances of your program (after using 31 | // m.CloneProgram for example), or if multiple programs with the exact same section are attaching 32 | // at the exact same hook point (using m.AddHook for example, or simply because another manager 33 | // on the system is planning on hooking there). 34 | EBPFFuncName: "do_mkdirat_exit", 35 | }, 36 | SyscallFuncName: "mkdirat", 37 | }, 38 | }, 39 | } 40 | 41 | func main() { 42 | if err := run(); err != nil { 43 | log.Fatal(err) 44 | } 45 | } 46 | 47 | func run() error { 48 | lvc, err := features.LinuxVersionCode() 49 | if err != nil { 50 | return err 51 | } 52 | if runtime.GOARCH == "arm64" && (lvc>>16) < 6 { 53 | // fentry unsupported 54 | return nil 55 | } 56 | 57 | options := manager.Options{ 58 | DefaultProbeRetry: 2, 59 | DefaultProbeRetryDelay: time.Second, 60 | } 61 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 62 | return err 63 | } 64 | defer func() { 65 | if err := m.Stop(manager.CleanAll); err != nil { 66 | log.Print(err) 67 | } 68 | }() 69 | 70 | if err := m.Start(); err != nil { 71 | return err 72 | } 73 | 74 | log.Println("successfully started") 75 | log.Println("=> head over to /sys/kernel/debug/tracing/trace_pipe") 76 | log.Println("=> checkout /sys/kernel/debug/tracing/kprobe_events, utimes_common might have become utimes_common.isra.0") 77 | 78 | if err := trigger(); err != nil { 79 | log.Print(err) 80 | } 81 | 82 | log.Println("=> Enter to exit") 83 | _, _ = fmt.Scanln() 84 | return nil 85 | } 86 | 87 | // trigger - Creates and then removes a tmp folder to trigger the probes 88 | func trigger() error { 89 | log.Println("Generating events to trigger the probes ...") 90 | tmpDir, err := os.MkdirTemp("", "example") 91 | if err != nil { 92 | return fmt.Errorf("mkdirtmp: %s", err) 93 | } 94 | log.Printf("removing %v", tmpDir) 95 | return os.RemoveAll(tmpDir) 96 | } 97 | -------------------------------------------------------------------------------- /examples/programs/kprobe/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/kprobe/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | 5 | struct bpf_map_def SEC("maps/cache") cache = { 6 | .type = BPF_MAP_TYPE_HASH, 7 | .key_size = sizeof(u32), 8 | .value_size = sizeof(u32), 9 | .max_entries = 10, 10 | }; 11 | 12 | SEC("kprobe/vfs_mkdir") 13 | int BPF_KPROBE(kprobe_vfs_mkdir, struct user_namespace *mnt_userns) 14 | { 15 | bpf_printk("mkdir (vfs hook point) user_ns_ptr:%p\n", mnt_userns); 16 | return 0; 17 | }; 18 | 19 | SEC("kretprobe/utimes_common") 20 | int kretprobe_utimes_common(struct pt_regs *ctx) 21 | { 22 | bpf_printk("utimes_common return\n"); 23 | return 0; 24 | }; 25 | 26 | SEC("kretprobe/mkdirat") 27 | int BPF_KRETPROBE(kretprobe_mkdirat, int ret) 28 | { 29 | bpf_printk("mkdirat return (syscall hook point) ret:%d\n", ret); 30 | return 0; 31 | } 32 | 33 | char _license[] SEC("license") = "GPL"; 34 | -------------------------------------------------------------------------------- /examples/programs/kprobe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "os" 9 | "time" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | UID: "MyVFSMkdir", 22 | EBPFFuncName: "kprobe_vfs_mkdir", 23 | }, 24 | }, 25 | { 26 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 27 | UID: "UtimesCommon", 28 | EBPFFuncName: "kretprobe_utimes_common", 29 | }, 30 | MatchFuncName: "utimes", 31 | KProbeMaxActive: 100, 32 | }, 33 | { 34 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 35 | UID: "", // UID is needed only if there are multiple instances of your program (after using 36 | // m.CloneProgram for example), or if multiple programs with the exact same section are attaching 37 | // at the exact same hook point (using m.AddHook for example, or simply because another manager 38 | // on the system is planning on hooking there). 39 | EBPFFuncName: "kretprobe_mkdirat", 40 | }, 41 | SyscallFuncName: "mkdirat", 42 | }, 43 | }, 44 | } 45 | 46 | func main() { 47 | if err := run(); err != nil { 48 | log.Fatal(err) 49 | } 50 | } 51 | 52 | func run() error { 53 | options := manager.Options{ 54 | DefaultProbeRetry: 2, 55 | DefaultProbeRetryDelay: time.Second, 56 | } 57 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 58 | return err 59 | } 60 | defer func() { 61 | if err := m.Stop(manager.CleanAll); err != nil { 62 | log.Print(err) 63 | } 64 | }() 65 | if err := m.Start(); err != nil { 66 | return err 67 | } 68 | 69 | log.Println("successfully started") 70 | log.Println("=> head over to /sys/kernel/debug/tracing/trace_pipe") 71 | log.Println("=> checkout /sys/kernel/debug/tracing/kprobe_events, utimes_common might have become utimes_common.isra.0") 72 | 73 | // Create a folder to trigger the probes 74 | if err := trigger(); err != nil { 75 | log.Print(err) 76 | } 77 | 78 | log.Println("=> Enter to exit") 79 | _, _ = fmt.Scanln() 80 | return nil 81 | } 82 | 83 | // trigger - Creates and then removes a tmp folder to trigger the probes 84 | func trigger() error { 85 | log.Println("Generating events to trigger the probes ...") 86 | tmpDir, err := os.MkdirTemp("", "example") 87 | if err != nil { 88 | return fmt.Errorf("mkdirtmp: %s", err) 89 | } 90 | log.Printf("removing %v", tmpDir) 91 | return os.RemoveAll(tmpDir) 92 | } 93 | -------------------------------------------------------------------------------- /examples/programs/lsm/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/lsm/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | 5 | struct bpf_map_def SEC("maps/cache") cache = { 6 | .type = BPF_MAP_TYPE_ARRAY, 7 | .key_size = sizeof(u32), 8 | .value_size = sizeof(u32), 9 | .max_entries = 10, 10 | }; 11 | 12 | SEC("lsm/inode_getattr") 13 | int BPF_PROG(lsm_security_inode_getattr, struct path *pp) { 14 | char p[128] = {}; 15 | int ret = bpf_d_path(pp, &p[0], 128); 16 | bpf_printk("ret:%d path:%s\n", ret, p); 17 | return 0; 18 | } 19 | 20 | SEC("lsm/bpf") 21 | int BPF_PROG(lsm_security_bpf, int cmd) { 22 | bpf_printk("lsm_security_bpf cmd:%d\n", cmd); 23 | return -EPERM; 24 | }; 25 | 26 | char _license[] SEC("license") = "GPL"; 27 | -------------------------------------------------------------------------------- /examples/programs/lsm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/cilium/ebpf" 12 | "github.com/cilium/ebpf/features" 13 | 14 | manager "github.com/DataDog/ebpf-manager" 15 | ) 16 | 17 | //go:embed ebpf/bin/main.o 18 | var Probe []byte 19 | 20 | var m = &manager.Manager{ 21 | Probes: []*manager.Probe{ 22 | { 23 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 24 | EBPFFuncName: "lsm_security_inode_getattr", 25 | }, 26 | }, 27 | { 28 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 29 | EBPFFuncName: "lsm_security_bpf", 30 | }, 31 | }, 32 | }, 33 | } 34 | 35 | func main() { 36 | if err := run(); err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func run() error { 42 | if features.HaveProgramType(ebpf.LSM) != nil { 43 | return nil 44 | } 45 | lvc, err := features.LinuxVersionCode() 46 | if err != nil { 47 | return err 48 | } 49 | if runtime.GOARCH == "arm64" && (lvc>>16) < 6 { 50 | // LSM unsupported 51 | return nil 52 | } 53 | 54 | options := manager.Options{ 55 | DefaultProbeRetry: 2, 56 | DefaultProbeRetryDelay: time.Second, 57 | } 58 | if err := m.InitWithOptions(bytes.NewReader(Probe), options); err != nil { 59 | return err 60 | } 61 | defer func() { 62 | if err := m.Stop(manager.CleanAll); err != nil { 63 | log.Print(err) 64 | } 65 | }() 66 | if err := m.Start(); err != nil { 67 | return err 68 | } 69 | 70 | if err := trigger(); err != nil { 71 | log.Print(err) 72 | } 73 | 74 | log.Println("successfully started") 75 | log.Println("=> head over to /sys/kernel/debug/tracing/trace_pipe") 76 | log.Println("=> Enter to exit") 77 | _, _ = fmt.Scanln() 78 | return nil 79 | } 80 | 81 | // trigger - lookup value in eBPF map to execute a bpf syscall 82 | func trigger() error { 83 | cache, _, err := m.GetMap("cache") 84 | if err != nil { 85 | return err 86 | } 87 | var key, val uint32 88 | if err = cache.Lookup(&key, &val); err == nil { 89 | log.Printf("No error detected while making a bpf syscall :(") 90 | } else { 91 | log.Printf("bpf syscall: got %v :)", err) 92 | } 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /examples/programs/perf_event/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/perf_event/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | SEC("perf_event/cpu_clock") 4 | int perf_event_cpu_clock(struct bpf_perf_event_data *ctx) 5 | { 6 | bpf_printk("pid %d is currently running on cpu %d (sample_period: %d)\n", bpf_get_current_pid_tgid() >> 32, bpf_get_smp_processor_id(), ctx->sample_period); 7 | return 0; 8 | }; 9 | 10 | char _license[] SEC("license") = "GPL"; 11 | -------------------------------------------------------------------------------- /examples/programs/perf_event/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | 9 | "golang.org/x/sys/unix" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | EBPFFuncName: "perf_event_cpu_clock", 22 | }, 23 | SampleFrequency: 1, 24 | PerfEventType: unix.PERF_TYPE_SOFTWARE, 25 | PerfEventConfig: unix.PERF_COUNT_SW_CPU_CLOCK, 26 | }, 27 | }, 28 | } 29 | 30 | func main() { 31 | if err := run(); err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | 36 | func run() error { 37 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 38 | return err 39 | } 40 | defer func() { 41 | if err := m.Stop(manager.CleanAll); err != nil { 42 | log.Print(err) 43 | } 44 | }() 45 | if err := m.Start(); err != nil { 46 | return err 47 | } 48 | 49 | log.Println("successfully started") 50 | log.Println("=> head over to /sys/kernel/debug/tracing/trace_pipe") 51 | log.Println("=> Enter to exit") 52 | _, _ = fmt.Scanln() 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /examples/programs/socket/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/socket/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | SEC("socket/sock_filter") 4 | int sock_filter(void *ctx) 5 | { 6 | bpf_printk("new packet received\n"); 7 | return 0; 8 | }; 9 | 10 | char _license[] SEC("license") = "GPL"; 11 | -------------------------------------------------------------------------------- /examples/programs/socket/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "log" 7 | "syscall" 8 | 9 | manager "github.com/DataDog/ebpf-manager" 10 | ) 11 | 12 | //go:embed ebpf/bin/main.o 13 | var Probe []byte 14 | 15 | var m = &manager.Manager{ 16 | Probes: []*manager.Probe{ 17 | { 18 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 19 | EBPFFuncName: "sock_filter", 20 | }, 21 | }, 22 | }, 23 | } 24 | 25 | func main() { 26 | if err := run(); err != nil { 27 | log.Fatal(err) 28 | } 29 | } 30 | 31 | func run() error { 32 | // Create a socket pair that will be used to trigger the socket filter 33 | sockPair, err := newSocketPair() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | // Set the socket file descriptor on which the socket filter should trigger 39 | m.Probes[0].SocketFD = sockPair[0] 40 | 41 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 42 | return err 43 | } 44 | defer func() { 45 | if err := m.Stop(manager.CleanAll); err != nil { 46 | log.Print(err) 47 | } 48 | }() 49 | if err := m.Start(); err != nil { 50 | return err 51 | } 52 | 53 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 54 | 55 | // Send a message through the socket pair to trigger the probe 56 | if err := trigger(sockPair); err != nil { 57 | log.Print(err) 58 | } 59 | return nil 60 | } 61 | 62 | // trigger - Send a message through the socket pair to trigger the probe 63 | func trigger(sockPair SocketPair) error { 64 | log.Println("Sending a message through the socket pair to trigger the probes ...") 65 | _, err := syscall.Write(sockPair[1], nil) 66 | if err != nil { 67 | return err 68 | } 69 | _, err = syscall.Read(sockPair[0], nil) 70 | return err 71 | } 72 | 73 | type SocketPair [2]int 74 | 75 | func (p SocketPair) Close() error { 76 | err1 := syscall.Close(p[0]) 77 | err2 := syscall.Close(p[1]) 78 | 79 | if err1 != nil { 80 | return err1 81 | } 82 | return err2 83 | } 84 | 85 | // newSocketPair - Create a socket pair 86 | func newSocketPair() (SocketPair, error) { 87 | return syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) 88 | } 89 | -------------------------------------------------------------------------------- /examples/programs/tc/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/tc/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | #include 4 | 5 | SEC("classifier/egress") 6 | int egress(struct __sk_buff *skb) 7 | { 8 | bpf_printk("new packet captured on egress (TC)\n"); 9 | return TC_ACT_OK; 10 | }; 11 | 12 | SEC("classifier/ingress") 13 | int ingress(struct __sk_buff *skb) 14 | { 15 | bpf_printk("new packet captured on ingress (TC)\n"); 16 | return TC_ACT_OK; 17 | }; 18 | 19 | char _license[] SEC("license") = "GPL"; 20 | -------------------------------------------------------------------------------- /examples/programs/tc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/vishvananda/netlink" 11 | "golang.org/x/sys/unix" 12 | 13 | manager "github.com/DataDog/ebpf-manager" 14 | ) 15 | 16 | //go:embed ebpf/bin/main.o 17 | var Probe []byte 18 | 19 | var m = &manager.Manager{ 20 | Probes: []*manager.Probe{ 21 | { 22 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 23 | UID: "MyUID", 24 | EBPFFuncName: "egress", 25 | }, 26 | IfName: "lo", // change this to the interface connected to the internet 27 | NetworkDirection: manager.Egress, 28 | }, 29 | { 30 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 31 | EBPFFuncName: "ingress", 32 | }, 33 | IfName: "lo", // change this to the interface connected to the internet 34 | NetworkDirection: manager.Ingress, 35 | TCFilterProtocol: unix.ETH_P_ARP, 36 | TCFilterPrio: 1000, 37 | TCFilterHandle: netlink.MakeHandle(0, 2), 38 | }, 39 | }, 40 | } 41 | 42 | func main() { 43 | if err := run(); err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | 48 | func run() error { 49 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 50 | return err 51 | } 52 | defer func() { 53 | if err := m.Stop(manager.CleanAll); err != nil { 54 | log.Print(err) 55 | } 56 | }() 57 | if err := m.Start(); err != nil { 58 | return err 59 | } 60 | 61 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 62 | 63 | // Generate some network traffic to trigger the probe 64 | trigger() 65 | 66 | log.Println("=> Enter to exit") 67 | _, _ = fmt.Scanln() 68 | return nil 69 | } 70 | 71 | // trigger - Generate some network traffic to trigger the probe 72 | func trigger() { 73 | log.Println("Generating some network traffic to trigger the probes ...") 74 | _, _ = http.Get("https://www.google.com/") 75 | } 76 | -------------------------------------------------------------------------------- /examples/programs/tracepoint/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/tracepoint/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | SEC("tracepoint/syscalls/sys_enter_mkdirat") 4 | int sys_enter_mkdirat(void *ctx) 5 | { 6 | bpf_printk("mkdirat enter (tracepoint)\n"); 7 | return 0; 8 | }; 9 | 10 | SEC("tracepoint/my_tracepoint") 11 | int my_tracepoint(void *ctx) 12 | { 13 | bpf_printk("my_tracepoint (tracepoint)\n"); 14 | return 0; 15 | }; 16 | 17 | char _license[] SEC("license") = "GPL"; 18 | -------------------------------------------------------------------------------- /examples/programs/tracepoint/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | "os" 9 | "os/exec" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | EBPFFuncName: "sys_enter_mkdirat", 22 | }, 23 | }, 24 | { 25 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 26 | EBPFFuncName: "my_tracepoint", 27 | }, 28 | TracepointCategory: "sched", 29 | TracepointName: "sched_process_exec", 30 | }, 31 | }, 32 | } 33 | 34 | func main() { 35 | if err := run(); err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | 40 | func run() error { 41 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 42 | return err 43 | } 44 | defer func() { 45 | if err := m.Stop(manager.CleanAll); err != nil { 46 | log.Print(err) 47 | } 48 | }() 49 | if err := m.Start(); err != nil { 50 | return err 51 | } 52 | 53 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 54 | 55 | // Create a folder to trigger the probes 56 | if err := trigger(); err != nil { 57 | log.Print(err) 58 | } 59 | return nil 60 | } 61 | 62 | // trigger - Creates and then removes a tmp folder to trigger the probes 63 | func trigger() (err error) { 64 | log.Println("Generating events to trigger the probes ...") 65 | tmpDir, err := os.MkdirTemp("", "example") 66 | if err != nil { 67 | return fmt.Errorf("mkdirtmp: %s", err) 68 | } 69 | defer func() { 70 | log.Printf("removing %v", tmpDir) 71 | err = os.RemoveAll(tmpDir) 72 | }() 73 | 74 | // trigger a fork by executing a binary 75 | out, err := exec.Command("date").Output() 76 | if err != nil { 77 | return err 78 | } 79 | log.Printf("The date is %s", out) 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /examples/programs/uprobe/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/uprobe/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | 3 | SEC("uprobe/readline") 4 | int readline(void *ctx) 5 | { 6 | bpf_printk("new bash command detected\n"); 7 | return 0; 8 | }; 9 | 10 | char _license[] SEC("license") = "GPL"; 11 | -------------------------------------------------------------------------------- /examples/programs/uprobe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "io" 7 | "log" 8 | "os/exec" 9 | "time" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | var m = &manager.Manager{ 18 | Probes: []*manager.Probe{ 19 | { 20 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 21 | EBPFFuncName: "readline", 22 | }, 23 | BinaryPath: "/usr/bin/bash", 24 | }, 25 | }, 26 | } 27 | 28 | func main() { 29 | if err := run(); err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | 34 | func run() error { 35 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 36 | return err 37 | } 38 | defer func() { 39 | if err := m.Stop(manager.CleanAll); err != nil { 40 | log.Print(err) 41 | } 42 | }() 43 | if err := m.Start(); err != nil { 44 | return err 45 | } 46 | 47 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 48 | 49 | // Spawn a bash and right a command to trigger the probe 50 | if err := trigger(); err != nil { 51 | log.Print(err) 52 | } 53 | return nil 54 | } 55 | 56 | // trigger - Spawn a bash and execute a command to trigger the probe 57 | func trigger() error { 58 | log.Println("Spawning a shell and executing `id` to trigger the probe ...") 59 | cmd := exec.Command("/usr/bin/bash", "-i") 60 | stdinPipe, _ := cmd.StdinPipe() 61 | go func() { 62 | _, _ = io.WriteString(stdinPipe, "id") 63 | time.Sleep(100 * time.Millisecond) 64 | _ = stdinPipe.Close() 65 | }() 66 | b, err := cmd.Output() 67 | if err != nil { 68 | return err 69 | } 70 | log.Printf("from bash: %v", string(b)) 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /examples/programs/xdp/Makefile: -------------------------------------------------------------------------------- 1 | include ../../Makefile.arch 2 | include ../../Makefile.clang 3 | include ../../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/programs/xdp/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | SEC("xdp/ingress") 5 | int ingress(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 | -------------------------------------------------------------------------------- /examples/programs/xdp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "log" 7 | "net/http" 8 | 9 | manager "github.com/DataDog/ebpf-manager" 10 | ) 11 | 12 | //go:embed ebpf/bin/main.o 13 | var Probe []byte 14 | 15 | var m = &manager.Manager{ 16 | Probes: []*manager.Probe{ 17 | { 18 | ProbeIdentificationPair: manager.ProbeIdentificationPair{ 19 | EBPFFuncName: "ingress", 20 | }, 21 | IfIndex: 2, // change this to the interface index connected to the internet 22 | XDPAttachMode: manager.XdpAttachModeSkb, 23 | }, 24 | }, 25 | } 26 | 27 | func main() { 28 | if err := run(); err != nil { 29 | log.Fatal(err) 30 | } 31 | } 32 | 33 | func run() error { 34 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 35 | return err 36 | } 37 | defer func() { 38 | if err := m.Stop(manager.CleanAll); err != nil { 39 | log.Print(err) 40 | } 41 | }() 42 | if err := m.Start(); err != nil { 43 | return err 44 | } 45 | 46 | log.Println("successfully started, head over to /sys/kernel/debug/tracing/trace_pipe") 47 | 48 | // Generate some network traffic to trigger the probe 49 | trigger() 50 | return nil 51 | } 52 | 53 | // trigger - Generate some network traffic to trigger the probe 54 | func trigger() { 55 | log.Println("Generating some network traffic to trigger the probes ...") 56 | _, _ = http.Get("https://www.google.com/") 57 | } 58 | -------------------------------------------------------------------------------- /examples/tests_and_benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.arch 2 | include ../Makefile.clang 3 | include ../Makefile.ebpf 4 | -------------------------------------------------------------------------------- /examples/tests_and_benchmarks/ebpf/main.c: -------------------------------------------------------------------------------- 1 | #include "all.h" 2 | #include 3 | 4 | __attribute__((always_inline)) static int my_func(u32 input) 5 | { 6 | return 2*input; 7 | } 8 | 9 | #define TEST_DATA_KEY 1 10 | 11 | struct my_func_test_data_t { 12 | u32 input; 13 | u32 output; 14 | }; 15 | 16 | struct bpf_map_def SEC("maps/my_func_test_data") my_func_test_data = { 17 | .type = BPF_MAP_TYPE_ARRAY, 18 | .key_size = sizeof(u32), 19 | .value_size = sizeof(struct my_func_test_data_t), 20 | .max_entries = 2, 21 | }; 22 | 23 | SEC("xdp/my_func_test") 24 | int my_func_test(struct __sk_buff *skb) 25 | { 26 | // Retrieve test data 27 | u32 key = TEST_DATA_KEY; 28 | struct my_func_test_data_t *data = bpf_map_lookup_elem(&my_func_test_data, &key); 29 | if (data == NULL) { 30 | bpf_printk("no test data\n"); 31 | return -1; 32 | } 33 | u32 ret = my_func(data->input); 34 | if (ret != data->output) { 35 | bpf_printk("expected %d for input %d, got %d\n", data->output, data->input, ret); 36 | return -1; 37 | } 38 | return 0; 39 | }; 40 | 41 | char _license[] SEC("license") = "GPL"; 42 | -------------------------------------------------------------------------------- /examples/tests_and_benchmarks/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "log" 8 | 9 | "github.com/cilium/ebpf" 10 | 11 | manager "github.com/DataDog/ebpf-manager" 12 | ) 13 | 14 | //go:embed ebpf/bin/main.o 15 | var Probe []byte 16 | 17 | type TestData struct { 18 | Input uint32 19 | Output uint32 20 | } 21 | 22 | func (td TestData) String() string { 23 | return fmt.Sprintf("{ Input:%v Output:%v }", td.Input, td.Output) 24 | } 25 | 26 | var testDataKey = uint32(1) 27 | 28 | var testData = []TestData{ 29 | {2, 4}, 30 | {10, 20}, 31 | {42, 128}, 32 | {42, 84}, 33 | } 34 | 35 | func main() { 36 | if err := run(); err != nil { 37 | log.Fatal(err) 38 | } 39 | } 40 | 41 | func run() error { 42 | var m = &manager.Manager{} 43 | if err := m.Init(bytes.NewReader(Probe)); err != nil { 44 | return err 45 | } 46 | defer func() { 47 | if err := m.Stop(manager.CleanAll); err != nil { 48 | log.Print(err) 49 | } 50 | }() 51 | 52 | // Get map used to send tests 53 | testMap, found, err := m.GetMap("my_func_test_data") 54 | if !found || err != nil { 55 | return fmt.Errorf("couldn't retrieve my_func_test_data %v", err) 56 | } 57 | 58 | // Get xdp program used to trigger the tests 59 | testProgs, found, err := m.GetProgram( 60 | manager.ProbeIdentificationPair{ 61 | EBPFFuncName: "my_func_test", 62 | }, 63 | ) 64 | if !found || err != nil { 65 | return fmt.Errorf("couldn't retrieve my_func_test %v", err) 66 | } 67 | testProg := testProgs[0] 68 | 69 | if err := runtTest(testMap, testProg); err != nil { 70 | return err 71 | } 72 | if err := runtBenchmark(testMap, testProg); err != nil { 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | func runtTest(testMap *ebpf.Map, testProg *ebpf.Program) error { 79 | log.Println("Running tests ...") 80 | for _, data := range testData { 81 | // insert data 82 | if err := testMap.Put(testDataKey, data); err != nil { 83 | return err 84 | } 85 | 86 | // Trigger test - (the 14 bytes is for the minimum packet size required to test an XDP program) 87 | outLen, _, err := testProg.Test(make([]byte, 14)) 88 | if err != nil { 89 | return err 90 | } 91 | if data.Input == 42 && data.Output == 128 { 92 | log.Printf("(failure expected on next test)") 93 | } 94 | if outLen == 0 { 95 | log.Printf("%v - PASS", data) 96 | } else { 97 | log.Printf("%v - FAIL (checkout /sys/kernel/debug/tracing/trace_pipe to see the logs)", data) 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | func runtBenchmark(testMap *ebpf.Map, testProg *ebpf.Program) error { 104 | log.Println("Running benchmark ...") 105 | for _, data := range testData { 106 | // insert data 107 | if err := testMap.Put(testDataKey, data); err != nil { 108 | return err 109 | } 110 | 111 | // Trigger test 112 | outLen, duration, err := testProg.Benchmark(make([]byte, 14), 1000, nil) 113 | if err != nil { 114 | return err 115 | } 116 | if data.Input == 42 && data.Output == 128 { 117 | log.Printf("(failure expected on next benchmark)") 118 | } 119 | if outLen == 0 { 120 | log.Printf("%v - PASS (duration: %v)", data, duration) 121 | } else { 122 | log.Printf("%v - benchmark FAILED (checkout /sys/kernel/debug/tracing/trace_pipe to see the logs)", data) 123 | } 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /fd.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "runtime" 6 | "strconv" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | // errClosedFd - Use of closed file descriptor error 12 | var errClosedFd = errors.New("use of closed file descriptor") 13 | 14 | // fd - File descriptor 15 | type fd struct { 16 | raw int64 17 | } 18 | 19 | // newFD - returns a new file descriptor 20 | func newFD(value uint32) *fd { 21 | f := &fd{int64(value)} 22 | runtime.SetFinalizer(f, func(f *fd) { 23 | _ = f.Close() 24 | }) 25 | return f 26 | } 27 | 28 | func (fd *fd) String() string { 29 | return strconv.FormatInt(fd.raw, 10) 30 | } 31 | 32 | func (fd *fd) Value() (uint32, error) { 33 | if fd.raw < 0 { 34 | return 0, errClosedFd 35 | } 36 | 37 | return uint32(fd.raw), nil 38 | } 39 | 40 | func (fd *fd) Close() error { 41 | if fd.raw < 0 { 42 | return nil 43 | } 44 | 45 | value := int(fd.raw) 46 | fd.raw = -1 47 | 48 | runtime.SetFinalizer(fd, nil) 49 | return unix.Close(value) 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DataDog/ebpf-manager 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/cilium/ebpf v0.20.0 7 | github.com/vishvananda/netlink v1.3.1 8 | github.com/vishvananda/netns v0.0.5 9 | golang.org/x/sync v0.18.0 10 | golang.org/x/sys v0.38.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cilium/ebpf v0.20.0 h1:atwWj9d3NffHyPZzVlx3hmw1on5CLe9eljR8VuHTwhM= 2 | github.com/cilium/ebpf v0.20.0/go.mod h1:pzLjFymM+uZPLk/IXZUL63xdx5VXEo+enTzxkZXdycw= 3 | github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s= 4 | github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= 8 | github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 9 | github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= 10 | github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE= 11 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 12 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 13 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 14 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 15 | github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= 16 | github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= 17 | github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= 18 | github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= 19 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 20 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 21 | github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= 22 | github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= 23 | github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= 24 | github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 25 | golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= 26 | golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 27 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 28 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 29 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 32 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 33 | -------------------------------------------------------------------------------- /innerouter_map_spec.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import "fmt" 4 | 5 | // InnerOuterMapSpec - An InnerOuterMapSpec defines the map that should be used as the inner map of the provided outer map. 6 | type InnerOuterMapSpec struct { 7 | // OuterMapName - Name of the BPF_MAP_TYPE_ARRAY_OF_MAPS or BPF_MAP_TYPE_HASH_OF_MAPS map, as defined in its 8 | // section SEC("maps/[OuterMapName]") 9 | OuterMapName string 10 | 11 | // InnerMapName - Name of the inner map of the provided outer map, as defined in its section SEC("maps/[InnerMapName]") 12 | InnerMapName string 13 | } 14 | 15 | // editInnerOuterMapSpecs - Update the inner maps of the maps of maps in the collection spec 16 | func (m *Manager) editInnerOuterMapSpec(spec InnerOuterMapSpec) error { 17 | // find the outer map 18 | outerSpec, exists, err := m.GetMapSpec(spec.OuterMapName) 19 | if err != nil { 20 | return err 21 | } 22 | if !exists { 23 | return fmt.Errorf("failed to set inner map for maps/%s: couldn't find outer map: %w", spec.OuterMapName, ErrUnknownSection) 24 | } 25 | // find the inner map 26 | innerMap, exists, err := m.GetMapSpec(spec.InnerMapName) 27 | if err != nil { 28 | return err 29 | } 30 | if !exists { 31 | return fmt.Errorf("failed to set inner map for maps/%s: couldn't find inner map %s: %w", spec.OuterMapName, spec.InnerMapName, ErrUnknownSection) 32 | } 33 | 34 | // set inner map 35 | outerSpec.InnerMap = innerMap 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/env.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | // getEnv retrieves the environment variable key. If it does not exist it returns the default. 9 | func getEnv(key string, dfault string, combineWith ...string) string { 10 | value := os.Getenv(key) 11 | if value == "" { 12 | value = dfault 13 | } 14 | 15 | switch len(combineWith) { 16 | case 0: 17 | return value 18 | case 1: 19 | return filepath.Join(value, combineWith[0]) 20 | default: 21 | all := make([]string, len(combineWith)+1) 22 | all[0] = value 23 | copy(all[1:], combineWith) 24 | return filepath.Join(all...) 25 | } 26 | } 27 | 28 | func hostProc(combineWith ...string) string { 29 | return getEnv("HOST_PROC", "/proc", combineWith...) 30 | } 31 | -------------------------------------------------------------------------------- /internal/procfs.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | // ProcessExists returns true if the process exists, using the HOST_PROC environment variable if present. 9 | func ProcessExists(pid int) bool { 10 | file, err := os.Open(hostProc(strconv.Itoa(pid))) 11 | if err != nil { 12 | return false 13 | } 14 | defer file.Close() 15 | return true 16 | } 17 | -------------------------------------------------------------------------------- /internal/retry.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "time" 4 | 5 | // Retry retries tryFn until success of maximum number of attempts reached. 6 | func Retry(tryFn func() error, attemptCount uint, delay time.Duration) error { 7 | var err error 8 | for i := uint(0); i < attemptCount; i++ { 9 | err = tryFn() 10 | if err == nil { 11 | return nil 12 | } 13 | time.Sleep(delay) 14 | } 15 | return err 16 | } 17 | -------------------------------------------------------------------------------- /kprobe.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type KprobeAttachMethod = AttachMethod 10 | 11 | const ( 12 | AttachKprobeMethodNotSet = AttachMethodNotSet 13 | AttachKprobeWithPerfEventOpen = AttachWithPerfEventOpen 14 | AttachKprobeWithKprobeEvents = AttachWithProbeEvents 15 | ) 16 | 17 | func (p *Probe) prefix() string { 18 | return tracefsPrefix(p.isReturnProbe) 19 | } 20 | 21 | type attachFunc func() (*tracefsLink, error) 22 | 23 | // attachKprobe - Attaches the probe to its kprobe 24 | func (p *Probe) attachKprobe() error { 25 | if len(p.HookFuncName) == 0 { 26 | return fmt.Errorf("HookFuncName, MatchFuncName or SyscallFuncName is required") 27 | } 28 | 29 | var eventsFunc attachFunc = p.attachWithKprobeEvents 30 | var pmuFunc attachFunc = func() (*tracefsLink, error) { 31 | pfd, err := perfEventOpenPMU(p.HookFuncName, 0, -1, kprobe, p.isReturnProbe, 0) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return &tracefsLink{perfEventLink: newPerfEventLink(pfd), Type: kprobe}, nil 36 | } 37 | 38 | pmuFirst := true 39 | startFunc, fallbackFunc := pmuFunc, eventsFunc 40 | // currently the perf event open ABI doesn't allow to specify the max active parameter 41 | if (p.KProbeMaxActive > 0 && p.isReturnProbe) || p.KprobeAttachMethod == AttachKprobeWithKprobeEvents { 42 | pmuFirst = false 43 | startFunc, fallbackFunc = eventsFunc, pmuFunc 44 | } 45 | 46 | var startErr, fallbackErr error 47 | var tl *tracefsLink 48 | if tl, startErr = startFunc(); startErr != nil { 49 | // do not fallback on tracefs events if PMU attach method is supported 50 | // and we got an actual error from it 51 | if pmuFirst && !errors.Is(startErr, ErrNotSupported) { 52 | return startErr 53 | } 54 | 55 | if tl, fallbackErr = fallbackFunc(); fallbackErr != nil { 56 | return errors.Join(startErr, fallbackErr) 57 | } 58 | } 59 | 60 | if err := attachPerfEvent(tl.perfEventLink, p.program); err != nil { 61 | _ = tl.Close() 62 | return fmt.Errorf("attach %s: %w", p.ProbeIdentificationPair, err) 63 | } 64 | p.progLink = tl 65 | return nil 66 | } 67 | 68 | // PerfEventFD returns the associated perf event's fd for this Probe 69 | func (p *Probe) PerfEventFD() (uint32, error) { 70 | v, ok := p.progLink.(*tracefsLink) 71 | if !ok { 72 | return 0, fmt.Errorf("Probe %q does not have a perf event link", p.programSpec.Name) 73 | } 74 | 75 | return v.perfEventLink.fd.Value() 76 | } 77 | 78 | // attachWithKprobeEvents attaches the kprobe using the kprobes_events ABI 79 | func (p *Probe) attachWithKprobeEvents() (*tracefsLink, error) { 80 | if p.kprobeHookPointNotExist { 81 | return nil, ErrKProbeHookPointNotExist 82 | } 83 | 84 | args := traceFsEventArgs{ 85 | Type: kprobe, 86 | ReturnProbe: p.isReturnProbe, 87 | Symbol: p.HookFuncName, 88 | UID: p.UID, 89 | MaxActive: p.KProbeMaxActive, 90 | AttachingPID: p.attachPID, 91 | } 92 | 93 | var kprobeID int 94 | var eventName string 95 | kprobeID, eventName, err := registerTraceFSEvent(args) 96 | if errors.Is(err, ErrProbeIDNotExist) { 97 | // The probe might have been loaded under a kernel generated event name. Clean up just in case. 98 | _ = unregisterTraceFSEvent(kprobe.eventsFilename(), getKernelGeneratedEventName(p.prefix(), p.HookFuncName)) 99 | // fallback without KProbeMaxActive 100 | args.MaxActive = 0 101 | kprobeID, eventName, err = registerTraceFSEvent(args) 102 | } 103 | 104 | if err != nil { 105 | if errors.Is(err, os.ErrNotExist) { 106 | p.kprobeHookPointNotExist = true 107 | } 108 | return nil, fmt.Errorf("couldn't enable kprobe %s: %w", p.ProbeIdentificationPair, err) 109 | } 110 | 111 | // create perf event fd 112 | pfd, err := perfEventOpenTracingEvent(kprobeID, -1) 113 | if err != nil { 114 | return nil, fmt.Errorf("couldn't open perf event fd for %s: %w", p.ProbeIdentificationPair, err) 115 | } 116 | return &tracefsLink{perfEventLink: newPerfEventLink(pfd), Type: kprobe, EventName: eventName}, nil 117 | } 118 | -------------------------------------------------------------------------------- /lsm.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf/link" 7 | ) 8 | 9 | // attachLSM - Attaches the probe to its LSM hook point 10 | func (p *Probe) attachLSM() error { 11 | var err error 12 | p.progLink, err = link.AttachLSM(link.LSMOptions{ 13 | Program: p.program, 14 | }) 15 | if err != nil { 16 | return fmt.Errorf("link lsm: %w", err) 17 | } 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /manager_perfmap.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | ) 8 | 9 | // NewPerfRing - Creates a new perf ring and start listening for events. 10 | // Use a MapRoute to make this map available to the programs of the manager. 11 | func (m *Manager) NewPerfRing(spec *ebpf.MapSpec, options MapOptions, perfMapOptions PerfMapOptions) (*ebpf.Map, error) { 12 | m.stateLock.Lock() 13 | defer m.stateLock.Unlock() 14 | if m.state < initialized { 15 | return nil, ErrManagerNotInitialized 16 | } 17 | 18 | // check if the name of the new map is available 19 | _, exists, _ := m.getMap(spec.Name) 20 | if exists { 21 | return nil, ErrMapNameInUse 22 | } 23 | 24 | // Create new map and perf ring buffer reader 25 | perfMap, err := loadNewPerfMap(spec, options, perfMapOptions) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | // Setup perf buffer reader 31 | if err := perfMap.init(m); err != nil { 32 | return nil, err 33 | } 34 | 35 | // Start perf buffer reader 36 | if err := perfMap.Start(); err != nil { 37 | // clean up 38 | _ = perfMap.Stop(CleanInternal) 39 | return nil, err 40 | } 41 | 42 | // Add map to the list of perf ring managed by the manager 43 | m.PerfMaps = append(m.PerfMaps, perfMap) 44 | return perfMap.array, nil 45 | } 46 | 47 | // ClonePerfRing - Clone an existing perf map and create a new one with the same spec. 48 | // Use a MapRoute to make this map available to the programs of the manager. 49 | func (m *Manager) ClonePerfRing(name string, newName string, options MapOptions, perfMapOptions PerfMapOptions) (*ebpf.Map, error) { 50 | // Select map to clone 51 | oldSpec, exists, err := m.GetMapSpec(name) 52 | if err != nil { 53 | return nil, err 54 | } 55 | if !exists { 56 | return nil, fmt.Errorf("failed to clone maps/%s: couldn't find map: %w", name, ErrUnknownSection) 57 | } 58 | 59 | // Duplicate spec and create a new map 60 | spec := oldSpec.Copy() 61 | spec.Name = newName 62 | return m.NewPerfRing(spec, options, perfMapOptions) 63 | } 64 | -------------------------------------------------------------------------------- /manager_ringbuffer.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import "github.com/cilium/ebpf" 4 | 5 | // NewRingBuffer - Creates a new ring buffer and start listening for events. 6 | // Use a MapRoute to make this map available to the programs of the manager. 7 | func (m *Manager) NewRingBuffer(spec *ebpf.MapSpec, options MapOptions, ringBufferOptions RingBufferOptions) (*ebpf.Map, error) { 8 | m.stateLock.Lock() 9 | defer m.stateLock.Unlock() 10 | if m.state < initialized { 11 | return nil, ErrManagerNotInitialized 12 | } 13 | 14 | // check if the name of the new map is available 15 | _, exists, _ := m.getMap(spec.Name) 16 | if exists { 17 | return nil, ErrMapNameInUse 18 | } 19 | 20 | // Create new map and ring buffer reader 21 | ringBuffer, err := loadNewRingBuffer(spec, options, ringBufferOptions) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | // Setup ring buffer reader 27 | if err := ringBuffer.init(m); err != nil { 28 | return nil, err 29 | } 30 | 31 | // Start perf buffer reader 32 | if err := ringBuffer.Start(); err != nil { 33 | // clean up 34 | _ = ringBuffer.Stop(CleanInternal) 35 | return nil, err 36 | } 37 | 38 | // Add map to the list of perf ring managed by the manager 39 | m.RingBuffers = append(m.RingBuffers, ringBuffer) 40 | return ringBuffer.array, nil 41 | } 42 | -------------------------------------------------------------------------------- /manager_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/cilium/ebpf" 12 | "github.com/cilium/ebpf/asm" 13 | "github.com/cilium/ebpf/rlimit" 14 | ) 15 | 16 | func TestVerifierError(t *testing.T) { 17 | err := rlimit.RemoveMemlock() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | m := &Manager{ 22 | collectionSpec: &ebpf.CollectionSpec{ 23 | Programs: map[string]*ebpf.ProgramSpec{"socket__filter": { 24 | Type: ebpf.SocketFilter, 25 | Instructions: asm.Instructions{ 26 | asm.LoadImm(asm.R0, 0, asm.DWord), 27 | // Missing Return 28 | }, 29 | License: "MIT", 30 | }}, 31 | }, 32 | } 33 | err = m.loadCollection() 34 | if err == nil { 35 | t.Fatal("expected error") 36 | } 37 | var ve *ebpf.VerifierError 38 | if !errors.As(err, &ve) { 39 | t.Fatal("expected to be able to unwrap to VerifierError") 40 | } 41 | if strings.Count(err.Error(), "\n") == 0 { 42 | t.Fatal("expected full verifier error") 43 | } 44 | } 45 | 46 | func TestExclude(t *testing.T) { 47 | err := rlimit.RemoveMemlock() 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | m := &Manager{ 53 | Probes: []*Probe{ 54 | {ProbeIdentificationPair: ProbeIdentificationPair{EBPFFuncName: "access_map_one"}}, 55 | }, 56 | Maps: []*Map{ 57 | {Name: "map_one"}, 58 | }, 59 | } 60 | opts := Options{ 61 | RemoveRlimit: true, 62 | ExcludedMaps: []string{"map_two"}, 63 | } 64 | 65 | f, err := os.Open("testdata/exclude.elf") 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | t.Cleanup(func() { _ = f.Close() }) 70 | err = m.InitWithOptions(f, opts) 71 | if err == nil || !strings.Contains(err.Error(), "missing map map_two") { 72 | t.Fatalf("expected error about missing map map_two, got `%s` instead", err) 73 | } 74 | 75 | opts.ExcludedFunctions = []string{"access_map_two"} 76 | err = m.InitWithOptions(f, opts) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | } 81 | 82 | func TestManager_getTracefsRegex(t *testing.T) { 83 | tests := []struct { 84 | name string 85 | Probes []*Probe 86 | expectedRegex string 87 | }{ 88 | { 89 | name: "sanity", 90 | Probes: []*Probe{ 91 | { 92 | ProbeIdentificationPair: ProbeIdentificationPair{ 93 | UID: "HTTP", 94 | }, 95 | }, 96 | { 97 | ProbeIdentificationPair: ProbeIdentificationPair{ 98 | UID: "tcp", 99 | }, 100 | }, 101 | }, 102 | expectedRegex: "(p|r)[0-9]*:(kprobes|uprobes)\\/(.*(HTTP|tcp)*_([0-9]*)) .*", 103 | }, 104 | { 105 | name: "duplications", 106 | Probes: []*Probe{ 107 | { 108 | ProbeIdentificationPair: ProbeIdentificationPair{ 109 | UID: "HTTP", 110 | }, 111 | }, 112 | { 113 | ProbeIdentificationPair: ProbeIdentificationPair{ 114 | UID: "HTTP", 115 | }, 116 | }, 117 | }, 118 | expectedRegex: "(p|r)[0-9]*:(kprobes|uprobes)\\/(.*(HTTP)*_([0-9]*)) .*", 119 | }, 120 | { 121 | name: "special character", 122 | Probes: []*Probe{ 123 | { 124 | ProbeIdentificationPair: ProbeIdentificationPair{ 125 | UID: "+++++", 126 | }, 127 | }, 128 | { 129 | ProbeIdentificationPair: ProbeIdentificationPair{ 130 | UID: "+3.*.*", 131 | }, 132 | }, 133 | }, 134 | expectedRegex: "(p|r)[0-9]*:(kprobes|uprobes)\\/(.*(\\+\\+\\+\\+\\+|\\+3\\.\\*\\.\\*)*_([0-9]*)) .*", 135 | }, 136 | } 137 | for _, tt := range tests { 138 | t.Run(tt.name, func(t *testing.T) { 139 | m := &Manager{ 140 | Probes: tt.Probes, 141 | } 142 | res, err := m.getTracefsRegex() 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | if res.String() != tt.expectedRegex { 147 | t.Fatalf("expected: %s, got: %s", tt.expectedRegex, res.String()) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func TestDumpMaps(t *testing.T) { 154 | err := rlimit.RemoveMemlock() 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | 159 | m := &Manager{ 160 | Probes: []*Probe{ 161 | {ProbeIdentificationPair: ProbeIdentificationPair{EBPFFuncName: "access_map_one"}}, 162 | }, 163 | Maps: []*Map{ 164 | {Name: "map_one"}, 165 | }, 166 | } 167 | 168 | opts := Options{ 169 | ExcludedFunctions: []string{"access_map_two"}, 170 | } 171 | 172 | f, err := os.Open("testdata/exclude.elf") 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | t.Cleanup(func() { _ = f.Close() }) 177 | 178 | err = m.InitWithOptions(f, opts) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | 183 | dumpContents := "mapdump" 184 | 185 | m.DumpHandler = func(w io.Writer, _ *Manager, mapName string, currentMap *ebpf.Map) { 186 | _, _ = io.WriteString(w, dumpContents) 187 | } 188 | 189 | var output bytes.Buffer 190 | err = m.DumpMaps(&output, "map_one") 191 | if err != nil { 192 | t.Fatal(err) 193 | } 194 | 195 | if dumpContents != output.String() { 196 | t.Errorf("expected %s, got %s", dumpContents, output.String()) 197 | } 198 | } 199 | 200 | func TestInstructionPatching(t *testing.T) { 201 | err := rlimit.RemoveMemlock() 202 | if err != nil { 203 | t.Fatal(err) 204 | } 205 | 206 | // We want to test multiple patchers, so we'll use a generic one 207 | // and call it twice with different constants. 208 | // The patching.c program contains two invalid calls, with constants 209 | // -1 and -2. We just replace them with a movimm instruction. 210 | genericPatcher := func(m *Manager, constant int64) error { 211 | specs, err := m.GetProgramSpecs() 212 | if err != nil { 213 | return err 214 | } 215 | for _, spec := range specs { 216 | if spec == nil { 217 | continue 218 | } 219 | iter := spec.Instructions.Iterate() 220 | for iter.Next() { 221 | ins := iter.Ins 222 | 223 | if !ins.IsBuiltinCall() { 224 | continue 225 | } 226 | 227 | if ins.Constant == constant { 228 | *ins = asm.Mov.Imm(asm.R1, int32(0xff)).WithMetadata(ins.Metadata) 229 | } 230 | } 231 | } 232 | return nil 233 | } 234 | 235 | m := &Manager{ 236 | Probes: []*Probe{ 237 | {ProbeIdentificationPair: ProbeIdentificationPair{EBPFFuncName: "patching_test"}}, 238 | }, 239 | InstructionPatchers: []InstructionPatcherFunc{ 240 | func(m *Manager) error { return genericPatcher(m, -1) }, 241 | func(m *Manager) error { return genericPatcher(m, -2) }, 242 | }, 243 | } 244 | 245 | f, err := os.Open("testdata/patching.elf") 246 | if err != nil { 247 | t.Fatal(err) 248 | } 249 | t.Cleanup(func() { _ = f.Close() }) 250 | 251 | // If any of the patchers fail, they will leave an invalid call instruction 252 | // in the program, which will cause the verifier to fail. This allows us 253 | // to not do any extra validation. 254 | err = m.InitWithOptions(f, Options{}) 255 | if err != nil { 256 | t.Fatal(err) 257 | } 258 | } 259 | 260 | func TestLoadELF(t *testing.T) { 261 | f, err := os.Open("testdata/patching.elf") 262 | if err != nil { 263 | t.Fatal(err) 264 | } 265 | t.Cleanup(func() { _ = f.Close() }) 266 | 267 | m := &Manager{ 268 | state: reset, 269 | } 270 | if err = m.LoadELF(f); err != nil { 271 | t.Errorf("LoadELF() error = %v, expected: %v", err, nil) 272 | } 273 | if err = m.LoadELF(f); !errors.Is(err, ErrManagerELFLoaded) { 274 | t.Errorf("LoadELF() error = %v, expected: %v", err, ErrManagerELFLoaded) 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/cilium/ebpf" 9 | ) 10 | 11 | // MapCleanupType - The map clean up type defines how the maps of a manager should be cleaned up on exit. 12 | // 13 | // A map can only be in one of the following categories 14 | // 15 | // ---------------------- 16 | // | Internally loaded | 17 | // ---------------------- 18 | // Categories: | Pinned | Not Pinned | 19 | // ---------------------- 20 | type MapCleanupType int 21 | 22 | const ( 23 | CleanInternalPinned MapCleanupType = 1 << 1 24 | CleanInternalNotPinned MapCleanupType = 1 << 2 25 | CleanInternal = CleanInternalPinned | CleanInternalNotPinned 26 | CleanAll = CleanInternal 27 | ) 28 | 29 | // MapOptions - Generic Map options that are not shared with the MapSpec definition 30 | type MapOptions struct { 31 | // PinPath - Once loaded, the eBPF map will be pinned to this path. If the map has already been pinned and is 32 | // already present in the kernel, then it will be loaded from this path. 33 | PinPath string 34 | 35 | // AlwaysCleanup - Overrides the cleanup type given to the manager. See CleanupType for more. 36 | AlwaysCleanup bool 37 | } 38 | 39 | type Map struct { 40 | array *ebpf.Map 41 | arraySpec *ebpf.MapSpec 42 | state state 43 | stateLock sync.Mutex 44 | 45 | // Name - Name of the map as defined in its section SEC("maps/[name]") 46 | Name string 47 | 48 | // Contents - The initial contents of the map. May be nil. 49 | Contents []ebpf.MapKV 50 | 51 | // Other options 52 | MapOptions 53 | } 54 | 55 | // loadNewMap - Creates a new map instance, loads it and returns a pointer to the Map structure 56 | func loadNewMap(spec *ebpf.MapSpec, options MapOptions) (*Map, error) { 57 | // Create new map 58 | managerMap := Map{ 59 | arraySpec: spec, 60 | Name: spec.Name, 61 | Contents: spec.Contents, 62 | MapOptions: options, 63 | } 64 | 65 | // Load map 66 | var err error 67 | if managerMap.array, err = ebpf.NewMap(spec); err != nil { 68 | return nil, err 69 | } 70 | 71 | // Pin map if need be 72 | if managerMap.PinPath != "" { 73 | if err = managerMap.array.Pin(managerMap.PinPath); err != nil { 74 | return nil, fmt.Errorf("couldn't pin map %s at %s: %w", managerMap.Name, managerMap.PinPath, err) 75 | } 76 | } 77 | return &managerMap, nil 78 | } 79 | 80 | // init - Initialize a map 81 | func (m *Map) init() error { 82 | m.stateLock.Lock() 83 | defer m.stateLock.Unlock() 84 | if m.state >= initialized { 85 | return ErrMapInitialized 86 | } 87 | 88 | m.state = initialized 89 | return nil 90 | } 91 | 92 | // Close - Close underlying eBPF map. 93 | func (m *Map) Close(cleanup MapCleanupType) error { 94 | m.stateLock.Lock() 95 | defer m.stateLock.Unlock() 96 | if m.state < initialized { 97 | return ErrMapInitialized 98 | } 99 | return m.close(cleanup) 100 | } 101 | 102 | // close - (not thread safe) close 103 | func (m *Map) close(cleanup MapCleanupType) error { 104 | shouldClose := false 105 | if m.AlwaysCleanup { 106 | shouldClose = true 107 | } 108 | if cleanup&CleanInternalPinned == CleanInternalPinned && m.array.IsPinned() { 109 | shouldClose = true 110 | } 111 | if cleanup&CleanInternalNotPinned == CleanInternalNotPinned && !m.array.IsPinned() { 112 | shouldClose = true 113 | } 114 | if shouldClose { 115 | err := errors.Join(m.array.Unpin(), m.array.Close()) 116 | if err != nil { 117 | return err 118 | } 119 | m.reset() 120 | } 121 | return nil 122 | } 123 | 124 | // reset - Cleans up the internal fields of the map 125 | func (m *Map) reset() { 126 | m.array = nil 127 | m.arraySpec = nil 128 | m.state = reset 129 | } 130 | -------------------------------------------------------------------------------- /map_route.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | ) 8 | 9 | // MapRoute - A map route defines how multiple maps should be routed between eBPF programs. 10 | // 11 | // The provided eBPF map will be inserted in the provided eBPF array of maps (or hash of maps), at the provided key. The 12 | // inserted eBPF map can be provided by its section or by its *ebpf.Map representation. 13 | type MapRoute struct { 14 | // RoutingMapName - Name of the BPF_MAP_TYPE_ARRAY_OF_MAPS or BPF_MAP_TYPE_HASH_OF_MAPS map, as defined in its 15 | // section SEC("maps/[RoutingMapName]") 16 | RoutingMapName string 17 | 18 | // Key - Key at which the program will be inserted in the routing map 19 | Key interface{} 20 | 21 | // RoutedName - Section of the map that will be inserted 22 | RoutedName string 23 | 24 | // Map - Map to insert in the routing map 25 | Map *ebpf.Map 26 | } 27 | 28 | // UpdateMapRoutes - Update one or multiple map of maps structures so that the provided keys point to the provided maps. 29 | func (m *Manager) UpdateMapRoutes(router ...MapRoute) error { 30 | m.stateLock.Lock() 31 | defer m.stateLock.Unlock() 32 | if m.collection == nil || m.state < initialized { 33 | return ErrManagerNotInitialized 34 | } 35 | 36 | for _, route := range router { 37 | if err := m.updateMapRoute(route); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | 44 | // updateMapRoute - Update a map of maps structure so that the provided key points to the provided map 45 | func (m *Manager) updateMapRoute(route MapRoute) error { 46 | // Select the routing map 47 | routingMap, found, err := m.getMap(route.RoutingMapName) 48 | if err != nil { 49 | return err 50 | } 51 | if !found { 52 | return fmt.Errorf("couldn't find routing map %s: %w", route.RoutingMapName, ErrUnknownSection) 53 | } 54 | 55 | // Get file descriptor of the routed map 56 | var fd uint32 57 | if route.Map != nil { 58 | fd = uint32(route.Map.FD()) 59 | } else { 60 | var routedMap *ebpf.Map 61 | routedMap, found, err = m.getMap(route.RoutedName) 62 | if err != nil { 63 | return err 64 | } 65 | if !found { 66 | return fmt.Errorf("couldn't find routed map %s: %w", route.RoutedName, ErrUnknownSection) 67 | } 68 | fd = uint32(routedMap.FD()) 69 | } 70 | 71 | // Insert map 72 | if err = routingMap.Put(route.Key, fd); err != nil { 73 | return fmt.Errorf("couldn't update routing map %s: %w", route.RoutingMapName, err) 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /mapspec_editor.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | "github.com/cilium/ebpf/btf" 8 | ) 9 | 10 | // MapSpecEditorFlag - Flag used to specify what a MapSpecEditor should edit. 11 | type MapSpecEditorFlag uint 12 | 13 | const ( 14 | EditType MapSpecEditorFlag = 1 << 1 15 | EditMaxEntries MapSpecEditorFlag = 1 << 2 16 | EditFlags MapSpecEditorFlag = 1 << 3 17 | EditKeyValue MapSpecEditorFlag = 1 << 4 18 | ) 19 | 20 | // MapSpecEditor - A MapSpec editor defines how specific parameters of specific maps should be updated at runtime 21 | // 22 | // For example, this can be used if you need to change the max_entries of a map before it is loaded in the kernel, but 23 | // you don't know what this value should be initially. 24 | type MapSpecEditor struct { 25 | // Type - Type of the map. 26 | Type ebpf.MapType 27 | // MaxEntries - Max Entries of the map. 28 | MaxEntries uint32 29 | // Flags - Flags provided to the kernel during the loading process. 30 | Flags uint32 31 | // KeySize - Defines the key size of the map 32 | KeySize uint32 33 | // Key - Defines the BTF type of the keys of the map 34 | Key btf.Type 35 | // ValueSize - Defines the value size of the map 36 | ValueSize uint32 37 | // Value - Defines the BTF type of the values of the map 38 | Value btf.Type 39 | // EditorFlag - Use this flag to specify what fields should be updated. See MapSpecEditorFlag. 40 | EditorFlag MapSpecEditorFlag 41 | } 42 | 43 | // editMapSpecs - Update the MapSpec with the provided MapSpec editors. 44 | func (m *Manager) editMapSpecs() error { 45 | for name, mapEditor := range m.options.MapSpecEditors { 46 | // select the map spec 47 | spec, exists, err := m.GetMapSpec(name) 48 | if err != nil { 49 | return err 50 | } 51 | if !exists { 52 | return fmt.Errorf("failed to edit maps/%s: couldn't find map: %w", name, ErrUnknownSection) 53 | } 54 | if mapEditor.EditorFlag == 0 { 55 | return fmt.Errorf("failed to edit maps/%s: %w", name, ErrMissingEditorFlags) 56 | } 57 | if EditType&mapEditor.EditorFlag == EditType { 58 | spec.Type = mapEditor.Type 59 | } 60 | if EditMaxEntries&mapEditor.EditorFlag == EditMaxEntries { 61 | spec.MaxEntries = mapEditor.MaxEntries 62 | } 63 | if EditFlags&mapEditor.EditorFlag == EditFlags { 64 | spec.Flags = mapEditor.Flags 65 | } 66 | if EditKeyValue&mapEditor.EditorFlag == EditKeyValue { 67 | spec.Key = mapEditor.Key 68 | spec.KeySize = mapEditor.KeySize 69 | spec.Value = mapEditor.Value 70 | spec.ValueSize = mapEditor.ValueSize 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /netlink.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | 8 | "github.com/vishvananda/netlink" 9 | "github.com/vishvananda/netns" 10 | "golang.org/x/sys/unix" 11 | ) 12 | 13 | // NetlinkSocket - (TC classifier programs and XDP) Netlink socket cache entry holding the netlink socket and the 14 | // TC filter count 15 | type NetlinkSocket struct { 16 | Sock *netlink.Handle 17 | filterMutex sync.Mutex 18 | tcFilterCount map[int]int 19 | } 20 | 21 | // NewNetlinkSocket - Returns a new NetlinkSocket instance 22 | func NewNetlinkSocket(nsHandle uint64) (*NetlinkSocket, error) { 23 | var err error 24 | var netnsHandle netns.NsHandle 25 | cacheEntry := NetlinkSocket{ 26 | tcFilterCount: make(map[int]int), 27 | } 28 | 29 | if nsHandle == 0 { 30 | netnsHandle = netns.None() 31 | } else { 32 | netnsHandle = netns.NsHandle(nsHandle) 33 | } 34 | 35 | // Open a netlink socket for the requested namespace 36 | cacheEntry.Sock, err = netlink.NewHandleAt(netnsHandle, unix.NETLINK_ROUTE) 37 | if err != nil { 38 | return nil, fmt.Errorf("couldn't open a netlink socket: %w", err) 39 | } 40 | return &cacheEntry, nil 41 | } 42 | 43 | // IncreaseFilterCount increases the count for the given index in a thread-safe manner. The return value is the new count. 44 | func (ns *NetlinkSocket) IncreaseFilterCount(index int) { 45 | ns.filterMutex.Lock() 46 | defer ns.filterMutex.Unlock() 47 | ns.tcFilterCount[index]++ 48 | } 49 | 50 | // DecreaseFilterCount decreases the count for the given index in a thread-safe manner. It will delete the entry if the count reaches 0. 51 | // The return value is the count after the decrease. If the entry was deleted, the return value is 0. 52 | func (ns *NetlinkSocket) DecreaseFilterCount(index int) int { 53 | ns.filterMutex.Lock() 54 | defer ns.filterMutex.Unlock() 55 | ns.tcFilterCount[index]-- 56 | 57 | if ns.tcFilterCount[index] <= 0 { 58 | delete(ns.tcFilterCount, index) 59 | return 0 60 | } 61 | 62 | return ns.tcFilterCount[index] 63 | } 64 | 65 | type netlinkSocketCache struct { 66 | sync.Mutex 67 | cache map[uint32]*NetlinkSocket 68 | } 69 | 70 | func newNetlinkSocketCache() *netlinkSocketCache { 71 | return &netlinkSocketCache{ 72 | cache: make(map[uint32]*NetlinkSocket), 73 | } 74 | } 75 | 76 | // getNetlinkSocket - Returns a netlink socket in the requested network namespace from cache or creates a new one. 77 | // TC classifiers are attached by creating a qdisc on the requested interface. A netlink socket 78 | // is required to create a qdisc (or to attach an XDP program to an interface). Since this socket can be re-used for 79 | // multiple probes, instantiate the connection at the manager level and cache the netlink socket. The provided nsID 80 | // should be the ID of the network namespaced returned by a readlink on `/proc/[pid]/ns/net` for a [pid] that lives in 81 | // the network namespace pointed to by the nsHandle. 82 | func (nsc *netlinkSocketCache) getNetlinkSocket(nsHandle uint64, nsID uint32) (*NetlinkSocket, error) { 83 | nsc.Lock() 84 | defer nsc.Unlock() 85 | 86 | sock, ok := nsc.cache[nsID] 87 | if ok { 88 | return sock, nil 89 | } 90 | 91 | cacheEntry, err := NewNetlinkSocket(nsHandle) 92 | if err != nil { 93 | return nil, fmt.Errorf("namespace %v: %w", nsID, err) 94 | } 95 | 96 | nsc.cache[nsID] = cacheEntry 97 | return cacheEntry, nil 98 | } 99 | 100 | // cleanup - Cleans up all opened netlink sockets in cache. This function is expected to be called when a 101 | // manager is stopped. 102 | func (nsc *netlinkSocketCache) cleanup() { 103 | nsc.Lock() 104 | defer nsc.Unlock() 105 | 106 | for key, s := range nsc.cache { 107 | delete(nsc.cache, key) 108 | // close the netlink socket 109 | s.Sock.Close() 110 | } 111 | } 112 | 113 | func (nsc *netlinkSocketCache) remove(nsID uint32) { 114 | nsc.Lock() 115 | defer nsc.Unlock() 116 | 117 | s, ok := nsc.cache[nsID] 118 | if ok { 119 | delete(nsc.cache, nsID) 120 | 121 | // close the netlink socket 122 | s.Sock.Close() 123 | } 124 | } 125 | 126 | func (m *Manager) GetNetlinkSocket(nsHandle uint64, nsID uint32) (*NetlinkSocket, error) { 127 | return m.netlinkSocketCache.getNetlinkSocket(nsHandle, nsID) 128 | } 129 | 130 | // CleanupNetworkNamespace - Cleans up all references to the provided network namespace within the manager. This means 131 | // that any TC classifier or XDP probe in that network namespace will be stopped and all opened netlink socket in that 132 | // namespace will be closed. 133 | // WARNING: Don't forget to call this method if you've provided a IfIndexNetns and IfIndexNetnsID to one of the probes 134 | // of this manager. Failing to call this cleanup function may lead to leaking the network namespace. Only call this 135 | // function when you're sure that the manager no longer needs to perform anything in the provided network namespace (or 136 | // else call NewNetlinkSocket first). 137 | func (m *Manager) CleanupNetworkNamespace(nsID uint32) error { 138 | m.stateLock.Lock() 139 | defer m.stateLock.Unlock() 140 | if m.state < initialized { 141 | return ErrManagerNotInitialized 142 | } 143 | 144 | var errs []error 145 | var toDelete []int 146 | for i, probe := range m.Probes { 147 | if probe.IfIndexNetnsID != nsID { 148 | continue 149 | } 150 | 151 | // stop the probe 152 | errs = append(errs, probe.Stop()) 153 | 154 | // disable probe 155 | probe.Enabled = false 156 | 157 | // append probe to delete (biggest indexes first) 158 | toDelete = append([]int{i}, toDelete...) 159 | } 160 | 161 | // delete all netlink sockets, along with netns handles 162 | m.netlinkSocketCache.remove(nsID) 163 | 164 | // delete probes 165 | for _, i := range toDelete { 166 | // we delete the biggest indexes first, so we should be good to go ! 167 | m.Probes = append(m.Probes[:i], m.Probes[i+1:]...) 168 | } 169 | return errors.Join(errs...) 170 | } 171 | 172 | // ResolveLink - Resolves the Probe's network interface 173 | func (p *Probe) ResolveLink() (netlink.Link, error) { 174 | return p.resolveLink() 175 | } 176 | 177 | func (p *Probe) resolveLink() (netlink.Link, error) { 178 | if p.link != nil { 179 | return p.link, nil 180 | } 181 | 182 | // get a netlink socket in the probe network namespace 183 | ntl, err := p.getNetlinkSocket() 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | if p.IfIndex > 0 { 189 | p.link, err = ntl.Sock.LinkByIndex(p.IfIndex) 190 | if err != nil { 191 | return nil, fmt.Errorf("couldn't resolve interface with IfIndex %d in namespace %d: %w", p.IfIndex, p.IfIndexNetnsID, err) 192 | } 193 | } else if len(p.IfName) > 0 { 194 | p.link, err = ntl.Sock.LinkByName(p.IfName) 195 | if err != nil { 196 | return nil, fmt.Errorf("couldn't resolve interface with IfName %s in namespace %d: %w", p.IfName, p.IfIndexNetnsID, err) 197 | } 198 | } else { 199 | return nil, ErrInterfaceNotSet 200 | } 201 | 202 | attrs := p.link.Attrs() 203 | if attrs != nil { 204 | p.IfIndex = attrs.Index 205 | p.IfName = attrs.Name 206 | } 207 | 208 | return p.link, nil 209 | } 210 | -------------------------------------------------------------------------------- /pauser.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | type pauser interface { 4 | Pause() error 5 | Resume() error 6 | } 7 | -------------------------------------------------------------------------------- /perf_event.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "syscall" 9 | "unsafe" 10 | 11 | "github.com/cilium/ebpf" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | // attachPerfEvent - Attaches the perf_event program 16 | func (p *Probe) attachPerfEvent() error { 17 | if p.PerfEventType != unix.PERF_TYPE_HARDWARE && p.PerfEventType != unix.PERF_TYPE_SOFTWARE { 18 | return fmt.Errorf("unknown PerfEventType parameter: %v (expected unix.PERF_TYPE_HARDWARE or unix.PERF_TYPE_SOFTWARE)", p.PerfEventType) 19 | } 20 | 21 | attr := unix.PerfEventAttr{ 22 | Type: uint32(p.PerfEventType), 23 | Sample: uint64(p.SamplePeriod), 24 | Config: uint64(p.PerfEventConfig), 25 | } 26 | 27 | if p.SampleFrequency > 0 { 28 | attr.Sample = uint64(p.SampleFrequency) 29 | attr.Bits |= unix.PerfBitFreq 30 | } 31 | 32 | if p.PerfEventCPUCount == 0 { 33 | p.PerfEventCPUCount = runtime.NumCPU() 34 | } 35 | 36 | pid := p.PerfEventPID 37 | if pid == 0 { 38 | pid = -1 39 | } 40 | 41 | link := &perfEventProgLink{ 42 | perfEventCPUFDs: make([]*perfEventLink, 0, p.PerfEventCPUCount), 43 | } 44 | for cpu := 0; cpu < p.PerfEventCPUCount; cpu++ { 45 | fd, err := perfEventOpenRaw(&attr, pid, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) 46 | if err != nil { 47 | return fmt.Errorf("couldn't attach perf_event program %s on pid %d and CPU %d: %v", p.ProbeIdentificationPair, pid, cpu, err) 48 | } 49 | pfd := newPerfEventLink(fd) 50 | link.perfEventCPUFDs = append(link.perfEventCPUFDs, pfd) 51 | if err := attachPerfEvent(pfd, p.program); err != nil { 52 | _ = link.Close() 53 | return fmt.Errorf("attach: %w", err) 54 | } 55 | } 56 | p.progLink = link 57 | return nil 58 | } 59 | 60 | type perfEventProgLink struct { 61 | perfEventCPUFDs []*perfEventLink 62 | } 63 | 64 | func (p *perfEventProgLink) Close() error { 65 | var errs []error 66 | for _, fd := range p.perfEventCPUFDs { 67 | errs = append(errs, fd.Close()) 68 | } 69 | p.perfEventCPUFDs = []*perfEventLink{} 70 | return errors.Join(errs...) 71 | } 72 | 73 | type perfEventLink struct { 74 | fd *fd 75 | } 76 | 77 | func newPerfEventLink(fd *fd) *perfEventLink { 78 | pe := &perfEventLink{fd} 79 | runtime.SetFinalizer(pe, (*perfEventLink).Close) 80 | return pe 81 | } 82 | 83 | func (pe *perfEventLink) Close() error { 84 | runtime.SetFinalizer(pe, nil) 85 | return pe.fd.Close() 86 | } 87 | 88 | func attachPerfEvent(pe *perfEventLink, prog *ebpf.Program) error { 89 | if err := ioctlPerfEventSetBPF(pe.fd, prog.FD()); err != nil { 90 | return fmt.Errorf("set perf event bpf: %w", err) 91 | } 92 | if err := ioctlPerfEventEnable(pe.fd); err != nil { 93 | return fmt.Errorf("enable perf event: %w", err) 94 | } 95 | return nil 96 | } 97 | 98 | // perfEventOpenPMU - Kernel API with e12f03d ("perf/core: Implement the 'perf_kprobe' PMU") allows 99 | // creating [k,u]probe with perf_event_open, which makes it easier to clean up 100 | // the [k,u]probe. This function tries to create pfd with the perf_kprobe PMU. 101 | func perfEventOpenPMU(name string, offset, pid int, eventType probeType, retProbe bool, referenceCounterOffset uint64) (*fd, error) { 102 | var err error 103 | var attr unix.PerfEventAttr 104 | 105 | // Getting the PMU type will fail if the kernel doesn't support 106 | // the perf_[k,u]probe PMU. 107 | attr.Type, err = getPMUEventType(eventType) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | if retProbe { 113 | var retProbeBit uint64 114 | retProbeBit, err = getRetProbeBit(eventType) 115 | if err != nil { 116 | return nil, err 117 | } 118 | attr.Config |= 1 << retProbeBit 119 | } 120 | 121 | if referenceCounterOffset > 0 { 122 | attr.Config |= referenceCounterOffset << 32 123 | } 124 | 125 | // transform the symbol name or the uprobe path to a byte array 126 | namePtr, err := syscall.BytePtrFromString(name) 127 | if err != nil { 128 | return nil, fmt.Errorf("couldn't create pointer to string %s: %w", name, err) 129 | } 130 | 131 | switch eventType { 132 | case kprobe: 133 | attr.Ext1 = uint64(uintptr(unsafe.Pointer(namePtr))) // Kernel symbol to trace 134 | pid = -1 135 | case uprobe: 136 | // The minimum size required for PMU uprobes is PERF_ATTR_SIZE_VER1, 137 | // since it added the config2 (Ext2) field. The Size field controls the 138 | // size of the internal buffer the kernel allocates for reading the 139 | // perf_event_attr argument from userspace. 140 | attr.Size = unix.PERF_ATTR_SIZE_VER1 141 | attr.Ext1 = uint64(uintptr(unsafe.Pointer(namePtr))) // Uprobe path 142 | attr.Ext2 = uint64(offset) // Uprobe offset 143 | // PID filter is only possible for uprobe events 144 | if pid <= 0 { 145 | pid = -1 146 | } 147 | } 148 | 149 | cpu := 0 150 | if pid != -1 { 151 | cpu = -1 152 | } 153 | var efd int 154 | efd, err = unix.PerfEventOpen(&attr, pid, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) 155 | 156 | // Since commit 97c753e62e6c, ENOENT is correctly returned instead of EINVAL 157 | // when trying to create a kretprobe for a missing symbol. Make sure ENOENT 158 | // is returned to the caller. 159 | if errors.Is(err, os.ErrNotExist) || errors.Is(err, unix.EINVAL) { 160 | return nil, fmt.Errorf("symbol '%s' not found: %w", name, syscall.EINVAL) 161 | } 162 | if err != nil { 163 | return nil, fmt.Errorf("opening perf event: %w", err) 164 | } 165 | 166 | // Ensure the string pointer is not collected before PerfEventOpen returns. 167 | runtime.KeepAlive(unsafe.Pointer(namePtr)) 168 | 169 | return newFD(uint32(efd)), nil 170 | } 171 | 172 | func perfEventOpenTracingEvent(probeID int, pid int) (*fd, error) { 173 | if pid <= 0 { 174 | pid = -1 175 | } 176 | cpu := 0 177 | if pid != -1 { 178 | cpu = -1 179 | } 180 | attr := unix.PerfEventAttr{ 181 | Type: unix.PERF_TYPE_TRACEPOINT, 182 | Sample_type: unix.PERF_SAMPLE_RAW, 183 | Sample: 1, 184 | Wakeup: 1, 185 | Config: uint64(probeID), 186 | } 187 | attr.Size = uint32(unsafe.Sizeof(attr)) 188 | return perfEventOpenRaw(&attr, pid, cpu, -1, unix.PERF_FLAG_FD_CLOEXEC) 189 | } 190 | 191 | func perfEventOpenRaw(attr *unix.PerfEventAttr, pid int, cpu int, groupFd int, flags int) (*fd, error) { 192 | efd, err := unix.PerfEventOpen(attr, pid, cpu, groupFd, flags) 193 | if efd < 0 { 194 | return nil, fmt.Errorf("perf_event_open error: %v", err) 195 | } 196 | return newFD(uint32(efd)), nil 197 | } 198 | 199 | func ioctlPerfEventSetBPF(perfEventOpenFD *fd, progFD int) error { 200 | return unix.IoctlSetInt(int(perfEventOpenFD.raw), unix.PERF_EVENT_IOC_SET_BPF, progFD) 201 | } 202 | 203 | func ioctlPerfEventEnable(perfEventOpenFD *fd) error { 204 | return unix.IoctlSetInt(int(perfEventOpenFD.raw), unix.PERF_EVENT_IOC_ENABLE, 0) 205 | } 206 | -------------------------------------------------------------------------------- /perfmap.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | 9 | "github.com/cilium/ebpf" 10 | "github.com/cilium/ebpf/perf" 11 | ) 12 | 13 | // PerfMapOptions - Perf map specific options 14 | type PerfMapOptions struct { 15 | // PerfRingBufferSize - Size in bytes of the perf ring buffer. Defaults to the manager value if not set. 16 | PerfRingBufferSize int 17 | 18 | // Watermark - The reader will start processing samples once their sizes in the perf ring buffer 19 | // exceed this value. Must be smaller than PerfRingBufferSize. Defaults to the manager value if not set. 20 | Watermark int 21 | 22 | // The number of events required in any per CPU buffer before 23 | // Read will process data. This is mutually exclusive with Watermark. 24 | // The default is zero, which means Watermark will take precedence. 25 | WakeupEvents int 26 | 27 | // PerfErrChan - Perf reader error channel 28 | PerfErrChan chan error 29 | 30 | // DataHandler - Callback function called when a new sample was retrieved from the perf 31 | // ring buffer. 32 | DataHandler func(CPU int, data []byte, perfMap *PerfMap, manager *Manager) 33 | 34 | // RecordHandler - Callback function called when a new record was retrieved from the perf 35 | // ring buffer. 36 | RecordHandler func(record *perf.Record, perfMap *PerfMap, manager *Manager) 37 | 38 | // LostHandler - Callback function called when one or more events where dropped by the kernel 39 | // because the perf ring buffer was full. 40 | LostHandler func(CPU int, count uint64, perfMap *PerfMap, manager *Manager) 41 | 42 | // RecordGetter - if specified this getter will be used to get a new record 43 | RecordGetter func() *perf.Record 44 | 45 | // TelemetryEnabled turns on telemetry about the usage of the perf ring buffer 46 | TelemetryEnabled bool 47 | } 48 | 49 | // PerfMap - Perf ring buffer reader wrapper 50 | type PerfMap struct { 51 | manager *Manager 52 | perfReader *perf.Reader 53 | wgReader sync.WaitGroup 54 | bufferSize int 55 | usageTelemetry []*atomic.Uint64 56 | lostTelemetry []*atomic.Uint64 57 | 58 | // Map - A PerfMap has the same features as a normal Map 59 | Map 60 | PerfMapOptions 61 | } 62 | 63 | // loadNewPerfMap - Creates a new perf map instance, loads it and sets up the perf ring buffer reader 64 | func loadNewPerfMap(spec *ebpf.MapSpec, options MapOptions, perfOptions PerfMapOptions) (*PerfMap, error) { 65 | perfMap := PerfMap{ 66 | Map: Map{ 67 | arraySpec: spec, 68 | Name: spec.Name, 69 | MapOptions: options, 70 | }, 71 | PerfMapOptions: perfOptions, 72 | } 73 | 74 | var err error 75 | if perfMap.array, err = ebpf.NewMap(spec); err != nil { 76 | return nil, err 77 | } 78 | 79 | if perfMap.PinPath != "" { 80 | if err = perfMap.array.Pin(perfMap.PinPath); err != nil { 81 | return nil, fmt.Errorf("couldn't pin map %s at %s: %w", perfMap.Name, perfMap.PinPath, err) 82 | } 83 | } 84 | 85 | return &perfMap, nil 86 | } 87 | 88 | // init - Initialize a map 89 | func (m *PerfMap) init(manager *Manager) error { 90 | m.manager = manager 91 | 92 | if m.DataHandler == nil && m.RecordHandler == nil { 93 | return fmt.Errorf("no DataHandler/RecordHandler set for %s", m.Name) 94 | } 95 | 96 | // Set default values if not already set 97 | if m.PerfRingBufferSize == 0 { 98 | m.PerfRingBufferSize = manager.options.DefaultPerfRingBufferSize 99 | } 100 | if m.WakeupEvents == 0 && m.Watermark == 0 { 101 | m.Watermark = manager.options.DefaultWatermark 102 | } 103 | 104 | if m.TelemetryEnabled { 105 | nCPU := m.array.MaxEntries() 106 | m.usageTelemetry = make([]*atomic.Uint64, nCPU) 107 | m.lostTelemetry = make([]*atomic.Uint64, nCPU) 108 | for cpu := range m.usageTelemetry { 109 | m.usageTelemetry[cpu] = &atomic.Uint64{} 110 | m.lostTelemetry[cpu] = &atomic.Uint64{} 111 | } 112 | } 113 | 114 | // Initialize the underlying map structure 115 | if err := m.Map.init(); err != nil { 116 | return err 117 | } 118 | return nil 119 | } 120 | 121 | // Start - Starts fetching events on a perf ring buffer 122 | func (m *PerfMap) Start() error { 123 | m.stateLock.Lock() 124 | defer m.stateLock.Unlock() 125 | if m.state == running { 126 | return nil 127 | } 128 | if m.state < initialized { 129 | return ErrMapNotInitialized 130 | } 131 | 132 | // Create and start the perf map 133 | var err error 134 | opt := perf.ReaderOptions{ 135 | Watermark: m.Watermark, 136 | WakeupEvents: m.WakeupEvents, 137 | } 138 | if m.perfReader, err = perf.NewReaderWithOptions(m.array, m.PerfRingBufferSize, opt); err != nil { 139 | return err 140 | } 141 | m.bufferSize = m.perfReader.BufferSize() 142 | 143 | m.wgReader.Add(1) 144 | 145 | // Start listening for data 146 | go func() { 147 | record := &perf.Record{} 148 | var err error 149 | 150 | for { 151 | if m.PerfMapOptions.RecordGetter != nil { 152 | record = m.PerfMapOptions.RecordGetter() 153 | } else if m.DataHandler != nil { 154 | record = new(perf.Record) 155 | } 156 | 157 | if err = m.perfReader.ReadInto(record); err != nil { 158 | if errors.Is(err, perf.ErrClosed) { 159 | m.wgReader.Done() 160 | return 161 | } 162 | // all records post-wakeup have been read, send sentinel empty record 163 | if errors.Is(err, perf.ErrFlushed) { 164 | record.RawSample = record.RawSample[:0] 165 | } else { 166 | if m.PerfErrChan != nil { 167 | m.PerfErrChan <- err 168 | } 169 | continue 170 | } 171 | } 172 | 173 | if record.LostSamples > 0 { 174 | if m.lostTelemetry != nil && record.CPU < len(m.lostTelemetry) { 175 | m.lostTelemetry[record.CPU].Add(record.LostSamples) 176 | // force usage to max because a sample was lost 177 | updateMaxTelemetry(m.usageTelemetry[record.CPU], uint64(m.bufferSize)) 178 | } 179 | if m.LostHandler != nil { 180 | m.LostHandler(record.CPU, record.LostSamples, m, m.manager) 181 | } 182 | continue 183 | } 184 | 185 | if m.usageTelemetry != nil && record.CPU < len(m.usageTelemetry) { 186 | updateMaxTelemetry(m.usageTelemetry[record.CPU], uint64(record.Remaining)) 187 | } 188 | if m.RecordHandler != nil { 189 | m.RecordHandler(record, m, m.manager) 190 | } else if m.DataHandler != nil { 191 | m.DataHandler(record.CPU, record.RawSample, m, m.manager) 192 | } 193 | } 194 | }() 195 | 196 | m.state = running 197 | return nil 198 | } 199 | 200 | // Flush unblocks the underlying reader and will cause the pending samples to be read 201 | func (m *PerfMap) Flush() { 202 | m.stateLock.Lock() 203 | defer m.stateLock.Unlock() 204 | if m.state != running { 205 | return 206 | } 207 | 208 | _ = m.perfReader.Flush() 209 | } 210 | 211 | // Stop - Stops the perf ring buffer 212 | func (m *PerfMap) Stop(cleanup MapCleanupType) error { 213 | m.stateLock.Lock() 214 | defer m.stateLock.Unlock() 215 | if m.state <= stopped { 216 | return nil 217 | } 218 | m.state = stopped 219 | 220 | // close perf reader 221 | err := m.perfReader.Close() 222 | 223 | m.wgReader.Wait() 224 | 225 | // close underlying map 226 | if errTmp := m.Map.close(cleanup); errTmp != nil { 227 | if err == nil { 228 | err = errTmp 229 | } else { 230 | err = fmt.Errorf("%s: %w", err.Error(), errTmp) 231 | } 232 | } 233 | 234 | return err 235 | } 236 | 237 | // Pause - Pauses a perf ring buffer reader 238 | func (m *PerfMap) Pause() error { 239 | m.stateLock.Lock() 240 | defer m.stateLock.Unlock() 241 | if m.state < running { 242 | return ErrMapNotRunning 243 | } 244 | if err := m.perfReader.Pause(); err != nil { 245 | return err 246 | } 247 | m.state = paused 248 | return nil 249 | } 250 | 251 | // Resume - Resumes a perf ring buffer reader 252 | func (m *PerfMap) Resume() error { 253 | m.stateLock.Lock() 254 | defer m.stateLock.Unlock() 255 | if m.state < paused { 256 | return ErrMapNotRunning 257 | } 258 | if err := m.perfReader.Resume(); err != nil { 259 | return err 260 | } 261 | m.state = running 262 | return nil 263 | } 264 | 265 | // BufferSize is the size in bytes of each per-CPU buffer 266 | func (m *PerfMap) BufferSize() int { 267 | return m.bufferSize 268 | } 269 | 270 | // Telemetry returns the usage and lost telemetry 271 | func (m *PerfMap) Telemetry() (usage []uint64, lost []uint64) { 272 | m.stateLock.Lock() 273 | defer m.stateLock.Unlock() 274 | if m.state < initialized || m.usageTelemetry == nil || m.lostTelemetry == nil { 275 | return nil, nil 276 | } 277 | usage = make([]uint64, len(m.usageTelemetry)) 278 | lost = make([]uint64, len(m.lostTelemetry)) 279 | for cpu := range m.usageTelemetry { 280 | // reset to zero, so we return the max value between each collection 281 | usage[cpu] = m.usageTelemetry[cpu].Swap(0) 282 | lost[cpu] = m.lostTelemetry[cpu].Swap(0) 283 | } 284 | return 285 | } 286 | 287 | func updateMaxTelemetry(a *atomic.Uint64, val uint64) { 288 | for { 289 | oldVal := a.Load() 290 | if val <= oldVal { 291 | return 292 | } 293 | if a.CompareAndSwap(oldVal, val) { 294 | return 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /pip.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import "fmt" 4 | 5 | type ProbeIdentificationPair struct { 6 | // UID - (optional) this field can be used to identify your probes when the same eBPF program is used on multiple 7 | // hook points. Keep in mind that the pair (probe section, probe UID) needs to be unique 8 | // system-wide for the kprobes and uprobes registration to work. 9 | UID string 10 | 11 | // EBPFFuncName - Name of the main eBPF function of your eBPF program. 12 | EBPFFuncName string 13 | } 14 | 15 | func (pip ProbeIdentificationPair) String() string { 16 | return fmt.Sprintf("{UID:%s EBPFFuncName:%s}", pip.UID, pip.EBPFFuncName) 17 | } 18 | 19 | // RenameProbeIdentificationPair - Renames the probe identification pair of a probe 20 | func (p *Probe) RenameProbeIdentificationPair(newID ProbeIdentificationPair) error { 21 | p.stateLock.Lock() 22 | defer p.stateLock.Unlock() 23 | if p.state >= paused { 24 | return fmt.Errorf("couldn't rename ProbeIdentificationPair of %s with %s: %w", p.ProbeIdentificationPair, newID, ErrProbeRunning) 25 | } 26 | p.UID = newID.UID 27 | return nil 28 | } 29 | 30 | // RenameProbeIdentificationPair - Renames a probe identification pair. This change will propagate to all the features in 31 | // the manager that will try to select the probe by its old ProbeIdentificationPair. 32 | func (m *Manager) RenameProbeIdentificationPair(oldID ProbeIdentificationPair, newID ProbeIdentificationPair) error { 33 | m.stateLock.Lock() 34 | defer m.stateLock.Unlock() 35 | 36 | // sanity check: make sure the newID doesn't already exist 37 | for _, mProbe := range m.Probes { 38 | if mProbe.ProbeIdentificationPair == newID { 39 | return ErrIdentificationPairInUse 40 | } 41 | } 42 | 43 | if oldID.EBPFFuncName != newID.EBPFFuncName { 44 | // edit the excluded sections 45 | for i, excludedFuncName := range m.options.ExcludedFunctions { 46 | if excludedFuncName == oldID.EBPFFuncName { 47 | m.options.ExcludedFunctions[i] = newID.EBPFFuncName 48 | } 49 | } 50 | } 51 | 52 | // edit the probe selectors 53 | for _, selector := range m.options.ActivatedProbes { 54 | selector.EditProbeIdentificationPair(oldID, newID) 55 | } 56 | 57 | // edit the probe 58 | p, ok := m.getProbe(oldID) 59 | if !ok { 60 | return ErrSymbolNotFound 61 | } 62 | return p.RenameProbeIdentificationPair(newID) 63 | } 64 | -------------------------------------------------------------------------------- /raw_tp.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/cilium/ebpf/link" 8 | ) 9 | 10 | // attachRawTracepoint - Attaches the probe to its raw_tracepoint 11 | func (p *Probe) attachRawTracepoint() error { 12 | if len(p.TracepointName) == 0 { 13 | traceGroup := strings.SplitN(p.programSpec.SectionName, "/", 2) 14 | if len(traceGroup) != 2 { 15 | return fmt.Errorf(`expected SEC("raw_tp/[name]") or SEC("raw_tracepoint/[name]") got %s: %w`, p.programSpec.SectionName, ErrSectionFormat) 16 | } 17 | p.TracepointName = traceGroup[1] 18 | } 19 | 20 | var err error 21 | p.progLink, err = link.AttachRawTracepoint(link.RawTracepointOptions{ 22 | Name: p.TracepointName, 23 | Program: p.program, 24 | }) 25 | if err != nil { 26 | return fmt.Errorf("link raw tracepoint: %w", err) 27 | } 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /ringbuffer.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | 9 | "github.com/cilium/ebpf" 10 | "github.com/cilium/ebpf/ringbuf" 11 | ) 12 | 13 | type RingBufferOptions struct { 14 | // ErrChan - Reader error channel 15 | ErrChan chan error 16 | 17 | // DataHandler - Callback function called when a new sample was retrieved from the perf 18 | // ring buffer. 19 | DataHandler func(CPU int, data []byte, ringBuffer *RingBuffer, manager *Manager) 20 | 21 | // RecordHandler - Callback function called when a new record was retrieved from the perf 22 | // ring buffer. 23 | RecordHandler func(record *ringbuf.Record, ringBuffer *RingBuffer, manager *Manager) 24 | 25 | // RecordGetter - if specified this getter will be used to get a new record 26 | RecordGetter func() *ringbuf.Record 27 | 28 | // TelemetryEnabled turns on telemetry about the usage of the ring buffer 29 | TelemetryEnabled bool 30 | } 31 | 32 | type RingBuffer struct { 33 | manager *Manager 34 | ringReader *ringbuf.Reader 35 | wgReader sync.WaitGroup 36 | bufferSize int 37 | usageTelemetry *atomic.Uint64 38 | 39 | // Map - A PerfMap has the same features as a normal Map 40 | Map 41 | RingBufferOptions 42 | } 43 | 44 | // loadNewRingBuffer - Creates a new ring buffer map instance, loads it and sets up the ring buffer reader 45 | func loadNewRingBuffer(spec *ebpf.MapSpec, options MapOptions, ringBufferOptions RingBufferOptions) (*RingBuffer, error) { 46 | ringBuffer := RingBuffer{ 47 | Map: Map{ 48 | arraySpec: spec, 49 | Name: spec.Name, 50 | MapOptions: options, 51 | }, 52 | RingBufferOptions: ringBufferOptions, 53 | } 54 | 55 | var err error 56 | if ringBuffer.array, err = ebpf.NewMap(spec); err != nil { 57 | return nil, err 58 | } 59 | 60 | if ringBuffer.PinPath != "" { 61 | if err = ringBuffer.array.Pin(ringBuffer.PinPath); err != nil { 62 | return nil, fmt.Errorf("couldn't pin map %s at %s: %w", ringBuffer.Name, ringBuffer.PinPath, err) 63 | } 64 | } 65 | 66 | return &ringBuffer, nil 67 | } 68 | 69 | // init - Initialize a ring buffer 70 | func (rb *RingBuffer) init(manager *Manager) error { 71 | rb.manager = manager 72 | 73 | if rb.DataHandler == nil && rb.RecordHandler == nil { 74 | return fmt.Errorf("no DataHandler/RecordHandler set for %s", rb.Name) 75 | } 76 | 77 | if rb.TelemetryEnabled { 78 | rb.usageTelemetry = &atomic.Uint64{} 79 | } 80 | 81 | // Initialize the underlying map structure 82 | if err := rb.Map.init(); err != nil { 83 | return err 84 | } 85 | return nil 86 | } 87 | 88 | // Start - Starts fetching events on a perf ring buffer 89 | func (rb *RingBuffer) Start() error { 90 | rb.stateLock.Lock() 91 | defer rb.stateLock.Unlock() 92 | if rb.state == running { 93 | return nil 94 | } 95 | if rb.state < initialized { 96 | return ErrMapNotInitialized 97 | } 98 | 99 | // Create and start the perf map 100 | var err error 101 | if rb.ringReader, err = ringbuf.NewReader(rb.array); err != nil { 102 | return err 103 | } 104 | rb.bufferSize = rb.ringReader.BufferSize() 105 | // Start listening for data 106 | rb.wgReader.Add(1) 107 | 108 | go func() { 109 | var record *ringbuf.Record 110 | var err error 111 | 112 | for { 113 | if rb.RingBufferOptions.RecordGetter != nil { 114 | record = rb.RingBufferOptions.RecordGetter() 115 | } else if rb.DataHandler != nil { 116 | record = new(ringbuf.Record) 117 | } 118 | 119 | if err = rb.ringReader.ReadInto(record); err != nil { 120 | if errors.Is(err, ringbuf.ErrClosed) { 121 | rb.wgReader.Done() 122 | return 123 | } 124 | if errors.Is(err, ringbuf.ErrFlushed) { 125 | record.RawSample = record.RawSample[:0] 126 | } else { 127 | if rb.ErrChan != nil { 128 | rb.ErrChan <- err 129 | } 130 | continue 131 | } 132 | } 133 | 134 | if rb.usageTelemetry != nil { 135 | updateMaxTelemetry(rb.usageTelemetry, uint64(record.Remaining)) 136 | } 137 | if rb.RecordHandler != nil { 138 | rb.RecordHandler(record, rb, rb.manager) 139 | } else if rb.DataHandler != nil { 140 | rb.DataHandler(0, record.RawSample, rb, rb.manager) 141 | } 142 | } 143 | }() 144 | 145 | rb.state = running 146 | return nil 147 | } 148 | 149 | // Flush unblocks the underlying reader and will cause the pending samples to be read 150 | func (rb *RingBuffer) Flush() { 151 | rb.stateLock.Lock() 152 | defer rb.stateLock.Unlock() 153 | if rb.state != running { 154 | return 155 | } 156 | 157 | _ = rb.ringReader.Flush() 158 | } 159 | 160 | // Stop - Stops the perf ring buffer 161 | func (rb *RingBuffer) Stop(cleanup MapCleanupType) error { 162 | rb.stateLock.Lock() 163 | defer rb.stateLock.Unlock() 164 | if rb.state <= stopped { 165 | return nil 166 | } 167 | rb.state = stopped 168 | 169 | // close ring reader 170 | err := rb.ringReader.Close() 171 | 172 | rb.wgReader.Wait() 173 | 174 | // close underlying map 175 | if errTmp := rb.Map.close(cleanup); errTmp != nil { 176 | if err == nil { 177 | err = errTmp 178 | } else { 179 | err = fmt.Errorf("%s: %w", err.Error(), errTmp) 180 | } 181 | } 182 | 183 | return err 184 | } 185 | 186 | // BufferSize returns the size in bytes of the ring buffer 187 | func (rb *RingBuffer) BufferSize() int { 188 | return rb.bufferSize 189 | } 190 | 191 | // Telemetry returns the usage telemetry 192 | func (rb *RingBuffer) Telemetry() (usage uint64, ok bool) { 193 | rb.stateLock.Lock() 194 | defer rb.stateLock.Unlock() 195 | if rb.state < initialized || rb.usageTelemetry == nil { 196 | return 0, false 197 | } 198 | // reset to zero, so we return the max value between each collection 199 | return rb.usageTelemetry.Swap(0), true 200 | } 201 | -------------------------------------------------------------------------------- /selectors.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // ProbesSelector - A probe selector defines how a probe (or a group of probes) should be activated. 9 | // 10 | // For example, this can be used to specify that out of a group of optional probes, at least one should be activated. 11 | type ProbesSelector interface { 12 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 13 | GetProbesIdentificationPairList() []ProbeIdentificationPair 14 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 15 | // For example, see OneOf. 16 | RunValidator(manager *Manager) error 17 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 18 | // mow select the new one 19 | EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) 20 | } 21 | 22 | // ProbeSelector - This selector is used to unconditionally select a probe by its identification pair and validate 23 | // that it is activated 24 | type ProbeSelector struct { 25 | ProbeIdentificationPair 26 | } 27 | 28 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 29 | func (ps *ProbeSelector) GetProbesIdentificationPairList() []ProbeIdentificationPair { 30 | if ps == nil { 31 | return nil 32 | } 33 | 34 | return []ProbeIdentificationPair{ps.ProbeIdentificationPair} 35 | } 36 | 37 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 38 | // For example, see OneOf. 39 | func (ps *ProbeSelector) RunValidator(manager *Manager) error { 40 | if ps == nil { 41 | return nil 42 | } 43 | 44 | p, ok := manager.GetProbe(ps.ProbeIdentificationPair) 45 | if !ok { 46 | return fmt.Errorf("probe not found: %s", ps.ProbeIdentificationPair) 47 | } 48 | if !p.IsRunning() && p.Enabled { 49 | return fmt.Errorf("%s: %w", ps.ProbeIdentificationPair.String(), p.GetLastError()) 50 | } 51 | if !p.Enabled { 52 | return fmt.Errorf( 53 | "%s: is disabled, add it to the activation list and check that it was not explicitly excluded by the manager options", 54 | ps.ProbeIdentificationPair.String()) 55 | } 56 | return nil 57 | } 58 | 59 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 60 | // mow select the new one 61 | func (ps *ProbeSelector) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 62 | if ps.ProbeIdentificationPair == old { 63 | ps.ProbeIdentificationPair = new 64 | } 65 | } 66 | 67 | // OneOf - This selector is used to ensure that at least of a list of probe selectors is valid. In other words, this 68 | // can be used to ensure that at least one of a list of optional probes is activated. 69 | type OneOf struct { 70 | Selectors []ProbesSelector 71 | } 72 | 73 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 74 | func (oo *OneOf) GetProbesIdentificationPairList() []ProbeIdentificationPair { 75 | var l []ProbeIdentificationPair 76 | for _, selector := range oo.Selectors { 77 | l = append(l, selector.GetProbesIdentificationPairList()...) 78 | } 79 | return l 80 | } 81 | 82 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 83 | // For example, see OneOf. 84 | func (oo *OneOf) RunValidator(manager *Manager) error { 85 | var errs []string 86 | for _, selector := range oo.Selectors { 87 | if err := selector.RunValidator(manager); err != nil { 88 | errs = append(errs, err.Error()) 89 | } 90 | } 91 | if len(errs) == len(oo.Selectors) { 92 | return fmt.Errorf( 93 | "OneOf requirement failed, none of the following probes are running [%s]", 94 | strings.Join(errs, " | ")) 95 | } 96 | // at least one selector was successful 97 | return nil 98 | } 99 | 100 | func (oo *OneOf) String() string { 101 | var strs []string 102 | for _, id := range oo.GetProbesIdentificationPairList() { 103 | str := id.String() 104 | strs = append(strs, str) 105 | } 106 | return "OneOf " + strings.Join(strs, ", ") 107 | } 108 | 109 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 110 | // now select the new one 111 | func (oo *OneOf) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 112 | for _, selector := range oo.Selectors { 113 | selector.EditProbeIdentificationPair(old, new) 114 | } 115 | } 116 | 117 | // AllOf - This selector is used to ensure that all the proves in the provided list are running. 118 | type AllOf struct { 119 | Selectors []ProbesSelector 120 | } 121 | 122 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 123 | func (ao *AllOf) GetProbesIdentificationPairList() []ProbeIdentificationPair { 124 | var l []ProbeIdentificationPair 125 | for _, selector := range ao.Selectors { 126 | l = append(l, selector.GetProbesIdentificationPairList()...) 127 | } 128 | return l 129 | } 130 | 131 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 132 | // For example, see OneOf. 133 | func (ao *AllOf) RunValidator(manager *Manager) error { 134 | var errMsg []string 135 | for _, selector := range ao.Selectors { 136 | if err := selector.RunValidator(manager); err != nil { 137 | errMsg = append(errMsg, err.Error()) 138 | } 139 | } 140 | if len(errMsg) > 0 { 141 | return fmt.Errorf( 142 | "AllOf requirement failed, the following probes are not running [%s]", 143 | strings.Join(errMsg, " | ")) 144 | } 145 | // no error means that all the selectors were successful 146 | return nil 147 | } 148 | 149 | func (ao *AllOf) String() string { 150 | var strs []string 151 | for _, id := range ao.GetProbesIdentificationPairList() { 152 | str := id.String() 153 | strs = append(strs, str) 154 | } 155 | return "AllOf " + strings.Join(strs, ", ") 156 | } 157 | 158 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 159 | // now select the new one 160 | func (ao *AllOf) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 161 | for _, selector := range ao.Selectors { 162 | selector.EditProbeIdentificationPair(old, new) 163 | } 164 | } 165 | 166 | // BestEffort - This selector is used to load probes in the best effort mode 167 | type BestEffort struct { 168 | Selectors []ProbesSelector 169 | } 170 | 171 | // GetProbesIdentificationPairList - Returns the list of probes that this selector activates 172 | func (be *BestEffort) GetProbesIdentificationPairList() []ProbeIdentificationPair { 173 | var l []ProbeIdentificationPair 174 | for _, selector := range be.Selectors { 175 | l = append(l, selector.GetProbesIdentificationPairList()...) 176 | } 177 | return l 178 | } 179 | 180 | // RunValidator - Ensures that the probes that were successfully activated follow the selector goal. 181 | // For example, see OneOf. 182 | func (be *BestEffort) RunValidator(_ *Manager) error { 183 | return nil 184 | } 185 | 186 | func (be *BestEffort) String() string { 187 | var strs []string 188 | for _, id := range be.GetProbesIdentificationPairList() { 189 | str := id.String() 190 | strs = append(strs, str) 191 | } 192 | return "BestEffort " + strings.Join(strs, ", ") 193 | } 194 | 195 | // EditProbeIdentificationPair - Changes all the selectors looking for the old ProbeIdentificationPair so that they 196 | // now select the new one 197 | func (be *BestEffort) EditProbeIdentificationPair(old ProbeIdentificationPair, new ProbeIdentificationPair) { 198 | for _, selector := range be.Selectors { 199 | selector.EditProbeIdentificationPair(old, new) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /socket.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "syscall" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | // attachSocket - Attaches the probe to the provided socket 10 | func (p *Probe) attachSocket() error { 11 | if err := sockAttach(p.SocketFD, p.program.FD()); err != nil { 12 | return err 13 | } 14 | p.progLink = &socketLink{p.SocketFD, p.program.FD()} 15 | return nil 16 | } 17 | 18 | type socketLink struct { 19 | sockFD int 20 | progFD int 21 | } 22 | 23 | func (s *socketLink) Close() error { 24 | return sockDetach(s.sockFD, s.progFD) 25 | } 26 | 27 | func (s *socketLink) Pause() error { 28 | return sockDetach(s.sockFD, s.progFD) 29 | } 30 | 31 | func (s *socketLink) Resume() error { 32 | return sockAttach(s.sockFD, s.progFD) 33 | } 34 | 35 | func sockAttach(sockFd int, progFd int) error { 36 | return syscall.SetsockoptInt(sockFd, syscall.SOL_SOCKET, unix.SO_ATTACH_BPF, progFd) 37 | } 38 | 39 | func sockDetach(sockFd int, progFd int) error { 40 | return syscall.SetsockoptInt(sockFd, syscall.SOL_SOCKET, unix.SO_DETACH_BPF, progFd) 41 | } 42 | -------------------------------------------------------------------------------- /state.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | type state uint 4 | 5 | const ( 6 | reset state = iota 7 | elfLoaded 8 | initialized 9 | stopped 10 | paused 11 | running 12 | ) 13 | -------------------------------------------------------------------------------- /syscalls.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | ) 12 | 13 | // cache of the syscall prefix depending on kernel version 14 | var syscallPrefix string 15 | 16 | // GetSyscallFnName - Returns the kernel function of the provided syscall, after reading /proc/kallsyms to retrieve 17 | // the list of symbols of the current kernel. 18 | func GetSyscallFnName(name string) (string, error) { 19 | return GetSyscallFnNameWithSymFile(name, defaultSymFile) 20 | } 21 | 22 | // GetSyscallFnNameWithSymFile - Returns the kernel function of the provided syscall, after reading symFile to retrieve 23 | // the list of symbols of the current kernel. 24 | func GetSyscallFnNameWithSymFile(name string, symFile string) (string, error) { 25 | if symFile == "" { 26 | symFile = defaultSymFile 27 | } 28 | if syscallPrefix == "" { 29 | syscallName, err := getSyscallName("open", symFile) 30 | if err != nil { 31 | return "", err 32 | } 33 | // copy to avoid memory leak due to go subslice 34 | // see: https://go101.org/article/memory-leaking.html 35 | var b strings.Builder 36 | b.WriteString(syscallName) 37 | syscallName = b.String() 38 | 39 | syscallPrefix = strings.TrimSuffix(syscallName, "open") 40 | } 41 | 42 | return syscallPrefix + name, nil 43 | } 44 | 45 | const defaultSymFile = "/proc/kallsyms" 46 | 47 | // Returns the qualified syscall named by going through '/proc/kallsyms' on the 48 | // system on which its executed. It allows bpf programs that may have been compiled 49 | // for older syscall functions to run on newer kernels 50 | func getSyscallName(name string, symFile string) (string, error) { 51 | // Get kernel symbols 52 | syms, err := os.Open(symFile) 53 | if err != nil { 54 | return "", err 55 | } 56 | defer syms.Close() 57 | 58 | return getSyscallFnNameWithKallsyms(name, syms, "") 59 | } 60 | 61 | func getSyscallFnNameWithKallsyms(name string, kallsymsContent io.Reader, arch string) (string, error) { 62 | if arch == "" { 63 | switch runtime.GOARCH { 64 | case "386": 65 | arch = "ia32" 66 | case "arm64": 67 | arch = "arm64" 68 | default: 69 | arch = "x64" 70 | } 71 | } 72 | 73 | // We should search for new syscall function like "__x64__sys_open" 74 | // Note the start of word boundary. Should return exactly one string 75 | newSyscall := regexp.MustCompile(`\b__` + arch + `_[Ss]y[sS]_` + name + `\b`) 76 | // If nothing found, search for old syscall function to be sure 77 | oldSyscall := regexp.MustCompile(`\b[Ss]y[sS]_` + name + `\b`) 78 | // check for '__' prefixed functions, like '__sys_open' 79 | prefixed := regexp.MustCompile(`\b__[Ss]y[sS]_` + name + `\b`) 80 | 81 | // the order of patterns is important 82 | // we first want to look for the new syscall format, then the old format, then the prefixed format 83 | patterns := []struct { 84 | pattern *regexp.Regexp 85 | result string 86 | }{ 87 | {newSyscall, ""}, 88 | {oldSyscall, ""}, 89 | {prefixed, ""}, 90 | } 91 | 92 | scanner := bufio.NewScanner(kallsymsContent) 93 | scanner.Split(bufio.ScanLines) 94 | 95 | for scanner.Scan() { 96 | line := scanner.Text() 97 | 98 | if !strings.Contains(line, name) { 99 | continue 100 | } 101 | 102 | for i := range patterns { 103 | p := &patterns[i] 104 | // if we already found a match for this pattern we continue 105 | if p.result != "" { 106 | continue 107 | } 108 | 109 | if res := p.pattern.FindString(line); res != "" { 110 | // fast path for first match on first pattern 111 | if i == 0 { 112 | return res, nil 113 | } 114 | 115 | p.result = res 116 | } 117 | } 118 | } 119 | if err := scanner.Err(); err != nil { 120 | return "", err 121 | } 122 | 123 | for _, p := range patterns { 124 | if p.result != "" { 125 | return p.result, nil 126 | } 127 | } 128 | 129 | return "", fmt.Errorf("could not find a valid syscall name") 130 | } 131 | -------------------------------------------------------------------------------- /syscalls_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestGetSyscallFnNameWithKallsyms(t *testing.T) { 10 | entries := []struct { 11 | fnName string 12 | kallsymsContent string 13 | expected string 14 | }{ 15 | { 16 | fnName: "open", 17 | kallsymsContent: ` 18 | 0000000000000000 T do_fchownat 19 | 0000000000000000 T __arm64_sys_fchownat 20 | 0000000000000000 T __arm64_sys_chown 21 | 0000000000000000 T __arm64_sys_lchown 22 | 0000000000000000 T vfs_fchown 23 | 0000000000000000 T ksys_fchown 24 | 0000000000000000 T __arm64_sys_fchown 25 | 0000000000000000 T vfs_open 26 | 0000000000000000 T build_open_how 27 | 0000000000000000 T build_open_flags 28 | 0000000000000000 t do_sys_openat2 29 | 0000000000000000 t __do_sys_openat2 30 | 0000000000000000 T __arm64_sys_openat2 31 | 0000000000000000 T __arm64_sys_creat 32 | 0000000000000000 T __arm64_compat_sys_open 33 | 0000000000000000 T __arm64_compat_sys_openat 34 | 0000000000000000 T __arm64_sys_open 35 | 0000000000000000 T __arm64_sys_openat 36 | 0000000000000000 T file_open_name 37 | 0000000000000000 T do_sys_open 38 | 0000000000000000 T vfs_setpos 39 | 0000000000000000 T generic_file_llseek_size 40 | 0000000000000000 T generic_file_llseek 41 | 0000000000000000 T fixed_size_llseek 42 | 0000000000000000 T no_seek_end_llseek 43 | 0000000000000000 T no_seek_end_llseek_size 44 | 0000000000000000 T noop_llseek 45 | 0000000000000000 T vfs_llseek 46 | 0000000000000000 T default_llseek 47 | 0000000000000000 t arch_local_irq_save 48 | `, 49 | expected: "__arm64_sys_open", 50 | }, 51 | { 52 | fnName: "connect", 53 | kallsymsContent: ` 54 | 0000000000000000 T __sys_connect 55 | 0000000000000000 T __arm64_sys_connect 56 | `, 57 | expected: "__arm64_sys_connect", 58 | }, 59 | { 60 | fnName: "open", 61 | kallsymsContent: ` 62 | 0000000000000000 T __SyS_open 63 | 0000000000000000 T __sys_open 64 | `, 65 | expected: "__SyS_open", 66 | }, 67 | } 68 | 69 | for i, testEntry := range entries { 70 | t.Run(fmt.Sprintf("%s_%d", testEntry.fnName, i), func(t *testing.T) { 71 | res, err := getSyscallFnNameWithKallsyms(testEntry.fnName, bytes.NewBuffer([]byte(testEntry.kallsymsContent)), "arm64") 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | if res != testEntry.expected { 77 | t.Errorf("expected %s, got %s", testEntry.expected, res) 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sysfs.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | // kprobePMUType is used to cache the kprobe PMY type value 14 | kprobePMUType = struct { 15 | once sync.Once 16 | value uint32 17 | err error 18 | }{} 19 | // uprobePMUType is used to cache the uprobe PMU type value 20 | uprobePMUType = struct { 21 | once sync.Once 22 | value uint32 23 | err error 24 | }{} 25 | ) 26 | 27 | func parsePMUEventType(eventType probeType) (uint32, error) { 28 | PMUTypeFile := fmt.Sprintf("/sys/bus/event_source/devices/%s/type", eventType) 29 | f, err := os.Open(PMUTypeFile) 30 | if err != nil { 31 | if errors.Is(err, os.ErrNotExist) { 32 | return 0, fmt.Errorf("pmu type %s: %w", eventType, ErrNotSupported) 33 | } 34 | return 0, fmt.Errorf("couldn't open %s: %w", PMUTypeFile, err) 35 | } 36 | 37 | var t uint32 38 | _, err = fmt.Fscanf(f, "%d\n", &t) 39 | if err != nil { 40 | return 0, fmt.Errorf("couldn't parse type at %s: %v", eventType, err) 41 | } 42 | return t, nil 43 | } 44 | 45 | // getPMUEventType reads a Performance Monitoring Unit's type (numeric identifier) 46 | // from /sys/bus/event_source/devices//type. 47 | func getPMUEventType(eventType probeType) (uint32, error) { 48 | switch eventType { 49 | case kprobe: 50 | kprobePMUType.once.Do(func() { 51 | kprobePMUType.value, kprobePMUType.err = parsePMUEventType(eventType) 52 | }) 53 | return kprobePMUType.value, kprobePMUType.err 54 | case uprobe: 55 | uprobePMUType.once.Do(func() { 56 | uprobePMUType.value, uprobePMUType.err = parsePMUEventType(eventType) 57 | }) 58 | return uprobePMUType.value, uprobePMUType.err 59 | default: 60 | return 0, fmt.Errorf("unknown event type: %s", eventType) 61 | } 62 | } 63 | 64 | var ( 65 | // kprobeRetProbeBit is used to cache the KProbe RetProbe bit value 66 | kprobeRetProbeBit = struct { 67 | once sync.Once 68 | value uint64 69 | err error 70 | }{} 71 | // uprobeRetProbeBit is used to cache the UProbe RetProbe bit value 72 | uprobeRetProbeBit = struct { 73 | once sync.Once 74 | value uint64 75 | err error 76 | }{} 77 | ) 78 | 79 | // parseRetProbeBit reads a Performance Monitoring Unit's retprobe bit 80 | // from /sys/bus/event_source/devices//format/retprobe. 81 | func parseRetProbeBit(eventType probeType) (uint64, error) { 82 | p := filepath.Join("/sys/bus/event_source/devices/", eventType.String(), "/format/retprobe") 83 | 84 | data, err := os.ReadFile(p) 85 | if err != nil { 86 | return 0, err 87 | } 88 | 89 | var rp uint64 90 | n, err := fmt.Sscanf(string(bytes.TrimSpace(data)), "config:%d", &rp) 91 | if err != nil { 92 | return 0, fmt.Errorf("parse retprobe bit: %w", err) 93 | } 94 | if n != 1 { 95 | return 0, fmt.Errorf("parse retprobe bit: expected 1 item, got %d", n) 96 | } 97 | 98 | return rp, nil 99 | } 100 | 101 | func getRetProbeBit(eventType probeType) (uint64, error) { 102 | switch eventType { 103 | case kprobe: 104 | kprobeRetProbeBit.once.Do(func() { 105 | kprobeRetProbeBit.value, kprobeRetProbeBit.err = parseRetProbeBit(eventType) 106 | }) 107 | return kprobeRetProbeBit.value, kprobeRetProbeBit.err 108 | case uprobe: 109 | uprobeRetProbeBit.once.Do(func() { 110 | uprobeRetProbeBit.value, uprobeRetProbeBit.err = parseRetProbeBit(eventType) 111 | }) 112 | return uprobeRetProbeBit.value, uprobeRetProbeBit.err 113 | default: 114 | return 0, fmt.Errorf("unknown event type %s", eventType) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /tailcall_route.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | ) 8 | 9 | // TailCallRoute - A tail call route defines how tail calls should be routed between eBPF programs. 10 | // 11 | // The provided eBPF program will be inserted in the provided eBPF program array, at the provided key. The eBPF program 12 | // can be provided by its function name or by its *ebpf.Program representation. 13 | type TailCallRoute struct { 14 | // ProgArrayName - Name of the BPF_MAP_TYPE_PROG_ARRAY map as defined in its section SEC("maps/[ProgArray]") 15 | ProgArrayName string 16 | 17 | // Key - Key at which the program will be inserted in the ProgArray map 18 | Key uint32 19 | 20 | // ProbeIdentificationPair - Selector of the program to insert in the ProgArray map 21 | ProbeIdentificationPair ProbeIdentificationPair 22 | 23 | // Program - Program to insert in the ProgArray map 24 | Program *ebpf.Program 25 | } 26 | 27 | // UpdateTailCallRoutes - Update one or multiple program arrays so that the provided keys point to the provided programs. 28 | func (m *Manager) UpdateTailCallRoutes(router ...TailCallRoute) error { 29 | m.stateLock.Lock() 30 | defer m.stateLock.Unlock() 31 | if m.collection == nil || m.state < initialized { 32 | return ErrManagerNotInitialized 33 | } 34 | 35 | for _, route := range router { 36 | if err := m.updateTailCallRoute(route); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | // updateTailCallRoute - Update a program array so that the provided key point to the provided program. 44 | func (m *Manager) updateTailCallRoute(route TailCallRoute) error { 45 | // Select the routing map 46 | routingMap, found, err := m.getMap(route.ProgArrayName) 47 | if err != nil { 48 | return err 49 | } 50 | if !found { 51 | return fmt.Errorf("couldn't find routing map %s: %w", route.ProgArrayName, ErrUnknownSection) 52 | } 53 | 54 | // Get file descriptor of the routed program 55 | var fd uint32 56 | if route.Program != nil { 57 | fd = uint32(route.Program.FD()) 58 | } else { 59 | progs, found, err := m.getProgram(route.ProbeIdentificationPair) 60 | if err != nil { 61 | return err 62 | } 63 | if !found || len(progs) == 0 { 64 | return fmt.Errorf("couldn't find program %v: %w", route.ProbeIdentificationPair, ErrUnknownSectionOrFuncName) 65 | } 66 | if progs[0] == nil { 67 | return fmt.Errorf("the program that you are trying to route to is empty") 68 | } 69 | fd = uint32(progs[0].FD()) 70 | } 71 | 72 | // Insert tail call 73 | if err = routingMap.Put(route.Key, fd); err != nil { 74 | return fmt.Errorf("couldn't update routing map %s: %w", route.ProgArrayName, err) 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /testdata/Makefile: -------------------------------------------------------------------------------- 1 | LLVM_PREFIX ?= /usr/bin 2 | CLANG ?= $(LLVM_PREFIX)/clang 3 | 4 | all: rewrite.elf exclude.elf patching.elf 5 | 6 | clean: 7 | -$(RM) *.elf 8 | 9 | %.elf : %.c 10 | $(CLANG) -target bpf -O2 -g \ 11 | -Wall -Werror \ 12 | -c $< -o $@ 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /testdata/exclude.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_one __section("maps") = { 9 | .type = 1, 10 | .key_size = sizeof(unsigned int), 11 | .value_size = sizeof(unsigned int), 12 | .max_entries = 1, 13 | }; 14 | 15 | struct map map_two __section("maps") = { 16 | .type = 1, 17 | .key_size = sizeof(unsigned int), 18 | .value_size = sizeof(unsigned int), 19 | .max_entries = 1, 20 | }; 21 | 22 | __section("socket/map1") int access_map_one() { 23 | unsigned int key = 0; 24 | unsigned int *value = map_lookup_elem(&map_one, &key); 25 | if (!value) { 26 | return 0; 27 | } 28 | return *value; 29 | } 30 | 31 | __section("socket/map2") int access_map_two() { 32 | unsigned int key = 0; 33 | unsigned int *value = map_lookup_elem(&map_two, &key); 34 | if (!value) { 35 | return 0; 36 | } 37 | return *value; 38 | } 39 | -------------------------------------------------------------------------------- /testdata/exclude.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf-manager/f9079af4617823d12787897c25b0cf53c596afa4/testdata/exclude.elf -------------------------------------------------------------------------------- /testdata/patching.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | char _license[] __section("license") = "MIT"; 4 | 5 | static void *(*bpf_patch_1)(unsigned long, ...) = (void *)-1; 6 | static void *(*bpf_patch_2)(unsigned long, ...) = (void *)-2; 7 | 8 | __section("socket") 9 | int patching_test() { 10 | int ret = 0; 11 | bpf_patch_1(ret); 12 | bpf_patch_2(ret); 13 | return 1; 14 | } 15 | -------------------------------------------------------------------------------- /testdata/patching.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf-manager/f9079af4617823d12787897c25b0cf53c596afa4/testdata/patching.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 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 | -------------------------------------------------------------------------------- /testdata/rewrite.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataDog/ebpf-manager/f9079af4617823d12787897c25b0cf53c596afa4/testdata/rewrite.elf -------------------------------------------------------------------------------- /tracefs/tracefs.go: -------------------------------------------------------------------------------- 1 | package tracefs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "sync" 9 | "syscall" 10 | 11 | "golang.org/x/sys/unix" 12 | ) 13 | 14 | const ( 15 | traceFSRoot = "/sys/kernel/tracing" 16 | debugFSRoot = "/sys/kernel/debug/tracing" 17 | ) 18 | 19 | var ( 20 | tracingRoot = struct { 21 | once sync.Once 22 | path string 23 | err error 24 | }{} 25 | ) 26 | 27 | func getRoot() (string, error) { 28 | tracingRoot.once.Do(func() { 29 | var statfs unix.Statfs_t 30 | var traceError error 31 | if traceError = unix.Statfs(traceFSRoot, &statfs); traceError == nil { 32 | if statfs.Type == unix.TRACEFS_MAGIC { 33 | tracingRoot.path = traceFSRoot 34 | return 35 | } 36 | traceError = fmt.Errorf("%s is not mounted with tracefs filesystem type", traceFSRoot) 37 | } 38 | var debugError error 39 | if debugError = unix.Statfs(debugFSRoot, &statfs); debugError == nil { 40 | if statfs.Type == unix.TRACEFS_MAGIC || statfs.Type == unix.DEBUGFS_MAGIC { 41 | tracingRoot.path = debugFSRoot 42 | return 43 | } 44 | debugError = fmt.Errorf("%s is not mounted with tracefs or debugfs filesystem type", debugFSRoot) 45 | } 46 | 47 | bestError := fmt.Errorf("tracefs: %s", traceError) 48 | // only fallback to debugfs error if tracefs doesn't exist at all and debugfs does 49 | if errors.Is(traceError, syscall.ENOENT) && !errors.Is(debugError, syscall.ENOENT) { 50 | bestError = fmt.Errorf("debugfs: %s", debugError) 51 | } 52 | tracingRoot.err = fmt.Errorf("tracefs or debugfs is not available: %s", bestError) 53 | }) 54 | return tracingRoot.path, tracingRoot.err 55 | } 56 | 57 | // Root returns the tracing root path in use, `/sys/kernel/tracing` (tracefs) or `/sys/kernel/debug/tracing` (debugfs) 58 | func Root() (string, error) { 59 | return getRoot() 60 | } 61 | 62 | // ReadFile reads the relative path provided, using the detected root of tracefs or debugfs 63 | func ReadFile(relname string) ([]byte, error) { 64 | root, err := getRoot() 65 | if err != nil { 66 | return nil, err 67 | } 68 | return os.ReadFile(filepath.Join(root, relname)) 69 | } 70 | 71 | // Open opens the relative path provided (similar to os.Open), using the detected root of tracefs or debugfs 72 | func Open(relname string) (*os.File, error) { 73 | return OpenFile(relname, os.O_RDONLY, 0) 74 | } 75 | 76 | // OpenFile opens the relative path provided (similar to os.OpenFile), using the detected root of tracefs or debugfs 77 | func OpenFile(relname string, flag int, perm os.FileMode) (*os.File, error) { 78 | root, err := getRoot() 79 | if err != nil { 80 | return nil, err 81 | } 82 | return os.OpenFile(filepath.Join(root, relname), flag, perm) 83 | } 84 | -------------------------------------------------------------------------------- /tracefs_test.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkFindFilterFunction(b *testing.B) { 9 | var needle string 10 | switch runtime.GOARCH { 11 | case "arm64": 12 | needle = "__arm64_sys_open" 13 | default: 14 | needle = "__x64_sys_open" 15 | } 16 | 17 | for i := 0; i < b.N; i++ { 18 | _, err := FindFilterFunction(needle) 19 | if err != nil { 20 | b.Error(err) 21 | } 22 | } 23 | } 24 | 25 | func TestGenerateEventName(t *testing.T) { 26 | probeType := "p" 27 | funcName := "func" 28 | UID := "UID" 29 | kprobeAttachPID := 1234 30 | 31 | eventName, err := generateEventName(probeType, funcName, UID, kprobeAttachPID) 32 | if err != nil { 33 | t.Error(err) 34 | } 35 | if len(eventName) > maxEventNameLen { 36 | t.Errorf("Event name too long, kernel limit is %d : maxEventNameLen", maxEventNameLen) 37 | } 38 | 39 | // should be truncated 40 | funcName = "01234567890123456790123456789012345678901234567890123456789" 41 | eventName, err = generateEventName(probeType, funcName, UID, kprobeAttachPID) 42 | if (err != nil) || (len(eventName) != maxEventNameLen) || (eventName != "p_01234567890123456790123456789012345678901234567890123_UID_1234") { 43 | t.Errorf("Should not failed and truncate the function name (len %d)", len(eventName)) 44 | } 45 | 46 | UID = "12345678901234567890123456789012345678901234567890" 47 | _, err = generateEventName(probeType, funcName, UID, kprobeAttachPID) 48 | if err == nil { 49 | t.Errorf("Test should failed as event name length is too big for the kernel and free space for function Name is < %d", minFunctionNameLen) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tracepoint.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // attachTracepoint - Attaches the probe to its tracepoint 9 | func (p *Probe) attachTracepoint() error { 10 | // Parse section 11 | if len(p.TracepointCategory) == 0 || len(p.TracepointName) == 0 { 12 | traceGroup := strings.SplitN(p.programSpec.SectionName, "/", 3) 13 | if len(traceGroup) != 3 { 14 | return fmt.Errorf("expected SEC(\"tracepoint/[category]/[name]\") got %s: %w", p.programSpec.SectionName, ErrSectionFormat) 15 | } 16 | p.TracepointCategory = traceGroup[1] 17 | p.TracepointName = traceGroup[2] 18 | } 19 | 20 | // Get the ID of the tracepoint to activate 21 | tracepointID, err := GetTracepointID(p.TracepointCategory, p.TracepointName) 22 | if err != nil { 23 | return fmt.Errorf("couldn't activate tracepoint %s: %w", p.ProbeIdentificationPair, err) 24 | } 25 | 26 | // Hook the eBPF program to the tracepoint 27 | fd, err := perfEventOpenTracingEvent(tracepointID, -1) 28 | if err != nil { 29 | return fmt.Errorf("couldn't enable tracepoint %s: %w", p.ProbeIdentificationPair, err) 30 | } 31 | pe := newPerfEventLink(fd) 32 | if err := attachPerfEvent(pe, p.program); err != nil { 33 | _ = pe.Close() 34 | return fmt.Errorf("attach %s: %w", p.ProbeIdentificationPair, err) 35 | } 36 | p.progLink = pe 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /tracing.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf/link" 7 | ) 8 | 9 | func (p *Probe) attachTracing() error { 10 | var err error 11 | p.progLink, err = link.AttachTracing(link.TracingOptions{ 12 | Program: p.program, 13 | AttachType: p.programSpec.AttachType, 14 | }) 15 | if err != nil { 16 | return fmt.Errorf("link tracing: %w", err) 17 | } 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /uprobe.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "debug/elf" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | ) 9 | 10 | // SanitizeUprobeAddresses - sanitizes the addresses of the provided symbols 11 | func SanitizeUprobeAddresses(f *elf.File, syms []elf.Symbol) { 12 | // If the binary is a non-PIE executable, addr must be a virtual address, otherwise it must be an offset relative to 13 | // the file load address. For executable (ET_EXEC) binaries and shared objects (ET_DYN), translate the virtual 14 | // address to physical address in the binary file. 15 | if f.Type == elf.ET_EXEC || f.Type == elf.ET_DYN { 16 | for i, sym := range syms { 17 | for _, prog := range f.Progs { 18 | if prog.Type == elf.PT_LOAD { 19 | if sym.Value >= prog.Vaddr && sym.Value < (prog.Vaddr+prog.Memsz) { 20 | syms[i].Value = sym.Value - prog.Vaddr + prog.Off 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | // OpenAndListSymbols - Opens an elf file and extracts all its symbols 29 | func OpenAndListSymbols(path string) (*elf.File, []elf.Symbol, error) { 30 | // open elf file 31 | f, err := elf.Open(path) 32 | if err != nil { 33 | return nil, nil, fmt.Errorf("couldn't open elf file %s: %w", path, err) 34 | } 35 | defer f.Close() 36 | 37 | // Loop through all symbols 38 | syms, errSyms := f.Symbols() 39 | dynSyms, errDynSyms := f.DynamicSymbols() 40 | syms = append(syms, dynSyms...) 41 | 42 | if len(syms) == 0 { 43 | var err error 44 | if errSyms != nil { 45 | err = fmt.Errorf("failed to list symbols: %v", errSyms) 46 | } 47 | if errDynSyms != nil { 48 | err = fmt.Errorf("failed to list dynamic symbols: %v", errDynSyms) 49 | } 50 | if err != nil { 51 | return nil, nil, err 52 | } 53 | return nil, nil, fmt.Errorf("no symbols found") 54 | } 55 | return f, syms, nil 56 | } 57 | 58 | // findSymbolOffsets - Parses the provided file and returns the offsets of the symbols that match the provided pattern 59 | func findSymbolOffsets(path string, pattern *regexp.Regexp) ([]elf.Symbol, error) { 60 | f, syms, err := OpenAndListSymbols(path) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | var matches []elf.Symbol 66 | for _, sym := range syms { 67 | if elf.ST_TYPE(sym.Info) == elf.STT_FUNC && pattern.MatchString(sym.Name) { 68 | matches = append(matches, sym) 69 | } 70 | } 71 | 72 | if len(matches) == 0 { 73 | return nil, ErrSymbolNotFound 74 | } 75 | 76 | SanitizeUprobeAddresses(f, matches) 77 | return matches, nil 78 | } 79 | 80 | // attachWithUprobeEvents attaches the uprobe using the uprobes_events ABI 81 | func (p *Probe) attachWithUprobeEvents() (*tracefsLink, error) { 82 | args := traceFsEventArgs{ 83 | Type: uprobe, 84 | ReturnProbe: p.isReturnProbe, 85 | Symbol: p.HookFuncName, // only used for event naming 86 | Path: p.BinaryPath, 87 | Offset: p.UprobeOffset, 88 | UID: p.UID, 89 | AttachingPID: p.attachPID, 90 | } 91 | 92 | var uprobeID int 93 | var eventName string 94 | uprobeID, eventName, err := registerTraceFSEvent(args) 95 | if err != nil { 96 | return nil, fmt.Errorf("couldn't enable uprobe %s: %w", p.ProbeIdentificationPair, err) 97 | } 98 | 99 | pfd, err := perfEventOpenTracingEvent(uprobeID, p.PerfEventPID) 100 | if err != nil { 101 | return nil, fmt.Errorf("couldn't open perf event fd for %s: %w", p.ProbeIdentificationPair, err) 102 | } 103 | return &tracefsLink{perfEventLink: newPerfEventLink(pfd), Type: uprobe, EventName: eventName}, nil 104 | } 105 | 106 | // attachUprobe - Attaches the probe to its Uprobe 107 | func (p *Probe) attachUprobe() error { 108 | // compute the offset if it was not provided 109 | if p.UprobeOffset == 0 { 110 | var funcPattern string 111 | 112 | // find the offset of the first symbol matching the provided pattern 113 | if len(p.MatchFuncName) > 0 { 114 | funcPattern = p.MatchFuncName 115 | } else { 116 | funcPattern = fmt.Sprintf("^%s$", p.HookFuncName) 117 | } 118 | pattern, err := regexp.Compile(funcPattern) 119 | if err != nil { 120 | return fmt.Errorf("failed to compile pattern %s: %w", funcPattern, err) 121 | } 122 | 123 | // Retrieve dynamic symbol offset 124 | offsets, err := findSymbolOffsets(p.BinaryPath, pattern) 125 | if err != nil { 126 | return fmt.Errorf("couldn't find symbol matching %s in %s: %w", pattern.String(), p.BinaryPath, err) 127 | } 128 | p.UprobeOffset = offsets[0].Value 129 | p.HookFuncName = offsets[0].Name 130 | } 131 | 132 | var eventsFunc attachFunc = p.attachWithUprobeEvents 133 | var pmuFunc attachFunc = func() (*tracefsLink, error) { 134 | pfd, err := perfEventOpenPMU(p.BinaryPath, int(p.UprobeOffset), p.PerfEventPID, uprobe, p.isReturnProbe, 0) 135 | if err != nil { 136 | return nil, err 137 | } 138 | return &tracefsLink{perfEventLink: newPerfEventLink(pfd), Type: uprobe}, nil 139 | } 140 | 141 | pmuFirst := true 142 | startFunc, fallbackFunc := pmuFunc, eventsFunc 143 | if p.UprobeAttachMethod == AttachWithProbeEvents { 144 | pmuFirst = false 145 | startFunc, fallbackFunc = eventsFunc, pmuFunc 146 | } 147 | 148 | var startErr, fallbackErr error 149 | var tl *tracefsLink 150 | if tl, startErr = startFunc(); startErr != nil { 151 | // do not fallback on tracefs events if PMU attach method is supported 152 | // and we got an actual error from it 153 | if pmuFirst && !errors.Is(startErr, ErrNotSupported) { 154 | return startErr 155 | } 156 | 157 | if tl, fallbackErr = fallbackFunc(); fallbackErr != nil { 158 | return errors.Join(startErr, fallbackErr) 159 | } 160 | } 161 | 162 | if err := attachPerfEvent(tl.perfEventLink, p.program); err != nil { 163 | _ = tl.Close() 164 | return fmt.Errorf("attach %s: %w", p.ProbeIdentificationPair, err) 165 | } 166 | p.progLink = tl 167 | return nil 168 | } 169 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | 8 | "github.com/cilium/ebpf" 9 | ) 10 | 11 | // getEnv retrieves the environment variable key. If it does not exist it returns the default. 12 | func getEnv(key string, dfault string, combineWith ...string) string { 13 | value := os.Getenv(key) 14 | if value == "" { 15 | value = dfault 16 | } 17 | 18 | switch len(combineWith) { 19 | case 0: 20 | return value 21 | case 1: 22 | return filepath.Join(value, combineWith[0]) 23 | default: 24 | all := make([]string, len(combineWith)+1) 25 | all[0] = value 26 | copy(all[1:], combineWith) 27 | return filepath.Join(all...) 28 | } 29 | } 30 | 31 | // hostProc returns joins the provided path with the host /proc directory 32 | func hostProc(combineWith ...string) string { 33 | return getEnv("HOST_PROC", "/proc", combineWith...) 34 | } 35 | 36 | // Getpid returns the current process ID in the host namespace if $HOST_PROC is defined, the pid in the current namespace 37 | // otherwise 38 | func Getpid() int { 39 | p, err := os.Readlink(hostProc("/self")) 40 | if err == nil { 41 | if pid, err := strconv.ParseInt(p, 10, 32); err == nil { 42 | return int(pid) 43 | } 44 | } 45 | return os.Getpid() 46 | } 47 | 48 | // cleanupProgramSpec removes unused internal fields to free up some memory 49 | func cleanupProgramSpec(spec *ebpf.ProgramSpec) { 50 | if spec != nil { 51 | spec.Instructions = nil 52 | } 53 | } 54 | 55 | // create slice of length n and fill with fillVal 56 | func makeAndSet[E any](n int, fillVal E) []E { 57 | s := make([]E, n) 58 | for i := range s { 59 | s[i] = fillVal 60 | } 61 | return s 62 | } 63 | -------------------------------------------------------------------------------- /xdp.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/cilium/ebpf/link" 9 | "github.com/vishvananda/netlink" 10 | ) 11 | 12 | // XdpAttachMode selects a way how XDP program will be attached to interface 13 | type XdpAttachMode int 14 | 15 | const ( 16 | // XdpAttachModeNone stands for "best effort" - the kernel automatically 17 | // selects the best mode (would try Drv first, then fallback to Generic). 18 | // NOTE: Kernel will not fall back to Generic XDP if NIC driver failed 19 | // to install XDP program. 20 | XdpAttachModeNone XdpAttachMode = 0 21 | // XdpAttachModeSkb is "generic", kernel mode, less performant comparing to native, 22 | // but does not requires driver support. 23 | XdpAttachModeSkb XdpAttachMode = 1 << 1 24 | // XdpAttachModeDrv is native, driver mode (support from driver side required) 25 | XdpAttachModeDrv XdpAttachMode = 1 << 2 26 | // XdpAttachModeHw suitable for NICs with hardware XDP support 27 | XdpAttachModeHw XdpAttachMode = 1 << 3 28 | ) 29 | 30 | var _ io.Closer = (*netlinkXDPLink)(nil) 31 | 32 | type netlinkXDPLink struct { 33 | link netlink.Link 34 | ifIndex int 35 | mode int 36 | } 37 | 38 | func (l *netlinkXDPLink) Close() error { 39 | err := netlink.LinkSetXdpFdWithFlags(l.link, -1, l.mode) 40 | if err != nil { 41 | return fmt.Errorf("detach XDP program from interface %d: %w", l.ifIndex, err) 42 | } 43 | return nil 44 | } 45 | 46 | // attachXDP - Attaches the probe to an interface with an XDP hook point 47 | func (p *Probe) attachXDP() error { 48 | var err error 49 | if _, err = p.resolveLink(); err != nil { 50 | return err 51 | } 52 | 53 | p.progLink, err = link.AttachXDP(link.XDPOptions{ 54 | Program: p.program, 55 | Interface: p.IfIndex, 56 | Flags: link.XDPAttachFlags(p.XDPAttachMode), 57 | }) 58 | if err != nil { 59 | if !errors.Is(err, link.ErrNotSupported) { 60 | return fmt.Errorf("link xdp to interface %v: %w", p.IfIndex, err) 61 | } 62 | 63 | err = netlink.LinkSetXdpFdWithFlags(p.link, p.program.FD(), int(p.XDPAttachMode)) 64 | if err != nil { 65 | return fmt.Errorf("attach XDP program %v to interface %v: %w", p.ProbeIdentificationPair, p.IfIndex, err) 66 | } 67 | p.progLink = &netlinkXDPLink{ 68 | link: p.link, 69 | ifIndex: p.IfIndex, 70 | mode: int(p.XDPAttachMode), 71 | } 72 | } 73 | return nil 74 | } 75 | --------------------------------------------------------------------------------