Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c2b6778
sta: StationSm::link_lost for a caller's liveness check (library)
snokvist Oct 3, 2026
3b1f870
mt7612u: the station arm owns the receive filter (managed 0x00015f97)
snokvist Oct 3, 2026
7553dc5
tests: generic adapter record / hand-back in the station lib
snokvist Oct 3, 2026
6299728
tests: station on-air cell for Realtek DUTs, a reconnect cell, ping w…
snokvist Oct 3, 2026
a8b658e
tests: sta_client's ledger reports the state the run ended in
snokvist Oct 3, 2026
2b2cf0f
tests: sta_client_onair review fixes, and an OFDM-only AP knob
snokvist Oct 3, 2026
374acb9
tests: sta_client confirms an open association, and re-joins one the …
snokvist Oct 3, 2026
15ad002
tests: sta_client_onair - HOSTAPD_DEBUG, and the kernel log of a fail…
snokvist Oct 3, 2026
90842b7
tests: sta_client nudges the AP after every association, once more on…
snokvist Oct 3, 2026
23934f9
tests: sta_client counts the plaintext a protected link refuses
snokvist Oct 3, 2026
834b054
tests: sta_client_onair - the MT7612U managed-filter cells
snokvist Oct 3, 2026
45796da
mt7612u: review fixes for the station receive filter
snokvist Oct 3, 2026
b6335c8
tests: sta_client's confirmation window opens at the host's first que…
snokvist Oct 3, 2026
a560385
mt7612u: a refused arm verifies its undo, and records one that misses
snokvist Oct 3, 2026
044026c
tests: a one-way uplink is never judged unconfirmed; harness review f…
snokvist Oct 3, 2026
a049c5f
tests: mt7612u_sta_identity - an INCONCLUSIVE gate reads as one
snokvist Oct 7, 2026
8b5fe5e
tests: station harnesses refuse a firmware dir the DUT cannot read
snokvist Oct 7, 2026
ebc5ddc
tests: every station harness exits 3 on INT/TERM
snokvist Oct 7, 2026
8d8e1bd
tests: mt7612u_ap_onair - reap reaps the children it KILLs
snokvist Oct 7, 2026
d0a3beb
tests: realtek_station_onair - tail_ms never reads negative
snokvist Oct 7, 2026
ef70994
tests: sta_client - one Unconfirmed verdict per BSS, a TCP SYN only
snokvist Oct 7, 2026
3d56480
tests: a second Ctrl-C cannot abandon a harness's hand-back
snokvist Oct 7, 2026
904083c
tests: sta_dut_take refuses an MT7612U that mt76x2u does not hold
snokvist Oct 7, 2026
d3c15c9
mt7612u: Stop() does not clear the arm; the RX log names its filter
snokvist Oct 7, 2026
4b3cf34
tests: sta_client_onair - a verdict on an association the AP held FAILs
snokvist Oct 7, 2026
f4f001d
mt7612u: staid's clear-after-drop check can fail
snokvist Oct 7, 2026
3165563
tests: the injector's disjoint sequence ranges are a precaution, said so
snokvist Oct 7, 2026
3651a9f
tests: the firmware preflight follows the library and names what it f…
snokvist Oct 7, 2026
6e7813e
tests: mt7612u_ap_onair - mark on_int as reached through the traps
snokvist Oct 7, 2026
078142a
tests: sta_client - a struck BSS backs off instead of being let off
snokvist Oct 7, 2026
13fbc34
tests: sta_client_onair - order each verdict against hostapd's log
snokvist Oct 7, 2026
00b811d
tests: sta_dut_take refuses only a live holder; hand-back waits for m…
snokvist Oct 7, 2026
ab205e3
mt7612u: staid reads its poison back and retries a missed clear; RX l…
snokvist Oct 7, 2026
d760103
tests: sta_client_onair - say why the preflight's ./firmware is sta_c…
snokvist Oct 7, 2026
aa97923
tests: sta_client_onair - the verdict check proves its stamp ordering…
snokvist Oct 7, 2026
0fa46cc
tests: sta_client pins its at= stamps; docs name the backoff's limits
snokvist Oct 7, 2026
6879f70
tests: sta_client_onair - every CONNECTED is accounted for, and a FAI…
snokvist Oct 7, 2026
e436a0c
tests: sta_client's stamp cell checks its redirect and allows a minute
snokvist Oct 7, 2026
4526680
tests: sta_client_onair - a verdict that began while hostapd held us …
snokvist Oct 7, 2026
2f6cffe
tests: sta_client_onair - an AP clock that stepped back is INCONCLUSIVE
snokvist Oct 7, 2026
407ea6d
tests: sta_client - a (QoS) Null from the AP does not confirm the ass…
snokvist Oct 7, 2026
c54226e
tests: a hostapd that outlives its kill stays recorded, and nothing s…
snokvist Oct 7, 2026
af19b9c
tests: pin the empty-body half of the confirmation lift; an empty PID…
snokvist Oct 7, 2026
26db041
tests: refuse a held adapter on every DUT and peer take
snokvist Oct 8, 2026
d4a93f7
tests: use the lib's sta_pid_alive, not a local proc_running copy
snokvist Oct 8, 2026
3f5a402
tests: finish the managed-filter injection before hostapd's first rekey
snokvist Oct 8, 2026
0ef7856
docs(mt7612u-station-identity): the managed filter's on-air numbers; …
snokvist Oct 8, 2026
42fab15
sta_client: call the nudge an SSID-specific probe request
snokvist Oct 8, 2026
232a631
tests: mark the Realtek DUT opened before its unbind; bound the injec…
snokvist Oct 8, 2026
b84f5da
docs(mt7612u-station-identity): the managed filter on the new injecti…
snokvist Oct 8, 2026
ddec632
docs, tests: correct the managed-filter table; a missing stamp is its…
snokvist Oct 8, 2026
a66f659
tests: inject after the cell's ping, with rekey defaults sized for it
snokvist Oct 8, 2026
fba6700
docs: the identity table carries the ddec632 and a66f659 runs
snokvist Oct 8, 2026
b862e3e
Merge branch 'master' into pr/sta-station
josephnef Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ if(OpenSSL_FOUND)

