Merge pull request #425 from mdlayher/wifi-update

Update vendored wifi, handle stations with missing info
This commit is contained in:
Ben Kochie 2017-01-20 08:43:44 -05:00 committed by GitHub
commit acb495ccab
6 changed files with 288 additions and 41 deletions

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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.

View file

@ -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

View file

@ -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
}

6
vendor/vendor.json vendored
View file

@ -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=",