docs: 5GHz/6GHz rogue AP feature and coexistence notes

This commit is contained in:
2026-08-18 07:30:06 -05:00
parent ff3bd16855
commit be2685aa15
2 changed files with 196 additions and 0 deletions
+5
View File
@@ -8,6 +8,11 @@ Recon (scans from `recon.db`), Handshakes/Loot, Payloads (embedded stock Pager
Portal), Logs, Settings (hostname/NTP/password/prefs), and a bottom-docked xterm
terminal.
- Rogue AP on the second radio (5GHz / 6GHz Wi-Fi 6E): Open AP and Evil WPA
(WPA2-PSK/WPA3-SAE/WPA3-OWE) on `radio1`, band-aware channel pickers,
6GHz requires WPA3. While a radio1 AP is enabled the stock monitor-hopping
(`wlan1mon`) is paused and resumed on disable; 2.4GHz PineAP is untouched.
## Requirements
- WiFi Pineapple Pager, firmware `Pineapple Pager 24.10.1`
+191
View File
@@ -0,0 +1,191 @@
# Radio1 5GHz/6GHz Rogue AP — Design Record
- **Date:** 2026-08-17
- **Status:** Implemented (Tasks 14), on-device verification pending
- **Owner:** Hak5 WiFi Pineapple Pager expansion project
- **Scope:** Mark VIII WebUI (`http://172.16.52.1:8080/`) running a rogue AP on
the Pager's second radio (`radio1` = MT7921U Wi-Fi 6E) on 5GHz and 6GHz, with
band-aware channel pickers, without breaking the stock Pager UI's control of
its own interfaces.
## 1. Goal
Today Mark VIII's PineAP Open AP and Evil WPA pages configure only `wlan0open`
/ `wlan0wpa` on `radio0` (2.4GHz) via the stock daemon's `set_ap` endpoint. The
Pager carries a second radio — an MT7921U Wi-Fi 6E on internal USB — that is
otherwise only used by the stock daemon's hopping monitor `wlan1mon`. This
feature lets Mark VIII run an Open AP or Evil WPA (WPA2-PSK / WPA3-SAE /
WPA3-OWE) on `radio1` at 5GHz and 6GHz, configured directly via UCI, with a
UI that picks channels by band and enforces WPA3 on 6GHz.
## 2. Hardware findings
Verified against the committed implementation (`server.py`, `views.js`) and the
plan's on-device groundwork:
- **`radio0`** — MT7628, **2.4GHz only**. The stock PineAP daemon owns
`wlan0open`, `wlan0wpa`, `wlan0cli`, `wlan0mon`, `wlan0mgmt`. This feature
does not change that ownership.
- **`radio1`** — MT7921U Wi-Fi 6E on internal USB (`phy1`), capable of
2.4/5/6GHz. The stock daemon owns `wlan1mon`, a daemon-managed hopping
monitor interface: `pineapd.wlan1mon` has `bands='2,5,6'` and `hop='1'`.
- Because `radio1` has a single channel shared by all its virtual interfaces,
a `wlan1` AP and the hopping `wlan1mon` cannot both be active with an
operator-chosen channel — hence the hop pause/resume mechanism (§6).
- UCI state (`wireless.radio1`): stock defaults are `band='5g'`,
`channel='auto'`, `htmode='VHT80'`, `country` set for the device region.
## 3. Band / channel model
`server.py` defines the authoritative mapping (helpers added near
`_uci_wifi_iface`):
| Band | `BAND_*` | Channels | `band_htmode` | `band_radio` |
|---|---|---|---|---|
| 2.4GHz | `BAND_2G = '2.4'` | `114` | `HT20` | `radio0` |
| 5GHz | `BAND_5G = '5'` | `36177` | `VHT80` | `radio1` |
| 6GHz | `BAND_6G = '6'` | `181233` (step 4) | `HE80` | `radio1` |
- `channel_band(ch)` classifies **2.4GHz (114) first, then 5GHz (36177)**, and
only then 6GHz (`1 ≤ ch ≤ 233` and `(ch 1) % 4 == 0`). Because 2.4 and 5GHz
take precedence, **the only reachable 6GHz channels are `181, 185, …, 233`**
(the low 6E channels `1,5,…,177` are swallowed by the 2.4/5GHz ranges). The
UI's 6GHz group therefore offers exactly `181..233 step 4`.
- `CHANNEL_BANDS` mirrors this: `range(1, 15)`, `range(36, 178)`,
`range(181, 234, 4)` — held consistent by tests.
- `DFS_CHANNELS` = `52..64` and `100..144` (step 4). DFS channels are surfaced
to the operator in the UI with a `(DFS)` marker; this feature does not attempt
radar-CAC handling on-device.
- `channel_freq(band, ch)`: 2.4 → `2412 + (ch1)·5`; 5 → `5180 + (ch36)·5`;
6 → `5955 + (ch1)·5`.
- `band_htmode` maps 2.4/5/6 → `HT20` / `VHT80` / `HE80` (written to
`wireless.radio1.htmode`). `band_radio` maps 2.4 → `radio0`, 5/6 → `radio1`.
## 4. API behavior
### 4.1 `h_pineap_wifi_get_ap` (POST `/api/pineap/wifi/get_ap`)
- If `wlan1open` **or** `wlan1wpa` exists in `wireless`, state is read from
`radio1` (`wlan1open`/`wlan1wpa`); otherwise from `radio0`
(`wlan0open`/`wlan0wpa`) — the 2.4GHz response shape is unchanged.
- `open.channel` and `wpa.channel` are reported **per interface**, falling back
to the owning radio's channel when the iface section has no channel option
(regression-tested). `wpa.enctype` is normalized to `psk2` / `sae` / `owe`.
- A new `radio1` object reports the raw `wireless.radio1` state:
`{band, channel, htmode, country}`, with `band` normalized from `2g`/`5g`/`6g`
to `'2.4'`/`'5'`/`'6'` (default `'5'`), and `channel` left as the raw string
(`'auto'` or a channel number) — the UI converts; `'auto'` is not int-coerced.
### 4.2 `h_pineap_wifi_set_ap` (POST `/api/pineap/wifi/set_ap`)
`channel` is now accepted in both `open` and `wpa` payloads. Behavior matrix:
- **2.4GHz channels (114):** exactly today's path — daemon
`PUT /api/settings/wifi/set_ap` for `wlan0open`/`wlan0wpa` followed by
`_apply_open_radio` (which persists `radio0.channel`/`country`). If a
`wlan1*` AP exists it is removed first (`_remove_radio1_ap`, §6) so a 2.4GHz
save tears down a stale radio1 AP.
- **5GHz (36177) / 6GHz (181233):** `_apply_radio1_ap` (below).
- **Mixed request:** a request carrying both a 2.4GHz object and a 5/6GHz
object returns `400 'cannot configure 2.4GHz and radio1 APs in one request'`
(radio1 is one physical radio — one band/channel per request).
- **Disable:** a radio1 request with no active 5/6GHz object (or a 2.4GHz save)
runs `_remove_radio1_ap()` + `wifi reload` and returns `200`.
`_apply_radio1_ap(openap, wpa)` (one of the two is active):
1. Validates the band — a radio1 AP requires a 5GHz or 6GHz channel
(`ValueError` → HTTP 400).
2. **6GHz requires WPA3:** when the active AP is a WPA AP on 6GHz, `enctype`
must be `sae` or `owe`; `psk2` is rejected with HTTP 400
(`'6GHz requires WPA3 (sae or owe)'`). An **open** 6GHz AP is accepted by
the backend, but the UI warns that real clients generally won't associate to
an open 6GHz network.
3. Deletes any existing `wlan1open`/`wlan1wpa` (idempotent), then writes UCI:
- `wireless.radio1.band` = `5g`/`6g`, `wireless.radio1.channel`,
`wireless.radio1.htmode` (`band_htmode`), `wireless.radio1.country`
(when supplied);
- a `wifi-iface` section `wlan1open` (encryption `none`, optional BSSID) or
`wlan1wpa` (`encryption` + `key`) on `device=radio1`, `mode=ap`, with the
interface-level `channel`/`hidden`/`ssid`.
4. `uci commit wireless`, `_pause_hop()` (§6), then `wifi reload`.
## 5. Coexistence rules — "don't break stock"
- **Mark VIII owns:** `wireless.wlan1open`, `wireless.wlan1wpa`, and
`wireless.radio1.{channel,band,htmode,country}`. These are new sections /
values it creates and tears down.
- **Stock owns (never modified by Mark VIII):** `wireless.wlan0open`,
`wlan0wpa`, `wlan0mgmt`, `wlan0cli`, `wlan0mon`, `wireless.wlan1mon`, and all
`pineapd.*` UCI values (bands configuration included).
- **The only stock-owned value this feature writes is
`pineapd.wlan1mon.hop`** — and it is always restored to its prior value
(`_resume_hop` sets it back to `1` only if it was `0`; `_pause_hop` sets it
to `0` only if it was not already `0`).
- The 2.4GHz daemon path (`PUT /api/settings/wifi/set_ap` for `wlan0open` /
`wlan0wpa`) is byte-for-byte unchanged.
- **Known risk (accepted):** the stock daemon's `set_ap`/pager-UI writes may
rewrite `wireless` wholesale and drop the `wlan1*` sections. Mitigation is
UCI-commit persistence, hop restore on disable, and on-device verification
(§9). If clobbering is observed, the deferred fix is a reconcile-on-load step
in `get_ap` that re-applies a saved radio1 AP from `PINEAP_STATE_FILE`.
## 6. Hop pause / resume
`wlan1mon` is the stock daemon's channel-hopping monitor. With a radio1 AP
active, hopping would fight the AP's fixed channel, so it is paused while the
AP is enabled:
- `_read_hop()` reads the value via `uci get pineapd.wlan1mon.hop` (a leaf
read — not `_uci_wifi_iface`, which forces the `wireless.` prefix).
- `_pause_hop()`: if `hop != '0'`, set `pineapd.wlan1mon.hop=0`, `uci commit
pineapd`, reload `/etc/init.d/pineapd`.
- `_resume_hop()`: if `hop == '0'`, set it back to `1`, commit, reload.
- `_apply_radio1_ap` calls `_pause_hop()` before `wifi reload`;
`_remove_radio1_ap` calls `_resume_hop()` after resetting
`radio1.channel=auto` / `radio1.band=5g`. Every code path that pauses hopping
also restores it.
## 7. Frontend (`www/js/views.js`)
- `BAND_GROUPS` drives the channel pickers shared by the Open AP and Evil WPA
views: a `2.4 GHz` optgroup (111), a `5 GHz` optgroup (36177, DFS channels
`52..64` / `100..144` labelled `(DFS)`), and a `6 GHz (WPA3/OWE only)`
optgroup (`181..233` step 4).
- `chanFreq`/`chanLabel` render `Channel N (… MHz)` (+` (DFS)`),
`chanSelect` builds the optgroups and restores a stored value when in range,
`bandOfChannel` mirrors `channel_band`.
- **Open AP:** channel select + a hint that appears on 6GHz ("…most devices
will not associate to an open 6 GHz network.").
- **Evil WPA:** a channel select added to the config card; selecting a 6GHz
channel disables the `psk2` option and switches to `sae`, with a
"6 GHz requires WPA3 (SAE or OWE)." hint. Save payloads for both views
include `channel` (Open AP also `country`).
## 8. Automated verification
- `tests/test_pineap_bands.py` covers the channel/band helpers
(`ChannelBandTest`, `ChannelBandsConsistencyTest`, `ChannelFreqTest`,
`BandAuxTest` incl. DFS marker), `get_ap` (`GetApRadio1Test`,
`GetApRadio1AbsentTest`, `GetApRadioChannelFallbackTest`) and `set_ap`
(`SetApRadio1Test`: 5GHz open writes `radio1` sections + hop pause; 6GHz
WPA3-SAE accepted; 6GHz `psk2` rejected; disable removes the radio1 AP and
restores hop; 2.4GHz still uses the daemon path; 2.4GHz save removes a stale
radio1 AP; mixed 2.4GHz + radio1 rejected).
- **All 14 test modules pass at HEAD (`ff3bd16`)**, run per-module in separate
processes per the repo convention (`test_auth`, `test_core`, `test_loot`,
`test_misc`, `test_pineap_bands`, `test_pineap_clients`,
`test_pineap_enterprise`, `test_pineap_modes`, `test_pineap_pool`,
`test_pineap_proxy`, `test_pineap_settings`, `test_recon`, `test_status`,
`test_ws`).
## 9. On-device verification
On-device verification is **pending** (Task 5 of the implementation plan,
deferred until it can be run against the user's Pager at `172.16.52.1` without
colliding with other agents' deployed builds). Planned checks: 2.4GHz behavior
unchanged (`wlan0open`/`radio0`/`wlan1mon` untouched), 5GHz Evil WPA
(WPA3-SAE) bringing up `wlan1wpa` with `hop='0'`, stock Pager UI toggles
continuing to work, 5GHz handshake capture, reboot persistence, disable path
restoring hopping, and 5GHz Open AP. Results will be recorded here once
complete.