[Netdryrun]
[CI] [OpenSSF Scorecard]
Test network changes before they touch production.
Netdryrun is an open-source (Apache-2.0) tool that dry-runs network changes in a real containerlab lab and returns a pass/fail verdict with evidence. It runs as a CLI, a GitHub Action, and an MCP server for AI agents.
Software teams don't ship code without running its tests. Network changes deserve the same step: run the change in a real lab, check how the network behaves, and ship only on a pass. Netdryrun makes that one command, one CI step, or one tool call for an AI agent.
Already have a containerlab lab? Add Netdryrun to it in 10 minutes: record its steady state, add one guard, run it on every pull request.
Netdryrun™ boots your topology as a real virtual network (containerlab), applies your configs, and checks how the network actually behaves: BGP sessions, prefix counts, routes, reachability. It catches the mistakes your checks describe, such as a route leak, before the change reaches production, and leaves the commands and raw output for a reviewer to inspect.
[Netdryrun catching a leaked default route that ping and session checks miss]
That run caught a leaked default route that was received but not selected. Ping and session checks alone would miss this example: every session is up and every address answers. (A static analyzer such as Batfish also catches this particular leak; the Batfish page shows one that it misses and a lab catches.)
Status: v0.4.0. Runs on Linux and WSL2, with drivers for FRR, Nokia SR Linux, SONiC (docker-sonic-vs), Arista EOS (cEOS, bring your own image) and Juniper cJunosEvolved (beta, bring your own image). On the verdict corpus v1 (30 realistic BGP changes: 19 harmful, 11 safe), Netdryrun judged 24/30 correctly, ping-only checks 20/30 and all-sessions-up checks 15/30. Netdryrun approved 5 of the 19 harmful changes (ping-only checks: 10 of 19) and blocked 1 of the 11 safe ones. On v2 (18 cases written after v1 to target v1's misses): Netdryrun 16 of 17 deterministic cases correct, ping-only 9/17, all-sessions-up 7/17; case 18 is nondeterministic in this lab and excluded from the score (listed with its reruns). Expected verdicts were committed before any run, on one FRR topology: a comparison of testing methods, not a production reliability estimate. Method, every miss and a one-command rerun are on the corpus page. Provenance: both ran on Netdryrun 0.3.1.
Why
- It tests behavior, not syntax. A config diff shows what changed, not whether BGP comes up or which routes each router ends up with. A real control plane shows what the routing software actually does.
- It's built for CI. One GitHub Action, a pass/fail exit code, a Markdown summary in the job, JUnit XML, and a JSON evidence file with every command and raw output.
- It waits for convergence, with stated rules. A positive check passes after 2 passing samples taken 3 s apart, within its timeout. Negative checks ("this route must be absent") and best-path attribute checks wait until every configured BGP session on their node is up with stable prefix counts (sessions a check expects down don't count), then a 10 s settle, then must hold for 10 s (settle and hold are configurable). If a node still reports no BGP neighbors at the timeout, its checks run anyway and the result says BGP convergence was not seen. In a failure scenario, checks start only once the fault is visible in BGP (or the run ends with exit 2), and positive checks must hold too. Exit 0 passed, 1 checks failed, 2 not tested (config or lab problem), 3 inconclusive. The rules are sample-based; see convergence semantics.
Quick start
Try it in your browser (no install)
[Open in GitHub Codespaces]
The codespace comes with Docker, containerlab and Netdryrun already installed. When the terminal shows Ready, run:
A route-leaking change is rejected, then the fix passes every check. It uses your GitHub account's free Codespaces hours (2-core machine). Details.
On your own machine
Requirements: Linux or WSL2, Docker, containerlab, Python 3.10+. On Ubuntu 24.04 and Debian 12 a plain pip install outside a virtualenv is refused ("externally managed environment"), so use pipx: sudo apt install pipx && pipx ensurepath, then open a new terminal. uv tool install netdryrun, or pip install netdryrun inside an activated virtualenv, work too.
On your own containerlab topology (3 commands)
record writes checks for every established BGP session, a prefix-count range for each (today's count up to 20% more, at least 2), guards against a default route or a link subnet arriving via BGP, and up to 50 BGP routes per node. A session that is down today isn't recorded.
Advisory (0.3.3 and 0.4.0): that range lets a leak of one or two extra prefixes from a neighbor pass. Until 0.4.1, record with --exact-prefixes and add present: false checks for prefixes that must never arrive. Advisory. Review it (remove what is allowed to change, add negative checks such as present: false), commit it, and every future change runs against it, locally or in CI. A pass means those checks passed, nothing more.
netdryrun.yml
Failure scenarios break something, check the network still behaves, then restore it. They can take links down, or (from 0.3.1) make an FRR neighbor announce routes it shouldn't, to prove a max-prefix limit or an import filter actually holds:
Every check also accepts name, timeout and vrf. Ping accepts expect: failure and count. Drivers are detected from the topology; override them with nodes: {r1: {driver: frr}}. Nodes that are configured after boot (SONiC) take setup commands: nodes: {sonic1: {setup: [...]}}.
Commands
Reports: --json FILE (full evidence), --markdown FILE, --junit FILE. Under GitHub Actions, a summary is appended to the job page automatically. Exit codes: 0 passed, 1 a check failed, 2 no verdict (config or lab error, a fault that did not take effect, or an internal error; add --debug for the traceback), 3 inconclusive (the lab never reached a testable state, so the run says nothing about the change), 130/143/129 interrupted by SIGINT/SIGTERM/SIGHUP after cleaning up the lab.
GitHub Action
The action installs containerlab, runs the checks, uploads the JSON and JUnit reports as an artifact, and fails the job if any check fails. Outputs: status, passed, failed, inconclusive-reason.
For AI agents (MCP)
Listed in the official MCP Registry (io.github.netdryrun/netdryrun) and on Glama; the agent skill is also on Smithery.
AI can propose network changes. Netdryrun verifies the outcome. Agents: read AGENTS.md.
Coding agents don't go looking for a verifier: in our own test they never searched for one. Put Netdryrun in their path in two minutes, as a pull-request check, an AGENTS.md rule or an MCP server: Add it where your agent works.
AI agents are starting to change networks. Netdryrun gives them a safety gate: an MCP server that lets an agent dry-run its change in a real lab and proceed only if every check passes.
Add it to your MCP client (for example Claude Code):
Tools: dry_run (deploy, check, destroy, verdict), run_checks (against a running lab), record_baseline (snapshot a known-good lab into checks), validate, check_types. Each result carries a separate verdict field: CHECKS PASSED (only what the checks cover), DO NOT APPLY THIS CHANGE, or INCONCLUSIVE: the lab did not reach a testable state; ... when the lab never came up. CHECKS PASSED (only what the checks cover) means only that every check in the file passed in the lab, so a pass never says "safe". A change can break something no check looks at and still get it: in our agent evaluation, an agent's "pure refactor" added redistribute connected and passed, because no check covered the extra routes. For a change that must not change behavior, record a baseline first. From 0.3.2 the verdict strings are frozen (0.3.1 said SAFE TO PROCEED for a pass; decide on status, not the text), a failed result lists each failed check with its reason, an evidence excerpt and one read-only next diagnostic command, and the result format is a published JSON Schema: mcp-result-v1.json.
Demo (examples/agent-demo): the ticket says the customer's new block must reach the upstream and the peer. The tempting change passes every ticket check, session and ping, but leaks routes; Netdryrun rejects it. The two-line fix passes the same checks. (A scripted replay of the two attempts; CI runs it.)
[Attempt 1 passes the ticket checks but fails five leak guards; attempt 2 passes all 14 checks]
The agent skill
skills/netdryrun/SKILL.md teaches a coding agent the rules: dry-run a routing change before applying it, paste the verdict, stop on inconclusive, never write or weaken checks to make a change pass, and use record_baseline when there are no checks. Install it from this repository's git URL (from 0.3.2):
To give it to everyone working in one repository, copy the folder to .claude/skills/netdryrun/ there and commit it. Other clients that read Agent Skills (a folder with a SKILL.md) take the same folder. The skill expects the MCP server above; without it, the agent falls back to the CLI.
Safety model:
- The server never touches production. It has no tool that pushes config anywhere; it only builds and tests labs.
- Tool results contain normalized check results, not full device output. A check's
detail can still quote a fragment of lab text (a banner, a route description), and from 0.3.2 each failed check carries a short evidence excerpt; each is kept to one line and capped in length, and none of them changes status or verdict. Raw evidence goes to the JSON report file.
- A lab is code: containerlab runs privileged containers. Only point the server at topologies you trust, on a machine you'd let run Docker.
How it compares
Netdryrun complements these tools; it doesn't replace them.
What Netdryrun adds: positive checks wait for convergence, negative checks run only after convergence and must hold, received-but-not-selected routes count as present, and every run produces one pass/fail plus an evidence file.
Limits
- Lab routers are not your production routers. A pass on FRR, SR Linux, SONiC VS or Arista cEOS is strong evidence for protocol logic and policy intent, but not proof of hardware, scale or version-specific behavior. For Arista, bring your own licensed cEOS image to test real vendor code.
- Checks cover IPv4/IPv6 unicast BGP (sessions, prefix counts, best-path attributes), routes and ping, with link-down, node-isolation and route-injection (FRR only) failure scenarios. Route checks work for routes from any protocol today (
protocol: ospf, isis, static, connected, ...); OSPF/IS-IS adjacency checks and path/traceroute are coming.
- No Cisco or Juniper driver in a release yet. Their lab images need a vendor account and can't be redistributed, so their drivers will be bring-your-own-image, like cEOS; Juniper and Cisco NX-OS are next on the roadmap.
How results are checked and released, with evidence for each claim: How it's built.
Roadmap
Direction, not a commitment: a version ships when its release gate is green, never on a date.
- 0.4.1 (current): the fix for GHSA-cg9f-xjw5-38ph, Cisco NX-OS and MikroTik betas, supply-chain hardening
- Later: a router-state contract across drivers, Cumulus beta, check strength and signed verdict receipts, hosted verification beta, then OSPF/IS-IS, paths, EVPN and MPLS by demand
Full roadmap: netdryrun.com/roadmap.
If Netdryrun caught something for you, a ⭐ helps other engineers and agents find it.
License
Apache-2.0. "Netdryrun" and the Netdryrun logo are trademarks; see TRADEMARKS.md.