nlink 0.24.0

Async netlink library for Linux network configuration
Documentation

nlink

Crates.io Documentation License

A Rust library for Linux network configuration via netlink. Async/tokio-native, type-safe, owns its wire format end-to-end (no rtnetlink / netlink-packet-* dependency). CLI binaries (nlink-ip, nlink-tc, nlink-ss, nlink-nft, nlink-wg, nlink-bridge, nlink-devlink, nlink-ethtool, nlink-wifi, nlink-config) ship as proof-of-concept demonstrations of the library — each nlink- prefixed so it never shadows the system tool it mirrors.

Install

nlink = "0.24"

Feature flags: sockdiag, tuntap, tuntap-async, output, namespace_watcher, syscall_batch (recvmmsg/sendmmsg batching), serde, schemars (JSON Schema for NetworkConfig), lab (test harness), full. Full list in the docs.rs feature table.

MSRV: Rust 1.95, edition 2024.

Quick start

use nlink::netlink::{Connection, Route, RtnetlinkGroup, NetworkEvent};
use tokio_stream::StreamExt;

#[tokio::main]
async fn main() -> nlink::Result<()> {
    let conn = Connection::<Route>::new()?;

    // Query
    for link in conn.get_links().await? {
        println!("{}: {} (up={})", link.ifindex(), link.name_or("?"), link.is_up());
    }

    // Modify
    conn.set_link_up("eth0").await?;
    conn.set_link_mtu("eth0", 9000).await?;

    // Observe
    conn.subscribe(&[RtnetlinkGroup::Link, RtnetlinkGroup::Ipv4Addr])?;
    let mut events = conn.events().await;
    while let Some(event) = events.try_next().await? {
        if let NetworkEvent::NewLink(link) = event {
            println!("link added: {}", link.name_or("?"));
        }
    }
    Ok(())
}

What's in the box

Networking core — links (20+ types: dummy, veth, bridge, bond, VLAN, VXLAN, GRE, IPIP, SIT, VTI, Geneve, netkit, IFB, VRF, MACsec, MACVLAN, IPVLAN, GTP, tun/tap), addresses, routes, neighbors, routing rules, nexthop objects + ECMP groups, MPLS, SRv6, bridge FDB + VLAN filtering, network namespaces.

Traffic control — 35 typed qdisc kinds, typed classes (HTB, HFSC, DRR, QFQ), 11 typed filter kinds, 19 typed action kinds, filter chains, BPF program attachment. Every typed config has a parse_params(&[&str]) so tc CLI syntax round-trips through the typed API. Strongly-typed units: Rate (bytes/sec internally, parses 100mbit / 1gbit), Bytes, Percent, TcHandle, FilterPriority.

Generic Netlink families — WireGuard, MACsec, MPTCP, ethtool, nl80211 (WiFi: PHY/wiphy band capabilities, scan/BSS, station-info, channel survey), devlink, DPLL (clock synchronization — SyncE/PTP/GNSS, kernel 6.7+), net_shaper (TX hardware shaping incl. hierarchical groups, kernel 6.13+), OpenVPN DCO.

Firewall — nftables tables/chains/rules/NAT/match expressions, named sets (imperative + declarative DeclaredSet with element-level diff), flowtables (Expr::FlowOffload), multicast event subscription, atomic single-batch commits via Transaction, declarative NftablesConfig reconcile, typed decoding of dumped rule expressions (RuleInfo::expressions() / per-rule hit counters via RuleInfo::counter()).

Diagnostics & observability — socket diagnostics (SockDiag) with kernel-side ss-expression filtering (compiled INET_DIAG_REQ_BYTECODE programs: ports, addresses, or/not, state hoisting), socket→process/cgroup attribution (SocketOwnerMap/CgroupPathMap), per-socket TCP goodput tracking (SocketRateTracker), typed congestion-control internals (BBR/DCTCP/vegas CcInfo); connection tracking (Netfilter/ctnetlink), Linux audit, SELinux events, FIB lookups, ethtool statistics + monitor, kobject uevent (device hotplug), process connector lifecycle events.

Cross-cutting — XFRM IPsec SA/SP management + hardware offload (XFRMA_OFFLOAD_DEV) + monitor event stream (Connection<Xfrm>: EventSource), ENOBUFS-resync event streams with a kube-rs-style Store watch-cache (ReflectExt::reflect), opt-in per-nlmsg_seq dispatcher mode (Connection::with_dispatcher) so events and requests coexist on one connection, 30-second default per-Connection timeout (override or opt out), NETLINK_EXT_ACK TLV parsing (kernel error messages with offset + attribute name), NETLINK_GET_STRICT_CHK opt-in.

High-level APIs

The lower-level imperative API is the foundation; these declarative layers collapse common configuration patterns.

