Library / SDK
dropbox/goebpf avatar
dropbox/goebpf

goebpf: loading and attaching eBPF programs from Go without a framework

Library to work with eBPF programs from Go

1,171 stars82 forksGoNOASSERTION

At a glance

What is it?
goebpf gives Go programs a direct handle on the kernel eBPF subsystem: read a clang-compiled ELF, load a program by name, attach it to an interface or a probe, and read maps, with perf events and kprobes supported.
Who is it for?
goebpf suits a team that needs direct control of eBPF programs from a Go process, for instance an XDP packet filter or a kprobe based tracer, and that is comfortable with the kernel side of the stack. It is a poor fit for a project that wants a batteries-included observability agent, since there is no such thing here, and a poor fit for a team that needs program types beyond the four listed, because tc-act is only partially implemented.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 113 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A thin layer over the kernel bpf syscall

goebpf is described as a nice and convenient way to work with eBPF programs and perf events from Go, and the design is deliberately thin. There is no plugin registry, no code generation for your business logic, no configuration DSL. The flow is: get a handle to the eBPF system, load an ELF that clang compiled, look up a program inside it by name, load that program into the kernel, attach it, and then read and write maps.

The requirements are Go 1.11 or newer and Linux kernel 4.15 or newer. The kernel floor comes from the program types and map features the library relies on, and it is a hard floor rather than a soft one, since attaching a program type the running kernel does not know about fails at load time.

What the library supports is a specific list of eBPF program types: SocketFilter, XDP, Kprobe and Kretprobe, and tc-cls, with tc-act partially implemented currently. Perf events are supported as well. The project says support for other program types and features can be added in the future, and welcomes contributions.

That list is the honest scope of the library. It is not a general eBPF framework, it is a way to drive a specific set of program types from Go, and choosing it means your use case has to fit inside those four and a half types.

The source layout matches. program_xdp.go, program_socket_filter.go, program_kprobe.go and program_tc.go are the per-type implementations, with program_base.go holding what they share, map.go the map access, loader.go the ELF loading, perf_events.go and its handler, poller and ring buffer files the perf event path, and bpf.h with bpf_helpers.h the C side headers that a program includes.

Read, load, attach, and read a map

The quick start is the whole API in one snippet, and the project notes that it does not handle errors in order to stay simple.

go
    bpf := goebpf.NewDefaultEbpfSystem()
    // Read clang compiled binary
    bpf.LoadElf("test.elf")
    // Load XDP program into kernel (name matches function name in C)
    xdp := bpf.GetProgramByName("xdp_test")
    xdp.Load()
    // Attach to interface
    xdp.Attach("eth0")
    defer xdp.Detach()
    // Work with maps
    test := bpf.GetMapByName("test")
    value, _ := test.LookupInt(0)
    fmt.Printf("Value at index 0 of map 'test': %d\n", value)

Three details carry the design. NewDefaultEbpfSystem gives you the default system, and the C side is a separate compilation step, so you need clang and a BPF-capable toolchain in your build. The program is looked up by name and the name matches the function name in the C source, which is a small convention that saves a lot of index bookkeeping. And maps are addressed by name too, with typed accessors such as LookupInt.

The map access is where a lot of eBPF code actually lives, and the library gives you the common operations. The map type is looked up by name from the loaded ELF, so a map defined in C is available in Go under the same name.

Two operational notes. The Attach call takes an interface name for the network program types, and Detach is deferred in the example, which matters because a program stays attached until it is detached or the process exits, and in production you want that explicit rather than incidental.

Perf events, where the C and Go sides bind together

Perf events are where an eBPF program and a Go process exchange data at high rate, and goebpf supports the most common pattern: an eBPF map whose key maps to a cpu_id, so the eBPF and Go parts bind cpu_id to the map index.

On the C side, that is a perf event array map:

c
    BPF_MAP_DEF(perfmap) = {
        .map_type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
        .max_entries = 128,     // Max supported CPUs
    };
    BPF_MAP_ADD(perfmap);

    // Emit perf event with "data" to map "perfmap" where index is current CPU_ID
    bpf_perf_event_output(ctx, &perfmap, BPF_F_CURRENT_CPU, &data, sizeof(data));

And on the Go side, you open the perf event source and start it for all processes and CPUs with a ring buffer size:

go
    perf, err := goebpf.NewPerfEvents("perfmap")
    // 4096 is ring buffer size
    perfEvents, err := perf.StartForAllProcessesAndCPUs(4096)
    defer perf.Stop()

    for {
        select {
            case data := <-perfEvents:
            fmt.Println(data)
        }
    }

The channel delivers the raw event data, and the select loop is the whole consumer. The library implements the perf side with a memory-mapped ring buffer, with mmap_ring_buffer.go in the source tree, and the events handler and poller files handle the delivery and the wakeup mechanism.

The ring buffer size is a parameter, 4096 in the example, and it is the buffer between the kernel and your process. If the consumer is slow, the buffer fills, and the behaviour at that point is worth knowing before you deploy this on a busy host. The overflow behaviour is worth checking in the source rather than assuming it is lossless.

Kprobes, and the cleanup step that comes first