# tests/sta_client.cpp - the station client over IRadio + src/sta: scan,
# join, the WPA2-PSK four-way, CCMP and a TAP data plane. Built under its
# real name for tests/mt7612u_sta_onair.sh; the target name ends in
# real name for tests/sta_client_onair.sh; the target name ends in
# "Selftest" so the `selftests` aggregate collects it, and `--self-test`
# runs its headless cells before libusb is touched (no adapter, no root).
# Linux-only: the data plane is a TAP device (<linux/if_tun.h>).
Expand Down
132 changes: 117 additions & 15 deletions docs/mt7612u-station-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ arm. That function writes `MT_RX_FILTR_CFG = PHY_ERR|CRC_ERR` and nothing else
— every address and BSS drop bit off — so all six arms ran promiscuous and
were identical by construction. Its null result is withdrawn. The reasoning
that let it through was also wrong: in the managed filter `0x00015f97`, bit 3
(`OTHER_BSS`) is clear but bit **2** (`PROMISC`) is set, and mt76 maps bit 2
to `FIF_OTHER_BSS`. The gate now leaves the managed value `mt_mac_start()`
programs, prints it per arm, and flags an arm that is not running it.
(`OTHER_BSS`) is clear but bit **2** (`PROMISC`) is set, and bit 2 is the
address drop: it drops unicast whose addr1 is not `MT_MAC_ADDR`, and mt76x2
sets it whenever the phy is not in monitor mode (the decode is in the
managed-filter section below). The gate leaves the managed value
`mt_mac_start()` programs, prints it per arm, and flags an arm that is not
running it.

Also withdrawn: a "0.8% retried vs 98% control" auto-ACK figure from the
probe-response method (below), whose control ran with the monitor filter and
Expand Down Expand Up @@ -281,20 +284,119 @@ Against it, and against the comparison:
first-arm excess every run shows.
- Two units, one peer model, one channel, near field, one run per arm.

## The managed receive filter belongs to the armed station

