mirror of
https://github.com/henrygd/beszel.git
synced 2026-10-03 06:47:46 +02:00
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.
161 lines
5.4 KiB
Go
161 lines
5.4 KiB
Go
//go:build linux
|
|
|
|
package wifi
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
native "github.com/mdlayher/wifi"
|
|
)
|
|
|
|
type fakeLinuxClient struct {
|
|
interfaces []*native.Interface
|
|
bss map[string]*native.BSS
|
|
stations map[string][]*native.StationInfo
|
|
interfacesErr, bssErr, stationErr, deadlineErr error
|
|
deadline time.Time
|
|
stationCalls int
|
|
}
|
|
|
|
func (f *fakeLinuxClient) Interfaces() ([]*native.Interface, error) {
|
|
return f.interfaces, f.interfacesErr
|
|
}
|
|
func (f *fakeLinuxClient) BSS(i *native.Interface) (*native.BSS, error) {
|
|
return f.bss[i.Name], f.bssErr
|
|
}
|
|
func (f *fakeLinuxClient) Station(i *native.Interface, mac net.HardwareAddr) (*native.StationInfo, error) {
|
|
f.stationCalls++
|
|
if f.stationErr != nil {
|
|
return nil, f.stationErr
|
|
}
|
|
for _, station := range f.stations[i.Name] {
|
|
if bytes.Equal(station.HardwareAddr, mac) {
|
|
return station, nil
|
|
}
|
|
}
|
|
return nil, errors.New("no such station")
|
|
}
|
|
func (f *fakeLinuxClient) SetDeadline(d time.Time) error { f.deadline = d; return f.deadlineErr }
|
|
func (f *fakeLinuxClient) Close() error { return nil }
|
|
|
|
func connectedClient() *fakeLinuxClient {
|
|
mac := net.HardwareAddr{1, 2, 3, 4, 5, 6}
|
|
return &fakeLinuxClient{
|
|
interfaces: []*native.Interface{{Name: "wlan0", Type: native.InterfaceTypeStation}},
|
|
bss: map[string]*native.BSS{"wlan0": {Status: native.BSSStatusAssociated, SSID: "home", BSSID: mac}},
|
|
stations: map[string][]*native.StationInfo{"wlan0": {{HardwareAddr: mac, Signal: -52}}},
|
|
}
|
|
}
|
|
|
|
func TestLinuxSnapshots(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
modify func(*fakeLinuxClient)
|
|
want int
|
|
wantSignal bool
|
|
}{
|
|
{"connected", func(f *fakeLinuxClient) {}, 1, true},
|
|
{"multiple", func(f *fakeLinuxClient) {
|
|
f.interfaces = append(f.interfaces, &native.Interface{Name: "wlan1", Type: native.InterfaceTypeStation})
|
|
f.bss["wlan1"] = f.bss["wlan0"]
|
|
}, 2, true},
|
|
{"unsupported", func(f *fakeLinuxClient) { f.interfacesErr = errors.New("unsupported") }, 0, false},
|
|
{"association denied", func(f *fakeLinuxClient) { f.bssErr = errors.New("denied") }, 0, false},
|
|
{"disconnected", func(f *fakeLinuxClient) { f.bss["wlan0"] = nil }, 0, false},
|
|
{"authenticated only", func(f *fakeLinuxClient) { f.bss["wlan0"].Status = native.BSSStatusAuthenticated }, 0, false},
|
|
{"cached nearby BSS", func(f *fakeLinuxClient) { f.bss["wlan0"].Status = native.BSSStatusNotAssociated }, 0, false},
|
|
{"access point", func(f *fakeLinuxClient) { f.interfaces[0].Type = native.InterfaceTypeAP }, 0, false},
|
|
{"ad hoc", func(f *fakeLinuxClient) { f.bss["wlan0"].Status = native.BSSStatusIBSSJoined }, 0, false},
|
|
{"station permission denied", func(f *fakeLinuxClient) {
|
|
f.stationErr = errors.New("permission denied")
|
|
f.bss["wlan0"].Signal = -4200
|
|
}, 1, false},
|
|
{"no station data", func(f *fakeLinuxClient) { f.stations = nil }, 1, false},
|
|
{"different AP", func(f *fakeLinuxClient) { f.stations["wlan0"][0].HardwareAddr = net.HardwareAddr{9, 8, 7, 6, 5, 4} }, 1, false},
|
|
{"missing signal", func(f *fakeLinuxClient) { f.stations["wlan0"][0].Signal = 0 }, 1, false},
|
|
{"invalid signal", func(f *fakeLinuxClient) { f.stations["wlan0"][0].Signal = -151 }, 1, false},
|
|
{"deadline failure", func(f *fakeLinuxClient) { f.deadlineErr = errors.New("deadline") }, 0, false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
f := connectedClient()
|
|
tc.modify(f)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
got := collectLinux(ctx, f)
|
|
if len(got) != tc.want {
|
|
t.Fatalf("got %#v", got)
|
|
}
|
|
if tc.want > 0 && (got["wlan0"].Signal != nil) != tc.wantSignal {
|
|
t.Fatalf("signal: %#v", got["wlan0"])
|
|
}
|
|
if tc.wantSignal && *got["wlan0"].Signal != -52 {
|
|
t.Fatal(got)
|
|
}
|
|
if tc.want == 0 && f.stationCalls != 0 {
|
|
t.Fatal("queried station without association")
|
|
}
|
|
deadline, _ := ctx.Deadline()
|
|
if f.deadline != deadline {
|
|
t.Fatal("deadline not shared")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReconnect(t *testing.T) {
|
|
f := connectedClient()
|
|
if len(collectLinux(context.Background(), f)) != 1 {
|
|
t.Fatal("initial")
|
|
}
|
|
f.bss["wlan0"].Status = native.BSSStatusNotAssociated
|
|
if len(collectLinux(context.Background(), f)) != 0 {
|
|
t.Fatal("stale association")
|
|
}
|
|
f.bss["wlan0"].Status = native.BSSStatusAssociated
|
|
f.bss["wlan0"].SSID = "new"
|
|
if collectLinux(context.Background(), f)["wlan0"].SSID != "new" {
|
|
t.Fatal("stale SSID")
|
|
}
|
|
}
|
|
|
|
func TestLinuxInvalidSSID(t *testing.T) {
|
|
f := connectedClient()
|
|
f.bss["wlan0"].SSID = "raw\xff"
|
|
got := collectLinux(context.Background(), f)
|
|
if len(got) != 1 || got["wlan0"].SSID != "" || got["wlan0"].Signal == nil {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
|
|
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()
|
|
f := connectedClient()
|
|
if len(collectLinux(ctx, f)) != 0 || f.stationCalls != 0 {
|
|
t.Fatal("ignored cancellation")
|
|
}
|
|
}
|