fix: health monitor detects down monitors via ip link flags + disables wlan2mon

operstate reports 'unknown' on monitors (normal), so _iface_up now parses
admin flags from ip link. Discovered a second pineapd SIGSEGV source: the
wlan2mon 6GHz monitor this hardware never creates, hopping on the missing
iface (~85s crash cadence even with the SSID pool off). The monitor now
disables it in the fix path.
This commit is contained in:
2026-08-18 20:26:16 -05:00
parent 4ce19693d1
commit adfe8f784f
2 changed files with 38 additions and 12 deletions
@@ -3341,9 +3341,20 @@ def _sigsegv_count():
return out.count('SIGSEGV')
def _iface_up(name):
"""True when the interface's admin flags contain UP. Monitor interfaces
report operstate 'unknown' even when usable, so parse `ip link` flags."""
rc, out, err = device_run(['ip', 'link', 'show', name], timeout=10)
if rc != 0:
return False
m = re.search(r'<([^>]+)>', out or '')
if not m:
return False
return 'UP' in m.group(1).split(',')
def _monitor_down(name):
rc, out, err = device_run(['iw', 'dev'], timeout=10)
return name not in out
return not _iface_up(name)
def health_check():
@@ -3366,11 +3377,23 @@ def health_check():
device_run(['uci', 'set', 'pineapd.@ssidpool[0].disable=1'])
device_run(['uci', 'commit', 'pineapd'])
h['last_action'] = 'SSID pool broadcast disabled (pineapd crash-loop fix)'
elif _monitor_down('wlan1mon'):
device_run(['ip', 'link', 'set', 'wlan1mon', 'up'], timeout=10)
h['last_action'] = 'wlan1mon brought up'
elif _monitor_down('wlan1mon') or _monitor_down('wlan0mon'):
for name in ('wlan1mon', 'wlan0mon'):
if _monitor_down(name):
device_run(['ip', 'link', 'set', name, 'up'], timeout=10)
h['last_action'] = 'monitor interfaces brought up'
else:
h['last_action'] = 'pineapd restart'
# wlan2mon is a 6GHz monitor this hardware never materializes; with
# hop enabled pineapd crashes on the missing iface (second SIGSEGV
# source after the SSID pool). Keep it disabled.
wlan2 = _uci_section('pineapd.wlan2mon')
if wlan2.get('disable') != '1':
device_run(['uci', 'set', 'pineapd.wlan2mon.disable=1'])
device_run(['uci', 'set', 'pineapd.wlan2mon.hop=0'])
device_run(['uci', 'commit', 'pineapd'])
h['last_action'] = 'wlan2mon disabled (missing 6GHz iface crash source)'
else:
h['last_action'] = 'pineapd restart'
device_run(['/etc/init.d/pineapd', 'restart'], timeout=30)
h['sigsegv_last'] = _sigsegv_count()
h['last_fix'] = now