Every cell above ran the managed filter `0x00015f97`.
`Mt7612uRadio::StartRxLoop` installs the monitor filter (`PHY_ERR|CRC_ERR`),
and under it the property that justifies the seam's refusal - "moving
`MT_MAC_ADDR` makes a station deaf" - does not hold: a station keeps
receiving and only stops acknowledging. So the armed station runs the
managed filter, and an unarmed one (`DEVOURER_STA_ARM=0`) the monitor
filter.

**The role is selected by the arm, with no new API.** A successful
`SetStationIdentity` reads `MT_RX_FILTR_CFG`, writes
`MT_RX_FILTR_CFG_MANAGED` and reads it back; `ClearStationIdentity` writes the
recorded value back and returns true only once that reads back (a failure
keeps the arm recorded, so a second clear retries). A refusal returns before
the filter is read, so it writes nothing; a managed write that does not read
back is undone (the undo read back too) and refused, and an undo that does
not read back either is recorded, so the clear still restores the pre-arm
value and a retried arm does not take the stranded managed filter for it.
While armed, `mt7612u_set_monitor_rx()` - which
`StartRxLoop` calls after every MAC start - keeps the managed filter and only
records the request, so the arm is order-independent. A beacon or ACK
responder that moves the port identity drops the arm and puts the pre-arm
filter back (the AP and responder paths depend on the monitor filter's `DUP`
clear); a failed beacon start that restores the arm reinstalls the managed
filter. All of it runs under `Mt7612uRadio::_mu`, the lock the existing
filter write and every channel change already take; the only other writes
are `mt_mac_start()` and `StartRxLoop`'s `mt7612u_set_monitor_rx()`, the
latter mediated as above, and the RX thread itself never writes it. The drop and restore writes are read back and a miss is
logged (the clear re-verifies). `Stop()` does not clear a still-armed
station: every bring-up rewrites the filter (the initvals, then
`mt_mac_start()`), and so does mt76, so a filter left armed at a close
reaches no later opener. The
policy half is `mt7612u_sta_rx_filter_request()` / `mt7612u_sta_arm()` in
`src/mt7612u/StationIdentity.h`, covered by ctest `mt7612u_station_identity`.

