Skip to content

wifi: no NetworkManager#37902

Draft
andiradulescu wants to merge 178 commits intocommaai:masterfrom
andiradulescu:nonetworkmanager
Draft

wifi: no NetworkManager#37902
andiradulescu wants to merge 178 commits intocommaai:masterfrom
andiradulescu:nonetworkmanager

Conversation

@andiradulescu
Copy link
Copy Markdown
Contributor

Resolves #37752
Continuation of #37662
Draft, opening for feedback

Summary

Replaces NetworkManager's wifi management with a direct wpa_supplicant ctrl-socket client + udhcpc + dnsmasq. Same architecture as #37662:

  • Daemon survives UI restart. wpa_supplicant, dnsmasq, and udhcpc all spawn with start_new_session=True; on UI restart _init_wifi_state adopts the running daemon via ctrl-socket attach instead of respawning. _our_wpa_supplicant_running gates every attach so we never latch onto NM's systemd wpa_supplicant.service
  • Tethering adoption. If STATUS reports mode=AP on init, we take a dedicated AP-adoption branch rather than falling through to STA DHCP
  • NM coexistence NM still manages eth0 and LTE. We unmanage wlan0 at runtime via nmcli dev set wlan0 managed no, wait for NM's async teardown (~800ms) to delete the shared ctrl socket, then spawn our own wpa_supplicant against /tmp/wpa_supplicant.conf
  • On-disk format unchanged. Saved networks still written as .nmconnection keyfiles for rollback + interop, same as bye bye NetworkManager #37662
  • GSM (cellular) unchanged. _GsmManager still shells out to nmcli. NM stays for cellular for now
  • Default route metric 600. busybox udhcpc installs wlan0 at metric 0, which silently hijacks traffic from a plugged-in eth. DhcpClient._fix_default_route_metric flush-and-re-adds at 600 (NM's wifi default). Required for dual-uplink to work right
  • NAT rule shape. Tethering MASQUERADE matches on source subnet (-s 192.168.43.0/24 ! -d 192.168.43.0/24), not -o <iface>. Mirrors NM's nm-firewall-utils.c. Tethering survives eth unplug, SIM pull, rmnet_data0 rename, no watchdog
  • SSID decoding. wpa_supplicant emits printf_encode'd SSIDs in STATUS, SCAN_RESULTS, LIST_NETWORKS, and events. Every ingress runs through decode_ssid() so non-ASCII networks round-trip and forget_connection/activate_connection don't leak runtime IDs

Architecture

Before:  UI → WifiManager → DBus/jeepney → NetworkManager → wpa_supplicant → kernel
After:   UI → WifiManager → Unix socket  → wpa_supplicant → kernel
                          → subprocess   → udhcpc (DHCP client)
                          → subprocess   → dnsmasq (tethering DHCP server)
                          → subprocess   → iptables (NAT for tethering)

GSM (unchanged, NM still owns):

UI → _GsmManager → nmcli → NetworkManager → ModemManager → modem

Benchmark

TODO. Will run the same methodology as #37662 (5 iterations each, on device, scan / connect-to-IP / disconnect)

adeebshihadeh and others added 30 commits April 12, 2026 12:29
- Fix CTRL-EVENT-SSID-TEMP-DISABLED event name (not CTRL-EVENT-TEMP-DISABLED)
- Use ADD_NETWORK/SET_NETWORK/SELECT_NETWORK instead of RECONFIGURE
  (RECONFIGURE fails when NM started wpa_supplicant)
- Add IP address fallback from wpa_cli STATUS output
- Add wifi_cli.py for on-device testing (status, scan, connect, forget, etc)
- Fix ruff lint issues (unused imports, string concatenation, line length)
- Fix test string construction for ruff ISC002

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Measures scan, connect (to IP), and disconnect for both
wpa_supplicant and NetworkManager backends. Each operation
is timed to the user-visible outcome, not internal state
changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _sanitize_for_conf() to prevent SSID/PSK injection in wpa_supplicant.conf
  and wpa_cli SET_NETWORK commands (strips ", \n, \r, \)
- Fix parse_scan_results to handle hidden networks with empty SSIDs
- Fix incorrect test assertion for callback queue length

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pre-populate network list from cached SCAN_RESULTS during init so the
  UI has data immediately when the panel opens (no "Scanning..." flash)
- Poll for IP after DHCP starts so "obtaining IP..." resolves within
  ~200ms of lease instead of waiting up to 5s for next scan event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Networks must be loaded before wifi state so that the connected SSID's
strength is available on first render. Also read IP on init when already
connected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The standard UI panel relied solely on the async callback to populate its
network list, causing a "Scanning..." flash every time the panel opened.
Read cached networks immediately in show_event, matching what MICI
already does.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The _active flag was gating both scan requests AND result processing.
When the panel was hidden, scan results were silently dropped, so
_networks went stale. On re-entry the UI read empty/stale data while
waiting for an async refresh — causing the "Scanning..." flash.

Fix: _active now only controls whether new SCAN commands are issued
(in the scanner thread). Scan results from wpa_supplicant events are
always processed, keeping _networks fresh regardless of panel
visibility. Also guard against replacing _networks with empty data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bugs fixed:
- dnsmasq launched with subprocess.run (blocks forever) → subprocess.Popen
- save_network overwrites metered/hidden fields → merge instead of replace
- DhcpClient.stop() flushes IP even when nothing to stop → guard with proc check

Code reuse:
- Extract _add_and_select_network helper (was duplicated in connect/activate)
- _remove_wpa_network reuses _find_network_id instead of duplicating parser
- hardware.py uses parse_status/dbm_to_percent/MeteredType from wpa_ctrl/wifi_manager
- Remove redundant _tethering_password field (derive from store)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove dead WPA_CTRL_PATH constant
- Fix _remove_wpa_network: single LIST_NETWORKS call instead of
  while-loop that could infinite-loop if REMOVE_NETWORK failed
- Fix _monitor_state: use finally block so monitor socket is always
  closed (was only closed in except branch)
- Store dnsmasq Popen in self._dnsmasq_proc and wait on stop to
  avoid zombie process / ResourceWarning
- Validate ADD_NETWORK response in _add_and_select_network
- Optimize networks property: snapshot saved SSIDs once via get_all()
  instead of N lock acquisitions via is_connection_saved()
- Prevent overlapping _poll_for_ip threads on rapid reconnects by
  capturing user_epoch at poll start

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
hardware.py imported WIFI_NETWORKS_JSON and MeteredType from
wifi_manager.py, which imports swaglog, which imports hardware —
causing a circular import at build time. Inline the constant and
use integer literals instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use atomic_write from common/utils instead of hand-rolled tmp+replace
- Extract _list_network_ids to deduplicate LIST_NETWORKS parsing in
  _find_network_id and _remove_wpa_network
- Hoist NM DBus constants to _GsmManager class level (were redefined
  as locals in 3 methods)
- Don't block monitor thread on SCAN_RESULTS: call _update_networks
  with block=False so event processing isn't stalled
- Remove dead SecurityType.WPA2 and WPA3 enum values (never used)
- Move _tethering_active=True to after AP setup succeeds, so
  DISCONNECTED events aren't suppressed if tethering setup fails

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lds don't shift from what the NM D-Bus Strength prop used to return
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

Process replay diff report

Replays driving segments through this PR and compares the behavior to master.
Please review any changes carefully to ensure they are expected.

✅ 0 changed, 66 passed, 0 errors

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 24, 2026

UI Preview

mici: ⚠️ Videos differ! View Diff Report
big: ⚠️ Videos differ! View Diff Report

@andiradulescu andiradulescu changed the title wifi_manager.py wifi: no NetworkManager Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove NetworkManager + DBus rom WiFi stack

2 participants