Manage BPF struct_ops Objects with bpftool
Changing which TCP congestion algorithm the kernel uses means registering a BPF struct_ops object, and bpftool struct_ops is the subcommand for it. It lists and inspects what is currently registered, registers an existing ELF object, optionally pins its links, and unregisters the right object afterwards, following the bpftool-struct_ops(8) manpage installed with linux-tools-common version 6.8.0-139.139. Allow about 15 minutes once your object is already built; building it is a separate job.
The route
Jump straight to the step you need, or tick off Done means at the end.
1. Check the local tool and its contract
Start with ordinary, read-only commands. struct_ops is a subcommand of bpftool, not a separate executable named bpftool-struct_ops:
$ command -v bpftool
$ bpftool version
$ bpftool struct_ops help
On the reference installation, the package version is 6.8.0-139.139. The actual binary may come from a kernel-specific linux-tools package, so a package version and a runnable binary are two different checks. If bpftool version warns it cannot find the tool for the running kernel, stop and install the matching package through your normal process. Do not work around the warning by copying a random binary into place.
2. See what is registered
Listing does not change kernel state and normally needs no elevated privileges. It prints the map ID first, then the map name and the kernel type:
$ bpftool struct_ops show
100: dctcp tcp_congestion_ops
105: cubic tcp_congestion_ops
The exact IDs and names vary by host. list is an alias for the same brief view:
$ bpftool struct_ops list
$ bpftool -j struct_ops show
Save the output before making a change, especially if more than one object has a similar name. The JSON form is useful for scripts; add -p for human-readable JSON. An empty result can be perfectly valid: it means no struct_ops objects are currently visible to the command.
3. Inspect one object before changing it
Use the saved numeric ID or an exact map name. dump shows detail for one object, or for all of them when you omit the selector:
$ bpftool struct_ops dump id 105
$ bpftool struct_ops dump name cubic
Confirm the object you intend to remove belongs to the expected subsystem before you touch it. Do not infer an ID from an old terminal capture: IDs change as objects are registered and unregistered.
4. Register an existing struct_ops object
Warning
Registration changes kernel state and normally needs elevated privileges. It loads the BPF struct_ops found in the ELF sections named .struct_ops and .struct_ops.link. Replace the placeholder with a real object from your build:
$ sudo bpftool struct_ops register /path/to/bpf_struct_ops.o
Registered tcp_congestion_ops cubic id 110
A single object can contain more than one struct_ops definition, and the command registers all of them. A verifier, BTF, permissions or subsystem error means registration did not complete: keep the diagnostic output and fix the object or its environment before retrying.
If the ELF contains entries in .struct_ops.link, pass a directory to pin the created links:
$ sudo install -d -m 0755 /run/bpf/struct_ops
$ sudo bpftool struct_ops register /path/to/bpf_struct_ops.o /run/bpf/struct_ops
$ sudo find /run/bpf/struct_ops -maxdepth 1 -type f -print
The links are pinned there using the corresponding map names. Choose a directory owned by the service that needs the pins, and record it in that service's configuration: the manpage specifies the directory argument, but it does not create a lifecycle policy for the pins.
5. Verify the new state
After registration, use a fresh listing rather than trusting a success message alone:
$ bpftool struct_ops show
$ bpftool struct_ops dump name cubic
$ bpftool -j struct_ops show
Match the returned name and kernel type to the object you meant to register. If you are automating this, parse JSON and treat a changed ID as expected: it identifies the current kernel object, not a durable name.
6. Unregister with a deliberate recovery plan
Warning
Unregistering is service-disrupting when the struct_ops supplies live kernel behaviour. Check the name, ID and owning service first. This normally needs elevated privileges and removes the registration from the kernel subsystem:
$ sudo bpftool struct_ops show
$ sudo bpftool struct_ops unregister id 105
Unregistered tcp_congestion_ops cubic id 105
Use name instead when the name is unambiguous:
$ sudo bpftool struct_ops unregister name cubic
Recovery
There is no rollback command in this interface. The practical undo is registering the original ELF object again, provided it is still available and the subsystem accepts it. Recheck the result:
$ bpftool struct_ops show
$ test -e /run/bpf/struct_ops/cubic && echo "pin remains" || echo "pin absent"
If you pinned links, account for their files separately: unregistering an object and removing a pin are different lifecycle actions. Do not delete pins merely to tidy a directory while a consumer may still depend on them.
Common traps
- Confusing the manpage name with the command: run
bpftool struct_ops, notbpftool-struct_ops. - Using a stale ID: list again immediately before an unregister operation.
- Assuming
registerloads every BPF section: it targets only the.struct_opsand.struct_ops.linksections described by the manpage. - Expecting plain output to be stable for a script: request JSON with
-jand handle an empty object list. - Debugging too late: add
-dto print libbpf and verifier diagnostics when a load fails, but do not treat verbose logs as proof registration succeeded.
Done means
- Identified each visible struct_ops by a fresh ID, name and kernel type.
- Inspected the intended object with
dumpbefore changing state. - Registered using the intended ELF file and, if needed, an explicit pin directory.
- Verified the post-change listing and kept the original ELF available for recovery.