**The value is the measured one, unchanged.** These are DROP bits
(`regs.h`, from mt76's `mt76x02_regs.h`):

| bit | name | `0x00015f97` | what a station gets |
|---|---|---|---|
| 0 | CRC_ERR | drop | no FCS failures (`rx.keep_corrupted` applies to the monitor filter only; FCS-bad frames are dropped while armed) |
| 1 | PHY_ERR | drop | |
| 2 | PROMISC | **drop** | unicast whose addr1 is not `MT_MAC_ADDR` is dropped - the deaf-on-move property |
| 3 | OTHER_BSS | keep | frames of every BSS still arrive |
| 4 | VER_ERR | drop | |
| 5 | MCAST | keep | group-addressed data |
| 6 | BCAST | keep | beacons of every BSS, broadcast probe responses, broadcast data |
| 7 | DUP | drop | hardware duplicate drop, as mt76 runs a station (sta_client's `DupDetector` stays) |
| 8-12 | CFACK, CFEND, ACK, CTS, RTS | drop | control frames a station has no use for |
| 13 | PSPOLL | keep | (mt76's `configure_filter` would drop it; immaterial to a station) |
| 14 | BA | drop | |
| 15 | BAR | keep | |
| 16 | CTRL_RSV | drop | |

What `tests/sta_client.cpp` needs while armed is all kept: beacons and probe
responses from every BSS (broadcast, or unicast to `own`) for a scan and a
re-join, authentication / association / EAPOL / data addressed to `own`, and
group-addressed data. What it loses is only what `StationSm::on_rx` already
refused: another station's unicast (`not-for-us`) and probe responses to
other stations. No disarm-while-scanning is needed.

**Witness.** `tests/sta_client_onair.sh` (an MT7612U DUT) injects two plaintext unicast
streams from the AP's BSSID while the station is associated: one at an address
nobody holds, one at the station's own address. The own stream is the positive
witness that the injection reaches the DUT - the station counts it as
`plaintext refused` (a WPA2 link), and it must reach half of what was
injected or the check is INCONCLUSIVE. Then armed (`wpa2`), `not-for-us` must
stay under 1% of the foreign stream; unarmed (`noarm`, the monitor filter) at
least half of it must arrive. Hardware gate:
`mt7612uprobe staid` checks the filter value across arm, re-request, refusal,
clear and drop.

On air, ch6, near field, an MT7612U station against an RTL8812BU AP (rtw88),
one run per row on two benches. In every run the `noarm` control of the same
run saw the foreign stream arrive. The rows from 0fa46cc, c54226e and af19b9c
ran the injection after the four-way's ping, with `REKEY_S=20`; the rows from
42fab15, 232a631 and ddec632 ran it before the ping, with `REKEY_S=30`; the
rows from a66f659 run it as the harness does, after the ping and clear of the
rekeys (`REKEY_S=90`, `PTK_REKEY_S=80`). Injected before the ping, the ping
lost its first echo on bench B, most likely behind the AP's retransmissions
of the foreign stream. Bench B reported one `noarm` figure for its two runs
together: own-addressed 612 of 706.

| bench | head | schedule | own-addressed arrived | foreign `not-for-us` | `noarm` own-addressed |
|---|---|---|---|---|---|
| A | 0fa46cc | after the ping | 943 of 943 | 0 of 609 | 724 of 740 |
| A | c54226e | after the ping | 943 of 943 | 0 of 927 | 728 of 739 |
| B | af19b9c | after the ping | 775 of 863 | 0 of 642 | (see above) |
| B | af19b9c | after the ping | 875 of 875 | 0 of 713 | (see above) |
| A | 42fab15 | before the ping | 746 of 746 | 0 of 684 | 821 of 821 |
| A | 232a631 | before the ping | 751 of 751 | 0 of 683 | 944 of 944 |
| A | 232a631 | before the ping | 833 of 833 | 0 of 721 | 746 of 746 |
| A | ddec632 | before the ping | 764 of 764 | 0 of 739 | 870 of 870 |
| A | a66f659 | after the ping | 893 of 893 | 0 of 796 | 720 of 943 |
| A | a66f659 | after the ping | 942 of 942 | 0 of 789 | 700 of 744 |

Unarmed, `not-for-us` runs far above the foreign count injected (bench A:
6836 of 501 on 0fa46cc, up to 27125 of 869 on 42fab15). The rtw88 AP very
likely retransmits each foreign frame, which nothing acknowledges, and every
copy is counted. The own-addressed stream, which the station acknowledges,
arrives 1:1. The control needs only half of the foreign count, so the excess
does not change its verdict.

## What is not established

- **The library's own RX path does not run the managed filter.**
`Mt7612uRadio::StartRxLoop` installs the monitor filter unconditionally, so
a station driven through `IRadio` runs promiscuous. Acknowledgement holds
there (the monitor-filter auto-ACK run above), but "moving `MT_MAC_ADDR`
makes a station deaf" is a managed-filter property: under the monitor filter
it would keep receiving and stop acknowledging. A role-selected managed
filter is not implemented.
- **No cell drove `SetStationIdentity` through `IRadio`.** The seam writes no
register - `mt7612uprobe staid` reads `MT_MAC_ADDR`, `MT_MAC_BSSID` and
all eight APC slots before and after arming and clearing and checks them
unchanged - so the measured state is what a successful arm leaves behind,
but "arm the seam, then measure" is unexercised here.
- **No BSSID/auto-ACK cell drove `SetStationIdentity` through `IRadio`.** The
seam writes no identity register - `mt7612uprobe staid` reads
`MT_MAC_ADDR`, `MT_MAC_BSSID` and all eight APC slots before and after
arming and clearing and checks them unchanged - and installs the managed
filter those cells ran, so the measured state is what a successful arm
leaves behind, but "arm the seam, then measure" is unexercised by those
cells.
- **Every cell is an unassociated station** receiving traffic it did not
negotiate: power save, TIM parsing, cross-BSS duplicate detection and
hardware key lookup are untested.
Expand Down
Loading
Loading