Commit a580933
tests: VM mode for kernel cells (aircrack-ng/rtl8812au on pinned kernel) (#33)
## What this is
Adds a libvirt-VM execution mode to `tests/regress.py` so the
kernel-side cells of the regression matrix can run against the
`aircrack-ng/rtl8812au` out-of-tree driver on a **pinned kernel**,
instead of fighting the host kernel.
## Why a VM
The OOT `aircrack-ng/rtl8812au` driver lags kernel API changes by 6-12
months (timer_*, cfg80211 callback signatures with MLO link_id, etc.).
On kernel 6.15+ it needs hand-patching to build. morrownr's README flags
that mainline `rtw88_*` is now the recommended path from kernel 6.14
onwards — but **mainline `rtw88_8814au` currently fails to probe**
RTL8814AU on this lab's adapter (`failed to download firmware`, `error
-22`). So for 8814 specifically, OOT aircrack-ng is the only working
kernel-side path.
Pinning a VM to Ubuntu 22.04 LTS (kernel 5.15) gives a stable platform
where aircrack-ng's driver builds and loads cleanly. The host can
upgrade freely without breaking the test rig.
## Pieces
**`tests/setup_vm.sh`** — one-shot VM provisioner. Clones an Ubuntu
22.04 cloud image (`jammy-base.qcow2`), generates a cloud-init seed
(creates `dima` user with caller's SSH key, NOPASSWD sudo, installs
build-essential / dkms / linux-headers / iw / tcpdump / python3-scapy /
aircrack-ng), `virt-install`s with `qemu-xhci` USB controller for
hot-plug, runs `make dkms_install` of `aircrack-ng/rtl8812au` inside via
`runcmd`. ~5-10 min end to end. `--teardown` and `--status` subcommands
included.
**`tests/regress.py` refactor** — introduces a `KernelHost` abstraction
owning every kernel-side operation (`modprobe`, sysfs reads, `iw`,
`tcpdump`, scapy). Local mode = `subprocess.run`. VM mode = `ssh ...
sudo` + `virsh attach-device`/`detach-device` for per-cell USB
passthrough. New CLI flags `--vm-name` / `--vm-ssh` (env:
`DEVOURER_VM_NAME`, `DEVOURER_VM_SSH`). When invoked under `sudo`, picks
up `SUDO_USER`'s SSH key — root usually doesn't have keys provisioned on
the VM.
**Per-cell DUT routing** — each cell calls `_ensure_dut_location` for
each DUT, which (in VM mode) moves the DUT between host and VM via virsh
as needed. State always restored to \"both DUTs on host\" between cells
via try/finally so a crashed cell doesn't poison the next one. Script
start has a `release_all_known_duts` pass for leftover-attached DUTs
from previous aborted runs.
## Validation on trainer-arch
Arch Linux host kernel 6.18, VM Ubuntu 22.04 LTS kernel 5.15, two USB
DUTs in a hub (0bda:8812 RTL8812AU + 0bda:8813 RTL8814AU):
```
## Regression matrix — channel 100, 2026-05-23 13:22:14
- TX adapter: 0bda:8812 (RTL8812AU)
- RX adapter: 0bda:8813 (RTL8814AU)
- Kernel host: VM devourer-testrig via dima@10.216.129.126
- Cell duration: 10s
- Pass threshold: ≥ 3 hits
| | TX = devourer | TX = kernel |
|---|---|---|
| RX = devourer | 0 hits / 4500 TX ✗ | 0 hits / 258 TX ✗ |
| RX = kernel | 4172 hits / 4500 TX ✓ | 229 hits / 259 TX ✓ |
```
- **Baseline ✓** kernel-TX 8812 → kernel-RX 8814 inside VM, **~88%
delivery**
- **devourer-TX validation ✓** devourer-TX 8812 on host → kernel-RX 8814
in VM, **~93% delivery** — confirms devourer's RTL8812AU TX really emits
valid frames at the wire level
- The two failing cells are the pre-existing devourer 8814 RX TODO, not
regressions; cell 3's new \"0 hits / 258 TX\" output correctly fingers
the RX side (TX side really did emit 258 frames; devourer-RX 8814
silent)
For comparison: the same hardware in local mode from #32's first run got
**1 hit** on the devourer-TX→kernel-RX cell because mainline
`rtw88_8814au` couldn't probe the chip. The VM with aircrack-ng gives
**~4000× the signal**.
## Smaller fixes folded in
- TX-count parser surfaces \"Failed to send packet\" failure count
separately from the rate-limited `<devourer-tx>` print count (previously
misleadingly low when sends were failing)
- `--no-baseline-abort` flag for partial-rig diagnostics
- `wait_for_wlan_iface` timeout bumped to 20s (kernel rebinds + VM
passthrough enumeration take 10s+)
- Kernel-TX cells `wait()` for `inject_beacon` to self-terminate instead
of killing the ssh wrapper — captures the final \"sent N frames\" line
(previously TX count showed 0 even though RX side received frames)
## Usage
```bash
sudo tests/setup_vm.sh # ~5-10 min, one-time
sudo tests/setup_vm.sh --status
sudo python3 tests/regress.py --channel 100 \
--vm-name devourer-testrig \
--vm-ssh dima@<VM-IP>
```
See [`tests/README.md`](tests/README.md) for full options, prereqs,
architecture notes.
## Known limitations (documented in README)
- VM mode assumes a single libvirt host running both `virsh` (locally)
and the VM. Pulling the VM onto a different host needs your own `virsh`
wrapper.
- Per matrix run: ~3-4 min in VM mode (USB hot-plug adds ~5s per cell
transition vs ~100s for local mode).
- Two-adapter scope today. >2 needs a pairing loop in `main()`.
- Cell 4 (`devourer-TX → devourer-RX`) needs both DUTs
devourer-claimable simultaneously — if one chipset has broken devourer
RX (current RTL8814AU TODO), that cell shows 0 regardless of TX.
## Test plan
- [x] VM provisioning succeeds end-to-end (`setup_vm.sh` clean run on
trainer-arch)
- [x] aircrack-ng/rtl8812au DKMS install works inside VM (kernel 5.15)
- [x] USB hot-plug of 8814AU into VM works (mainline rtw88 couldn't
probe; aircrack-ng claims cleanly)
- [x] Full 4-cell matrix runs end-to-end in VM mode
- [x] Baseline cell passes (rig sanity)
- [x] devourer-TX → kernel-RX cell passes (cross-driver validation)
- [x] Failing cells produce diagnostic output (TX count vs RX hits)
- [ ] Validate on a different distro / different VM base image
- [ ] Validate with a 2× same-chip DUT setup (both cells with
both-devourer pass)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 63e737d commit a580933
3 files changed
Lines changed: 820 additions & 349 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | | - | |
| 4 | + | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | | - | |
18 | | - | |
| 16 | + | |
| 17 | + | |
19 | 18 | | |
20 | | - | |
| 19 | + | |
21 | 20 | | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
| 21 | + | |
35 | 22 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
40 | 28 | | |
41 | 29 | | |
42 | 30 | | |
43 | 31 | | |
44 | 32 | | |
45 | | - | |
46 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
47 | 42 | | |
48 | 43 | | |
49 | | - | |
50 | | - | |
| 44 | + | |
| 45 | + | |
51 | 46 | | |
52 | 47 | | |
53 | | - | |
54 | | - | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
55 | 94 | | |
56 | 95 | | |
57 | | - | |
| 96 | + | |
58 | 97 | | |
59 | 98 | | |
60 | 99 | | |
61 | | - | |
62 | | - | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
63 | 103 | | |
64 | 104 | | |
65 | 105 | | |
66 | | - | |
67 | | - | |
| 106 | + | |
| 107 | + | |
68 | 108 | | |
69 | 109 | | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
75 | 113 | | |
76 | | - | |
77 | | - | |
| 114 | + | |
| 115 | + | |
78 | 116 | | |
79 | | - | |
| 117 | + | |
80 | 118 | | |
81 | | - | |
82 | | - | |
83 | | - | |
84 | | - | |
85 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
86 | 129 | | |
87 | | - | |
| 130 | + | |
88 | 131 | | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
| 132 | + | |
96 | 133 | | |
97 | | - | |
| 134 | + | |
| 135 | + | |
98 | 136 | | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
104 | 157 | | |
105 | | - | |
106 | | - | |
107 | | - | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
0 commit comments