Skip to content

tests: fix radiotap parser + per-cell pcap collision in --encoding-matrix#46

Merged
josephnef merged 1 commit into
masterfrom
fix/sniff-air-radiotap-parser
May 26, 2026
Merged

tests: fix radiotap parser + per-cell pcap collision in --encoding-matrix#46
josephnef merged 1 commit into
masterfrom
fix/sniff-air-radiotap-parser

Conversation

@josephnef
Copy link
Copy Markdown
Collaborator

Two bugs in the sniffer integration from #40 / #41 that together hid the real finding about what kernel TX actually emits on-air. Fixing them surfaces a definitive answer to the open LDPC question.

Bug 1: _parse_radiotap returned None on every real-world frame

The parser only iterated it_present[0] and bailed when it hit a bit not in _RT_FIELDS. Every captured frame from ath9k_htc has presence 0xa000402f with bits [0,1,2,3,5,14,29,31] — bits 29 (RADIOTAP_NS marker) and 31 (EXT — continuation word) are control bits, not data fields. The parser hit bit 29 on every frame → 408 parse-errors / 408 frames on the first AR9271 sniffer run.

Fix: iterate bits across ALL presence words; recognise the three control bits (29 RADIOTAP_NS, 30 VENDOR_NS, 31 EXT) and skip them with the right data consumption (6 bytes for VENDOR_NS, none for the others); when hitting an unknown radiotap field, return the parsed-so-far dict rather than discarding the whole frame.

Round-trip against inject_beacon.build_beacon for every (HT, VHT) × (BCC, LDPC, STBC) combo: byte-identical decoded fields. Real AR9271-captured beacon: parses cleanly as kind=legacy.

Bug 2: cell pcap filename collision in --encoding-matrix

cell_id = f"tx-{tx_side}_rx-{rx_side}" doesn't include the encoding label, so six encoding cells per driver-mode wrote to the same /tmp/devourer-regress-*/tx-{tx}_rx-{rx}.sniffer.pcap. The last cell overwrote the first five — so --keep-logs retained only the LAST encoding combo per mode (typically VHT-LDPC, where AR9271 captures 0 frames since it's n-only). Made post-hoc debugging impossible.

Fix: optional cell_tag parameter on run_cell, set by run_encoding_matrix to the sanitised encoding label (ht-bcc, ht-ldpc, vht-ldpcstbc, …). Other matrix modes (run_matrix, run_full_matrix) leave it empty — they only run one cell per (tx_side, rx_side) pair.

What this surfaces

Encoding matrix re-run on the rig (8814 TX → 8821 RX, ch6, AR9271 sniffer):

Mode Encoding requested Sniffer decoded
k/k HT-BCC HT MCS1 BCC 20MHz STBC=0 (412)
k/k HT-LDPC HT MCS1 **BCC** 20MHz STBC=0 (386)
k/k HT-STBC=1 HT MCS1 BCC 20MHz **STBC=0** (418)
k/k HT-LDPC+STBC=1 HT MCS1 **BCC** **STBC=0** (422)
k/k VHT-BCC 0 frames (AR9271 is n-only)
k/k VHT-LDPC 0 frames

Same in the k/d row.

aircrack-ng/88XXau (or mac80211 in its TX path) strips the radiotap LDPC bit and STBC stream count. MCS index (1) and the HT-vs-VHT distinction DO survive. So every "LDPC" kernel-TX cell in #40 and #41 was actually emitting BCC on-air — the flat k/d row in those PRs' results never disproved @RomanLut's 8821AU LDPC-RX-no claim, it just meant we never tested the chip with an actual LDPC frame.

VHT cells show 0 to AR9271 (n-only) but >0 to the 8821 RX (AC chip), so the HT/VHT distinction IS honoured. We still can't see whether mac80211 strips the VHT-LDPC bit specifically — would need an AC-capable sniffer (or to capture on the 8821 RX itself in monitor mode).

Implications

  • The --encoding-matrix mode is most useful for chip-side asymmetries reachable through MCS-index and HT-vs-VHT; it can't validate LDPC- or STBC-specific RX behaviour through the kernel TX path as currently wired.
  • A proper LDPC-RX validation needs a userspace TX path that writes the radiotap directly to the chip — devourer's txdemo does this (DEVOURER_TX_LDPC=1 env var is ground-truth). 8814 TX being broken on master is the blocker for using this from the d/k cells.
  • The AR9271 sniffer integration is functional; the data it produces is now reliable. Future hotplug / encoding investigations can rely on it.

Test plan

  • Parser round-trips every encoding combo inject_beacon.build_beacon emits
  • Parser handles real AR9271 capture (multi-word presence, bits 29/31 set)
  • --encoding-matrix produces distinct pcap-per-cell with --keep-logs
  • Re-ran end-to-end on the rig — all 24 cells emit reliable sniffer summaries
  • CI builds — no C++ changes here, Python-only

