Skip to content

Drive Wi-Fi through wpa_cli, save networks only once they connect - #127

Open
ericbsd wants to merge 1 commit into
masterfrom
issue-99-wifi-auth-feedback
Open

ericbsd wants to merge 1 commit into
masterfrom
issue-99-wifi-auth-feedback

Conversation

@ericbsd

@ericbsd ericbsd commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Wireless is now handled through wpa_supplicant's control socket instead of by editing /etc/wpa_supplicant.conf and restarting the daemon. Scanning, saving, forgetting, connecting and disconnecting all go through wpa_cli, and the daemon started by rc is the only one that ever runs. networkmgr never runs the wpa_supplicant binary, never restarts a service on the Wi-Fi path, and never uses shell=True.

Connecting

  • connect_to_ssid() joins the clicked network with select_network, so with two saved networks in range the one clicked is the one joined. wait_for_connection() requires wpa_state=COMPLETED and a matching ssid; ifconfig's "associated" is not used, since it appears before the key handshake finishes.
  • Save on connect. A new network, or a retyped passphrase, lives in the daemon only until wait_for_connection() succeeds, then save_config writes the file. A passphrase that never works never reaches disk, and retyping one badly cannot overwrite a working one.
  • A failed attempt reopens the credentials dialog, three attempts, then the network is forgotten. A refused key cannot be told from a card that never answered on driver_bsd (wpas_auth_failed never fires, the network is never marked TEMP-DISABLED), so both are reported as "Could not connect".
  • reconfigure is sent only at the moment of committing to a connection or a forget, so an edit left in the daemon by an abandoned attempt is discarded before the next one is written. Opening and cancelling a dialog no longer touches the daemon.
  • Open networks connect on the first click; the first click used to write the block and the second to connect.
  • A "Forget Network" submenu lists the saved networks from the daemon, including ones out of range.
  • WPA3-only networks are listed greyed out as "(WPA3 only)". Base wpa_supplicant is built with SAE but driver_bsd offers no SAE key management, so such a network can never be joined here; transition networks (WPA2-PSK+SAE) still join with a passphrase.

Reading

  • Scan results, saved networks and connection state come from wpa_cli and are printf-decoded, so an SSID with non-ASCII bytes, quotes or backslashes is found, shown and joined by its real name. ifconfig list scan renders such a name as a truncated hex column and cannot represent it.
  • The tray refresh reads only the default-route interface every 30 s (2-4 commands, about 5 ms); the full interface list is collected when the menu opens. Signal for the connected AP comes from bss current.
  • Wired interface state is Disabled, Unplug, Connected or Disconnected, tested in that order; a static card with the cable out no longer reads Connected. Disconnected offers Disable and Configure instead of a no-op Enable.
  • The IPv6 tab's interface chooser passed no argument to its handler and raised on every change.
  • The configuration window reads the gateway from the newest lease, toggles the Search domains entry with the other fields, and refuses to save a Manual configuration with no primary DNS server (IPv4) or no address (IPv6).
  • The resolv.conf search domain regex no longer stops at a digit or hyphen.

Saving

  • Networks are written with add_network/set_network/enable_network/ save_config. Strings use wpa_supplicant's P"..." form except psk, which only accepts plain quotes. wpa_save_config re-chmods the file to 0600 because the daemon's own chmod is Android-only.
  • WEP keys are quoted unless they are 10, 26 or 32 hex digits; a bare ASCII key was read as hex and rejected.
  • EAP-TTLS uses autheap= for EAP inner methods (GTC, MD5); auth= made the method refuse to start. EAP retries replace the block rather than appending one.
  • src/wpa_supplicant.conf ships the globals the daemon needs (ctrl_interface, ctrl_interface_group=operator, update_config=1, pmf, autoscan). setup-nic.py installs it when the file is missing and prepends any missing global to an existing one.
  • src/sudoers.d/networkmgr grants %operator rather than %wheel, to match ctrl_interface_group.

Wired and devd

  • Wired Enable/Disable are ifconfig up/down. Handing the default route over on link loss is left to devd's auto-switch.py; switch_default in net_api duplicated it and raced it for the link-down marker.
  • The link-down marker moves from /tmp to /var/run: /tmp is world-writable with a predictable name and was opened for writing as root, and it is not cleared at boot on a stock install (clear_tmp_enable=NO), so a stale marker could send link-up.py down the wrong branch. /var/run is root-only and cleanvar empties it at every boot.
  • link-up.py, auto-switch.py and setup-nic.py run their commands without a shell. sysrc values are passed as one argument.

Housekeeping

  • Every function and method name is word_an_other_word; classes are TrayIcon and NetCardConfigWindow. Every touched function has a docstring with its parameters.
  • Translation extraction pointed at a directory renamed in 2023; networkmgr.pot went from 22 to 50 msgids, merged into every .po, and the f-string call sites that could never match a catalogue entry are fixed.
  • Dialog headings use Pango attributes rather than markup, so an SSID containing & or < renders as text.
  • pylint 8.10 to 9.65 with a rebuilt .pylintrc; no shell=True anywhere in NetworkMgr/ or src/.
  • The tests/ tree and requirements.txt are removed; the suite had not been run since the 2023 rename. The nine net_api functions only the tests imported go with them.
  • Version 7.0.

Tested on rtwn0 (RTL8811AU): connecting to a saved network, switching between two, disconnecting, a stale network timing out and keeping its block, a wrong passphrase running the full timeout three times, and joining a WPA2/WPA3 transition hotspot with a non-ASCII name. Not tested: a real open AP, iwlwifi, the reconfigure-at-commit change and the wired state rework, both from today.

Closes ghostbsd/issues#99
Closes ghostbsd/issues#81
Closes ghostbsd/issues#55
Closes ghostbsd/issues#165
Closes #122
Closes #85
Closes #78

Claude-Session: https://claude.ai/code/session_019oi7jvrVW78ZoZKWADbGTN

Wireless is now handled through wpa_supplicant's control socket
instead of by editing /etc/wpa_supplicant.conf and restarting the
daemon. Scanning, saving, forgetting, connecting and disconnecting all
go through wpa_cli, and the daemon started by rc is the only one that
ever runs. networkmgr never runs the wpa_supplicant binary, never
restarts a service on the Wi-Fi path, and never uses shell=True.

Connecting

- connect_to_ssid() joins the clicked network with select_network, so
  with two saved networks in range the one clicked is the one joined.
  wait_for_connection() requires wpa_state=COMPLETED and a matching
  ssid; ifconfig's "associated" is not used, since it appears before
  the key handshake finishes.
- Save on connect. A new network, or a retyped passphrase, lives in the
  daemon only until wait_for_connection() succeeds, then save_config
  writes the file. A passphrase that never works never reaches disk,
  and retyping one badly cannot overwrite a working one.
- A failed attempt reopens the credentials dialog, three attempts, then
  the network is forgotten. A refused key cannot be told from a card
  that never answered on driver_bsd (wpas_auth_failed never fires, the
  network is never marked TEMP-DISABLED), so both are reported as
  "Could not connect".
- reconfigure is sent only at the moment of committing to a connection
  or a forget, so an edit left in the daemon by an abandoned attempt is
  discarded before the next one is written. Opening and cancelling a
  dialog no longer touches the daemon.
- Open networks connect on the first click; the first click used to
  write the block and the second to connect.
- A "Forget Network" submenu lists the saved networks from the daemon,
  including ones out of range.
- WPA3-only networks are listed greyed out as "(WPA3 only)". Base
  wpa_supplicant is built with SAE but driver_bsd offers no SAE key
  management, so such a network can never be joined here; transition
  networks (WPA2-PSK+SAE) still join with a passphrase.

Reading

- Scan results, saved networks and connection state come from wpa_cli
  and are printf-decoded, so an SSID with non-ASCII bytes, quotes or
  backslashes is found, shown and joined by its real name. ifconfig
  list scan renders such a name as a truncated hex column and cannot
  represent it.
