Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Netlink-bindings
Type-safe Rust bindings for encoding/decoding Netlink messages generated from YAML specifications.
Overview
Netlink is a structured way for various kernel subsystems to expose their userspace API using hierarchical format with binary-encoded messages exchanged over a socket. Many kernel subsystems already have a machine-readable API descriptions, which we use to generate Rust bindings.
This project provides easy-to-use type-safe interface, while being reasonably fast and supporting all properties of all sensible Netlink families.
Features
- Simple type-safe interface, see below Making requests and Examples.
- Support for all documented netlink subsystems, see Support status.
- Netlink messages can be Debug-printed, with enum variants and flags annotated.
- Examine netlink messages of existing programs using reverse-lookup.
Support status
All upstream specifications are supported as of Linux 7.2.
- ✅ supported, has tests: conntrack, inet-diag, nftables, nl80211, nlctrl, rt-addr, rt-route, rt-link, tc, wireguard.
- ✔️ compiles, testing needed: binder, dev-energymodel, devlink, dpll, drm-ras, ethtool, fou, handshake, lockd, mptcp_pm, netdev, net-shaper, nfsd, ovpn, ovs_datapath, ovs_flow, ovs_packet, ovs_vport, psp, rt-neigh, rt-rule, sunrpc, tcp_metrics, team, unix-diag.
Installation
[]
= { = "0.3", = [ "wireguard" ] }
= { = "0.3", = [ ] }
Making requests
A typical Netlink family, say wireguard, supports multiple operations: "get-device", "set-device", etc. Each operation may be of kind "do" or "dump".
As an example, to gather info about a device you would use a "dump" kind (returning multiple replies) of "get-device" request. That's usually what it means, although different subsystems may imply different things. A typical request looks like this:
use wireguard;
use NetlinkSocket;
let mut sock = new;
let mut request = new
.op_get_device_dump;
request.encode
.push_ifname_bytes;
let mut iter = sock.request.unwrap;
while let Some = iter.recv.transpose.unwrap
Your LSP should be able to nicely suggest appropriate methods both for encoding and decoding as you type.
More complicated requests
Let's say you have a network interface and you want to assign it an ip address. This is domain of "rt-addr" family. It was one of the first subsystems created, inheriting some now-discouraged quirks like a fixed-header - a struct that's always present in a message. It's use depends on the request type, with unused fields usually zeroed-out.
The relevant operation is "newaddr" of kind "do" (only returning an acknowledgment).
use IpAddr;
use rt_addr;
use NetlinkSocket;
let mut sock = new;
let addr: IpAddr = "10.0.0.1".parse.unwrap;
let ifindex = unsafe ;
// Create fixed-header for the request
let header = Ifaddrmsg ;
let mut request = new
.set_change // Don't fail if address already assigned
.op_newaddr_do;
request.encode
.push_local;
sock.request.unwrap
.recv_ack.unwrap;
You may also notice ".set_change()" setting a flag. Similar to the fixed-header, these flags may trigger additional behavior in certain operations, or do nothing in others.
See full code in the example.
Async sockets
Generally, Netlink requests resolve immediately, which is to say it's safe to
use the "blocking" NetlinkSocket in the async context, which is recommended.
The only exceptions are a multicast socket, receiving notification asynchronously, or a hypothetical subsystem, choosing to deliberately delay replies.
Netlink-socket crate allows the facilities for different runtimes to coexist
under different paths, i.e. netlink_socket2::tokio::{NetlinkSocket, MulticastSocketRaw}, with async functionality available with the exactly same
structure as the "blocking" one.
[]
= { .. , features = [ "std", "tokio" ] } # or "smol"
Other examples
- wireguard-setup - Create and configure wireguard interface.
- ip-route-show - Dump routing entries.
- conntrack - Dump tracked network
connections, similar to
conntrack -L. - extack - Showcase handing of extended ack attributes in error reporting.
- nftables - Create nftables rules.
- nftables-api - A high-level wrapper for creating nftables rules.
- nl80211 - Basic interactions with nl80211.
- nl80211-raw - Same as nl80211, but manually encoding/decoding netlink messages.
- tc-prio - Add, show, and delete traffic control queueing discipline.
- tcp-rtt - Dump socket information, including RTT of a TCP socket.
- multicast-simple - Listen for multicast notifications emitted when a network device is created, changed, or deleted.
- multicast-generic - Listen for all multicast notifications on a generic netlink subsystem.
- multicast-raw - Listen for multicast notifications on legacy rtnetlink subsystem.
Advanced usage
Working off of existing tools
If there's an existing tool using Netlink, you can use reverse-lookup tool to
decipher it's Netlink communications and work off of that.
Let's say you want to see what wg command does:
)
This way you can study the structure of the real messages the kernel expects
and replies with. In order to translate it into code, you simply need to
convert attributes from CamelCase to snake_case, adding occasional .get_*(),
.push_*(), or .nested_*() prefix.
This tool is merely interpreting the output of strace(1), see reverse-lookup --help for more details.
Attribute encoding
Under the hood, calling .encode() is just a convenience to switch to a
correct Push* wrapper for encoding, which is given an internal buffer. For
example, directly encoding a "do" request of "set-device" operation looks like
this:
use wireguard as wg;
let mut vec = Vecnew;
// Do set-device (request)
encode_request
.push_ifname // &CStr
// .push_ifname_bytes("wg0".as_bytes()) // &[u8]
.push_flags // Remove existing peers
.array_peers
.entry_nested
.push_public_key // &[u8]
.push_endpoint // SocketAddr
.array_allowedips
.entry_nested
.push_family // aka ipv4
.push_ipaddr // IpAddr
.push_cidr_mask // stands for "/0" in "0.0.0.0/0"
.end_nested
// More allowed ips...
.end_array // Explicitly closing isn't necessary
.end_nested
// More peers...
.end_array;
Additionally, check out all available methods, along with the in-line documentation.
Attribute decoding
Similarly, under the hood, receiving a reply yields an attribute decoder. The decoder itself is just a wrapper on a slice, therefore it can be cheaply cloned, copying it's frame. The low-level interface is based on iterators, with nicer helper functions on top.
use NetlinkRequest;
use OpGetDeviceDump;
// Dump get-device (reply)
let attrs = decode_reply;
println!; // &CStr
for peer in attrs.get_peers.unwrap
See full code in the example. And as previously, check out all available methods, along with the in-line documentation.
Cloning attrset contents
In some cases, it's infeasible to construct the message in one go, for example when you want to be able to freely alternate between encoding two different attribute sets.
The kernel only expects a single nested attribute set of a certain kind to appear at the same level of nesting, so you have to first encode them in separate temporary buffers before cloning them into the final message. This trick applies to reusing parts of an already encoded message received from the kernel.
Writing raw attributes is possible using .as_vec_mut() method method of
Pusher
trait, implemented for all encoding Push* structs, giving access to the
internal &mut Vec<u8> buffer. And a corresponding .get_buf() method
available on all decoding structs returning their &[u8] frame.
use ;
let mut link_attrs = Vecnew;
let mut bridge_attrs = Vecnew;
new
.push_priority;
new
.push_link;
// ...
let mut req = new
.op_getlink_do;
req.encode
.as_vec_mut
.extend_from_slice;
req.encode
.nested_linkinfo
.nested_data_bridge
.as_vec_mut
.extend_from_slice;
Low-level decoding
A low-level decoding interface is exposed as an iterator, that yields enum variants, containing either a target type, e.g. SockAddr, or another iterator, in case of a nested attribute set. But as you can see, using it directly quickly turns very ugly.
use NetlinkRequest;
use ;
for attr in decode_reply
Alternatives
Both don't use codegen to generate bindings, hence many Netlink families are not supported.
Another difference is that they represent netlink messages as lists of rust enums, while this project works with the binary representation directly, with a separate interfaces for encoding and decoding: a builder pattern-like interface for encoding, and an iterator interface for decoding (internally).
Contribute
See CONTRIBUTING.md for information on how to work with this repo, its structure, codegen stuff, etc.
If your want to contribute, you can, for example:
- Just use netlink-bindings. If you encounter a shortcoming of the current API, report it.
- Write straightforward higher-level abstractions on top of netlink-bindings.
- Add testing: collect netlink messages and check that they are parsed correctly. See wireguard tests as an example. Additional examples are also very welcome.
- Implement yet unsupported netlink functionality.
- Improve compilation time, reduce the size of generated bindings.
- Experiment with a better Rust interface (for encoding/decoding and the sockets).
- Sponsor the project (contact the author for options).