EHT capture with the A9000¶
Setup on 2026-10-13¶
| Part | Details |
|---|---|
| AP | UniFi U7 Pro Max, SSID linkside on 6 GHz only, WPA3-Personal, MLO off, channel 37 (6135 MHz) at 160 MHz, center1 6185 MHz |
| Client | Stick B on the Fedora PC, mt7925u firmware build 20260813113118. iw dev link: rx EHT-MCS 9 NSS 2, tx EHT-MCS 13 NSS 2, 160 MHz, GI 0.8, signal −46 to −49 dBm |
| Monitor | Stick A in the Pi as capA, kernel 6.18.50, firmware build 20260414153105, tune capA 6135 160 |
| Traffic | TCP download on the client (Hetzner 10 GB file) over Wi-Fi, the PC's Ethernet disconnected |
| Contrast | The Mac (Wi-Fi 6E, HE) on 5 GHz channel 48 at 80 MHz, tune capA 5240 80, with a download on the Mac |
Each capture: 30 s of linkside-cap with wlan addr1 <client> or wlan addr2 <client>; drop=0, utrunc=0, kernel_filter=yes in all of them.
Radiotap from the mt7925 driver¶
- EHT data frames carry the U-SIG and EHT blocks. MCS, NSS, GI and bandwidth are all known; none sits under a not-known field.
- NSS in the EHT user info is zero-based: mt76 writes
status->nss - 1. The raw value 1 is two streams. - HE data frames carry the HE block with MCS, NSS (actual count), bandwidth and GI known.
- Control frames from the AP (Block Ack, RTS) and from the client (Block Ack) come at legacy 24 and 6 Mbps. RTS with bandwidth signaling carries the BSSID with the Individual/Group bit set (
9f:…for9e:…). - Per-chain signal: antennas 0 and 1, plus the combined value.
- No FCS: a frame comes without it, and the radiotap Flags field says so (its FCS bit clear on all 3,350 frames checked). In the recordings of the test data and
~/linkside-capturesup to 2026-10-29, 743 EAPOL frames in the clear and 2,603 deauthentications and disassociations measure to the length their own fields give (the four longer by 18 bytes, broadcast deauthentications, carry BIP's MME element); an encrypted frame is its header, CCMP's 8, the body and the MIC. In Mist's reauthentication of the iPhone over its keys (2026-10-29 10:37) the AP's encrypted EAP-Request/Identity has 17 bytes of payload, its EAP-TLS fragments 1032, its EAP-Success 16, M1 129 and M3 363, and the iPhone's M4 107: each LLC/SNAP (8), the EAPOL header (4) and the body the same frames have in the clear (5, 1020, 4, 117, 351, 95). - Every MPDU carries its own A-MPDU reference, the subframes of one A-MPDU too, and a TSF (
radiotap.mactime) of its own, later than the subframe before it. The reference does not group the MPDUs of a PPDU: 2,702 references for 2,706 EHT QoS data frames ineht-6g-160-full-downlink, 63,760 for 65,298 HT ones intestlab-intermittent-loss(2026-10-29). Wireshark therefore puts its preamble on every HT MPDU: 144 µs for 1,566 bytes at 117 Mbit/s, 107 µs of them at the rate. It is a bug of mt76, not reported upstream: the engine groups the MPDUs by the TSF instead, and item 070 of the plan (kernel 7.x) checks a new driver or firmware for that. VHT data from mt76 is in no recording. A run of data frames at one rate with no other frame between them does group them (HE from the AP45, 2026-10-21, power-save.md), and the TSF tells where the run's PPDUs start: it stamps each MPDU at its end, so within a PPDU it steps by the next MPDU's length at the rate. Of 271 steps between consecutive sequence numbers whose lengths at the rate differ by more than 20 µs ineht-6g-160-full-downlinkit is 0.6 µs above the next MPDU's length (median) and 27 µs below the previous one's, of 5,166 intestlab-intermittent-loss(HT on 2.4 GHz) 4.9 µs below the next and 42 µs below the previous. The lengths here are the 802.11 frame's:frame.lenincludes the radiotap header, 128 bytes on EHT frames, 76 on HE, 64 on HT and 56 on legacy. Between two MPDUs of one transmitter to the other with no frame between them, 90 % of the 2,296 steps ineht-6g-160-full-downlinklie within 1.4 µs below and 1.6 µs above the next MPDU's length, none from 10 to 120 µs above; intestlab-intermittent-lossthe HT stamps scatter more, up to 60 µs above, 6 of 19,776 from 60 to 90 µs. An MPDU the sensor missed inside an A-MPDU lengthens the step by its own length and skips a sequence number: ineht-6g-160-low-capture17 steps one MPDU long with one skipped, 4 two with two and 2 three with three, inu7-phone-search-copies47 one with one. Two MPDUs of one pair with a frame captured between them are two PPDUs: over 30,704 such pairs ineht-6g-160-full-downlink,eht-6g-160-low-capture,testlab-intermittent-loss,testlab-power-save-second-clientandu7-phone-search-copiesthe rule of the engine, 60 µs beyond the next MPDU's length and the missed ones' (definitions), joins none, where 80 µs joined one. With a preamble per PPDU, the downlink airtime ofeht-6g-160-full-downlink(linkside-cli replay) went from 234.8 to 139.0 ms, oftestlab-long-quietfrom 2,993 to 2,302 ms (2026-10-29).
Data frames against acknowledged MPDUs¶
Captured data frames against the MPDUs the Block Acks of the other side report as delivered plus failed. The second EHT capture had the sensor 1 m from the client, farther from the AP. The engine reports the same measure against the delivered MPDUs alone as the capture share (linkside-cli replay, capture_share).
| Capture | Direction | Signal at the sensor | Captured / acknowledged | Rates captured |
|---|---|---|---|---|
eht-6g-160-A |
AP to client | −47 dBm | 17,138 / 112,689 (15 %) | EHT MCS 5–9, 2 streams, 160 and 80 MHz |
eht-6g-160-A |
client to AP | −48 dBm | 401 / 82,422 (0.5 %) | EHT MCS 7–12, 2 streams |
eht-6g-160-A-near |
AP to client | −57 dBm | 3,445 / 107,360 (3 %) | EHT MCS 6–7, 2 streams |
eht-6g-160-A-near |
client to AP | −40 dBm | 27,047 / 85,590 (32 %) | EHT MCS 9–12, 2 streams; no MCS 13 |
he-5g-80-mac |
AP to client | −36 dBm | 42,201 / 45,407 (93 %) | HE MCS 6–11, 2 streams |
he-5g-80-mac |
client to AP | −41 dBm | 3,109 / 3,175 (98 %) | HE MCS 11, 2 streams |
The A9000 decodes EHT data PPDUs at 160 MHz in both directions, with two streams up to MCS 12. Which data frames it gets follows its own SNR: closer to the client, the uplink went from 0.5 % to 32 %; farther from the AP, the downlink went from 15 % to 3 %, and the captured MCS shifted down with the signal. The bulk of the traffic ran at MCS 9 (AP) and MCS 13 (client); at the sensor's position those were mostly not decodable. The AP beamforms its two streams toward the client, which leaves a receiver elsewhere worse off than its RSSI suggests.
The Block Acks were complete in every capture (about 1,000 per second per direction), so BA success covers the traffic the data frames miss.
5 GHz at 80 MHz¶
The SSID moved to 5 GHz only, on the same AP's 5 GHz radio: channel 48 (5240 MHz) at 80 MHz, center1 5210. The client: rx and tx EHT-MCS 13 NSS 2, signal −36 dBm. The Mac on 2.4 GHz with awdl0 down. tune capA 5240 80, the sensor 1 m from the client (eht-5g-80-A).
| Direction | Signal at the sensor | Captured / acknowledged | Rates captured |
|---|---|---|---|
| AP to client | −40 dBm | 114,846 / 126,107 (91 %) | EHT MCS 9–13, 2 streams, mostly MCS 12; 80, 40 and a few 20 MHz |
| client to AP | −37 dBm | 37,116 / 83,015 (45 %) | EHT MCS 13 (87 %) and 11–12, 2 streams |
The sensor decodes MCS 13 (4096-QAM) at this signal. The uplink was SU: 45 trigger frames in 30 s. linkside-cap reported utrunc=3, all in the first second; the count did not grow after that.
Stick B as the monitor¶
The sticks swapped: stick B in the Pi as capB (phy1, USB 2-1, SuperSpeed), stick A as the client on the Fedora PC. NetworkManager kept the same random client address. The client: rx and tx EHT-MCS 13 NSS 2 at 80 MHz, signal −38 dBm.
| Capture | Direction | Signal at the sensor | Captured / acknowledged | Rates captured |
|---|---|---|---|---|
eht-5g-80-B (1 m) |
AP to client | −38 dBm | 129,922 / 136,855 (95 %) | EHT MCS 8–12, 2 streams, 80 MHz |
eht-5g-80-B (1 m) |
client to AP | −33 dBm | 159 / 85,933 (0.2 %) | EHT MCS 12–13 at 40 and 20 MHz only; none at 80 MHz |
eht-6g-160-B (1 m) |
AP to client | −45 dBm | 131,774 / 130,813 (all; the count includes retries) | EHT MCS 4–6, 2 streams, 160 MHz; some 1 stream at MCS 8–11 |
eht-6g-160-B (1 m) |
client to AP | −36 dBm | 1,271 / 86,172 (1.5 %) | EHT MCS 7–11, 2 streams; no MCS 13 |
The uplink was SU (46 and 13 trigger frames in 30 s). On 6 GHz the AP ran at MCS 4–6 this time, within the sensor's SNR, and the sensor got every downlink MPDU at 160 MHz with two streams.
Outcome¶
Both A9000s decode EHT data at 5 GHz/80 MHz and 6 GHz/160 MHz in both directions, with radiotap MCS, NSS, GI and bandwidth known. The share of data frames follows the sensor's SNR against the MCS in use. Uplink at MCS 13 (4096-QAM) from a client 1 m away was mostly missed with both sticks on both bands; on 2026-10-23 stick A caught 63 % of it at 1 m and 54 % at 3 m (below).
Counter-test at 20 MHz¶
tune capA 6135 20 with the same download, the sensor 1 m from the client (eht-6g-20-A): 66,536 control frames and 1 data frame (from the AP, legacy 6 Mbps); no EHT data. BA success still came out: 99.7 % downlink over 30,127 BAs, 98.0 % uplink over 24,461. The EHT data frames above therefore come from decoding at the full width, and BA success does not depend on it.
Uplink against distance, 2026-10-23¶
Stick A in the Pi, stick B the client on AP A's 5 GHz ch 48/80 (iw dev link: rx and tx EHT-MCS 13 NSS 2, signal −39 dBm), the client in place and the Pi moved. 30 s of TCP uplink per distance, 94 Mbit/s: a 100 Mbit/s link between the AP and the iperf3 server kept the A-MPDUs short. The MPDUs the AP's Block Acks acknowledged, each once, against the client's data frames the sensor decoded:
| Sensor from the client | Client at the sensor | Rate of the frames decoded | Uplink captured |
|---|---|---|---|
| 1 m | −42 dBm | EHT MCS 13 86 %, MCS 12 12 %, 2 streams | 63 % (150,380 MPDUs) |
| 3 m | −42 to −46 dBm | MCS 13 83 %, MCS 12 14 % | 54 % (150,010) |
Per second the share went from 48 to 77 % at 1 m and from 10 to 91 % at 3 m. At 1 m the same stick on the same channel caught 45 % on 2026-10-13 (above). 5 m needs the sensor on a battery. The recording is ~/linkside-captures/2026-10-23-uplink-distance, outside the repo.
6 GHz at 160 MHz at 2 Mbit/s, 2026-10-27¶
Item 077's recordings: stick A in the Pi as capA on 6135/160, stick B the client on the Fedora PC at about 1 m, on AP A's linkside (6 GHz ch 37 at 160 MHz; U7 Pro Max firmware 8.7.11), UDP at 2 Mbit/s each way for two minutes. Once on one link, once with MLO (5 GHz ch 48 and 6 GHz ch 37), where the Block Acks show the traffic ran on the 6 GHz link: the same 20,700 Block Acks each way as on one link. The client's iw dev link: tx EHT-MCS 12, 2 streams, 160 MHz.
| Recording | Direction | Signal at the sensor | Block Acks | Captured | Rates captured |
|---|---|---|---|---|---|
| one link | AP to client | −34 dBm (AP) | 20,740 | 7 % | |
| one link | client to AP | 20,278 | 0 % | ||
| MLO | AP to client | −45 dBm | 20,732 | 0 % (35 data frames) | EHT MCS 6, 2 streams, 160 MHz |
| MLO | client to AP | −31 dBm | 20,273 | 0 % (3 data frames) | EHT MCS 9, 2 streams, 160 MHz |
At 6 GHz and 160 MHz the sensor 1 m from the client decoded next to none of the data in either direction, as stick B did as the monitor at 1 m (1.5 %, above); the Block Acks stayed complete and BA success read 100 % both ways. A capture share this low is the expected state at 160 MHz on 6 GHz, not a fault of the setup.
The retry bit and the MCS against the capture share, 2026-10-25¶
The sensor misses the fast frames, so the retry bit and the median MCS of what it decodes describe those frames, not the direction. How far they move with the share captured: CaptureShareMeasureTests drops a direction's data frames before the analyzer, the fastest first (MCS, then the number of streams, ties in a fixed random order), in steps of 5 % of them, as a sensor with a worse SNR would miss them. The Block Acks stay, so BA success holds and the capture share falls. The first step, nothing dropped, is the truth where the recording captured the whole direction.
LINKSIDE_CAPTURE_SHARE=$PWD/../../testdata/sessions/testlab-long-quiet swift test --filter CaptureShareMeasureTests
From which capture share the retry bit left the whole direction's by more than 2 points, and the median MCS fell by one:
| Recording | Direction | Captured | Retry bit | Median MCS | Retry bit 2 points off from | Median one lower from |
|---|---|---|---|---|---|---|
testlab-long-quiet (the phone, AP45) |
AP to client | 99.6 % | 14.6 % | 8 | 93 % (low) | 75 % |
testlab-power-save-second-client |
AP to client | 99.9 % | 21.2 % | 9 | 72 % (high) | 50 % |
testlab-power-save-video |
AP to client | 100 % | 22.6 % | 8 | 65 % (low) | 48 % |
testlab-intermittent-loss (the Mac, 2.4 GHz) |
AP to client | 92.9 % | 20.3 % | 4 | not above 5 % | 62 % |
testlab-intermittent-loss |
client to AP | 99.9 % | 27.6 % | – | 76 % (high) | – |
eht-6g-160-full-downlink |
AP to client | 91.4 % | 2.4 % | 4 | not above 5 % | not above 5 % |
The phone's uplink in the AP45 recordings, with 1 to 2 % retries, stayed within 2 points down to 10 to 23 % captured. The retry bit errs both ways. Where retries go at a lower rate than first tries, what is left reads high (the Mac's uplink, the phone beside a second client); where the frames sent again went at the fast rates, it reads low (the phone in normal use). The median only reads low.
The real low shares sit with the drops. ap45-phone-band-roam read 27.2 % at 63 % captured against Mist's 12.5 %, but the same phone on the same AP read 1.7 to 1.8 times Mist's share with 99.4 to 99.6 % captured (testlab-long-quiet, testlab-client-leaves-eduroam, mist-events.md): of the factor 2.2 at 63 %, the capture share accounts for about a fifth, as the drops give. eht-6g-160-low-capture decoded 3.9 % of the downlink at a median of MCS 6, three below the AP's bulk at MCS 9 (above); the drops give two at 5 %. The drops leave out a fade at the sensor that also hits the client beside it and makes the AP send frames again; the two real points are all that measures it.
From this, on 2026-10-25: the retry bit and the MCS of a direction count from 90 % captured (minimumCaptureShare, 50 % before), 14 points above where the retry bit first read high and 15 above where the median moved; between 76 and 93 % they left the truth one way or the other. The AP's frames sent again by the retry bit (retransmitCaptureShare) stay at 90 %: within 2.1 points from 93 % up, and Mist's count, which takes over below it, read 6.4 points under the retry bit at 99.6 % for the phone. low-mcs (rateCaptureShare) stays at 90 %. No golden verdict changed: the finding that reads the retry bit at the lowest share, congestion in testlab-co-channel, had 96 % of the uplink in its window.