diff --git a/payload/user/remote_access/pager-webui/server.py b/payload/user/remote_access/pager-webui/server.py index bf9065e..f70e51a 100644 --- a/payload/user/remote_access/pager-webui/server.py +++ b/payload/user/remote_access/pager-webui/server.py @@ -1360,7 +1360,6 @@ def h_recon_status(ctx): 'hopper_online': _hopper_online(), 'wlan0_pinned': _wlan0_pinned(), 'wlan0_sta': _sta_uplink_enabled(), - 'wlan0_hopping': _wlan0_hopping(), 'history_reset': _recon_history_reset()} @@ -3727,43 +3726,14 @@ def _sta_uplink_enabled(): def _disable_sta_uplink(): + """Disable the dummy_radio0 STA without bouncing the radios. The UCI flag + keeps it off across reboots/wifi reloads; taking wlan0 down immediately + frees phy0's channel for wlan0mon. Never runs `wifi reload` here — that + tears down live APs and drops the monitors mid-assessment.""" device_run(['uci', 'set', 'wireless.dummy_radio0.disabled=1']) device_run(['uci', 'commit', 'wireless']) - device_run(['wifi', 'reload'], timeout=30) - - -def _pineap_interfaces(): - """Runtime pineapd interface table from ``_pineap INTERFACE LIST``: - {name: {'channels': int, 'bands': str, 'hop': str, 'pkts': int}}.""" - rc, out, err = _pineap('INTERFACE', 'LIST', timeout=15) - if rc != 0: - return {} - result = {} - for line in (out or '').splitlines(): - parts = line.split() - if len(parts) < 7 or not parts[0].startswith('wlan'): - continue - result[parts[0]] = { - 'channels': int(parts[1]) if parts[1].isdigit() else None, - 'bands': parts[2], - 'type': parts[3], - 'hop': parts[4], - 'chan': parts[5], - 'pkts': int(parts[6]) if parts[6].isdigit() else None, - } - return result - - -def _wlan0_hopping(): - """Whether pineapd is actually hopping wlan0mon at runtime. The band UCI - config can be correct while runtime hopping is off, which starves 2.4GHz - recon results entirely.""" - ifaces = _pineap_interfaces() - mon = ifaces.get('wlan0mon') - if not mon: - return None - hop = mon.get('hop') - return bool(hop) and hop not in ('0', '', 'none', 'false') + for iface in ('wlan0',): + rc, out, err = device_run(['ip', 'link', 'set', iface, 'down'], timeout=10) def env_check(): @@ -3831,16 +3801,6 @@ def env_check(): else: _env_step(report, 'pass', 'no radio0 AP pins wlan0mon') - hopping = _wlan0_hopping() - if hopping is False: - _env_step(report, 'warn', '2.4GHz recon starved: wlan0mon hopping is off at ' - 'runtime (INTERFACE LIST hop=0)') - elif hopping is None: - _env_step(report, 'warn', 'could not read pineapd interface state ' - '(INTERFACE LIST failed)') - else: - _env_step(report, 'pass', 'wlan0mon hopping on (2.4GHz scanning active)') - ENV_CHECK_STATE['report'] = report ENV_CHECK_STATE['overall'] = _env_overall(report) ENV_CHECK_STATE['updated'] = time.time() diff --git a/payload/user/remote_access/pager-webui/www/js/views.js b/payload/user/remote_access/pager-webui/www/js/views.js index 2e2692b..f6bbfc4 100644 --- a/payload/user/remote_access/pager-webui/www/js/views.js +++ b/payload/user/remote_access/pager-webui/www/js/views.js @@ -1462,7 +1462,7 @@ views.recon = (root) => { compare: [], history: {}, mapBand: null, archive: null, archives: [], scanRemaining: null, hopperOnline: null, historyReset: false, scanErr: null, - wlan0Pinned: false, wlan0Sta: false, wlan0Hopping: null }; + wlan0Pinned: false, wlan0Sta: false }; const cols = reconLoadCols(); // ---- title cards (stat cards with optional mini charts) ---- @@ -1662,10 +1662,9 @@ views.recon = (root) => { if (state.historyReset) bits.push('History reset — previous scans archived (see Previous Scans)'); if (state.wlan0Pinned) bits.push('2.4GHz under-sampled — OpenAP/Evil WPA holds wlan0mon'); if (state.wlan0Sta) bits.push('2.4GHz starved — client STA (wlan0) pins phy0'); - if (state.wlan0Hopping === false) bits.push('2.4GHz starved — wlan0mon hopping is off'); if (state.scanErr) bits.push(state.scanErr); scanStatus.textContent = bits.join(' · '); - scanStatus.classList.toggle('warn', state.hopperOnline === false || state.historyReset || state.wlan0Pinned || state.wlan0Sta || state.wlan0Hopping === false || !!state.scanErr); + scanStatus.classList.toggle('warn', state.hopperOnline === false || state.historyReset || state.wlan0Pinned || state.wlan0Sta || !!state.scanErr); } scanToggle.addEventListener('change', () => { if (pendingScan) { scanToggle.checked = !scanToggle.checked; return; } @@ -2529,7 +2528,6 @@ views.recon = (root) => { state.historyReset = !!r.data.history_reset; state.wlan0Pinned = !!r.data.wlan0_pinned; state.wlan0Sta = !!r.data.wlan0_sta; - state.wlan0Hopping = r.data.wlan0_hopping; if (!pendingScan) scanToggle.checked = scanning; renderScanBar(); if (wasScanning !== scanning) restartPoll(); diff --git a/tests/test_env_check.py b/tests/test_env_check.py index 5cbfe2d..0b86ea2 100644 --- a/tests/test_env_check.py +++ b/tests/test_env_check.py @@ -30,10 +30,6 @@ class EnvCheckTest(unittest.TestCase): self.daemon_ok = True self.iface_up = {'wlan0mon': True, 'wlan1mon': True} self.uci_state = {} - self.interface_list = ( - 'Interface #ch Bands Type Hop Chan Pkts \n' - 'wlan1mon 25 5 max fast hop 21567\n' - 'wlan0mon 11 2 max fast hop 452\n') server.ENV_CHECK_STATE.update({'report': None, 'overall': None, 'updated': 0, 'pool_runtime': None}) self.old_iface_up = server._iface_up @@ -81,8 +77,6 @@ class EnvCheckTest(unittest.TestCase): return (0, ''.join("%s=%s\n" % (k, v) for k, v in self.uci_state.items() if k.startswith(sec + '.')), '') if a[0] == '_pineap': - if len(a) > 1 and a[1] == 'INTERFACE': - return (0, self.interface_list, '') return (0, '', '') if a[0] in ('ip', '/etc/init.d/pineapd'): return (0, '', '') @@ -197,26 +191,6 @@ class EnvCheckTest(unittest.TestCase): report = server.env_check() self.assertEqual(self.steps(report, 'no radio0 AP pins wlan0mon')[0]['ok'], 'pass') - def test_wlan0_starved_warns(self): - self.safe_set() - self.interface_list = ( - 'Interface #ch Bands Type Hop Chan Pkts \n' - 'wlan1mon 25 5 max fast hop 21567\n' - 'wlan0mon 11 2 max 0 hop 452\n') - report = server.env_check() - self.assertEqual(self.steps(report, 'wlan0mon hopping is off')[0]['ok'], 'warn') - - def test_wlan0_hopping_unknown_warns(self): - self.safe_set() - self.interface_list = '' - report = server.env_check() - self.assertEqual(self.steps(report, 'could not read pineapd interface state')[0]['ok'], 'warn') - - def test_wlan0_hopping_pass(self): - self.safe_set() - report = server.env_check() - self.assertEqual(self.steps(report, 'wlan0mon hopping on')[0]['ok'], 'pass') - def test_sta_uplink_disabled_when_enabled(self): self.safe_set() self.uci_state['wireless.dummy_radio0.mode'] = 'sta' @@ -225,7 +199,9 @@ class EnvCheckTest(unittest.TestCase): report = server.env_check() self.assertEqual(self.steps(report, 'dummy_radio0 STA uplink disabled')[0]['ok'], 'fixed') self.assertEqual(self.uci_state['wireless.dummy_radio0.disabled'], '1') - self.assertIn(['wifi', 'reload'], self.runs) + self.assertIn(['ip', 'link', 'set', 'wlan0', 'down'], self.runs) + self.assertNotIn(['wifi', 'reload'], self.runs, + 'STA fix must not bounce the radios') def test_sta_uplink_pass_when_absent(self): self.safe_set()