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()