WifiManager: cache connections until new/removed connection (#37175)

* new/removed conns signal

* clean up

* only get connections when adding/removing not every refresh

* add debug

* block

* Revert "block"

This reverts commit 30bbffca8d2db21c53d7a3601ae46bf05e2a7cd5.

* rm debug

* block on any new message, faster conn rem/add reaction

* better names
This commit is contained in:
Shane Smiskol
2026-02-11 17:07:41 -08:00
committed by GitHub
parent f03efab907
commit 0072449b01

View File

@@ -146,6 +146,7 @@ class WifiManager:
self._wifi_device: str | None = None
# State
self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals
self._connecting_to_ssid: str = ""
self._ipv4_address: str = ""
self._current_network_metered: MeteredType = MeteredType.UNKNOWN
@@ -181,7 +182,8 @@ class WifiManager:
self._scan_thread.start()
self._state_thread.start()
if Params is not None and self._tethering_ssid not in self._get_connections():
self._update_connections()
if Params is not None and self._tethering_ssid not in self._connections:
self._add_tethering_connection()
self._tethering_password = self._get_tethering_password()
@@ -235,45 +237,69 @@ class WifiManager:
self._last_network_update = 0.0
def _monitor_state(self):
rule = MatchRule(
type="signal",
interface=NM_DEVICE_IFACE,
member="StateChanged",
path=self._wifi_device,
# Filter for signals
rules = (
MatchRule(
type="signal",
interface=NM_DEVICE_IFACE,
member="StateChanged",
path=self._wifi_device,
),
MatchRule(
type="signal",
interface=NM_SETTINGS_IFACE,
member="NewConnection",
path=NM_SETTINGS_PATH,
),
MatchRule(
type="signal",
interface=NM_SETTINGS_IFACE,
member="ConnectionRemoved",
path=NM_SETTINGS_PATH,
)
)
# Filter for StateChanged signal
self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule))
for rule in rules:
self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule))
with self._conn_monitor.filter(rule, bufsize=SIGNAL_QUEUE_SIZE) as q:
with (self._conn_monitor.filter(rules[0], bufsize=SIGNAL_QUEUE_SIZE) as state_q,
self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q,
self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q):
while not self._exit:
if not self._active:
time.sleep(1)
continue
# Block until a matching signal arrives
try:
msg = self._conn_monitor.recv_until_filtered(q, timeout=1)
self._conn_monitor.recv_messages(timeout=1)
except TimeoutError:
continue
new_state, previous_state, change_reason = msg.body
# Connection added/removed
if len(new_conn_q) or len(removed_conn_q):
new_conn_q.clear()
removed_conn_q.clear()
self._update_connections()
# BAD PASSWORD
if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid):
self.forget_connection(self._connecting_to_ssid, block=True)
self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid)
self._connecting_to_ssid = ""
# Device state changes
while len(state_q):
new_state, previous_state, change_reason = state_q.popleft().body
elif new_state == NMDeviceState.ACTIVATED:
if len(self._activated):
self._update_networks()
self._enqueue_callbacks(self._activated)
self._connecting_to_ssid = ""
# BAD PASSWORD
if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid):
self.forget_connection(self._connecting_to_ssid, block=True)
self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid)
self._connecting_to_ssid = ""
elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION:
self._connecting_to_ssid = ""
self._enqueue_callbacks(self._forgotten)
elif new_state == NMDeviceState.ACTIVATED:
if len(self._activated):
self._update_networks()
self._enqueue_callbacks(self._activated)
self._connecting_to_ssid = ""
elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION:
self._connecting_to_ssid = ""
self._enqueue_callbacks(self._forgotten)
def _network_scanner(self):
while not self._exit:
@@ -307,7 +333,7 @@ class WifiManager:
cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}")
return None
def _get_connections(self) -> dict[str, str]:
def _update_connections(self) -> None:
settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE)
known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0]
@@ -323,7 +349,7 @@ class WifiManager:
ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace")
if ssid != "":
conns[ssid] = conn_path
return conns
self._connections = conns
def _get_active_connections(self):
return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1]
@@ -413,7 +439,7 @@ class WifiManager:
def forget_connection(self, ssid: str, block: bool = False):
def worker():
conn_path = self._get_connections().get(ssid, None)
conn_path = self._connections.get(ssid, None)
if conn_path is not None:
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete'))
@@ -429,7 +455,7 @@ class WifiManager:
def activate_connection(self, ssid: str, block: bool = False):
def worker():
conn_path = self._get_connections().get(ssid, None)
conn_path = self._connections.get(ssid, None)
if conn_path is not None:
if self._wifi_device is None:
cloudlog.warning("No WiFi device found")
@@ -465,7 +491,7 @@ class WifiManager:
def set_tethering_password(self, password: str):
def worker():
conn_path = self._get_connections().get(self._tethering_ssid, None)
conn_path = self._connections.get(self._tethering_ssid, None)
if conn_path is None:
cloudlog.warning('No tethering connection found')
return
@@ -490,7 +516,7 @@ class WifiManager:
threading.Thread(target=worker, daemon=True).start()
def _get_tethering_password(self) -> str:
conn_path = self._get_connections().get(self._tethering_ssid, None)
conn_path = self._connections.get(self._tethering_ssid, None)
if conn_path is None:
cloudlog.warning('No tethering connection found')
return ''
@@ -601,8 +627,7 @@ class WifiManager:
# catch all for parsing errors
cloudlog.exception(f"Failed to parse AP properties for {ap_path}")
known_connections = self._get_connections()
networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()]
networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()]
# sort with quantized strength to reduce jumping
networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower()))
self._networks = networks