From 3bcaab3c3408c08547d410cae139699b2ce5baf2 Mon Sep 17 00:00:00 2001 From: DN2 Date: Sat, 21 Apr 2018 00:51:57 -0700 Subject: [PATCH] Shuffle things around a bit more. --- clients.go | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.go | 94 ++------------------------- main.go | 188 +++++++++++++++-------------------------------------- 3 files changed, 241 insertions(+), 224 deletions(-) create mode 100644 clients.go diff --git a/clients.go b/clients.go new file mode 100644 index 00000000..44ff1237 --- /dev/null +++ b/clients.go @@ -0,0 +1,183 @@ +package main + +import ( + "log" + "strconv" + "time" + + influx "github.com/influxdata/influxdb/client/v2" +) + +// ClientResponse marshalls the payload from the controller. +type ClientResponse struct { + Clients []Client `json:"data"` + Meta struct { + Rc string `json:"rc"` + } `json:"meta"` +} + +// DpiStat is for deep packet inspection stats. +// Does not seem to exist in Unifi 5.7.20. +type DpiStat struct { + App int64 + Cat int64 + RxBytes int64 + RxPackets int64 + TxBytes int64 + TxPackets int64 +} + +// Client defines all the data a connected-network client contains. +type Client struct { + ID string `json:"_id"` + IsGuestByUAP bool `json:"_is_guest_by_uap"` + IsGuestByUGW bool `json:"_is_guest_by_ugw"` + IsGuestByUSW bool `json:"_is_guest_by_usw"` + LastSeenByUAP int64 `json:"_last_seen_by_uap"` + LastSeenByUGW int64 `json:"_last_seen_by_ugw"` + LastSeenByUSW int64 `json:"_last_seen_by_usw"` + UptimeByUAP int64 `json:"_uptime_by_uap"` + UptimeByUGW int64 `json:"_uptime_by_ugw"` + UptimeByUSW int64 `json:"_uptime_by_usw"` + ApMac string `json:"ap_mac"` + AssocTime int64 `json:"assoc_time"` + Authorized bool `json:"authorized"` + Bssid string `json:"bssid"` + BytesR int64 `json:"bytes-r"` + Ccq int64 `json:"ccq"` + Channel int `json:"channel"` + DpiStats []DpiStat `json:"dpi_stats"` + DpiStatsLastUpdated int64 `json:"dpi_stats_last_updated"` + Essid string `json:"essid"` + FirstSeen int64 `json:"first_seen"` + FixedIP string `json:"fixed_ip"` + Hostname string `json:"hostname"` + GwMac string `json:"gw_mac"` + IdleTime int64 `json:"idle_time"` + IP string `json:"ip"` + Is11R bool `json:"is_11r"` + IsGuest bool `json:"is_guest"` + IsWired bool `json:"is_wired"` + LastSeen int64 `json:"last_seen"` + LatestAssocTime int64 `json:"latest_assoc_time"` + Mac string `json:"mac"` + Name string `json:"name"` + Network string `json:"network"` + NetworkID string `json:"network_id"` + Noise int64 `json:"noise"` + Note string `json:"note"` + Noted bool `json:"noted"` + Oui string `json:"oui"` + PowersaveEnabled bool `json:"powersave_enabled"` + QosPolicyApplied bool `json:"qos_policy_applied"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + RadioProto string `json:"radio_proto"` + RoamCount int64 `json:"roam_count"` + Rssi int64 `json:"rssi"` + RxBytes int64 `json:"rx_bytes"` + RxBytesR int64 `json:"rx_bytes-r"` + RxPackets int64 `json:"rx_packets"` + RxRate int64 `json:"rx_rate"` + Signal int64 `json:"signal"` + SiteID string `json:"site_id"` + SwDepth int `json:"sw_depth"` + SwMac string `json:"sw_mac"` + SwPort int `json:"sw_port"` + TxBytes int64 `json:"tx_bytes"` + TxBytesR int64 `json:"tx_bytes-r"` + TxPackets int64 `json:"tx_packets"` + TxPower int64 `json:"tx_power"` + TxRate int64 `json:"tx_rate"` + Uptime int64 `json:"uptime"` + UserID string `json:"user_id"` + UserGroupID string `json:"usergroup_id"` + UseFixedIP bool `json:"use_fixedip"` + Vlan int `json:"vlan"` +} + +// Point generates a client's datapoint for InfluxDB. +func (c Client) Point() *influx.Point { + if c.Name == "" && c.Hostname != "" { + c.Name = c.Hostname + } else if c.Hostname == "" && c.Name != "" { + c.Hostname = c.Name + } else if c.Hostname == "" && c.Name == "" { + c.Hostname = "-no-name-" + c.Name = "-no-name-" + } + tags := map[string]string{ + "id": c.ID, + "mac": c.Mac, + "user_id": c.UserID, + "site_id": c.SiteID, + "ip": c.IP, + "fixed_ip": c.FixedIP, + "essid": c.Essid, + "bssid": c.Bssid, + "network": c.Network, + "network_id": c.NetworkID, + "usergroup_id": c.UserGroupID, + "ap_mac": c.ApMac, + "gw_mac": c.GwMac, + "sw_mac": c.SwMac, + "sw_port": strconv.Itoa(c.SwPort), + "oui": c.Oui, + "name": c.Name, + "hostname": c.Hostname, + "radio_name": c.RadioName, + "radio": c.Radio, + "radio_proto": c.RadioProto, + "authorized": strconv.FormatBool(c.Authorized), + "is_11r": strconv.FormatBool(c.Is11R), + "is_wired": strconv.FormatBool(c.IsWired), + "is_guest": strconv.FormatBool(c.IsGuest), + "is_guest_by_uap": strconv.FormatBool(c.IsGuestByUAP), + "is_guest_by_ugw": strconv.FormatBool(c.IsGuestByUGW), + "is_guest_by_usw": strconv.FormatBool(c.IsGuestByUSW), + "noted": strconv.FormatBool(c.Noted), + "powersave_enabled": strconv.FormatBool(c.PowersaveEnabled), + "qos_policy_applied": strconv.FormatBool(c.QosPolicyApplied), + "use_fixedip": strconv.FormatBool(c.UseFixedIP), + "channel": strconv.Itoa(c.Channel), + "vlan": strconv.Itoa(c.Vlan), + } + fields := map[string]interface{}{ + "dpi_stats_last_updated": c.DpiStatsLastUpdated, + "last_seen_by_uap": c.LastSeenByUAP, + "last_seen_by_ugw": c.LastSeenByUGW, + "last_seen_by_usw": c.LastSeenByUSW, + "uptime_by_uap": c.UptimeByUAP, + "uptime_by_ugw": c.UptimeByUGW, + "uptime_by_usw": c.UptimeByUSW, + "assoc_time": c.AssocTime, + "bytes_r": c.BytesR, + "ccq": c.Ccq, + "first_seen": c.FirstSeen, + "idle_time": c.IdleTime, + "last_seen": c.LastSeen, + "latest_assoc_time": c.LatestAssocTime, + "noise": c.Noise, + "note": c.Note, + "roam_count": c.RoamCount, + "rssi": c.Rssi, + "rx_bytes": c.RxBytes, + "rx_bytes_r": c.RxBytesR, + "rx_packets": c.RxPackets, + "rx_rate": c.RxRate, + "signal": c.Signal, + "tx_bytes": c.TxBytes, + "tx_bytes_r": c.TxBytesR, + "tx_packets": c.TxPackets, + "tx_power": c.TxPower, + "tx_rate": c.TxRate, + "uptime": c.Uptime, + } + + pt, err := influx.NewPoint("clients", tags, fields, time.Now()) + if err != nil { + log.Println("Error creating point:", err) + return nil + } + return pt +} diff --git a/config.go b/config.go index d9ed1b99..fd595e20 100644 --- a/config.go +++ b/config.go @@ -10,16 +10,14 @@ const ( LoginPath = "/api/login" // ClientPath is Unifi Clients API Path ClientPath = "/api/s/default/stat/sta" + // DevicePath is where we get data about Unifi devices. + DevicePath = "/api/s/default/stat/device" + // NetworkPath contains network-configuration data. Not really graphable. + NetworkPath = "/api/s/default/rest/networkconf" + // UserGroupPath contains usergroup configurations. + UserGroupPath = "/api/s/default/rest/usergroup" ) -// ClientResponse marshalls the payload from the controller. -type ClientResponse struct { - Clients []Client `json:"data"` - Meta struct { - Rc string `json:"rc"` - } `json:"meta"` -} - // Config represents the data needed to poll a controller and report to influxdb. type Config struct { Interval time.Duration `json:"interval",toml:"interval",yaml:"interval"` @@ -32,83 +30,3 @@ type Config struct { UnifiBase string `json:"unifi_url",toml:"unifi_url",yaml:"unifi_url"` uniClient *http.Client } - -// DpiStat is for deep packet inspection stats. -// Does not seem to exist in Unifi 5.7.20. -type DpiStat struct { - App int64 - Cat int64 - RxBytes int64 - RxPackets int64 - TxBytes int64 - TxPackets int64 -} - -// Client defines all the data a connected-network client contains. -type Client struct { - ID string `json:"_id"` - IsGuestByUAP bool `json:"_is_guest_by_uap"` - IsGuestByUGW bool `json:"_is_guest_by_ugw"` - IsGuestByUSW bool `json:"_is_guest_by_usw"` - LastSeenByUAP int64 `json:"_last_seen_by_uap"` - LastSeenByUGW int64 `json:"_last_seen_by_ugw"` - LastSeenByUSW int64 `json:"_last_seen_by_usw"` - UptimeByUAP int64 `json:"_uptime_by_uap"` - UptimeByUGW int64 `json:"_uptime_by_ugw"` - UptimeByUSW int64 `json:"_uptime_by_usw"` - ApMac string `json:"ap_mac"` - AssocTime int64 `json:"assoc_time"` - Authorized bool `json:"authorized"` - Bssid string `json:"bssid"` - BytesR int64 `json:"bytes-r"` - Ccq int64 `json:"ccq"` - Channel int `json:"channel"` - DpiStats []DpiStat `json:"dpi_stats"` - DpiStatsLastUpdated int64 `json:"dpi_stats_last_updated"` - Essid string `json:"essid"` - FirstSeen int64 `json:"first_seen"` - FixedIP string `json:"fixed_ip"` - Hostname string `json:"hostname"` - GwMac string `json:"gw_mac"` - IdleTime int64 `json:"idle_time"` - IP string `json:"ip"` - Is11R bool `json:"is_11r"` - IsGuest bool `json:"is_guest"` - IsWired bool `json:"is_wired"` - LastSeen int64 `json:"last_seen"` - LatestAssocTime int64 `json:"latest_assoc_time"` - Mac string `json:"mac"` - Name string `json:"name"` - Network string `json:"network"` - NetworkID string `json:"network_id"` - Noise int64 `json:"noise"` - Note string `json:"note"` - Noted bool `json:"noted"` - Oui string `json:"oui"` - PowersaveEnabled bool `json:"powersave_enabled"` - QosPolicyApplied bool `json:"qos_policy_applied"` - Radio string `json:"radio"` - RadioName string `json:"radio_name"` - RadioProto string `json:"radio_proto"` - RoamCount int64 `json:"roam_count"` - Rssi int64 `json:"rssi"` - RxBytes int64 `json:"rx_bytes"` - RxBytesR int64 `json:"rx_bytes-r"` - RxPackets int64 `json:"rx_packets"` - RxRate int64 `json:"rx_rate"` - Signal int64 `json:"signal"` - SiteID string `json:"site_id"` - SwDepth int `json:"sw_depth"` - SwMac string `json:"sw_mac"` - SwPort int `json:"sw_port"` - TxBytes int64 `json:"tx_bytes"` - TxBytesR int64 `json:"tx_bytes-r"` - TxPackets int64 `json:"tx_packets"` - TxPower int64 `json:"tx_power"` - TxRate int64 `json:"tx_rate"` - Uptime int64 `json:"uptime"` - UserID string `json:"user_id"` - UserGroupID string `json:"usergroup_id"` - UseFixedIP bool `json:"use_fixedip"` - Vlan int `json:"vlan"` -} diff --git a/main.go b/main.go index 9c70a8e3..b9cb9727 100644 --- a/main.go +++ b/main.go @@ -5,104 +5,16 @@ import ( "crypto/tls" "encoding/json" "errors" - "io" "io/ioutil" "log" "net/http" "net/http/cookiejar" "os" - "strconv" "time" influx "github.com/influxdata/influxdb/client/v2" ) -// Point generates a client's datapoint for InfluxDB. -func (c Client) Point() *influx.Point { - if c.Name == "" && c.Hostname != "" { - c.Name = c.Hostname - } else if c.Hostname == "" && c.Name != "" { - c.Hostname = c.Name - } else if c.Hostname == "" && c.Name == "" { - c.Hostname = "-no-name-" - c.Name = "-no-name-" - } - tags := map[string]string{ - "id": c.ID, - "mac": c.Mac, - "user_id": c.UserID, - "site_id": c.SiteID, - "ip": c.IP, - "fixed_ip": c.FixedIP, - "essid": c.Essid, - "bssid": c.Bssid, - "network": c.Network, - "network_id": c.NetworkID, - "usergroup_id": c.UserGroupID, - "ap_mac": c.ApMac, - "gw_mac": c.GwMac, - "sw_mac": c.SwMac, - "sw_port": strconv.Itoa(c.SwPort), - "oui": c.Oui, - "name": c.Name, - "hostname": c.Hostname, - "radio_name": c.RadioName, - "radio": c.Radio, - "radio_proto": c.RadioProto, - "authorized": strconv.FormatBool(c.Authorized), - "is_11r": strconv.FormatBool(c.Is11R), - "is_wired": strconv.FormatBool(c.IsWired), - "is_guest": strconv.FormatBool(c.IsGuest), - "is_guest_by_uap": strconv.FormatBool(c.IsGuestByUAP), - "is_guest_by_ugw": strconv.FormatBool(c.IsGuestByUGW), - "is_guest_by_usw": strconv.FormatBool(c.IsGuestByUSW), - "noted": strconv.FormatBool(c.Noted), - "powersave_enabled": strconv.FormatBool(c.PowersaveEnabled), - "qos_policy_applied": strconv.FormatBool(c.QosPolicyApplied), - "use_fixedip": strconv.FormatBool(c.UseFixedIP), - "channel": strconv.Itoa(c.Channel), - "vlan": strconv.Itoa(c.Vlan), - } - fields := map[string]interface{}{ - "dpi_stats_last_updated": c.DpiStatsLastUpdated, - "last_seen_by_uap": c.LastSeenByUAP, - "last_seen_by_ugw": c.LastSeenByUGW, - "last_seen_by_usw": c.LastSeenByUSW, - "uptime_by_uap": c.UptimeByUAP, - "uptime_by_ugw": c.UptimeByUGW, - "uptime_by_usw": c.UptimeByUSW, - "assoc_time": c.AssocTime, - "bytes_r": c.BytesR, - "ccq": c.Ccq, - "first_seen": c.FirstSeen, - "idle_time": c.IdleTime, - "last_seen": c.LastSeen, - "latest_assoc_time": c.LatestAssocTime, - "noise": c.Noise, - "note": c.Note, - "roam_count": c.RoamCount, - "rssi": c.Rssi, - "rx_bytes": c.RxBytes, - "rx_bytes_r": c.RxBytesR, - "rx_packets": c.RxPackets, - "rx_rate": c.RxRate, - "signal": c.Signal, - "tx_bytes": c.TxBytes, - "tx_bytes_r": c.TxBytesR, - "tx_packets": c.TxPackets, - "tx_power": c.TxPower, - "tx_rate": c.TxRate, - "uptime": c.Uptime, - } - - pt, err := influx.NewPoint("clients", tags, fields, time.Now()) - if err != nil { - log.Println("Error creating point:", err) - return nil - } - return pt -} - func main() { config := GetConfig() if err := config.AuthController(); err != nil { @@ -122,6 +34,48 @@ func main() { config.PollUnifiController(infdb) } +// GetConfig parses and returns our configuration data. +func GetConfig() Config { + // TODO: A real config file. + var err error + config := Config{ + InfluxAddr: os.Getenv("INFLUXDB_ADDR"), + InfluxUser: os.Getenv("INFLUXDB_USERNAME"), + InfluxPass: os.Getenv("INFLUXDB_PASSWORD"), + InfluxDB: os.Getenv("INFLUXDB_DATABASE"), + UnifiUser: os.Getenv("UNIFI_USERNAME"), + UnifiPass: os.Getenv("UNIFI_PASSWORD"), + UnifiBase: "https://" + os.Getenv("UNIFI_ADDR") + ":" + os.Getenv("UNIFI_PORT"), + } + if config.Interval, err = time.ParseDuration(os.Getenv("INTERVAL")); err != nil { + log.Println("Invalid Interval, defaulting to 15 seconds.") + config.Interval = time.Duration(time.Second * 15) + } + return config +} + +// AuthController creates a http.Client with authenticated cookies. +// Used to make additional, authenticated requests to the APIs. +func (c *Config) AuthController() error { + json := `{"username": "` + c.UnifiUser + `","password": "` + c.UnifiPass + `"}` + jar, err := cookiejar.New(nil) + if err != nil { + return err + } + c.uniClient = &http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, + Jar: jar, + } + if req, err := c.uniRequest(LoginPath, json); err != nil { + return err + } else if resp, err := c.uniClient.Do(req); err != nil { + return err + } else if resp.StatusCode != http.StatusOK { + return errors.New("Error Authenticating with Unifi Controller") + } + return nil +} + // PollUnifiController runs forever, polling and pushing. func (c *Config) PollUnifiController(infdb influx.Client) { ticker := time.NewTicker(c.Interval) @@ -150,30 +104,10 @@ func (c *Config) PollUnifiController(infdb influx.Client) { } } -// GetConfig parses and returns our configuration data. -func GetConfig() Config { - // TODO: A real config file. - var err error - config := Config{ - InfluxAddr: os.Getenv("INFLUXDB_ADDR"), - InfluxUser: os.Getenv("INFLUXDB_USERNAME"), - InfluxPass: os.Getenv("INFLUXDB_PASSWORD"), - InfluxDB: os.Getenv("INFLUXDB_DATABASE"), - UnifiUser: os.Getenv("UNIFI_USERNAME"), - UnifiPass: os.Getenv("UNIFI_PASSWORD"), - UnifiBase: "https://" + os.Getenv("UNIFI_ADDR") + ":" + os.Getenv("UNIFI_PORT"), - } - if config.Interval, err = time.ParseDuration(os.Getenv("INTERVAL")); err != nil { - log.Println("Invalid Interval, defaulting to 15 seconds.") - config.Interval = time.Duration(time.Second * 15) - } - return config -} - // GetUnifiClients returns a response full of clients' data from the Unifi Controller. func (c *Config) GetUnifiClients() ([]Client, error) { response := &ClientResponse{} - if req, err := uniRequest("GET", c.UnifiBase+ClientPath, nil); err != nil { + if req, err := c.uniRequest(ClientPath, ""); err != nil { return nil, err } else if resp, err := c.uniClient.Do(req); err != nil { return nil, err @@ -187,33 +121,15 @@ func (c *Config) GetUnifiClients() ([]Client, error) { return response.Clients, nil } -// AuthController creates a http.Client with authenticated cookies. -// Used to make additional, authenticated requests to the APIs. -func (c *Config) AuthController() error { - json := bytes.NewBufferString(`{"username": "` + c.UnifiUser + `","password": "` + c.UnifiPass + `"}`) - jar, err := cookiejar.New(nil) - if err != nil { - return err +// uniRequest is a small helper function that adds an Accept header. +func (c *Config) uniRequest(url string, params string) (req *http.Request, err error) { + if params != "" { + req, err = http.NewRequest("POST", c.UnifiBase+url, bytes.NewBufferString(params)) + } else { + req, err = http.NewRequest("GET", c.UnifiBase+url, nil) } - c.uniClient = &http.Client{ - Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, - Jar: jar, + if err == nil { + req.Header.Add("Accept", "application/json") } - if req, err := uniRequest("POST", c.UnifiBase+LoginPath, json); err != nil { - return err - } else if resp, err := c.uniClient.Do(req); err != nil { - return err - } else if resp.StatusCode != http.StatusOK { - return errors.New("Error Authenticating with Unifi Controller") - } - return nil -} - -func uniRequest(method string, url string, data io.Reader) (*http.Request, error) { - req, err := http.NewRequest(method, url, data) - if err != nil { - return nil, err - } - req.Header.Add("Accept", "application/json") - return req, nil + return }