// Declarative network state — diff against kernel, apply changes idempotently.
// With the `serde` feature it also round-trips through JSON/YAML: the typed
// config validates as it parses (CIDR addresses, MAC strings), so a bad value
// is a deserialize error, not a silently-wrong config.
use nlink::netlink::config::NetworkConfig;
NetworkConfig::new()
    .link("br0", |l| l.bridge().up())
    .link("dummy0", |l| l.dummy().mtu(9000).up().master("br0"))
    .address("br0", "192.168.100.1/24")?
    .apply(&conn).await?;
let cfg = NetworkConfig::from_json_str(r#"{ "links": [{ "name": "br0", "link-type": "bridge" }] }"#)?;

// Declarative nftables ruleset — atomic batch commit.
use nlink::netlink::nftables::config::NftablesConfig;
use nlink::netlink::nftables::types::{Family, Hook, Policy, Priority};
let cfg = NftablesConfig::new().table("filter", Family::Inet, |t| {
    t.chain("input", |c| c.hook(Hook::Input).priority(Priority::Filter).policy(Policy::Drop))
        .rule("input", |r| r.match_iif("lo").accept())
        .rule("input", |r| r.match_tcp_dport(22).accept())
});
cfg.diff(&conn).await?.apply(&conn).await?;

// Rate limiting — typed Rate, no bits-vs-bytes confusion.
use nlink::{Rate, netlink::ratelimit::RateLimiter};
RateLimiter::new("eth0").egress(Rate::mbit(100)).ingress(Rate::mbit(50))
    .apply(&conn).await?;

// Per-peer impairment — netem per destination on shared L2.
use nlink::netlink::impair::PerPeerImpairer;
PerPeerImpairer::new("vethA-br")
    .impair_dst_ip("172.100.3.18".parse()?, /* netem config */)
    .apply(&conn).await?;

// Network diagnostics — find issues, score bottlenecks.
let report = nlink::netlink::diagnostics::Diagnostics::new(conn).scan().await?;

// Reflector / watch-cache — keep an in-memory Store up to date from a
// resync-aware event stream (kube-rs style), then read it from anywhere.
use nlink::{Store, StoreOp};
use nlink::netlink::reflector::ReflectExt;
let store: Store<u32, NetworkEvent> = Store::new();
let watch = conn.into_events_with_resync(factory)?.reflect(store.clone(), |ev| match ev {
    NetworkEvent::NewLink(l) => StoreOp::Upsert(l.ifindex()),
    NetworkEvent::DelLink(l) => StoreOp::Remove(l.ifindex()),
    _ => StoreOp::Ignore,
});
// drive `watch` in a task; `store.len()` / `store.get(&idx)` read the cache.

// JSON Schema for config files (feature `schemars`) — editor/CI validation.
let schema = NetworkConfig::json_schema();

Building blocks for downstream code

  • ConnectionPool<P> — bounded async pool of typed connections; each lease gets its own kernel-side socket, so the kernel processes them in parallel.
  • DumpStream<T>Stream adapter over netlink dumps; O(1) memory on BGP/conntrack/IPsec-scale tables instead of buffering the full response.
  • ResyncStream<T> — wraps multicast subscriptions with ENOBUFS recovery (ResyncedEvent::Marker(ResyncStart) → snapshot replay → ResyncEnd).
  • nlink-macros — declare a custom Generic Netlink family in ~30 lines via #[genl_family] + #[derive(GenlMessage)]; consume through conn.send_typed(req).await? / dump_typed_stream. The in-tree DPLL and net_shaper families are the canonical dogfoods.

Documentation

  • Library guide — detailed examples: namespaces, TC, WireGuard, error handling, concurrency.
  • Cookbook recipes — end-to-end walkthroughs (per-peer impairment, VLAN-aware bridges, bidirectional rate limiting, WireGuard mesh in namespaces, ENOBUFS-resync loops, define-your-own-GENL-family).
  • CLI reference — command coverage for all ten demo binaries (nlink-ip, nlink-tc, nlink-ss, nlink-nft, nlink-bridge, nlink-wg, nlink-config, nlink-devlink, nlink-ethtool, nlink-wifi).
  • Migration guides — per-release upgrade notes.
  • Examples — 40+ runnable demos.
  • docs.rs/nlink — full API reference.

Project status

API stable and used in production. Wire format pinned by build-time sizeof(struct …) CI gates; concurrent shared-Arc<Connection> use is safe (serialized via internal mutex — use ConnectionPool for parallel throughput). 19 CI gates on every push (build × 2 feature sets, tests × 2, clippy --deny warnings, doc with strict intra-doc links, semver-checks, public-api diff, machete, msrv, s390x big-endian cross-check, README/manifest sync, plus 8 nlink-specific audit scripts) + a privileged integration job that runs the root-gated kernel round-trip suite.

Building from source

cargo build --release
cargo run --release -p nlink-ip -- link show
cargo run --release -p nlink-tc -- qdisc show

License

Apache 2.0 OR MIT, at your option.