diff --git a/collector/wifi_linux.go b/collector/wifi_linux.go index 4eb1b687..994f8080 100644 --- a/collector/wifi_linux.go +++ b/collector/wifi_linux.go @@ -18,10 +18,12 @@ import ( "flag" "fmt" "io/ioutil" + "os" "path/filepath" "github.com/mdlayher/wifi" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" ) type wifiCollector struct { @@ -35,8 +37,6 @@ type wifiCollector struct { StationTransmitRetriesTotal *prometheus.Desc StationTransmitFailedTotal *prometheus.Desc StationBeaconLossTotal *prometheus.Desc - - stat wifiStater } var ( @@ -51,16 +51,12 @@ var _ wifiStater = &wifi.Client{} // wifiStater is an interface used to swap out a *wifi.Client for end to end tests. type wifiStater interface { + Close() error Interfaces() ([]*wifi.Interface, error) StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) } func NewWifiCollector() (Collector, error) { - stat, err := newWifiStater(*collectorWifi) - if err != nil { - return nil, fmt.Errorf("failed to access wifi data: %v", err) - } - const ( subsystem = "wifi" ) @@ -132,13 +128,23 @@ func NewWifiCollector() (Collector, error) { labels, nil, ), - - stat: stat, }, nil } func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { - ifis, err := c.stat.Interfaces() + stat, err := newWifiStater(*collectorWifi) + if err != nil { + // Cannot access wifi metrics, report no error + if os.IsNotExist(err) { + log.Debug("wifi collector metrics are not available for this system") + return nil + } + + return fmt.Errorf("failed to access wifi data: %v", err) + } + defer stat.Close() + + ifis, err := stat.Interfaces() if err != nil { return fmt.Errorf("failed to retrieve wifi interfaces: %v", err) } @@ -149,7 +155,7 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error { continue } - info, err := c.stat.StationInfo(ifi) + info, err := stat.StationInfo(ifi) if err != nil { return fmt.Errorf("failed to retrieve station info for device %s: %v", ifi.Name, err) @@ -260,6 +266,8 @@ func (s *mockWifiStater) unmarshalJSONFile(filename string, v interface{}) error return json.Unmarshal(b, v) } +func (s *mockWifiStater) Close() error { return nil } + func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) { var ifis []*wifi.Interface if err := s.unmarshalJSONFile("interfaces.json", &ifis); err != nil { diff --git a/vendor/github.com/mdlayher/netlink/conn.go b/vendor/github.com/mdlayher/netlink/conn.go index c658c771..4b4c7377 100644 --- a/vendor/github.com/mdlayher/netlink/conn.go +++ b/vendor/github.com/mdlayher/netlink/conn.go @@ -2,7 +2,7 @@ package netlink import ( "errors" - "os" + "sync" "sync/atomic" ) @@ -23,6 +23,12 @@ type Conn struct { // seq is an atomically incremented integer used to provide sequence // numbers when Conn.Send is called. seq *uint32 + + // pid is an atomically set/loaded integer which is set to the PID assigned + // by netlink, when netlink sends its first response message. pidOnce performs + // the assignment exactl once. + pid *uint32 + pidOnce sync.Once } // An osConn is an operating-system specific implementation of netlink @@ -53,6 +59,7 @@ func newConn(c osConn) *Conn { return &Conn{ c: c, seq: new(uint32), + pid: new(uint32), } } @@ -96,8 +103,8 @@ func (c *Conn) Execute(m Message) ([]Message, error) { // If m.Header.Sequence is 0, it will be automatically populated using the // next sequence number for this connection. // -// If m.Header.PID is 0, it will be automatically populated using the -// process ID (PID) of this process. +// If m.Header.PID is 0, it will be automatically populated using a PID +// assigned by netlink. func (c *Conn) Send(m Message) (Message, error) { ml := nlmsgLength(len(m.Data)) @@ -115,7 +122,7 @@ func (c *Conn) Send(m Message) (Message, error) { } if m.Header.PID == 0 { - m.Header.PID = uint32(os.Getpid()) + m.Header.PID = atomic.LoadUint32(c.pid) } if err := c.c.Send(m); err != nil { @@ -127,14 +134,29 @@ func (c *Conn) Send(m Message) (Message, error) { // Receive receives one or more messages from netlink. Multi-part messages are // handled transparently and returned as a single slice of Messages, with the -// final empty "multi-part done" message removed. If any of the messages -// indicate a netlink error, that error will be returned. +// final empty "multi-part done" message removed. +// +// If a PID has not yet been assigned to this Conn by netlink, the PID will +// be set from the first received message. This PID will be used in all +// subsequent communications with netlink. +// +// If any of the messages indicate a netlink error, that error will be returned. func (c *Conn) Receive() ([]Message, error) { msgs, err := c.receive() if err != nil { return nil, err } + if len(msgs) > 0 { + // netlink multicast messages from kernel have PID of 0, so don't + // assign 0 as the expected PID for next messages + if pid := msgs[0].Header.PID; pid != 0 { + c.pidOnce.Do(func() { + atomic.StoreUint32(c.pid, pid) + }) + } + } + // Trim the final message with multi-part done indicator if // present if m := msgs[len(msgs)-1]; m.Header.Flags&HeaderFlagsMulti != 0 && m.Header.Type == HeaderTypeDone { @@ -200,10 +222,19 @@ func (c *Conn) nextSequence() uint32 { // ensuring that they contain matching sequence numbers and PIDs. func Validate(request Message, replies []Message) error { for _, m := range replies { - if m.Header.Sequence != request.Header.Sequence { + // Check for mismatched sequence, unless: + // - request had no sequence, meaning we are probably validating + // a multicast reply + if m.Header.Sequence != request.Header.Sequence && request.Header.Sequence != 0 { return errMismatchedSequence } - if m.Header.PID != request.Header.PID { + + // Check for mismatched PID, unless: + // - request had no PID, meaning we are either: + // - validating a multicast reply + // - netlink has not yet assigned us a PID + // - response had no PID, meaning it's from the kernel as a multicast reply + if m.Header.PID != request.Header.PID && request.Header.PID != 0 && m.Header.PID != 0 { return errMismatchedPID } } diff --git a/vendor/github.com/mdlayher/wifi/client.go b/vendor/github.com/mdlayher/wifi/client.go index cac93f7f..47fe6d50 100644 --- a/vendor/github.com/mdlayher/wifi/client.go +++ b/vendor/github.com/mdlayher/wifi/client.go @@ -1,5 +1,15 @@ package wifi +import ( + "errors" +) + +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") +) + // A Client is a type which can access WiFi device actions and statistics // using operating system-specific operations. type Client struct { @@ -18,6 +28,11 @@ func New() (*Client, error) { }, nil } +// Close releases resources used by a Client. +func (c *Client) Close() error { + return c.c.Close() +} + // Interfaces returns a list of the system's WiFi network interfaces. func (c *Client) Interfaces() ([]*Interface, error) { return c.c.Interfaces() @@ -26,11 +41,16 @@ func (c *Client) Interfaces() ([]*Interface, error) { // StationInfo retrieves statistics about a WiFi interface operating in // station mode. func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { + if ifi.Type != InterfaceTypeStation { + return nil, errNotStation + } + return c.c.StationInfo(ifi) } // An osClient is the operating system-specific implementation of Client. type osClient interface { + Close() error Interfaces() ([]*Interface, 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 09796512..1eb5398a 100644 --- a/vendor/github.com/mdlayher/wifi/client_linux.go +++ b/vendor/github.com/mdlayher/wifi/client_linux.go @@ -36,6 +36,7 @@ type client struct { // genl is an interface over generic netlink, so netlink interactions can // be stubbed in tests. type genl interface { + Close() error GetFamily(name string) (genetlink.Family, error) Execute(m genetlink.Message, family uint16, flags netlink.HeaderFlags) ([]genetlink.Message, error) } @@ -66,6 +67,11 @@ func initClient(c genl) (*client, error) { }, nil } +// Close closes the client's generic netlink connection. +func (c *client) Close() error { + return c.c.Close() +} + // Interfaces requests that nl80211 return a list of all WiFi interfaces present // on this system. func (c *client) Interfaces() ([]*Interface, error) { @@ -117,7 +123,12 @@ func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { return nil, err } - if len(msgs) > 1 { + switch len(msgs) { + case 0: + return nil, os.ErrNotExist + case 1: + break + default: return nil, errMultipleMessages } diff --git a/vendor/github.com/mdlayher/wifi/client_others.go b/vendor/github.com/mdlayher/wifi/client_others.go index 4d7b4f43..a694e7c8 100644 --- a/vendor/github.com/mdlayher/wifi/client_others.go +++ b/vendor/github.com/mdlayher/wifi/client_others.go @@ -24,6 +24,11 @@ func newClient() (*client, error) { return nil, errUnimplemented } +// Close always returns an error. +func (c *client) Close() error { + return errUnimplemented +} + // Interfaces always returns an error. func (c *client) Interfaces() ([]*Interface, error) { return nil, errUnimplemented diff --git a/vendor/vendor.json b/vendor/vendor.json index 65c87f81..ef571fe6 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -51,10 +51,10 @@ "revisionTime": "2016-04-24T11:30:07Z" }, { - "checksumSHA1": "87nUxyFGVJFXB6MQpGCGUHi5NY0=", + "checksumSHA1": "n31d2o+dY0HXZTDWE5Rc0+7NEjc=", "path": "github.com/mdlayher/netlink", - "revision": "a65cbc3bb3f7a793b7d79ad7d19b16d471ddbd78", - "revisionTime": "2017-01-10T22:29:47Z" + "revision": "e5da4eb480835e5bce0dc5e526fe5f9a8002b54e", + "revisionTime": "2017-01-13T17:56:52Z" }, { "checksumSHA1": "+2roeIWCAjCC58tZcs12Vqgf1Io=", @@ -69,10 +69,10 @@ "revisionTime": "2017-01-04T04:59:06Z" }, { - "checksumSHA1": "dSy4F6HRYyadhiQbv3Bof/Tdxec=", + "checksumSHA1": "l8M/rZH5s/ZVtCCyeiUQXZ5FosA=", "path": "github.com/mdlayher/wifi", - "revision": "88fd1c0ec178645c1b7d300090b5a9d4b226b8e1", - "revisionTime": "2017-01-07T15:17:58Z" + "revision": "eb8b29b956ba5ff2fdd2d2f1f0b988b57fd3d8a3", + "revisionTime": "2017-01-12T20:47:29Z" }, { "checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=",