BPF From Scratch In Rust
17 points by airdrop
17 points by airdrop
At the time of this writing the bpfel-unknown-none target is only available on nightly
That is not entirely correct. The bpf targets themselves (there are two, little endian and big endian) do not require nightly, but build-std = ["core"]
does. Thankfully, build-std is not a hard requirement to compile to the bpf targets. This was the motivation for my first and so far only rustc pull request. Before, you could not bootstrap the Rust compiler with both full-featured targets such as aarch64-apple-darwin
or x86_64-unknown-linux-gnu
and a no-std target such as bpfel-unknown-none
while also generating docs. However, including the bpf targets in the bootstrap is required to avoid depending on build-std = ["core"]
later on, as we then need an already precompiled core library in our toolchain. Disabling the documentation step wasn’t an option for NixOS either, so I’m very happy we were able to fix this in rustc itself. This allowed us to include both bpf targets in nixpkgs’ rustc, so if you pull in rustc from nixpkgs unstable (and soon NixOS 25.05, but not from fenix or rust-overlay!), there is no need for any nightly flags such as build-std
to follow this article.
I just issued a correction and gave you a shoutout thank you for pointing this out.
For most Rust users (i.e., those using rustup), saying BPF requires nightly is correct, but I think it’s good to understand why this is the case. :) Thank you!
it may take a bit to get deployed because that service is currently getting pummeled by the load balancer :/
will reply when its up.
The article proceeds to remove all boilerplate and end up with a function written entirely in asm, because for some reason it can’t be written in Rust. Maybe could use a more interesting demo, that actually uses Rust for processing the events.
As someone who has been living under a rock since reading about the OSI model back in ~ y2k and got out in 2025 reading about eBPF, please calibrate my (mis)understanding:
A linux-based router with a modern kernel could, in principle, see all traffic that goes through it as plaintext? Filter/route traffic selectively based on said content?
Well, as a “plain binary” I guess, but yes. Of course if the traffic is encrypted (e.g. https) the content isn’t super interesting. But you can also see all the metadata, all the other fun TCP flags, etc.
How come loop {}
passes for a panic handler? I thought that would not appease the verifier, as it does not terminate. Or is it simply pruned by the linker as the code does not panic anywhere?
But then panic=abort
is also specified in the compilation flags, how does that interact with defining a panic_handler
manually? Maybe I just don’t understand what the panic=abort
flag actually does.
It seems the gist is that std normally provides a panic_handler
that calls all of the remaining machinery, as configured (either unwind or abort generally). In case of no_std
, there is no panic_handler
so it must be provided, and the -C panic=
option affects both what the “std panic machinery” does, and also informs the codegen to be unwindable or not. The -C panic=
option itself has no effect on the panic_handler
directly.
This article goes over it quite nicely, although I am not sure if it is up to date. https://www.ralfj.de/blog/2019/11/25/how-to-panic-in-rust.html
Also the cargo profile panic=
option is distinct from the -C panic=
codegen option. However the cargo flag simply seems to cause -C panic=
to be passed to rustc. Not sure if it has any other effects.
The point about why loop {}
passes the verification still stands.
It says no macros, but isn’t asm!
a macro?
I guess you’re right but asm!
is a little bit special.
The BPF program needs to trap back into the kernel to do I/O anyway so no matter how you cut the cake someone somewhere needs to do the asm!
it’s just a question of whether or not it’s hidden from you behind a nice rust function.
I figured for the sake of example and explaining how to do it from scratch we should hide nothing
Not really: asm!
is a built-in pretending to be a macro. I don’t think a user could define a macro that does what it does exactly.