Skip to content

tests: --sniffer-iface integrates sniff_air into per-cell matrix flow#41

Merged
josephnef merged 1 commit into
masterfrom
feat/regress-sniffer-cell
May 25, 2026
Merged

tests: --sniffer-iface integrates sniff_air into per-cell matrix flow#41
josephnef merged 1 commit into
masterfrom
feat/regress-sniffer-cell

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Closes the last of #39's three follow-ups: wires the radiotap verifier from #40 (tests/sniff_air.py) into tests/regress.py so it runs alongside every cell. Optional and opt-in — --sniffer-iface defaults to off, all prior modes unchanged.

What this is

sudo python3 tests/regress.py --encoding-matrix \
    --tx-pid 0x8813 --rx-pid 0x0120 --channel 100 \
    --vm-name devourer-testrig --vm-ssh <user>@<VM-IP> \
    --sniffer-iface wlan0mon

Per-cell output gains a ↪ sniffer: N frames — <encoding>=N, ... line under the existing hit-count line. Reports what actually flew on-air for each cell — closes the open question from #40 ("did the kernel-TX path actually emit LDPC, or strip the flag?").

Intended for an AR9271: vanilla radiotap, no driver-side filtering on what the cell injects. The chipset is widely used as a sniffer for exactly this reason.

Implementation

Component Behaviour
_spawn_sniffer(iface, channel, pcap_path) Sets iface to monitor mode, runs tcpdump -w pcap -U -nn 'ether src CANONICAL_SA'. Always host-local; sniffer never moved into the VM via USB passthrough.
_summarise_sniffer_pcap(pcap_path) Imports sniff_air at runtime (sits next to regress.py), reuses _read_pcap_frames + _parse_radiotap + _frame_sa + CANONICAL_SA to bucket captured frames. Returns a one-line summary for the cell's notes field.
run_cell(sniffer_iface=...) Spawns sniffer between RX and TX stages so the full TX window gets captured. Sniffer failures are observational — never fail the cell on sniffer issues.
run_matrix / run_full_matrix / run_encoding_matrix Pass sniffer_iface through to run_cell. When active + r.notes is set, print an extra ↪ <notes> line per cell.
--sniffer-iface IFACE New CLI flag + DEVOURER_SNIFFER_IFACE env equivalent.

Validation

The dormant path (sniffer_iface=None) preserves the exact prior behaviour of every matrix mode — only structural change in run_cell is initializing sniffer_proc=None and a no-op cleanup if it stays None.

Active-path validation requires an AR9271 plugged in, which isn't in the rig today. CI matrix builds will confirm the code paths compile / import. Functional end-to-end pending hardware.

The sniffer parser itself was unit-tested in #40: every combo inject_beacon.build_beacon emits round-trips back through sniff_air._parse_radiotap with byte-identical decoded fields.

What this PR doesn't touch

  • The markdown table emit functions are unchanged — sniffer notes go to per-cell stdout, not into the table. Could be added as a separate column in a future PR if interesting.
  • No new combos; ENCODING_COMBOS unchanged from tests: VHT encoding combos + sniff_air.py radiotap verifier #40.
  • AR9271-specific bring-up (driver loading, monitor capability detection). The flag takes a generic iface name; the user is expected to have a working monitor iface before pointing the matrix at it.

Test plan

  • --help lists --sniffer-iface
  • Code parses, imports successfully
  • CI matrix builds — pending on this push
  • --sniffer-iface IFACE with an AR9271 plugged in: verify per-cell ↪ sniffer: lines appear and decode reasonably
  • Confirm --sniffer-iface running alongside --encoding-matrix shows different encoding distributions for --ldpc vs default cells (the actual goal — proves whether mac80211 / 88XXau emits the LDPC bit on-air)

🤖 Generated with Claude Code

Wires the radiotap verifier from #40 (tests/sniff_air.py) into
regress.py so it runs alongside every cell in default / --full-matrix /
--encoding-matrix modes. Optional and opt-in — defaults preserved.

## Usage

    sudo python3 tests/regress.py --encoding-matrix \
        --tx-pid 0x8813 --rx-pid 0x0120 --channel 100 \
        --vm-name devourer-testrig --vm-ssh <user>@<VM-IP> \
        --sniffer-iface wlan0mon

Per-cell output gains a `↪ sniffer: N frames — <encoding>=N, ...` line
under the existing hit-count line. Reports what actually flew on-air,
which the matrix itself can't see — closes the open question from #40
("did the kernel-TX path actually emit LDPC, or strip the flag?").

Intended for an AR9271. The chipset speaks vanilla radiotap without
driver-side flag filtering, which makes it a reliable on-air observer
of what other adapters emit.

## Implementation

- `_spawn_sniffer(iface, channel, pcap_path)`: sets iface to monitor
  mode on `channel`, runs `tcpdump -w pcap -U -nn 'ether src
  CANONICAL_SA'`. Always host-local; sniffer adapters don't move into
  the VM via virsh USB passthrough.
- `_summarise_sniffer_pcap(pcap_path)`: imports sniff_air at runtime
  (next to regress.py), reuses `_read_pcap_frames` + `_parse_radiotap`
  + `_frame_sa` + `CANONICAL_SA` to bucket captured frames by
  encoding kind / MCS / LDPC / STBC / BW. Returns a one-line summary
  string suitable for the cell's `notes` field.
- `run_cell(sniffer_iface=...)`: spawns sniffer between RX and TX
  stages so the full TX window gets captured. Sniffer failures are
  observational — never fail the cell on sniffer issues.
- `run_matrix`, `run_full_matrix`, `run_encoding_matrix`: pass
  `sniffer_iface` through to `run_cell`. When the sniffer is active
  and `r.notes` is set, print an extra `↪ <notes>` line per cell.
- `--sniffer-iface IFACE` CLI flag + `DEVOURER_SNIFFER_IFACE` env
  equivalent in `main()`.

## Validation

Code path is dormant when `--sniffer-iface` is not set — defaults
preserved, prior runs unaffected. Hardware validation requires an
AR9271 plugged in and is left as the next-up follow-up; not blocking
because the dormant path is verified by all existing CI / matrix runs
on master (which the immediately preceding PR #40 validated end-to-end).

The sniffer parser itself was unit-tested in #40: every combo
`inject_beacon.build_beacon` emits round-trips back through
`sniff_air._parse_radiotap` with byte-identical decoded fields.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@josephnef josephnef merged commit 8c87cd5 into master May 25, 2026
5 checks passed
@josephnef josephnef deleted the feat/regress-sniffer-cell branch May 25, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant