# nlink - Rust IP utilities
A modern Rust implementation of Linux network management tools, providing both a library crate and CLI binaries.
## Overview
nlink is a from-scratch implementation of Linux netlink-based network management. The primary goal is to provide a high-quality Rust library for programmatic network configuration, with CLI tools serving as proof-of-concept binaries.
**Key design principles:**
- **Library-first**: Core functionality lives in a single, well-designed crate
- **Async/tokio-native**: Built for async Rust from the ground up
- **Custom netlink**: No dependency on rtnetlink or netlink-packet-* crates
- **Type-safe**: Leverage Rust's type system for correctness
- **Modern CLI**: Not a drop-in replacement for iproute2 - free to improve
## Installation
Add to your `Cargo.toml`:
```toml
# Core netlink functionality
nlink = "0.1"
# With additional features
nlink = { version = "0.1", features = ["sockdiag", "tuntap", "tc", "output"] }
# All features
nlink = { version = "0.1", features = ["full"] }
```
### Features
| `sockdiag` | Socket diagnostics via NETLINK_SOCK_DIAG |
| `tuntap` | TUN/TAP device management |
| `tc` | Traffic control utilities |
| `output` | JSON/text output formatting |
| `namespace_watcher` | Filesystem-based namespace watching via inotify |
| `full` | All features enabled |
## Using as a Library
```rust
use nlink::netlink::{Connection, Protocol};
use nlink::netlink::events::{EventStream, NetworkEvent};
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = Connection::new(Protocol::Route)?;
// Query network state with convenience methods
let links = conn.get_links().await?;
for link in &links {
println!("{}: {} (up={})",
link.ifindex(),
link.name.as_deref().unwrap_or("?"),
link.is_up());
}
// Get addresses for a specific interface
let addrs = conn.get_addresses_for("eth0").await?;
// Get TC qdiscs
let qdiscs = conn.get_qdiscs().await?;
// Modify interface state
conn.set_link_up("eth0").await?;
conn.set_link_mtu("eth0", 9000).await?;
// Monitor network events
let mut stream = EventStream::builder()
.links(true)
.addresses(true)
.tc(true)
.build()?;
while let Some(event) = stream.next().await? {
match event {
NetworkEvent::NewLink(link) => println!("Link added: {:?}", link.name),
NetworkEvent::NewAddress(addr) => println!("Address added: {:?}", addr.address),
_ => {}
}
}
Ok(())
}
```
### Working with Network Namespaces
```rust
use nlink::netlink::{Connection, Protocol};
use nlink::netlink::namespace;
#[tokio::main]
async fn main() -> nlink::Result<()> {
// Connect to a named namespace (created via `ip netns add myns`)
let conn = namespace::connection_for("myns")?;
let links = conn.get_links().await?;
// Connect to a container's namespace by PID
let conn = namespace::connection_for_pid(1234)?;
let links = conn.get_links().await?;
// Or use a path directly
let conn = Connection::new_in_namespace_path(
Protocol::Route,
"/proc/1234/ns/net"
)?;
// List available namespaces
for ns in namespace::list()? {
println!("Namespace: {}", ns);
}
Ok(())
}
```
### Namespace-aware Event Monitoring
```rust
use nlink::netlink::events::{EventStream, NetworkEvent};
#[tokio::main]
async fn main() -> nlink::Result<()> {
// Monitor events in a named namespace
let mut stream = EventStream::builder()
.namespace("myns")
.links(true)
.tc(true)
.build()?;
// Or by PID (e.g., container process)
let mut stream = EventStream::builder()
.namespace_pid(1234)
.links(true)
.build()?;
// Or by path
let mut stream = EventStream::builder()
.namespace_path("/proc/1234/ns/net")
.all()
.build()?;
while let Some(event) = stream.next().await? {
println!("{:?}", event);
}
Ok(())
}
```
### Watching Namespace Changes
Two complementary approaches for monitoring network namespace lifecycle:
```rust
use nlink::netlink::{NamespaceWatcher, NamespaceEvent};
use nlink::netlink::{NamespaceEventSubscriber, NamespaceNetlinkEvent};
#[tokio::main]
async fn main() -> nlink::Result<()> {
// Option 1: Filesystem-based watching (feature: namespace_watcher)
// Watches /var/run/netns/ for named namespace creation/deletion
let mut watcher = NamespaceWatcher::new().await?;
while let Some(event) = watcher.recv().await? {
match event {
NamespaceEvent::Created { name } => println!("Created: {}", name),
NamespaceEvent::Deleted { name } => println!("Deleted: {}", name),
_ => {}
}
}
// Atomically list existing + watch for changes (no race condition)
let (existing, mut watcher) = NamespaceWatcher::list_and_watch().await?;
println!("Existing: {:?}", existing);
// Option 2: Netlink-based events (always available)
// Receives RTM_NEWNSID/RTM_DELNSID kernel events
let mut sub = NamespaceEventSubscriber::new().await?;
while let Some(event) = sub.recv().await? {
match event {
NamespaceNetlinkEvent::NewNsId { nsid, pid, fd } => {
println!("New NSID {}: pid={:?}", nsid, pid);
}
NamespaceNetlinkEvent::DelNsId { nsid } => {
println!("Deleted NSID {}", nsid);
}
}
}
Ok(())
}
```
### Namespace-aware TC Operations
For TC operations in namespaces, use the `*_by_index` methods to avoid reading
`/sys/class/net/` from the host namespace:
```rust
use nlink::netlink::namespace;
use nlink::netlink::tc::NetemConfig;
use std::time::Duration;
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = namespace::connection_for("myns")?;
// First, get the interface index via netlink (namespace-aware)
let link = conn.get_link_by_name("eth0").await?;
let netem = NetemConfig::new()
.delay(Duration::from_millis(100))
.loss(1.0)
.build();
// Use ifindex instead of device name
conn.add_qdisc_by_index(link.ifindex(), netem).await?;
// All TC methods have *_by_index variants:
// - add_qdisc_by_index / add_qdisc_by_index_full
// - del_qdisc_by_index / del_qdisc_by_index_full
// - replace_qdisc_by_index / replace_qdisc_by_index_full
// - change_qdisc_by_index / change_qdisc_by_index_full
Ok(())
}
```
### Reading Existing TC Configurations
The library provides strongly-typed parsing for qdisc options, useful for detecting
existing TC configurations:
```rust
use nlink::netlink::{Connection, Protocol};
use nlink::netlink::tc_options::QdiscOptions;
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = Connection::new(Protocol::Route)?;
let qdiscs = conn.get_qdiscs_for("eth0").await?;
for qdisc in &qdiscs {
// Quick type checks
if qdisc.is_netem() && qdisc.is_root() {
println!("Found root netem qdisc");
}
// Get netem options with full details
if let Some(netem) = qdisc.netem_options() {
println!("Netem qdisc detected:");
// Time values with convenience methods
println!(" delay: {:?}", netem.delay());
println!(" jitter: {:?}", netem.jitter());
// Percentages
println!(" loss: {}% (correlation: {}%)", netem.loss_percent, netem.loss_corr);
println!(" duplicate: {}%", netem.duplicate_percent);
println!(" reorder: {}% (gap: {})", netem.reorder_percent, netem.gap);
println!(" corrupt: {}%", netem.corrupt_percent);
// Rate with overhead parameters
if netem.rate > 0 {
println!(" rate: {} bytes/sec", netem.rate);
println!(" packet_overhead: {}, cell_size: {}",
netem.packet_overhead, netem.cell_size);
}
// ECN and slot-based transmission
println!(" ecn: {}", netem.ecn);
if let Some(slot) = &netem.slot {
println!(" slot: {}ns - {}ns", slot.min_delay_ns, slot.max_delay_ns);
}
// Loss models (Gilbert-Intuitive or Gilbert-Elliot)
if let Some(loss_model) = &netem.loss_model {
use nlink::netlink::tc_options::NetemLossModel;
match loss_model {
NetemLossModel::GilbertIntuitive { p13, p31, p32, p14, p23 } => {
println!(" loss model: Gilbert-Intuitive (4-state)");
println!(" p13={:.2}%, p31={:.2}%, p32={:.2}%", p13, p31, p32);
}
NetemLossModel::GilbertElliot { p, r, h, k1 } => {
println!(" loss model: Gilbert-Elliot (2-state)");
println!(" p={:.2}%, r={:.2}%, h={:.2}%, k1={:.2}%", p, r, h, k1);
}
}
}
}
// Use parsed_options() for all qdisc types
match qdisc.parsed_options() {
Some(QdiscOptions::FqCodel(fq)) => {
println!("fq_codel: target={}us, interval={}us", fq.target_us, fq.interval_us);
}
Some(QdiscOptions::Htb(htb)) => {
println!("htb: default class={:#x}", htb.default_class);
}
Some(QdiscOptions::Tbf(tbf)) => {
println!("tbf: rate={} bytes/sec, burst={}", tbf.rate, tbf.burst);
}
Some(QdiscOptions::Netem(netem)) => {
println!("netem: loss={}%, delay={:?}", netem.loss_percent, netem.delay());
}
_ => {}
}
}
Ok(())
}
```
### Monitoring TC Statistics
Track throughput and statistics changes over time:
```rust
use nlink::netlink::{Connection, Protocol};
use std::time::Duration;
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = Connection::new(Protocol::Route)?;
let mut prev_stats = None;
loop {
let qdiscs = conn.get_qdiscs_for("eth0").await?;
for qdisc in &qdiscs {
// Real-time rate from kernel's rate estimator
println!("Rate: {} bps, {} pps", qdisc.bps(), qdisc.pps());
// Calculate deltas from previous sample
if let (Some(curr), Some(prev)) = (&qdisc.stats_basic, &prev_stats) {
let delta = curr.delta(prev);
println!("Delta: {} bytes, {} packets", delta.bytes, delta.packets);
}
// Queue statistics
println!("Queue: {} packets, {} drops", qdisc.qlen(), qdisc.drops());
prev_stats = qdisc.stats_basic;
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
```
### Error Handling with Context
The library provides rich error types with context support for better debugging:
```rust
use nlink::netlink::{Connection, Protocol, Error};
#[tokio::main]
async fn main() -> nlink::Result<()> {
let conn = Connection::new(Protocol::Route)?;
// Check error types for recovery logic
match conn.del_qdisc("eth0", "root").await {
Ok(()) => println!("Qdisc deleted"),
Err(e) if e.is_not_found() => println!("No qdisc to delete"),
Err(e) if e.is_permission_denied() => println!("Need root privileges"),
Err(e) if e.is_busy() => println!("Device is busy"),
Err(e) => return Err(e),
}
// Semantic error types provide clear messages
// Error::InterfaceNotFound { name: "eth99" } -> "interface not found: eth99"
// Error::NamespaceNotFound { name: "myns" } -> "namespace not found: myns"
// Error::QdiscNotFound { kind: "netem", interface: "eth0" } -> "qdisc not found: netem on eth0"
Ok(())
}
```
## Library Modules
### `nlink::netlink` - Core netlink functionality
- **High-level API**: `Connection` with convenience query methods (`get_links()`, `get_addresses()`, etc.)
- **Link state management**: `set_link_up()`, `set_link_down()`, `set_link_mtu()`, `del_link()`
- **Namespace support**: `Connection::new_in_namespace_path()` and `namespace` module helpers
- **Namespace watching**: `NamespaceWatcher` (inotify) and `NamespaceEventSubscriber` (netlink)
- **Event monitoring**: `EventStream` for real-time network change notifications
- **Strongly-typed messages**: `LinkMessage`, `AddressMessage`, `RouteMessage`, `TcMessage`
- **TC options parsing**: Typed access to qdisc parameters (fq_codel, htb, tbf, netem, etc.)
- **Netem loss models**: Support for Gilbert-Intuitive and Gilbert-Elliot state-based loss
- **Statistics tracking**: `StatsSnapshot` and `StatsTracker` for rate calculation
- **Error handling**: Semantic error types with `is_not_found()`, `is_permission_denied()`, etc.
- **Low-level access**: `MessageBuilder` for custom netlink messages
### `nlink::util` - Shared utilities
- Argument parsing (`get_u8`, `get_u16`, `get_u32`, `get_rate`, `get_size`)
- Address utilities (parse/format IP addresses and prefixes)
- Name resolution (protocol names, scope names, table names)
- Interface name/index mapping
### `nlink::sockdiag` - Socket diagnostics (feature: `sockdiag`)
- Query TCP, UDP, Unix, and other socket types
- Filter by state, port, address, and other criteria
- Retrieve detailed socket information (memory, TCP info, etc.)
### `nlink::tuntap` - TUN/TAP devices (feature: `tuntap`)
- Create and manage TUN/TAP virtual network devices
- Set device ownership and permissions
- Async read/write support
### `nlink::tc` - Traffic control (feature: `tc`)
- Qdisc option builders for htb, fq_codel, tbf, netem, etc.
- Handle parsing and formatting
- Class and filter builders
### `nlink::output` - Output formatting (feature: `output`)
- Text and JSON output modes
- `Printable` trait for consistent formatting
- Configurable options (stats, details, color, numeric)
## Binaries
### ip
Network interface and routing management:
```bash
# List interfaces
ip link show
# Create interfaces (each type is a subcommand with specific options)
ip link add dummy test0
ip link add veth veth0 --peer veth1
ip link add bridge br0 --stp --vlan-filtering
ip link add bond bond0 --mode 802.3ad --miimon 100
ip link add vlan eth0.100 --link eth0 --id 100
ip link add vxlan vxlan0 --vni 100 --remote 10.0.0.1 --dstport 4789
# Delete interfaces
ip link del test0
# Modify interfaces
ip link set eth0 --up --mtu 9000
# Show addresses
ip addr show
# Add/remove addresses
ip addr add 192.168.1.1/24 -d eth0
ip addr del 192.168.1.1/24 -d eth0
# Show routes
ip route show
# Add/remove routes
ip route add 10.0.0.0/8 --via 192.168.1.1
ip route del 10.0.0.0/8
# Show neighbors
ip neigh show
# Add/remove neighbors
ip neigh add 192.168.1.2 --lladdr 00:11:22:33:44:55 -d eth0
ip neigh del 192.168.1.2 -d eth0
# Show policy routing rules
ip rule show
# Add/remove rules
ip rule add --from 10.0.0.0/8 --table 100 --priority 1000
ip rule add --fwmark 0x100 --table 200
ip rule del --priority 1000
# Query route for a destination
ip route get 8.8.8.8
# Flush neighbor entries
ip neigh flush dev eth0
# Monitor netlink events (link, address, route, neighbor changes)
ip monitor all
ip monitor link address --timestamp
ip monitor -j # JSON output
# Multicast addresses
ip maddress show
ip maddress show dev eth0
# VRF (Virtual Routing and Forwarding)
ip vrf show
ip vrf exec vrf0 ping 10.0.0.1
ip vrf identify $$
ip vrf pids vrf0
# XFRM (IPSec)
ip xfrm state show
ip xfrm state count
ip xfrm policy show
ip xfrm policy count
# Network namespaces
ip netns list
ip netns add myns
ip netns exec myns ip link show
ip netns del myns
ip netns identify $$ # Identify namespace of a PID
ip netns pids myns # List PIDs in a namespace
ip netns monitor # Watch namespace creation/deletion
# Tunnels (GRE, IPIP, SIT, VTI)
ip tunnel show
ip tunnel add gre1 --mode gre --remote 10.0.0.1 --local 10.0.0.2 --ttl 64
ip tunnel add tun0 --mode ipip --remote 192.168.1.1 --local 192.168.1.2
ip tunnel change gre1 --remote 10.0.0.3
ip tunnel del gre1
```
### tc
Traffic control (qdisc, class, filter):
```bash
# List qdiscs
tc qdisc show
tc qdisc show dev eth0
# Add qdiscs with type-specific options
tc qdisc add dev eth0 --parent root htb default 10 r2q 10
tc qdisc add dev eth0 --parent root fq_codel limit 10000 target 5ms interval 100ms ecn
tc qdisc add dev eth0 --parent root tbf rate 1mbit burst 32kb limit 100kb
tc qdisc add dev eth0 --parent root prio bands 3
tc qdisc add dev eth0 --parent root sfq perturb 10 limit 127
# Replace/change qdiscs
tc qdisc replace dev eth0 --parent root fq_codel limit 5000
tc qdisc change dev eth0 --parent root fq_codel target 10ms
# Netem - network emulation (delay, loss, reorder, corrupt, duplicate)
tc qdisc add dev eth0 --parent root netem delay 100ms 10ms 25%
tc qdisc add dev eth0 --parent root netem loss 1% 25%
tc qdisc add dev eth0 --parent root netem duplicate 1%
tc qdisc add dev eth0 --parent root netem corrupt 0.1%
tc qdisc add dev eth0 --parent root netem reorder 25% 50% gap 5
tc qdisc add dev eth0 --parent root netem rate 1mbit
tc qdisc add dev eth0 --parent root netem delay 100ms loss 1% duplicate 0.5%
# Delete qdiscs
tc qdisc del dev eth0 --parent root
# List classes
tc class show
tc class show dev eth0
# Add HTB classes with rate limiting
tc class add dev eth0 --parent 1: --classid 1:10 htb rate 10mbit ceil 100mbit prio 1
tc class add dev eth0 --parent 1: --classid 1:20 htb rate 5mbit ceil 50mbit burst 15k
# Monitor TC events
tc monitor all
tc monitor qdisc class --timestamp
tc monitor -j # JSON output
# List filters
tc filter show
tc filter show dev eth0
```
## Building
Requires Rust 1.85+ (edition 2024).
```bash
# Build all crates and binaries
cargo build --release
# Run ip command
cargo run --release -p ip -- link show
# Run tc command
cargo run --release -p tc -- qdisc show
```
## Project Status
The library API is production-ready for network monitoring and querying. Currently implemented:
- [x] Core netlink socket and connection handling
- [x] Message building with nested attributes
- [x] Link operations (show, add, del, set)
- [x] Link types: dummy, veth, bridge, bond, vlan, vxlan, macvlan, ipvlan, vrf, gre, ipip, sit, wireguard
- [x] Address operations (show, add, del)
- [x] Route operations (show, add, del, replace)
- [x] Neighbor operations (show, add, del, replace)
- [x] Policy routing rules (ip rule show, add, del, flush)
- [x] Event monitoring (ip monitor) for link, address, route, neighbor changes
- [x] TC qdisc operations (show, add, del, replace, change)
- [x] TC qdisc types: fq_codel, htb, tbf, prio, sfq, netem (with full parameter support)
- [x] TC netem qdisc (delay, loss, reorder, corrupt, duplicate, rate limiting)
- [x] TC class operations with HTB parameters (rate, ceil, burst, prio, quantum)
- [x] TC monitor for qdisc/class/filter events
- [x] TC filter operations (show, add, del)
- [x] TC filter types: u32 (match ip/ip6/tcp/udp/icmp), flower, basic, fw, bpf
- [x] TC actions: gact (pass/drop/pipe), mirred (mirror/redirect), police (rate limiting)
- [x] Network namespace support (ip netns list, add, del, exec, identify, pids, monitor, set, attach)
- [x] Tunnel management (ip tunnel show, add, del, change) for GRE, IPIP, SIT, VTI
- [x] Route lookup (ip route get)
- [x] Neighbor flush (ip neigh flush)
- [x] Multicast addresses (ip maddress show)
- [x] VRF management (ip vrf show, exec, identify, pids)
- [x] XFRM/IPSec framework (ip xfrm state/policy show, count)
**Library features:**
- [x] High-level event stream API (`EventStream`, `NetworkEvent`)
- [x] Convenience query methods (`get_links()`, `get_addresses()`, `get_qdiscs()`, etc.)
- [x] Link state management (`set_link_up()`, `set_link_down()`, `set_link_mtu()`, `del_link()`)
- [x] Namespace-aware connections (`Connection::new_in_namespace_path()`, `namespace` module)
- [x] Namespace-aware event monitoring (`EventStream::builder().namespace()`)
- [x] Namespace-aware TC operations (`add_qdisc_by_index()`, etc.)
- [x] Namespace watching (`NamespaceWatcher` via inotify, `NamespaceEventSubscriber` via netlink)
- [x] Typed TC options parsing (fq_codel, htb, tbf, netem, prio, sfq)
- [x] Statistics helpers with rate calculation (`StatsSnapshot`, `StatsTracker`)
- [x] Thread-safe `Connection` (`Send + Sync`)
## License
Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.