import glob import re import shutil import socket import subprocess import time from typing import Any import psutil _wifi_cache: dict[str, tuple[float, str | None]] = {} _WIFI_TTL = 15.0 _SSID_RE = re.compile(r"SSID:\s+(\S.*)") def _wifi_ifaces() -> set[str]: """Return the names of interfaces that are wireless. Returns: Interface names having a /sys/class/net//wireless entry. """ return {p.split("/")[-2] for p in glob.glob("/sys/class/net/*/wireless")} def _ssid(iface: str) -> str | None: r"""Get the SSID currently associated on a wifi interface. Shells out to `iw dev link` and matches the unquoted `SSID: name` line; the working regex is `SSID:\s+(\S.*)` (a `$` anchor would only match the final line of the output without MULTILINE). The result is cached per interface for 15 s so the 2 s poll doesn't spawn a subprocess every cycle. Args: iface: network interface name. Returns: The SSID, or None if not associated or `iw` is unavailable. """ hit = _wifi_cache.get(iface) now = time.monotonic() if hit is not None and now - hit[0] < _WIFI_TTL: return hit[1] ssid: str | None = None if shutil.which("iw"): try: out = subprocess.run( ["iw", "dev", iface, "link"], capture_output=True, text=True, timeout=3, check=False ).stdout m = _SSID_RE.search(out) if m: ssid = m.group(1).strip().strip('"') or None except (OSError, subprocess.SubprocessError): pass _wifi_cache[iface] = (now, ssid) return ssid def sample() -> dict[str, Any | None]: """Collect interface list and wifi association for the overview page. Only interfaces that are up and are not the loopback are included; each entry carries its IPv4 addresses. The first up wifi interface (alphabetical order) provides the displayed SSID. Returns: A dict with "net_ifaces" (list of {name, ipv4}) and "net_wifi" ({iface, ssid} or None). """ addrs = psutil.net_if_addrs() stats = psutil.net_if_stats() wifi_set = _wifi_ifaces() ifaces: list[dict[str, Any]] = [] wifi: dict[str, Any] | None = None for name in sorted(addrs): if name == "lo": continue st = stats.get(name) if st is None or not bool(st.isup): continue ipv4 = [a.address for a in addrs[name] if a.family == socket.AF_INET] ifaces.append({"name": name, "ipv4": ipv4}) if name in wifi_set and wifi is None: wifi = {"iface": name, "ssid": _ssid(name)} return {"net_ifaces": ifaces, "net_wifi": wifi}