- Rust 98%
- JavaScript 1%
- Tree-sitter Query 0.8%
- Just 0.2%
Adopts nlink 0.21 → 0.25 and turns the new declarative/ergonomic APIs into three long-standing closeouts, plus two observability wins and the free upstream shaping/firewall correctness fixes. ### Workspace dep bump - **`nlink = "0.21" → "0.25"`.** Four minor versions of upstream correctness work, adopted clean — no nlink-lab-side compile change (the sole NamespaceGuard is on a synchronous capture thread so 0.25's !Send guard is a non-issue; Chain::new Result, LinkStats accessors, and the #[non_exhaustive] diff/nl80211/sockdiag changes touch no call site). nlink-lab feeds nlink only typed Rate/Duration/Percent, so 0.25's silent tc-string unit changes cannot reach it. ### Adoptions - **WireGuard fully declarative.** The imperative device pre-create loop (deploy step 6c + its apply_diff mirror) is gone; WireguardConfig::ensure_devices bootstraps the link inside apply_stack_for_node before the NetworkConfig apply so tunnel addresses land on an existing link. Closes feedback item #3 vs 0.19. - **RateLimiter::reconcile.** Deploy step 15 and the live diff path reconcile idempotently instead of tearing down and rebuilding the HTB tree — zero kernel calls when unchanged, no packet-drop window. Closes Plan 158g. - **del_*_if_exists teardown.** clear_impairment, mgmt veth/bridge teardown, and apply_diff link/route removal use the typed helpers instead of QdiscNotFound matching or `let _ = del_*`. - **Stale-marker self-heal.** The pre-create guard uses namespace::is_namespace and clears a stale netns marker left by an unclean shutdown, so a crashed deploy re-deploys without a manual destroy --orphans. ### Observability - **watch nftables rule counters.** NewRule drift lines show the typed (packets, bytes) counter via RuleInfo::counter(). - **Per-process TCP bandwidth.** The backend collector dumps each node's TCP sockets (Connection<SockDiag>), tracks goodput with a per-node SocketRateTracker, and attributes flows via SocketOwnerMap. Top flows ride on the new NodeMetrics.sockets field (serde(default) for wire-compat) and render in the metrics stream. ### Free upstream fixes now in effect - Traffic shaping is now correct (psched/HTB/TBF tick fixes, #191–#218; every RateLimiter / PerPeerImpairer shape was previously 15–125× off). - Firewall rules install in declared order (#195); reject really rejects (#205); impairment flower filters match IPv6/ARP/VLAN (#201). ### Breaking: apply --check --json schema v3 The v1 `diff` / `layered_summary` / `layered_summary_deprecated` fields (deprecated in 0.6.0 for a one-release window) are removed; schema_version is now 3. Branch on it and read the typed network / nftables maps. The human-readable apply --check output is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| bins | ||
| crates | ||
| docs | ||
| editors | ||
| examples | ||
| .gitattributes | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| justfile | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| nlink-0.19-feedback.md | ||
| nlink-0.19-realignment.md | ||
| nlink-feedback.md | ||
| nlink-upstream-asks.md | ||
| README.md | ||
nlink-lab
Reproducible Linux network labs in 100ms, scriptable from Rust, with deeper TC, nftables, VRF, WireGuard, and macvlan/ipvlan control than any container-based alternative.
network radio {
members [hq, alpha, bravo]
subnet 172.100.3.0/24
impair hq -- alpha { delay 15ms loss 1% }
impair hq -- bravo { delay 40ms loss 5% rate-cap 10mbit }
impair alpha -- bravo { delay 60ms loss 8% }
}
That's a 3-node satellite mesh with distance-dependent impairment. There is no way to express this in a single containerlab or Docker-Compose topology — Docker's bridge network gives every container the same egress queue, with no per-destination netem on a shared L2. nlink-lab uses the kernel's HTB+netem+flower TC primitives directly via netlink. Three lines of NLL.
For the full version — 12 satellites in an Iridium-style ring,
generated by a for loop with modulo arithmetic, deployed in well
under a second — see
the satellite-mesh cookbook recipe.
Quickstart
git clone https://github.com/p13marc/nlink-lab && cd nlink-lab
cargo build --release
sudo ./target/release/nlink-lab deploy examples/simple.nll
sudo ./target/release/nlink-lab exec simple host -- ping -c 3 router
sudo ./target/release/nlink-lab destroy simple
Or use the #[lab_test] macro from a cargo test integration
suite — no Docker daemon, no compose file, just cargo test:
use nlink_lab::lab_test;
#[lab_test("examples/simple.nll")]
async fn test_connectivity(lab: RunningLab) {
let out = lab.exec("host", "ping", &["-c1", "10.0.0.1"]).unwrap();
assert_eq!(out.exit_code, 0);
// Auto-destroy on drop.
}
Why nlink-lab
- Deep Linux networking, no Docker required. TC (per-pair
netem, HTB, flower, u32, matchall), nftables, VRF, WireGuard
(with auto-key generation), macvlan, ipvlan, VXLAN, bonds,
bridges with VLAN filtering — all via netlink directly. Runs in
any CI runner with
CAP_NET_ADMIN+CAP_SYS_ADMIN. No daemon. Sub-second deploy. - A real DSL, not stringly-typed YAML.
forloops,letvariables, parametricimport, glob patterns, IP arithmetic (subnet(),host()), conditional blocks. Errors come with miette source spans. Type-checked at parse time. - Library-first.
use nlink_lab::Topologyand deploy from#[tokio::test]. The CLI is a thin wrapper around the library. - Reconcile, don't redeploy.
nlink-lab applyconverges live state to NLL with zero packet loss for unchanged links.
What it isn't
- A vendor NOS lab tool. If you need cEOS, SR Linux, vMX, vJunos, Palo Alto, or Fortinet images, use containerlab. nlink-lab targets pure-Linux topologies.
- A multi-host orchestrator. Single-host only.
- A GUI. CLI + library only.
How it compares to containerlab
The two tools target different jobs. Use containerlab when you need to run a vendor NOS image, want a web UI, or need multi-host clustering. Use nlink-lab when you need depth in Linux networking primitives, library-first Rust testing, or a CI environment without a Docker daemon.
| containerlab | nlink-lab | |
|---|---|---|
| Vendor NOS support | ✅ 80+ images | ❌ pure Linux only |
| Per-pair netem on shared L2 | ❌ structural | ✅ first-class |
| TC depth (HTB / flower / u32) | ⚠️ raw exec: |
✅ first-class |
| VRF + WireGuard composition | ⚠️ raw exec: |
✅ first-class |
| Multi-host | ✅ | ❌ |
| Web UI | ✅ | ❌ |
| Deploy speed | seconds | sub-second |
| CI footprint | Docker daemon | namespace + caps |
| Library API | Go (undocumented) | Rust (first-class) |
| DSL: loops / imports / arithmetic | ❌ static YAML | ✅ |
A full feature-by-feature comparison — including capability matrix, side-by-side examples, and migration notes — lives at docs/COMPARISON.md.
Documentation
- User guide — install, first lab, CLI tour, container management, CI/CD, packet capture.
- Harness guide — patterns for building Rust test harnesses on top of nlink-lab (spawn ordering, capture endpoint selection, failure-mode debugging, parallel-lab concurrency).
- NLL language spec — every keyword, with examples.
- Testing guide —
#[lab_test]macro and integration test patterns. - Troubleshooting — permission errors, MTU mismatches, namespace cleanup.
- Examples — 40 NLL files covering loops, imports, VRF, WireGuard, VXLAN, containers, scenarios, benchmarks, Wi-Fi.
- Architecture / design — the why and how.
- Architecture for contributors — code map, the 18-step deploy sequence, how to add an NLL feature end-to-end.
- Comparison vs containerlab — capability matrix + side-by-side examples.
- Active plans — what's coming next (docs overhaul, killer examples, full reconcile, lab archives).
- Changelog — per-release notes (Keep-a-Changelog format). The README's
## Statussection pins the current minor.
Status
Beta. NLL syntax and Rust API stable across patch releases;
breaking changes flagged in CHANGELOG with migration notes. Built
on nlink 0.25 as of
nlink-lab 0.7.0.
Current release: 0.7.0 (2026-07-15) — the "Plan 160 / nlink
0.25" arc. WireGuard is fully declarative (device bootstrap via
WireguardConfig::ensure_devices), rate limits reconcile instead
of rebuild (RateLimiter::reconcile), teardown uses the typed
del_*_if_exists helpers, and deploy self-heals a stale namespace
marker. watch shows per-rule nftables counters, and live metrics
gain per-process TCP bandwidth (sockdiag goodput + attribution).
The nlink bump also makes traffic shaping correct (psched-tick
fixes) and installs firewall rules in declared order. apply --check --json is now schema v3 — the deprecated v1 diff /
layered_summary / layered_summary_deprecated fields are
removed; read the typed network / nftables maps. See
CHANGELOG.md for the full per-commit record + migration notes.
Requirements
- Linux kernel 4.19+ (5.x recommended)
- One of: root, SUID install, or
CAP_NET_ADMIN+CAP_SYS_ADMINcapabilities. Some features need extra caps:CAP_DAC_OVERRIDEfor DNS injection,CAP_SYS_MODULEfor Wi-Fi (mac80211_hwsim auto-load). - Rust 1.85+ (edition 2024)
Editor support
NLL files (.nll) have grammars for VS Code, Neovim
(tree-sitter), Helix, and Zed under editors/.
Each editor's directory has its own README with install
instructions.
License
MIT OR Apache-2.0