From b115e2aa6ae22652f6a9ee4d25475af9250a0bd4 Mon Sep 17 00:00:00 2001 From: henrygd Date: Fri, 2 Oct 2026 12:46:38 -0400 Subject: [PATCH] wifi(linux): skip nl80211 when no wireless devices are registered Check /sys/class/ieee80211 before opening netlink sockets. Looking up the nl80211 family while cfg80211 is not loaded makes the kernel run modprobe, which loads the module on hosts without wireless hardware, or fails again on every poll when the module is unavailable. --- agent/wifi/README.md | 1 + agent/wifi/wifi_linux.go | 21 +++++++++++++++++++++ agent/wifi/wifi_linux_test.go | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/agent/wifi/README.md b/agent/wifi/README.md index d8dd83446..e195e18ce 100644 --- a/agent/wifi/README.md +++ b/agent/wifi/README.md @@ -6,6 +6,7 @@ snapshot keyed by interface (`s` SSID, `r` RSSI in dBm when available); interval only; real-time requests reuse the last snapshot. - Linux: nl80211 via `github.com/mdlayher/wifi`. Docker needs `network_mode: host`. + Skipped when `/sys/class/ieee80211` lists no wireless devices. - macOS: CoreWLAN via `osascript` (JXA). SSID may be redacted by privacy settings. - Windows: native WLAN API, keyed by adapter alias (e.g. `Wi-Fi`), falling back to interface GUID. diff --git a/agent/wifi/wifi_linux.go b/agent/wifi/wifi_linux.go index d2c212897..926d658fe 100644 --- a/agent/wifi/wifi_linux.go +++ b/agent/wifi/wifi_linux.go @@ -6,6 +6,7 @@ import ( "context" "errors" "net" + "os" "time" "github.com/henrygd/beszel/internal/entities/system" @@ -80,7 +81,27 @@ func (c *nl80211Client) Close() error { return errors.Join(c.conn.Close(), c.Client.Close()) } +// wiphyClassDir lists the wireless devices registered with cfg80211. +const wiphyClassDir = "/sys/class/ieee80211" + +// hasWirelessDevice gates nl80211 access. Looking up the nl80211 family while +// cfg80211 is not loaded makes the kernel run modprobe, which loads the module +// on hosts without wireless hardware, or fails again on every poll. Not cached, +// so devices registered after startup are still picked up. +func hasWirelessDevice(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return false + } + defer f.Close() + names, _ := f.Readdirnames(1) + return len(names) > 0 +} + func collect(ctx context.Context) map[string]system.WiFi { + if !hasWirelessDevice(wiphyClassDir) { + return nil + } client, err := newNL80211Client() if err != nil { return nil diff --git a/agent/wifi/wifi_linux_test.go b/agent/wifi/wifi_linux_test.go index 338c77451..139601c82 100644 --- a/agent/wifi/wifi_linux_test.go +++ b/agent/wifi/wifi_linux_test.go @@ -7,6 +7,8 @@ import ( "context" "errors" "net" + "os" + "path/filepath" "testing" "time" @@ -132,6 +134,22 @@ func TestLinuxInvalidSSID(t *testing.T) { } } +func TestHasWirelessDevice(t *testing.T) { + dir := t.TempDir() + if hasWirelessDevice(filepath.Join(dir, "missing")) { + t.Fatal("missing class directory") + } + if hasWirelessDevice(dir) { + t.Fatal("empty class directory") + } + if err := os.Mkdir(filepath.Join(dir, "phy0"), 0o755); err != nil { + t.Fatal(err) + } + if !hasWirelessDevice(dir) { + t.Fatal("registered device not detected") + } +} + func TestLinuxCancelled(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel()