From ca3f07feef0d482ac4413458c3e7b17a86d5627f Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Tue, 17 Jan 2017 00:54:18 -0500 Subject: [PATCH] Update vendored wifi, handle stations with missing info --- collector/wifi_linux.go | 16 +- vendor/github.com/mdlayher/wifi/client.go | 13 ++ .../github.com/mdlayher/wifi/client_linux.go | 158 ++++++++++++++++-- .../github.com/mdlayher/wifi/client_others.go | 17 +- vendor/github.com/mdlayher/wifi/wifi.go | 119 ++++++++++++- vendor/vendor.json | 6 +- 6 files changed, 288 insertions(+), 41 deletions(-) diff --git a/collector/wifi_linux.go b/collector/wifi_linux.go index 994f8080..959d5f46 100644 --- a/collector/wifi_linux.go +++ b/collector/wifi_linux.go @@ -155,12 +155,6 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { continue } - info, err := stat.StationInfo(ifi) - if err != nil { - return fmt.Errorf("failed to retrieve station info for device %s: %v", - ifi.Name, err) - } - ch <- prometheus.MustNewConstMetric( c.InterfaceFrequencyHertz, prometheus.GaugeValue, @@ -168,6 +162,16 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { ifi.Name, ) + info, err := stat.StationInfo(ifi) + if err != nil { + if os.IsNotExist(err) { + continue + } + + return fmt.Errorf("failed to retrieve station info for device %s: %v", + ifi.Name, err) + } + c.updateStationStats(ch, ifi.Name, info) } diff --git a/vendor/github.com/mdlayher/wifi/client.go b/vendor/github.com/mdlayher/wifi/client.go index 47fe6d50..3de6dace 100644 --- a/vendor/github.com/mdlayher/wifi/client.go +++ b/vendor/github.com/mdlayher/wifi/client.go @@ -2,12 +2,19 @@ package wifi import ( "errors" + "fmt" + "runtime" ) var ( // errNotStation is returned when attempting to query station info for // an interface which is not a station. errNotStation = errors.New("interface is not a station") + + // errUnimplemented is returned by all functions on platforms that + // do not have package wifi implemented. + errUnimplemented = fmt.Errorf("package wifi not implemented on %s/%s", + runtime.GOOS, runtime.GOARCH) ) // A Client is a type which can access WiFi device actions and statistics @@ -38,6 +45,11 @@ func (c *Client) Interfaces() ([]*Interface, error) { return c.c.Interfaces() } +// BSS retrieves the BSS associated with a WiFi interface. +func (c *Client) BSS(ifi *Interface) (*BSS, error) { + return c.c.BSS(ifi) +} + // StationInfo retrieves statistics about a WiFi interface operating in // station mode. func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { @@ -52,5 +64,6 @@ func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { type osClient interface { Close() error Interfaces() ([]*Interface, error) + BSS(ifi *Interface) (*BSS, error) StationInfo(ifi *Interface) (*StationInfo, error) } diff --git a/vendor/github.com/mdlayher/wifi/client_linux.go b/vendor/github.com/mdlayher/wifi/client_linux.go index 1eb5398a..4d2c4e99 100644 --- a/vendor/github.com/mdlayher/wifi/client_linux.go +++ b/vendor/github.com/mdlayher/wifi/client_linux.go @@ -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. diff --git a/vendor/github.com/mdlayher/wifi/client_others.go b/vendor/github.com/mdlayher/wifi/client_others.go index a694e7c8..1848b88d 100644 --- a/vendor/github.com/mdlayher/wifi/client_others.go +++ b/vendor/github.com/mdlayher/wifi/client_others.go @@ -2,18 +2,6 @@ package wifi -import ( - "fmt" - "runtime" -) - -var ( - // errUnimplemented is returned by all functions on platforms that - // do not have package wifi implemented. - errUnimplemented = fmt.Errorf("package wifi not implemented on %s/%s", - runtime.GOOS, runtime.GOARCH) -) - var _ osClient = &client{} // A conn is the no-op implementation of a netlink sockets connection. @@ -34,6 +22,11 @@ func (c *client) Interfaces() ([]*Interface, error) { return nil, errUnimplemented } +// BSS always returns an error. +func (c *client) BSS(ifi *Interface) (*BSS, error) { + return nil, errUnimplemented +} + // StationInfo always returns an error. func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { return nil, errUnimplemented diff --git a/vendor/github.com/mdlayher/wifi/wifi.go b/vendor/github.com/mdlayher/wifi/wifi.go index d39b8051..4eabec1f 100644 --- a/vendor/github.com/mdlayher/wifi/wifi.go +++ b/vendor/github.com/mdlayher/wifi/wifi.go @@ -1,10 +1,17 @@ package wifi import ( + "errors" + "fmt" "net" "time" ) +var ( + // errInvalidIE is returned when one or more IEs are malformed. + errInvalidIE = errors.New("invalid 802.11 information element") +) + // An InterfaceType is the operating mode of an Interface. type InterfaceType int @@ -96,16 +103,16 @@ type StationInfo struct { Inactive time.Duration // The number of bytes received by this station. - ReceivedBytes uint64 + ReceivedBytes int // The number of bytes transmitted by this station. - TransmittedBytes uint64 + TransmittedBytes int // The number of packets received by this station. - ReceivedPackets uint32 + ReceivedPackets int // The number of packets transmitted by this station. - TransmittedPackets uint32 + TransmittedPackets int // The current data receive bitrate, in bits/second. ReceiveBitrate int @@ -117,11 +124,109 @@ type StationInfo struct { Signal int // The number of times the station has had to retry while sending a packet. - TransmitRetries uint32 + TransmitRetries int // The number of times a packet transmission failed. - TransmitFailed uint32 + TransmitFailed int // The number of times a beacon loss was detected. - BeaconLoss uint32 + BeaconLoss int +} + +// A BSS is an 802.11 basic service set. It contains information about a wireless +// network associated with an Interface. +type BSS struct { + // The service set identifier, or "network name" of the BSS. + SSID string + + // The BSS service set identifier. In infrastructure mode, this is the + // hardware address of the wireless access point that a client is associated + // with. + BSSID net.HardwareAddr + + // The frequency used by the BSS, in MHz. + Frequency int + + // The interval between beacon transmissions for this BSS. + BeaconInterval time.Duration + + // The time since the client last scanned this BSS's information. + LastSeen time.Duration + + // The status of the client within the BSS. + Status BSSStatus +} + +// A BSSStatus indicates the current status of client within a BSS. +type BSSStatus int + +const ( + // BSSStatusAuthenticated indicates that a client is authenticated with a BSS. + BSSStatusAuthenticated BSSStatus = iota + + // BSSStatusAssociated indicates that a client is associated with a BSS. + BSSStatusAssociated + + // BSSStatusIBSSJoined indicates that a client has joined an independent BSS. + BSSStatusIBSSJoined +) + +// String returns the string representation of a BSSStatus. +func (s BSSStatus) String() string { + switch s { + case BSSStatusAuthenticated: + return "authenticated" + case BSSStatusAssociated: + return "associated" + case BSSStatusIBSSJoined: + return "IBSS joined" + default: + return fmt.Sprintf("unknown(%d)", s) + } +} + +// List of 802.11 Information Element types. +const ( + ieSSID = 0 +) + +// An ie is an 802.11 information element. +type ie struct { + ID uint8 + // Length field implied by length of data + Data []byte +} + +// parseIEs parses zero or more ies from a byte slice. +// Reference: +// https://www.safaribooksonline.com/library/view/80211-wireless-networks/0596100523/ch04.html#wireless802dot112-CHP-4-FIG-31 +func parseIEs(b []byte) ([]ie, error) { + var ies []ie + var i int + for { + if len(b[i:]) == 0 { + break + } + if len(b[i:]) < 2 { + return nil, errInvalidIE + } + + id := b[i] + i++ + l := int(b[i]) + i++ + + if len(b[i:]) < l { + return nil, errInvalidIE + } + + ies = append(ies, ie{ + ID: id, + Data: b[i : i+l], + }) + + i += l + } + + return ies, nil } diff --git a/vendor/vendor.json b/vendor/vendor.json index ef571fe6..c32c33e5 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -69,10 +69,10 @@ "revisionTime": "2017-01-04T04:59:06Z" }, { - "checksumSHA1": "l8M/rZH5s/ZVtCCyeiUQXZ5FosA=", + "checksumSHA1": "J6L0K9aHO8riicB4BY8/WHb6wBI=", "path": "github.com/mdlayher/wifi", - "revision": "eb8b29b956ba5ff2fdd2d2f1f0b988b57fd3d8a3", - "revisionTime": "2017-01-12T20:47:29Z" + "revision": "85a20a7adc659e5007fb9dd0961ba4e8b7ea2f80", + "revisionTime": "2017-01-17T05:43:47Z" }, { "checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=",