|
|
|
@ -3,11 +3,13 @@
|
|
|
|
|
package wifi |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"errors" |
|
|
|
|
"math" |
|
|
|
|
"net" |
|
|
|
|
"os" |
|
|
|
|
"time" |
|
|
|
|
"unicode/utf8" |
|
|
|
|
|
|
|
|
|
"github.com/mdlayher/netlink" |
|
|
|
|
"github.com/mdlayher/netlink/genetlink" |
|
|
|
@ -96,10 +98,40 @@ func (c *client) Interfaces() ([]*Interface, error) {
|
|
|
|
|
return parseInterfaces(msgs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// BSS requests that nl80211 return the BSS for the specified Interface.
|
|
|
|
|
func (c *client) BSS(ifi *Interface) (*BSS, error) { |
|
|
|
|
b, err := netlink.MarshalAttributes(ifi.idAttrs()) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ask nl80211 to retrieve BSS information for the interface specified
|
|
|
|
|
// by its attributes
|
|
|
|
|
req := genetlink.Message{ |
|
|
|
|
Header: genetlink.Header{ |
|
|
|
|
Command: nl80211.CmdGetScan, |
|
|
|
|
Version: c.familyVersion, |
|
|
|
|
}, |
|
|
|
|
Data: b, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
flags := netlink.HeaderFlagsRequest | netlink.HeaderFlagsDump |
|
|
|
|
msgs, err := c.c.Execute(req, c.familyID, flags) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := c.checkMessages(msgs, nl80211.CmdNewScanResults); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return parseBSS(msgs) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StationInfo requests that nl80211 return station info for the specified
|
|
|
|
|
// Interface.
|
|
|
|
|
func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { |
|
|
|
|
b, err := netlink.MarshalAttributes(ifi.stationInfoAttrs()) |
|
|
|
|
b, err := netlink.MarshalAttributes(ifi.idAttrs()) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
@ -176,9 +208,9 @@ func parseInterfaces(msgs []genetlink.Message) ([]*Interface, error) {
|
|
|
|
|
return ifis, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// stationInfoAttrs returns the netlink attributes required from an Interface
|
|
|
|
|
// to retrieve a StationInfo.
|
|
|
|
|
func (ifi *Interface) stationInfoAttrs() []netlink.Attribute { |
|
|
|
|
// idAttrs returns the netlink attributes required from an Interface to retrieve
|
|
|
|
|
// more data about it.
|
|
|
|
|
func (ifi *Interface) idAttrs() []netlink.Attribute { |
|
|
|
|
return []netlink.Attribute{ |
|
|
|
|
{ |
|
|
|
|
Type: nl80211.AttrIfindex, |
|
|
|
@ -217,6 +249,80 @@ func (ifi *Interface) parseAttributes(attrs []netlink.Attribute) error {
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// parseBSS parses a single BSS with a status attribute from nl80211 BSS messages.
|
|
|
|
|
func parseBSS(msgs []genetlink.Message) (*BSS, error) { |
|
|
|
|
for _, m := range msgs { |
|
|
|
|
attrs, err := netlink.UnmarshalAttributes(m.Data) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, a := range attrs { |
|
|
|
|
if a.Type != nl80211.AttrBss { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nattrs, err := netlink.UnmarshalAttributes(a.Data) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// The BSS which is associated with an interface will have a status
|
|
|
|
|
// attribute
|
|
|
|
|
if !attrsContain(nattrs, nl80211.BssStatus) { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var bss BSS |
|
|
|
|
if err := (&bss).parseAttributes(nattrs); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &bss, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil, os.ErrNotExist |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// parseAttributes parses netlink attributes into a BSS's fields.
|
|
|
|
|
func (b *BSS) parseAttributes(attrs []netlink.Attribute) error { |
|
|
|
|
for _, a := range attrs { |
|
|
|
|
switch a.Type { |
|
|
|
|
case nl80211.BssBssid: |
|
|
|
|
b.BSSID = net.HardwareAddr(a.Data) |
|
|
|
|
case nl80211.BssFrequency: |
|
|
|
|
b.Frequency = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.BssBeaconInterval: |
|
|
|
|
// Raw value is in "Time Units (TU)". See:
|
|
|
|
|
// https://en.wikipedia.org/wiki/Beacon_frame
|
|
|
|
|
b.BeaconInterval = time.Duration(nlenc.Uint16(a.Data)) * 1024 * time.Microsecond |
|
|
|
|
case nl80211.BssSeenMsAgo: |
|
|
|
|
// * @NL80211_BSS_SEEN_MS_AGO: age of this BSS entry in ms
|
|
|
|
|
b.LastSeen = time.Duration(nlenc.Uint32(a.Data)) * time.Millisecond |
|
|
|
|
case nl80211.BssStatus: |
|
|
|
|
// NOTE: BSSStatus copies the ordering of nl80211's BSS status
|
|
|
|
|
// constants. This may not be the case on other operating systems.
|
|
|
|
|
b.Status = BSSStatus(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.BssInformationElements: |
|
|
|
|
ies, err := parseIEs(a.Data) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO(mdlayher): return more IEs if they end up being generally useful
|
|
|
|
|
for _, ie := range ies { |
|
|
|
|
switch ie.ID { |
|
|
|
|
case ieSSID: |
|
|
|
|
b.SSID = decodeSSID(ie.Data) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// parseStationInfo parses StationInfo attributes from a byte slice of
|
|
|
|
|
// netlink attributes.
|
|
|
|
|
func parseStationInfo(b []byte) (*StationInfo, error) { |
|
|
|
@ -262,23 +368,23 @@ func (info *StationInfo) parseAttributes(attrs []netlink.Attribute) error {
|
|
|
|
|
// * @NL80211_STA_INFO_INACTIVE_TIME: time since last activity (u32, msecs)
|
|
|
|
|
info.Inactive = time.Duration(nlenc.Uint32(a.Data)) * time.Millisecond |
|
|
|
|
case nl80211.StaInfoRxBytes64: |
|
|
|
|
info.ReceivedBytes = nlenc.Uint64(a.Data) |
|
|
|
|
info.ReceivedBytes = int(nlenc.Uint64(a.Data)) |
|
|
|
|
case nl80211.StaInfoTxBytes64: |
|
|
|
|
info.TransmittedBytes = nlenc.Uint64(a.Data) |
|
|
|
|
info.TransmittedBytes = int(nlenc.Uint64(a.Data)) |
|
|
|
|
case nl80211.StaInfoSignal: |
|
|
|
|
// Converted into the typical negative strength format
|
|
|
|
|
// * @NL80211_STA_INFO_SIGNAL: signal strength of last received PPDU (u8, dBm)
|
|
|
|
|
info.Signal = int(a.Data[0]) - math.MaxUint8 |
|
|
|
|
case nl80211.StaInfoRxPackets: |
|
|
|
|
info.ReceivedPackets = nlenc.Uint32(a.Data) |
|
|
|
|
info.ReceivedPackets = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.StaInfoTxPackets: |
|
|
|
|
info.TransmittedPackets = nlenc.Uint32(a.Data) |
|
|
|
|
info.TransmittedPackets = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.StaInfoTxRetries: |
|
|
|
|
info.TransmitRetries = nlenc.Uint32(a.Data) |
|
|
|
|
info.TransmitRetries = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.StaInfoTxFailed: |
|
|
|
|
info.TransmitFailed = nlenc.Uint32(a.Data) |
|
|
|
|
info.TransmitFailed = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.StaInfoBeaconLoss: |
|
|
|
|
info.BeaconLoss = nlenc.Uint32(a.Data) |
|
|
|
|
info.BeaconLoss = int(nlenc.Uint32(a.Data)) |
|
|
|
|
case nl80211.StaInfoRxBitrate, nl80211.StaInfoTxBitrate: |
|
|
|
|
rate, err := parseRateInfo(a.Data) |
|
|
|
|
if err != nil { |
|
|
|
@ -299,10 +405,10 @@ func (info *StationInfo) parseAttributes(attrs []netlink.Attribute) error {
|
|
|
|
|
// If the 64-bit counters appear later in the slice, they will overwrite
|
|
|
|
|
// these values.
|
|
|
|
|
if info.ReceivedBytes == 0 && a.Type == nl80211.StaInfoRxBytes { |
|
|
|
|
info.ReceivedBytes = uint64(nlenc.Uint32(a.Data)) |
|
|
|
|
info.ReceivedBytes = int(nlenc.Uint32(a.Data)) |
|
|
|
|
} |
|
|
|
|
if info.TransmittedBytes == 0 && a.Type == nl80211.StaInfoTxBytes { |
|
|
|
|
info.TransmittedBytes = uint64(nlenc.Uint32(a.Data)) |
|
|
|
|
info.TransmittedBytes = int(nlenc.Uint32(a.Data)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -345,6 +451,32 @@ func parseRateInfo(b []byte) (*rateInfo, error) {
|
|
|
|
|
return &info, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// attrsContain checks if a slice of netlink attributes contains an attribute
|
|
|
|
|
// with the specified type.
|
|
|
|
|
func attrsContain(attrs []netlink.Attribute, typ uint16) bool { |
|
|
|
|
for _, a := range attrs { |
|
|
|
|
if a.Type == typ { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// decodeSSID safely parses a byte slice into UTF-8 runes, and returns the
|
|
|
|
|
// resulting string from the runes.
|
|
|
|
|
func decodeSSID(b []byte) string { |
|
|
|
|
buf := bytes.NewBuffer(nil) |
|
|
|
|
for len(b) > 0 { |
|
|
|
|
r, size := utf8.DecodeRune(b) |
|
|
|
|
b = b[size:] |
|
|
|
|
|
|
|
|
|
buf.WriteRune(r) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return buf.String() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ genl = &sysGENL{} |
|
|
|
|
|
|
|
|
|
// sysGENL is the system implementation of genl, using generic netlink.
|
|
|
|
|