🤖 Generated with Claude Code

…TX flag stripping

Two bugs in the sniffer integration from #40/#41 that together hid the
real finding about what kernel TX actually puts on-air. Both fixed
here, and the resulting AR9271 capture conclusively answers the open
question about LDPC asymmetry that has been hanging since #40.

## Bug 1: `_parse_radiotap` returned None on every real-world frame

The parser only iterated `it_present[0]` and bailed on bits not in
`_RT_FIELDS`. Every captured frame from `ath9k_htc` has presence
0xa000402f with bits [0,1,2,3,5,14,29,31]: bit 29 (RADIOTAP_NS marker)
and bit 31 (EXT — continuation word) are control bits, not data
fields. The parser hit bit 29 and bailed → 408 parse-errors for every
captured frame in the first AR9271 sniffer matrix run.

Fix: iterate bits across ALL presence words; recognise the three
control bits (29 RADIOTAP_NS, 30 VENDOR_NS, 31 EXT) and skip them
correctly (with 6-byte data consumption for VENDOR_NS); when hitting
an unknown radiotap field, return what was parsed so far rather than
discarding the whole frame.

Round-trip against `inject_beacon.build_beacon` for every (HT, VHT) ×
(BCC, LDPC, STBC) combo: byte-identical decoded fields ✓. Real
AR9271-captured beacon: now parses cleanly as `kind=legacy` (real APs
emit DSSS/OFDM, no MCS info).

## Bug 2: cell pcap filename collision in --encoding-matrix

`cell_id = f"tx-{tx_side}_rx-{rx_side}"` doesn't include the encoding
label, so six encoding cells per driver-mode wrote to the same
`/tmp/devourer-regress-*/tx-{tx}_rx-{rx}.sniffer.pcap`. The last cell
overwrote the first five — so `--keep-logs` retained only the LAST
encoding combo per mode (typically VHT-LDPC, where the AR9271 captures
0 frames since it's n-only). Made post-hoc debugging impossible.

Fix: optional `cell_tag` parameter on `run_cell`, set by
`run_encoding_matrix` to the sanitised encoding label
(`ht-bcc / ht-ldpc / vht-ldpcstbc / ...`). Other matrix modes leave it
empty (they only run one cell per driver-mode pair).

## What this surfaces

Encoding matrix re-run on the rig (8814 TX → 8821 RX, ch6, AR9271 as
sniffer):

| Mode | Encoding requested | Sniffer decoded                   |
|------|--------------------|-----------------------------------|
| k/k  | HT-BCC             | HT MCS1 BCC 20MHz STBC=0 (412)    |
| k/k  | HT-LDPC            | HT MCS1 **BCC** 20MHz STBC=0 (386)|
| k/k  | HT-STBC=1          | HT MCS1 BCC 20MHz **STBC=0** (418)|
| k/k  | HT-LDPC+STBC=1     | HT MCS1 **BCC** **STBC=0** (422)  |
| k/k  | VHT-BCC            | 0 frames (AR9271 is n-only)       |
| k/k  | VHT-LDPC           | 0 frames                          |

Same pattern in the k/d row. Definitive finding:

  aircrack-ng/88XXau (or mac80211 in its TX path) STRIPS the
  radiotap LDPC bit and STBC stream count. MCS index (1) and the
  HT-vs-VHT distinction DO survive. So every "LDPC" kernel-TX cell
  in #40 and #41 was actually emitting BCC on-air — the flat k/d
  row never disproved Roman's 8821AU LDPC-RX-no claim, it just
  meant we never tested the chip with an actual LDPC frame.

VHT cells show 0 to AR9271 (n-only) but >0 to the 8821 RX (AC chip),
confirming the HT/VHT distinction is honoured at the TX path. We
still can't see whether mac80211 strips the VHT-LDPC bit
specifically — would need an AC-capable sniffer, or capture from
8821 RX itself in monitor mode.

## Implications

- The encoding-matrix mode is most useful for chip-side encoding
  asymmetries reachable through MCS-index and HT/VHT only; it can't
  validate LDPC- or STBC-specific RX behaviour through the kernel TX
  path as currently wired.
- A proper LDPC-RX validation needs a userspace TX path that writes
  the radiotap directly to the chip (devourer's txdemo does this —
  ground-truth verified by `DEVOURER_TX_LDPC=1` env var). 8814 TX
  being broken on master is the blocker for using this from the d/k
  cells.
- The AR9271 sniffer integration is functional; the data it produces
  is now reliable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@josephnef josephnef merged commit 8de09ed into master May 26, 2026
5 checks passed
@josephnef josephnef deleted the fix/sniff-air-radiotap-parser branch May 26, 2026 06:25
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