Skip to content

Commit 57c1925

Browse files
Frandoclaude
andcommitted
docs: ctor test pattern, VM guide, reorder NAT64, rework holepunching
- Add ctor paragraph for test namespace init in getting-started - Reorder IPv6 NAT table to put NAT64 first after None - Dial down motivation criticism, add performance testing use case - Add Running in a VM guide page covering patchbay-vm - Rework holepunching reference with better prose, mark as advanced - Update SUMMARY with VM page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4810393 commit 57c1925

File tree

6 files changed

+310
-154
lines changed

6 files changed

+310
-154
lines changed

docs/SUMMARY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
- [Building Topologies](guide/topology.md)
1010
- [NAT and Firewalls](guide/nat-and-firewalls.md)
1111
- [Running Code in Namespaces](guide/running-code.md)
12+
- [Running in a VM](guide/vm.md)
1213

1314
# Reference
1415

1516
- [IPv6 Deployments](reference/ipv6.md)
1617
- [Network Patterns](reference/patterns.md)
17-
- [NAT Hole-Punching](reference/holepunching.md)
18+
- [NAT Hole-Punching (Advanced)](reference/holepunching.md)
1819
- [TOML Simulation Reference](reference/toml-reference.md)

docs/guide/getting-started.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,28 @@ async fn async_main() -> anyhow::Result<()> {
7070
If you skip this call, `Lab::new()` will fail because the process lacks
7171
the network namespace capabilities it needs.
7272

73+
In integration tests, you can avoid the `main` / `async_main` split by
74+
using a `#[ctor]` initializer that runs before any test thread is spawned:
75+
76+
```rust
77+
#[cfg(test)]
78+
#[ctor::ctor]
79+
fn init() {
80+
patchbay::init_userns().expect("failed to enter user namespace");
81+
}
82+
83+
#[tokio::test]
84+
async fn my_test() -> anyhow::Result<()> {
85+
let lab = patchbay::Lab::new().await?;
86+
// ...
87+
Ok(())
88+
}
89+
```
90+
91+
The `ctor` crate runs the function at load time, before `main` or the
92+
test harness starts. This keeps your test functions clean and avoids
93+
repeating the namespace setup in every binary.
94+
7395
## Creating a lab
7496

7597
A `Lab` is the top-level container for a topology. When you create one, it

docs/guide/motivation.md

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ WiFi-to-cellular handoff without dropping state. Those questions require
1010
actual network stacks with actual packet processing, and the only way most
1111
teams answer them today is by deploying to staging and hoping for the best.
1212

13-
Some projects work around this with Docker Compose topologies, Mininet
14-
graphs, or bespoke iptables scripts. These approaches share a few
15-
drawbacks: they require root, they leave state behind when something
16-
crashes, they are difficult to parameterize from a test harness, and they
17-
tend to drift from the real-world conditions they are meant to represent.
13+
Tools like Docker Compose, Mininet, and custom iptables scripts can help,
14+
but each comes with trade-offs around privilege requirements, cleanup
15+
reliability, and how easily you can parameterize topologies from a test
16+
harness. patchbay was built to make this kind of testing ergonomic for Rust
17+
projects: no root, no cleanup, and a builder API that fits naturally into
18+
`#[tokio::test]` functions.
1819

1920
## What patchbay does
2021

@@ -34,34 +35,41 @@ automatically.
3435

3536
## Where it fits
3637

37-
patchbay is a testing and development tool. It is designed for two primary
38+
patchbay is a testing and development tool, designed for three primary
3839
use cases:
3940

4041
**Integration tests.** Write `#[tokio::test]` functions that build a
4142
topology, run your networking code inside it, and assert on outcomes. Each
4243
test gets an isolated lab with its own address space, so tests can run in
4344
parallel without interfering with each other or with the host.
4445

46+
**Performance and regression testing.** Apply link conditions to simulate
47+
constrained networks (3G, satellite, lossy WiFi) and measure throughput,
48+
latency, or reconnection time under controlled impairment. Because tc
49+
netem operates at the kernel level, the shaping is realistic enough for
50+
comparative benchmarks, though absolute numbers will differ from hardware
51+
links due to scheduling overhead and the absence of real radio or cable
52+
physics.
53+
4554
**Interactive experimentation.** Build a topology in a binary or script,
4655
attach to device namespaces with shell commands, and observe how traffic
4756
flows. This is useful for understanding NAT behavior, debugging
4857
connectivity issues, or validating protocol assumptions before writing
4958
tests.
5059

51-
patchbay is not a production networking tool, a container orchestrator, or
52-
a replacement for network simulation frameworks like ns-3. It operates at
53-
the kernel namespace level with real TCP/IP stacks, not at the packet
54-
simulation level. This means the fidelity is high (you are testing against
55-
real Linux networking), but the scale is limited to what a single machine
56-
can support (typically dozens of namespaces, not thousands).
60+
patchbay operates at the kernel namespace level with real TCP/IP stacks,
61+
not at the packet simulation level. This means the fidelity is high (you
62+
are testing against real Linux networking), but the scale is limited to
63+
what a single machine can support (typically dozens of namespaces, not
64+
thousands).
5765

5866
## Scope of this book
5967

6068
The **Guide** section walks through patchbay's concepts in order: setting
61-
up a lab, building topologies, configuring NAT and firewalls, and running
62-
code inside namespaces. Each chapter builds on the previous one and
63-
includes runnable examples.
69+
up a lab, building topologies, configuring NAT and firewalls, running code
70+
inside namespaces, and running labs in a VM on non-Linux hosts. Each
71+
chapter builds on the previous one and includes runnable examples.
6472

6573
The **Reference** section covers specialized topics in depth: real-world
66-
IPv6 deployment patterns, NAT traversal and hole-punching mechanics,
67-
network event simulation recipes, and the TOML simulation file format.
74+
IPv6 deployment patterns, network event simulation recipes, NAT traversal
75+
and hole-punching internals, and the TOML simulation file format.

docs/guide/nat-and-firewalls.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ let router = lab.add_router("r")
9797
| Mode | Description |
9898
|------|-------------|
9999
| `None` | No IPv6 NAT. Devices get globally routable addresses. This is the default and the most common real-world configuration. |
100+
| `Nat64` | Stateless IP/ICMP Translation (RFC 6145). Allows IPv6-only devices to reach IPv4 hosts through the well-known prefix `64:ff9b::/96`. The most important v6 NAT mode in practice; used by major mobile carriers. |
100101
| `Nptv6` | Network Prefix Translation (RFC 6296). Performs stateless 1:1 prefix mapping at the border, preserving end-to-end connectivity while hiding internal prefixes. |
101102
| `Masquerade` | IPv6 masquerade, analogous to IPv4 NAPT. Rare in production but useful for testing applications that must handle v6 address rewriting. |
102-
| `Nat64` | Stateless IP/ICMP Translation (RFC 6145). Allows IPv6-only devices to reach IPv4 hosts through the well-known prefix `64:ff9b::/96`. |
103103

104104
### NAT64
105105

docs/guide/vm.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Running in a VM
2+
3+
patchbay requires Linux network namespaces, which means it cannot run
4+
natively on macOS or Windows. The `patchbay-vm` crate solves this by
5+
wrapping your simulations and tests in a QEMU Linux VM, giving you the
6+
same experience on any development machine.
7+
8+
## Installing patchbay-vm
9+
10+
```bash
11+
cargo install --git https://github.com/n0-computer/patchbay patchbay-vm
12+
```
13+
14+
## Running simulations
15+
16+
The `run` command boots a VM (or reuses a running one), stages the
17+
simulation files and binaries, and executes them inside the guest:
18+
19+
```bash
20+
patchbay-vm run ./sims/iperf-baseline.toml
21+
```
22+
23+
Results and logs are written to the work directory (`.patchbay-work/` by
24+
default). You can pass multiple simulation files, and they run
25+
sequentially in the same VM.
26+
27+
### Controlling the patchbay version
28+
29+
By default, `patchbay-vm` downloads the latest release of the patchbay
30+
runner binary. You can pin a version, build from a Git ref, or point to a
31+
local binary:
32+
33+
```bash
34+
patchbay-vm run sim.toml --patchbay-version v0.10.0
35+
patchbay-vm run sim.toml --patchbay-version git:main
36+
patchbay-vm run sim.toml --patchbay-version path:/usr/local/bin/patchbay
37+
```
38+
39+
### Binary overrides
40+
41+
If your simulation references custom binaries (test servers, protocol
42+
implementations), you can stage them into the VM:
43+
44+
```bash
45+
patchbay-vm run sim.toml --binary myserver:path:./target/release/myserver
46+
```
47+
48+
The binary is copied into the guest's work directory and made available
49+
at the path the simulation expects.
50+
51+
## Running tests
52+
53+
The `test` command cross-compiles your Rust tests for musl, stages the
54+
test binaries in the VM, and runs them:
55+
56+
```bash
57+
patchbay-vm test
58+
patchbay-vm test --package patchbay
59+
patchbay-vm test -- --test-threads=4
60+
```
61+
62+
This is the recommended way to run patchbay integration tests on macOS.
63+
The VM has all required tools pre-installed (nftables, iproute2, iperf3)
64+
and unprivileged user namespaces enabled.
65+
66+
## VM lifecycle
67+
68+
The VM boots on first use and stays running between commands. Subsequent
69+
`run` or `test` calls reuse the existing VM, which avoids the 30-60
70+
second boot time on repeated invocations.
71+
72+
```bash
73+
patchbay-vm up # Boot the VM (or verify it is running)
74+
patchbay-vm status # Show VM state, SSH port, mount paths
75+
patchbay-vm down # Shut down the VM
76+
patchbay-vm cleanup # Remove stale sockets and PID files
77+
```
78+
79+
You can also SSH into the guest directly for debugging:
80+
81+
```bash
82+
patchbay-vm ssh -- ip netns list
83+
patchbay-vm ssh -- nft list ruleset
84+
```
85+
86+
## How it works
87+
88+
`patchbay-vm` downloads a Debian cloud image (cached in
89+
`~/.local/share/patchbay/qemu-images/`), creates a COW disk backed by
90+
it, and boots QEMU with cloud-init for initial provisioning. The guest
91+
gets SSH access via a host-forwarded port (default 2222) and three shared
92+
mount points:
93+
94+
| Guest path | Host path | Access | Purpose |
95+
|------------|-----------|--------|---------|
96+
| `/app` | Workspace root | Read-only | Source code and simulation files |
97+
| `/target` | Cargo target dir | Read-only | Build artifacts |
98+
| `/work` | Work directory | Read-write | Simulation output and logs |
99+
100+
File sharing uses virtiofs when available (faster, requires virtiofsd on
101+
the host) and falls back to 9p. Hardware acceleration is auto-detected:
102+
KVM on Linux, HVF on macOS, TCG emulation as a last resort.
103+
104+
## Configuration
105+
106+
All settings have sensible defaults. Override them through environment
107+
variables when needed:
108+
109+
| Variable | Default | Description |
110+
|----------|---------|-------------|
111+
| `QEMU_VM_MEM_MB` | 4096 | Guest RAM in megabytes |
112+
| `QEMU_VM_CPUS` | 4 | Guest CPU count |
113+
| `QEMU_VM_SSH_PORT` | 2222 | Host port forwarded to guest SSH |
114+
| `QEMU_VM_NAME` | patchbay-vm | VM instance name |
115+
| `QEMU_VM_DISK_GB` | 40 | Disk size in gigabytes |
116+
117+
VM state lives in `.qemu-vm/<name>/` in your project directory. The disk
118+
image uses COW backing, so it only consumes space for blocks that differ
119+
from the base image.

0 commit comments

Comments
 (0)