mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-28 12:27:46 +02:00
wifi(linux): query associated station directly
mdlayher/wifi only dumps stations, which some full-MAC drivers (e.g. out-of-tree Realtek USB) answer with an empty list, leaving RSSI unavailable. Request the associated BSSID's station directly, as `iw link` does, and parse it with native.ParseStationInfo.
This commit is contained in:
@@ -3,24 +3,85 @@
|
|||||||
package wifi
|
package wifi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/system"
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
|
"github.com/mdlayher/genetlink"
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
native "github.com/mdlayher/wifi"
|
native "github.com/mdlayher/wifi"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type linuxClient interface {
|
type linuxClient interface {
|
||||||
Interfaces() ([]*native.Interface, error)
|
Interfaces() ([]*native.Interface, error)
|
||||||
BSS(*native.Interface) (*native.BSS, error)
|
BSS(*native.Interface) (*native.BSS, error)
|
||||||
StationInfo(*native.Interface) ([]*native.StationInfo, error)
|
Station(*native.Interface, net.HardwareAddr) (*native.StationInfo, error)
|
||||||
SetDeadline(time.Time) error
|
SetDeadline(time.Time) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func collect(ctx context.Context) map[string]system.WiFi {
|
// nl80211Client adds a targeted GET_STATION request, as used by `iw link`.
|
||||||
|
// mdlayher/wifi only dumps stations, which some full-MAC drivers (e.g.
|
||||||
|
// out-of-tree Realtek USB) answer with an empty list.
|
||||||
|
type nl80211Client struct {
|
||||||
|
*native.Client
|
||||||
|
conn *genetlink.Conn
|
||||||
|
family genetlink.Family
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNL80211Client() (*nl80211Client, error) {
|
||||||
client, err := native.New()
|
client, err := native.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := genetlink.Dial(nil)
|
||||||
|
if err != nil {
|
||||||
|
client.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
family, err := conn.GetFamily(unix.NL80211_GENL_NAME)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
client.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &nl80211Client{Client: client, conn: conn, family: family}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *nl80211Client) Station(ifi *native.Interface, mac net.HardwareAddr) (*native.StationInfo, error) {
|
||||||
|
ae := netlink.NewAttributeEncoder()
|
||||||
|
ae.Uint32(unix.NL80211_ATTR_IFINDEX, uint32(ifi.Index))
|
||||||
|
ae.Bytes(unix.NL80211_ATTR_MAC, mac)
|
||||||
|
data, err := ae.Encode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msgs, err := c.conn.Execute(genetlink.Message{
|
||||||
|
Header: genetlink.Header{Command: unix.NL80211_CMD_GET_STATION, Version: c.family.Version},
|
||||||
|
Data: data,
|
||||||
|
}, c.family.ID, netlink.Request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return nil, errors.New("no station info")
|
||||||
|
}
|
||||||
|
return native.ParseStationInfo(msgs[0].Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *nl80211Client) SetDeadline(t time.Time) error {
|
||||||
|
return errors.Join(c.Client.SetDeadline(t), c.conn.SetDeadline(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *nl80211Client) Close() error {
|
||||||
|
return errors.Join(c.conn.Close(), c.Client.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func collect(ctx context.Context) map[string]system.WiFi {
|
||||||
|
client, err := newNL80211Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -59,17 +120,12 @@ func collectLinux(ctx context.Context, client linuxClient) map[string]system.WiF
|
|||||||
// Station statistics may require permissions unavailable in default
|
// Station statistics may require permissions unavailable in default
|
||||||
// containers. Keep association even when RSSI cannot be read. Do not
|
// containers. Keep association even when RSSI cannot be read. Do not
|
||||||
// substitute cached scan signal, which may be arbitrarily old.
|
// substitute cached scan signal, which may be arbitrarily old.
|
||||||
stations, err := client.StationInfo(iface)
|
if len(bss.BSSID) > 0 {
|
||||||
if err == nil {
|
if station, err := client.Station(iface, bss.BSSID); err == nil && station != nil {
|
||||||
for _, station := range stations {
|
|
||||||
if station == nil || len(bss.BSSID) == 0 || !bytes.Equal(station.HardwareAddr, bss.BSSID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
signal := float64(station.Signal)
|
signal := float64(station.Signal)
|
||||||
if signal >= -150 && signal < 0 {
|
if signal >= -150 && signal < 0 {
|
||||||
reading.Signal = &signal
|
reading.Signal = &signal
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result[iface.Name] = reading
|
result[iface.Name] = reading
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package wifi
|
package wifi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
@@ -27,9 +28,17 @@ func (f *fakeLinuxClient) Interfaces() ([]*native.Interface, error) {
|
|||||||
func (f *fakeLinuxClient) BSS(i *native.Interface) (*native.BSS, error) {
|
func (f *fakeLinuxClient) BSS(i *native.Interface) (*native.BSS, error) {
|
||||||
return f.bss[i.Name], f.bssErr
|
return f.bss[i.Name], f.bssErr
|
||||||
}
|
}
|
||||||
func (f *fakeLinuxClient) StationInfo(i *native.Interface) ([]*native.StationInfo, error) {
|
func (f *fakeLinuxClient) Station(i *native.Interface, mac net.HardwareAddr) (*native.StationInfo, error) {
|
||||||
f.stationCalls++
|
f.stationCalls++
|
||||||
return f.stations[i.Name], f.stationErr
|
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) SetDeadline(d time.Time) error { f.deadline = d; return f.deadlineErr }
|
||||||
func (f *fakeLinuxClient) Close() error { return nil }
|
func (f *fakeLinuxClient) Close() error { return nil }
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -10,6 +10,8 @@ require (
|
|||||||
github.com/fxamacker/cbor/v2 v2.9.4
|
github.com/fxamacker/cbor/v2 v2.9.4
|
||||||
github.com/gliderlabs/ssh v0.3.8
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/lxzan/gws v1.10.2
|
github.com/lxzan/gws v1.10.2
|
||||||
|
github.com/mdlayher/genetlink v1.4.0
|
||||||
|
github.com/mdlayher/netlink v1.11.2
|
||||||
github.com/mdlayher/wifi v0.8.0
|
github.com/mdlayher/wifi v0.8.0
|
||||||
github.com/nicholas-fedor/shoutrrr v0.21.0
|
github.com/nicholas-fedor/shoutrrr v0.21.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
@@ -52,8 +54,6 @@ require (
|
|||||||
github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 // indirect
|
github.com/lufia/plan9stats v0.0.0-20260802145828-341c2f0c90b5 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/mdlayher/genetlink v1.4.0 // indirect
|
|
||||||
github.com/mdlayher/netlink v1.11.2 // indirect
|
|
||||||
github.com/mdlayher/socket v0.6.0 // indirect
|
github.com/mdlayher/socket v0.6.0 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pocketbase/ozzo-validation/v4 v4.3.0 // indirect
|
github.com/pocketbase/ozzo-validation/v4 v4.3.0 // indirect
|
||||||
|
|||||||
Reference in New Issue
Block a user