fix: passive health check + hop=0 in stabilization pass

Active PINGs on pineapd's command socket collided with the stock daemon's
own socket writes ('[PineAp] Error writing'), making the daemon watchdog
SIGTERM pineapd every ~30s while hopping. Monitor now checks pidof only
(no socket writes) and the stabilization pass pins wlan1mon hop=0.
This commit is contained in:
2026-08-18 21:05:39 -05:00
parent 46e437e8b8
commit b8e38bfefd
2 changed files with 18 additions and 12 deletions
@@ -3347,10 +3347,11 @@ def _stabilize_pineapd():
1. SSID-pool broadcast (segfault, ~15s cadence) 1. SSID-pool broadcast (segfault, ~15s cadence)
2. wlan2mon: a 6GHz monitor this hardware never creates; hopping the 2. wlan2mon: a 6GHz monitor this hardware never creates; hopping the
missing iface segfaults pineapd missing iface segfaults pineapd
3. wlan1mon fast-hopping the 6GHz channel list (ASIO io thread exit) 3. wlan1mon fast-hopping (stalls the command socket; the stock daemon's
The pool list itself is also cleared: collect refills it, and a large watchdog then SIGTERMs pineapd every ~30s)
pool was observed crashing even with broadcast disabled. 4. a large refilled pool (collect refills it; crashes observed even
Returns a human-readable description of what was changed.""" with broadcast disabled)
The pool list itself is cleared. Returns what changed."""
actions = [] actions = []
wanted = { wanted = {
'pineapd.@ssidpool[0].disable': '1', 'pineapd.@ssidpool[0].disable': '1',
@@ -3358,6 +3359,7 @@ def _stabilize_pineapd():
'pineapd.wlan2mon.hop': '0', 'pineapd.wlan2mon.hop': '0',
'pineapd.wlan1mon.bands': '5', 'pineapd.wlan1mon.bands': '5',
'pineapd.wlan0mon.bands': '2', 'pineapd.wlan0mon.bands': '2',
'pineapd.wlan1mon.hop': '0',
} }
for key, value in wanted.items(): for key, value in wanted.items():
rc, out, err = device_run(['uci', 'get', key]) rc, out, err = device_run(['uci', 'get', key])
@@ -3394,12 +3396,13 @@ def health_check():
"""One health pass. Returns the health dict. Fix actions are """One health pass. Returns the health dict. Fix actions are
rate-limited by HEALTH_FIX_COOLDOWN. rate-limited by HEALTH_FIX_COOLDOWN.
The stock SSID-pool broadcast segfaults pineapd on this firmware; when The check is PASSIVE (pidof) — actively pinging pineapd's command
pineapd is unreachable the pool broadcast is disabled before restarting, socket every 15s collides with the stock daemon's own socket writes
since it is the only known crash cause.""" ('[PineAp] Error writing' -> daemon watchdog SIGTERMs pineapd).
"""
h = _health h = _health
rc, out, err = device_run([HAK5CMD, 'PING'], timeout=10) rc, out, err = device_run(['pidof', 'pineapd'], timeout=10)
h['pineap_up'] = rc == 0 and 'PONG' in (out or '') h['pineap_up'] = rc == 0 and bool((out or '').strip())
if h['pineap_up']: if h['pineap_up']:
return dict(h) return dict(h)
now = time.time() now = time.time()
+6 -3
View File
@@ -26,10 +26,10 @@ class HealthCheckTest(unittest.TestCase):
def fake_run(args, timeout=20, input_data=None): def fake_run(args, timeout=20, input_data=None):
self.runs.append((list(args), timeout)) self.runs.append((list(args), timeout))
a = list(args) a = list(args)
if a[0] == '/usr/bin/hak5cmd' and a[1] == 'PING': if a[0] == 'pidof' and a[1] == 'pineapd':
if self.ping_ok: if self.ping_ok:
return (0, 'Sending PING...\nGot PONG response\n', '') return (0, '12345\n', '')
return (1, '', 'could not connect to pineap: connection refused') return (1, '', '')
if a[0] == 'logread': if a[0] == 'logread':
return (0, 'SIGSEGV\n' * self.sigsegs if hasattr(self, 'sigsegs') else '', '') return (0, 'SIGSEGV\n' * self.sigsegs if hasattr(self, 'sigsegs') else '', '')
if a[:2] == ['uci', 'set']: if a[:2] == ['uci', 'set']:
@@ -56,6 +56,8 @@ class HealthCheckTest(unittest.TestCase):
result = server.health_check() result = server.health_check()
self.assertTrue(result['pineap_up']) self.assertTrue(result['pineap_up'])
self.assertIsNone(result['last_action']) self.assertIsNone(result['last_action'])
self.assertEqual([r[0] for r in self.runs], [['pidof', 'pineapd']],
'health check must not write to the pineapd socket')
def test_down_with_growing_sigsegv_disables_pool(self): def test_down_with_growing_sigsegv_disables_pool(self):
self.ping_ok = False self.ping_ok = False
@@ -73,6 +75,7 @@ class HealthCheckTest(unittest.TestCase):
self.uci_state['pineapd.wlan2mon.hop'] = '0' self.uci_state['pineapd.wlan2mon.hop'] = '0'
self.uci_state['pineapd.wlan1mon.bands'] = '5' self.uci_state['pineapd.wlan1mon.bands'] = '5'
self.uci_state['pineapd.wlan0mon.bands'] = '2' self.uci_state['pineapd.wlan0mon.bands'] = '2'
self.uci_state['pineapd.wlan1mon.hop'] = '0'
result = server.health_check() result = server.health_check()
self.assertEqual(result['last_action'], 'pineapd restart') self.assertEqual(result['last_action'], 'pineapd restart')
self.assertIn(['/etc/init.d/pineapd', 'restart'], [r[0] for r in self.runs]) self.assertIn(['/etc/init.d/pineapd', 'restart'], [r[0] for r in self.runs])