- The tray refresh reads only the default-route interface every 30 s
  (2-4 commands, about 5 ms); the full interface list is collected when
  the menu opens. Signal for the connected AP comes from bss current.
- Wired interface state is Disabled, Unplug, Connected or Disconnected,
  tested in that order; a static card with the cable out no longer
  reads Connected. Disconnected offers Disable and Configure instead of
  a no-op Enable.
- The IPv6 tab's interface chooser passed no argument to its handler
  and raised on every change.
- The configuration window reads the gateway from the newest lease,
  toggles the Search domains entry with the other fields, and refuses
  to save a Manual configuration with no primary DNS server (IPv4) or
  no address (IPv6).
- The resolv.conf search domain regex no longer stops at a digit or
  hyphen.

Saving

- Networks are written with add_network/set_network/enable_network/
  save_config. Strings use wpa_supplicant's P"..." form except psk,
  which only accepts plain quotes. wpa_save_config re-chmods the file
  to 0600 because the daemon's own chmod is Android-only.
- WEP keys are quoted unless they are 10, 26 or 32 hex digits; a bare
  ASCII key was read as hex and rejected.
- EAP-TTLS uses autheap= for EAP inner methods (GTC, MD5); auth= made
  the method refuse to start. EAP retries replace the block rather than
  appending one.
- src/wpa_supplicant.conf ships the globals the daemon needs
  (ctrl_interface, ctrl_interface_group=operator, update_config=1, pmf,
  autoscan). setup-nic.py installs it when the file is missing and
  prepends any missing global to an existing one.
- src/sudoers.d/networkmgr grants %operator rather than %wheel, to
  match ctrl_interface_group.

Wired and devd

- Wired Enable/Disable are ifconfig up/down. Handing the default route
  over on link loss is left to devd's auto-switch.py; switch_default in
  net_api duplicated it and raced it for the link-down marker.
- The link-down marker moves from /tmp to /var/run: /tmp is
  world-writable with a predictable name and was opened for writing as
  root, and it is not cleared at boot on a stock install
  (clear_tmp_enable=NO), so a stale marker could send link-up.py down
  the wrong branch. /var/run is root-only and cleanvar empties it at
  every boot.
- link-up.py, auto-switch.py and setup-nic.py run their commands
  without a shell. sysrc values are passed as one argument.

Housekeeping

- Every function and method name is word_an_other_word; classes are
  TrayIcon and NetCardConfigWindow. Every touched function has a
  docstring with its parameters.
- Translation extraction pointed at a directory renamed in 2023;
  networkmgr.pot went from 22 to 50 msgids, merged into every .po, and
  the f-string call sites that could never match a catalogue entry are
  fixed.
- Dialog headings use Pango attributes rather than markup, so an SSID
  containing & or < renders as text.
- pylint 8.10 to 9.65 with a rebuilt .pylintrc; no shell=True anywhere
  in NetworkMgr/ or src/.
- The tests/ tree and requirements.txt are removed; the suite had not
  been run since the 2023 rename. The nine net_api functions only the
  tests imported go with them.
- Version 7.0.

Tested on rtwn0 (RTL8811AU): connecting to a saved network, switching
between two, disconnecting, a stale network timing out and keeping its
block, a wrong passphrase running the full timeout three times, and
joining a WPA2/WPA3 transition hotspot with a non-ASCII name. Not
tested: a real open AP, iwlwifi, the reconfigure-at-commit change and
the wired state rework, both from today.

Closes ghostbsd/issues#99
Closes ghostbsd/issues#81
Closes ghostbsd/issues#55
Closes ghostbsd/issues#165
Closes #122
Closes #85
Closes #78

Claude-Session: https://claude.ai/code/session_019oi7jvrVW78ZoZKWADbGTN

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @ericbsd, your pull request is larger than the review limit of 150,000 diff characters

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review
Status: To triage

2 participants