rtnetlink

Async Rust crate for managing Linux network links, addresses, routes, and rules via netlink

Library
Cargo
v0.23.0
184stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
82/100Excellent
Development Activity100
Maintenance84
Community72
Maturity52
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture78
Code Quality62
Innovation85
Learning Curve65

rtnetlink is a Rust crate that gives programs direct, async access to the Linux kernel’s netlink route protocol (RTNETLINK) — the same interface the ip command uses to manage network links, IP addresses, routing tables, routing rules, neighbour (ARP/NDP) entries, and traffic-control qdiscs/filters/classes. Rather than shelling out to ip and parsing text output, applications open a netlink socket through new_connection(), drive it on a Tokio (or smol, via a feature flag) executor, and issue strongly typed requests through a fluent Handle API that mirrors the ip subcommand structure (handle.link(), handle.address(), handle.route(), handle.rule(), handle.neighbours()).

It’s built on top of sibling crates from the rust-netlink organization — netlink-sys, netlink-proto, and netlink-packet-route — which handle the raw socket, connection multiplexing, and message (de)serialization layers respectively, letting rtnetlink focus purely on exposing an ergonomic, resource-oriented request/response API for network configuration and monitoring (including multicast subscriptions equivalent to ip monitor).

What You Get

  • A Handle type with per-resource sub-handles (link(), address(), route(), rule(), neighbours(), qdisc(), traffic_class(), traffic_filter(), traffic_chain()) mirroring ip/tc subcommands
  • Fluent Add/Del/Get/Set request builders (e.g. LinkAddRequest, AddressAddRequest, RouteAddRequest) that return async Streams of decoded netlink responses
  • Message builders (LinkMessageBuilder, RouteMessageBuilder, AddressMessageBuilder) plus dedicated virtual-interface types (VLAN, VXLAN, bridge, bond, veth, WireGuard, MACsec, netkit, xfrm, and more)
  • Runtime-agnostic sockets via feature flags — tokio_socket (default) or smol_socket — so the crate fits either async ecosystem
  • Network namespace helpers (NetworkNamespace) and multicast group subscriptions equivalent to ip monitor for live event streams
  • A typed Error enum (via thiserror) distinguishing netlink protocol errors, invalid addresses, invalid NLAs, and namespace failures

Common Use Cases

  • Building container networking and CNI plugins that create veth pairs, move interfaces into network namespaces, and assign IPs without spawning ip subprocesses
  • Writing VPN and overlay networking daemons (WireGuard, VXLAN, xfrm) that need to provision and monitor interfaces programmatically
  • Implementing network monitoring or diagnostics tools that stream live link/address/route change events via multicast groups
  • Automating routing table and policy-routing (rules) management for load balancers, service meshes, or SDN control planes
  • Managing Linux traffic control (qdiscs, filters, classes) from Rust services that need programmatic QoS or traffic shaping

Under The Hood

Architecture — rtnetlink is layered cleanly on top of the rust-netlink family: netlink-sys owns the raw socket, netlink-proto (via new_connection/new_connection_with_socket in src/connection.rs) multiplexes requests and demultiplexes responses into a background Connection future plus a cloneable Handle, and netlink-packet-route encodes/decodes RouteNetlinkMessage payloads. Handle (src/handle.rs) exposes request()/notify() primitives and factory methods (link(), address(), route(), rule(), neighbours(), qdisc(), traffic_class(), traffic_filter(), traffic_chain()) that construct resource-specific sub-handles, each of which lives in its own module (src/link/, src/addr/, src/route/, src/rule/, src/neighbour/, src/traffic_control/) with symmetric add.rs/del.rs/get.rs/handle.rs files following the same request-builder pattern throughout.

Tech Stack — Rust 2021 edition targeting rust-version 1.71, built on futures-util/futures-channel for stream-based async, thiserror 2 for the Error enum, and the co-versioned netlink-sys 0.8 / netlink-packet-route 0.31 / netlink-packet-core 0.8 / netlink-proto 0.12 crates from the same organization. Async execution is runtime-agnostic via mutually exclusive feature flags: tokio_socket (default, pulls in tokio) or smol_socket (pulls in async-global-executor). nix 0.30 supplies namespace/mount/signal syscalls for the NetworkNamespace helpers. Dev-dependencies (env_logger, ipnetwork, async-std, macaddr) support the 49-file examples/ suite covering nearly every supported interface type and operation.

Code Quality — Error handling is centralized in a single thiserror-derived Error enum (src/errors.rs) with specific variants for unexpected messages, netlink protocol errors, request failures, namespace errors, and malformed addresses/NLAs, giving callers precise failure information instead of opaque I/O errors. Naming is highly consistent across resource modules (*AddRequest, *DelRequest, *GetRequest, *Handle per resource type). Automated test coverage is comparatively thin relative to the crate’s surface area — #[test] appears in only 4 files (link/test.rs, traffic_control/test.rs, traffic_control/add_qdisc.rs, traffic_control/add_filter.rs) — because most functional behavior requires real root privileges and a live netlink socket (gated behind the test_as_root feature) rather than being unit-testable in isolation; the extensive examples/ directory partly substitutes as executable documentation and manual verification surface.

API Design — The public API is deliberately ergonomic and mirrors the ip/tc command mental model almost one-to-one (handle.link().add()...execute(), handle.route().get(...)), which sharply reduces the learning curve for anyone already familiar with Linux networking tools. Request builders return impl Stream for get-style queries and plain futures for mutations, kept consistent throughout every resource module. Getting started requires only new_connection() plus spawning the returned Connection future on an executor — minimal boilerplate for a low-level systems crate.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search