Kprobes and kretprobes are supported, and the example is the kind of thing people build this library for: attaching to a syscall entry point and streaming the arguments out.

The C side is a probe handler in a section named for the probe:

c
    // kprobe handler function
    SEC("kprobe/guess_execve")
    int execve_entry(struct pt_regs *ctx) {
      // ...
      buf_perf_output(ctx);
      return 0;
    }

The Go side starts by cleaning up old probes, which is the step people forget:

go
	// Cleanup old probes
	err := goebpf.CleanupProbes()

	// Attach all probe programs
	for _, prog := range bpf.GetPrograms() {
		err := prog.Attach(nil)
	}

	// Create perf events
	eventsMap := p.bpf.GetMapByName("events")
	p.pe, err = goebpf.NewPerfEvents(eventsMap)
	events, err := p.pe.StartForAllProcessesAndCPUs(4096)
	defer events.Stop()

	for {
		select {
		case data := <-events:
			fmt.Println(data) // kProbe event
		}
	}

CleanupProbes exists because a probe attached by a previous run that did not shut down cleanly is still attached in the kernel, and the next run would attach a second handler to the same point. Calling CleanupProbes first makes the attach step idempotent across restarts, which matters for anything running under a supervisor that restarts processes.

Note also that prog.Attach(nil) takes an argument for probes, unlike the interface name that the XDP attach takes, and that GetPrograms returns all the programs in the loaded ELF so you can attach them in a loop rather than one by one.

The examples directory in the repository has one directory per program type, with kprobe, socket_filter, tc and xdp, and a README explaining them, including a full XDP dump example and an exec dump example.

Generated sources, a mock package, and a custom licence

A few structural details matter for anyone planning to build on this.

Two source files are generated: attach_type.gen.go and program_type.gen.go, alongside the hand-written bpf.h and bpf_helpers.h. The .gen.go naming is the Go convention for generated code, and the presence of the headers alongside means the C side of the program is part of this repository rather than something you write entirely yourself. If a kernel header changes and the generator is re-run, the generated files change with it, which is a real maintenance consideration when you track this dependency in a vendored form.

There is a mock package, goebpf_mock, installed separately with go get github.com/dropbox/goebpf/goebpf_mock. That is a deliberate design decision worth appreciating: the eBPF system is a kernel interface, so anything using this library is hard to unit test. A mock implementation of the system interface lets you test the Go logic that drives the kernel without a kernel.

The test setup in the repository is visible in the file listing, with loader_test.go, map_test.go, mmap_ring_buffer_test.go, perf_events_handler_test.go and utils_test.go, plus an itest directory, and a cmd directory for command line tools.

The licence is the item to check. The project is identified as having a custom licence that GitHub cannot classify, with the LICENSE file at the root, so an organisation with a standard open source policy should read that file rather than assume a permissive licence from the fact that the source is public.

On maintenance, the repository is not archived, the last push was on 2026-07-02, and there are no published GitHub releases, so there is no version tag to pin to and the tip of master is what you would consume.

Editorial conclusion

goebpf suits a team that needs direct control of eBPF programs from a Go process, for instance an XDP packet filter or a kprobe based tracer, and that is comfortable with the kernel side of the stack. It is a poor fit for a project that wants a batteries-included observability agent, since there is no such thing here, and a poor fit for a team that needs program types beyond the four listed, because tc-act is only partially implemented. Before adopting, confirm your kernel is 4.15 or newer, check the LICENSE file yourself since it is a custom licence GitHub cannot classify, and plan for the generated sources, since attach_type.gen.go and program_type.gen.go mean a kernel header change can require regenerating code.

Frequently asked questions

How do I install goebpf and load my first eBPF program?

Run go get github.com/dropbox/goebpf, and go get github.com/dropbox/goebpf/goebpf_mock if you need the mock version. Then create a system with NewDefaultEbpfSystem, load a clang compiled ELF with LoadElf, fetch a program with GetProgramByName, call Load and Attach, and read maps with GetMapByName and LookupInt.

Which eBPF program types does goebpf support?

SocketFilter, XDP, Kprobe and Kretprobe, and tc-cls, with tc-act partially implemented currently. Perf events are supported as well, and the project says other program types can be added in future.

What are the requirements for running goebpf?

Go 1.11 or newer and a Linux kernel 4.15 or newer. The library also needs a C compiler toolchain that can produce a BPF ELF, since the program is compiled separately and loaded by name.

How do I consume perf events from an eBPF program in Go?

On the C side define a perf event array map and emit with bpf_perf_event_output. In Go, open it with goebpf.NewPerfEvents, call StartForAllProcessesAndCPUs with a ring buffer size, and read the delivered data from the returned channel, stopping the source when you are done.

Why should I call goebpf.CleanupProbes before attaching kprobes?

A probe attached by a previous run that did not shut down cleanly stays attached in the kernel, so a new attach would add a second handler to the same point. Calling CleanupProbes first makes the attach step idempotent across restarts.

Official sources

  1. dropbox/goebpf on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dropbox-goebpf.svg)](https://hysenlabs.com/projects/dropbox-goebpf)

Was this useful?