diff --git a/core/unifi/.gitignore b/core/unifi/.gitignore new file mode 100644 index 00000000..6d292b19 --- /dev/null +++ b/core/unifi/.gitignore @@ -0,0 +1,2 @@ +/vendor +*.swp diff --git a/core/unifi/.travis.yml b/core/unifi/.travis.yml new file mode 100644 index 00000000..050bba31 --- /dev/null +++ b/core/unifi/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: +- 1.16.x +before_install: + # download super-linter: golangci-lint +- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.38.0 +script: +- golangci-lint run --enable-all -D exhaustivestruct,nlreturn,wrapcheck,maligned,interfacer +- go test ./... diff --git a/core/unifi/LICENSE b/core/unifi/LICENSE new file mode 100644 index 00000000..2c74f096 --- /dev/null +++ b/core/unifi/LICENSE @@ -0,0 +1,22 @@ +MIT LICENSE. +Copyright (c) 2018-2020 David Newhall II +Copyright (c) 2016 Garrett Bjerkhoel + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/core/unifi/README.md b/core/unifi/README.md new file mode 100644 index 00000000..9b7df0da --- /dev/null +++ b/core/unifi/README.md @@ -0,0 +1,64 @@ +# Go Library: `unifi` + +It connects to a Unifi Controller, given a url, username and password. Returns +an authenticated http Client you may use to query the device for data. Also +contains some built-in methods for de-serializing common client and device +data. The data is provided in a large struct you can consume in your application. + +This library is designed to PULL data FROM the controller. It has no methods that +update settings or change things on the controller. +[Someone expressed interest](https://github.com/unpoller/unifi/issues/31) in +adding methods to update data, and I'm okay with that. I'll even help add them. +[Tell me what you want to do](https://github.com/unpoller/unifi/issues/new), and we'll make it happen. + +Pull requests, feature requests, code reviews and feedback are welcomed! + +Here's a working example: +```golang +package main + +import "log" +import "github.com/unpoller/unifi" + +func main() { + c := *unifi.Config{ + User: "admin", + Pass: "superSecret1234", + URL: "https://127.0.0.1:8443/", + // Log with log.Printf or make your own interface that accepts (msg, fmt) + ErrorLog: log.Printf, + DebugLog: log.Printf, + } + uni, err := unifi.NewUnifi(c) + if err != nil { + log.Fatalln("Error:", err) + } + + sites, err := uni.GetSites() + if err != nil { + log.Fatalln("Error:", err) + } + clients, err := uni.GetClients(sites) + if err != nil { + log.Fatalln("Error:", err) + } + devices, err := uni.GetDevices(sites) + if err != nil { + log.Fatalln("Error:", err) + } + + log.Println(len(sites), "Unifi Sites Found: ", sites) + log.Println(len(clients), "Clients connected:") + for i, client := range clients { + log.Println(i+1, client.ID, client.Hostname, client.IP, client.Name, client.LastSeen) + } + + log.Println(len(devices.USWs), "Unifi Switches Found") + log.Println(len(devices.USGs), "Unifi Gateways Found") + + log.Println(len(devices.UAPs), "Unifi Wireless APs Found:") + for i, uap := range devices.UAPs { + log.Println(i+1, uap.Name, uap.IP) + } +} +``` diff --git a/core/unifi/alarms.go b/core/unifi/alarms.go new file mode 100644 index 00000000..975786b2 --- /dev/null +++ b/core/unifi/alarms.go @@ -0,0 +1,120 @@ +package unifi + +import ( + "fmt" + "sort" + "time" +) + +type Alarm struct { + Archived FlexBool `json:"archived"` + DestPort int `json:"dest_port"` + SrcPort int `json:"src_port"` + FlowID int64 `json:"flow_id"` + InnerAlertGID int64 `json:"inner_alert_gid"` + InnerAlertRev int64 `json:"inner_alert_rev"` + InnerAlertSeverity int64 `json:"inner_alert_severity"` + InnerAlertSignatureID int64 `json:"inner_alert_signature_id"` + Time int64 `json:"time"` + Timestamp int64 `json:"timestamp"` + Datetime time.Time `json:"datetime"` + HandledTime time.Time `json:"handled_time,omitempty"` + AppProto string `json:"app_proto,omitempty"` + Catname string `json:"catname"` + DestIP string `json:"dest_ip"` + DstMAC string `json:"dst_mac"` + DstIPASN string `json:"dstipASN,omitempty"` + DstIPCountry string `json:"dstipCountry,omitempty"` + EventType string `json:"event_type"` + HandledAdminID string `json:"handled_admin_id,omitempty"` + Host string `json:"host"` + ID string `json:"_id"` + InIface string `json:"in_iface"` + InnerAlertAction string `json:"inner_alert_action"` + InnerAlertCategory string `json:"inner_alert_category"` + InnerAlertSignature string `json:"inner_alert_signature"` + Key string `json:"key"` + Msg string `json:"msg"` + Proto string `json:"proto"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + SourceName string `json:"-"` + SrcIP string `json:"src_ip"` + SrcIPASN string `json:"srcipASN,omitempty"` + SrcIPCountry string `json:"srcipCountry,omitempty"` + SrcMAC string `json:"src_mac"` + Subsystem string `json:"subsystem"` + UniqueAlertID string `json:"unique_alertid"` + USGIP string `json:"usgip"` + USGIPASN string `json:"usgipASN"` + USGIPCountry string `json:"usgipCountry"` + TxID FlexInt `json:"tx_id,omitempty"` + DestIPGeo IPGeo `json:"dstipGeo"` + SourceIPGeo IPGeo `json:"usgipGeo"` + USGIPGeo IPGeo `json:"srcipGeo,omitempty"` +} + +// GetAlarms returns Alarms for a list of Sites. +func (u *Unifi) GetAlarms(sites []*Site) ([]*Alarm, error) { + data := []*Alarm{} + + for _, site := range sites { + response, err := u.GetAlarmsSite(site) + if err != nil { + return data, err + } + + data = append(data, response...) + } + + return data, nil +} + +// GetAlarmsSite retreives the Alarms for a single Site. +func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) { + if site == nil || site.Name == "" { + return nil, ErrNoSiteProvided + } + + u.DebugLog("Polling Controller for Alarms, site %s", site.SiteName) + + var ( + path = fmt.Sprintf(APIEventPathAlarms, site.Name) + alarms struct { + Data alarms `json:"data"` + } + ) + + if err := u.GetData(path, &alarms, ""); err != nil { + return alarms.Data, err + } + + for i := range alarms.Data { + // Add special SourceName value. + alarms.Data[i].SourceName = u.URL + // Add the special "Site Name" to each event. This becomes a Grafana filter somewhere. + alarms.Data[i].SiteName = site.SiteName + } + + sort.Sort(alarms.Data) + + return alarms.Data, nil +} + +// alarms satisfies the sort.Sort Interface. +type alarms []*Alarm + +// Len satisfies sort.Interface. +func (a alarms) Len() int { + return len(a) +} + +// Swap satisfies sort.Interface. +func (a alarms) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +// Less satisfies sort.Interface. Sort our list by Datetime. +func (a alarms) Less(i, j int) bool { + return a[i].Datetime.Before(a[j].Datetime) +} diff --git a/core/unifi/anomalies.go b/core/unifi/anomalies.go new file mode 100644 index 00000000..cdb66141 --- /dev/null +++ b/core/unifi/anomalies.go @@ -0,0 +1,130 @@ +package unifi + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" +) + +// anomaly is the type UniFi returns, but not the type this library returns. +type anomaly struct { + Anomaly string `json:"anomaly"` + MAC string `json:"mac"` + Timestamps []int64 `json:"timestamps"` +} + +// Anomaly is the reformatted data type that this library returns. +type Anomaly struct { + Datetime time.Time + SourceName string + SiteName string + Anomaly string + DeviceMAC string + // DeviceName string // we do not have this.... +} + +// GetAnomalies returns Anomalies for a list of Sites. +func (u *Unifi) GetAnomalies(sites []*Site, timeRange ...time.Time) ([]*Anomaly, error) { + data := []*Anomaly{} + + for _, site := range sites { + response, err := u.GetAnomaliesSite(site, timeRange...) + if err != nil { + return data, err + } + + data = append(data, response...) + } + + return data, nil +} + +// GetAnomaliesSite retreives the Anomalies for a single Site. +func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) { + if site == nil || site.Name == "" { + return nil, ErrNoSiteProvided + } + + u.DebugLog("Polling Controller for Anomalies, site %s", site.SiteName) + + var ( + path = fmt.Sprintf(APIAnomaliesPath, site.Name) + anomalies = anomalies{} + data struct { + Data []*anomaly `json:"data"` + } + ) + + if params, err := makeAnomalyParams("hourly", timeRange...); err != nil { + return anomalies, err + } else if err := u.GetData(path+params, &data, ""); err != nil { + return anomalies, err + } + + for _, d := range data.Data { + for _, ts := range d.Timestamps { + anomalies = append(anomalies, &Anomaly{ + Datetime: time.Unix(ts/int64(time.Microsecond), 0), + SourceName: u.URL, + SiteName: site.SiteName, + Anomaly: d.Anomaly, + DeviceMAC: d.MAC, + // DeviceName: d.Anomaly, + }) + } + } + + sort.Sort(anomalies) + + return anomalies, nil +} + +// anomalies satisfies the sort.Sort interface. +type anomalies []*Anomaly + +// Len satisfies sort.Interface. +func (a anomalies) Len() int { + return len(a) +} + +// Swap satisfies sort.Interface. +func (a anomalies) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +// Less satisfies sort.Interface. Sort our list by Datetime. +func (a anomalies) Less(i, j int) bool { + return a[i].Datetime.Before(a[j].Datetime) +} + +func makeAnomalyParams(scale string, timeRange ...time.Time) (string, error) { + out := []string{} + + if scale != "" { + out = append(out, "scale="+scale) + } + + switch len(timeRange) { + case 0: + end := time.Now().Unix() * int64(time.Microsecond) + out = append(out, "end="+strconv.FormatInt(end, 10)) + case 1: + start := timeRange[0].Unix() * int64(time.Microsecond) + end := time.Now().Unix() * int64(time.Microsecond) + out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) + case 2: // nolint: gomnd + start := timeRange[0].Unix() * int64(time.Microsecond) + end := timeRange[1].Unix() * int64(time.Microsecond) + out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) + default: + return "", ErrInvalidTimeRange + } + + if len(out) == 0 { + return "", nil + } + + return "?" + strings.Join(out, "&"), nil +} diff --git a/core/unifi/clients.go b/core/unifi/clients.go new file mode 100644 index 00000000..1e3b4ddf --- /dev/null +++ b/core/unifi/clients.go @@ -0,0 +1,154 @@ +package unifi + +import ( + "fmt" + "strings" +) + +// GetClients returns a response full of clients' data from the UniFi Controller. +func (u *Unifi) GetClients(sites []*Site) ([]*Client, error) { + data := make([]*Client, 0) + + for _, site := range sites { + var response struct { + Data []*Client `json:"data"` + } + + u.DebugLog("Polling Controller, retreiving UniFi Clients, site %s ", site.SiteName) + + clientPath := fmt.Sprintf(APIClientPath, site.Name) + if err := u.GetData(clientPath, &response); err != nil { + return nil, err + } + + for i, d := range response.Data { + // Add special SourceName value. + response.Data[i].SourceName = u.URL + // Add the special "Site Name" to each client. This becomes a Grafana filter somewhere. + response.Data[i].SiteName = site.SiteName + // Fix name and hostname fields. Sometimes one or the other is blank. + response.Data[i].Hostname = strings.TrimSpace(pick(d.Hostname, d.Name, d.Mac)) + response.Data[i].Name = strings.TrimSpace(pick(d.Name, d.Hostname)) + } + + data = append(data, response.Data...) + } + + return data, nil +} + +// GetClientsDPI garners dpi data for clients. +func (u *Unifi) GetClientsDPI(sites []*Site) ([]*DPITable, error) { + var data []*DPITable + + for _, site := range sites { + u.DebugLog("Polling Controller, retreiving Client DPI data, site %s", site.SiteName) + + var response struct { + Data []*DPITable `json:"data"` + } + + clientDPIpath := fmt.Sprintf(APIClientDPI, site.Name) + if err := u.GetData(clientDPIpath, &response, `{"type":"by_app"}`); err != nil { + return nil, err + } + + for _, d := range response.Data { + d.SourceName = site.SourceName + d.SiteName = site.SiteName + data = append(data, d) + } + } + + return data, nil +} + +// Client defines all the data a connected-network client contains. +type Client struct { + SourceName string `json:"-"` + Anomalies int64 `json:"anomalies,omitempty"` + ApMac string `json:"ap_mac"` + ApName string `json:"-"` + AssocTime int64 `json:"assoc_time"` + Blocked bool `json:"blocked,omitempty"` + Bssid string `json:"bssid"` + BytesR int64 `json:"bytes-r"` + Ccq int64 `json:"ccq"` + Channel FlexInt `json:"channel"` + DevCat FlexInt `json:"dev_cat"` + DevFamily FlexInt `json:"dev_family"` + DevID FlexInt `json:"dev_id"` + DevVendor FlexInt `json:"dev_vendor,omitempty"` + DhcpendTime FlexInt `json:"dhcpend_time,omitempty"` + Satisfaction FlexInt `json:"satisfaction,omitempty"` + Essid string `json:"essid"` + FirstSeen int64 `json:"first_seen"` + FixedIP string `json:"fixed_ip"` + GwMac string `json:"gw_mac"` + GwName string `json:"-"` + Hostname string `json:"hostname"` + ID string `json:"_id"` + IP string `json:"ip"` + IdleTime int64 `json:"idle_time"` + Is11R FlexBool `json:"is_11r"` + IsGuest FlexBool `json:"is_guest"` + IsGuestByUAP FlexBool `json:"_is_guest_by_uap"` + IsGuestByUGW FlexBool `json:"_is_guest_by_ugw"` + IsGuestByUSW FlexBool `json:"_is_guest_by_usw"` + IsWired FlexBool `json:"is_wired"` + LastSeen int64 `json:"last_seen"` + LastSeenByUAP int64 `json:"_last_seen_by_uap"` + LastSeenByUGW int64 `json:"_last_seen_by_ugw"` + LastSeenByUSW int64 `json:"_last_seen_by_usw"` + 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 FlexBool `json:"noted"` + OsClass FlexInt `json:"os_class"` + OsName FlexInt `json:"os_name"` + Oui string `json:"oui"` + PowersaveEnabled FlexBool `json:"powersave_enabled"` + QosPolicyApplied FlexBool `json:"qos_policy_applied"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + RadioProto string `json:"radio_proto"` + RadioDescription string `json:"-"` + 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"` + SiteName string `json:"-"` + SwDepth int `json:"sw_depth"` + SwMac string `json:"sw_mac"` + SwName string `json:"-"` + SwPort FlexInt `json:"sw_port"` + TxBytes int64 `json:"tx_bytes"` + TxBytesR int64 `json:"tx_bytes-r"` + TxPackets int64 `json:"tx_packets"` + TxRetries int64 `json:"tx_retries"` + TxPower int64 `json:"tx_power"` + TxRate int64 `json:"tx_rate"` + Uptime int64 `json:"uptime"` + UptimeByUAP int64 `json:"_uptime_by_uap"` + UptimeByUGW int64 `json:"_uptime_by_ugw"` + UptimeByUSW int64 `json:"_uptime_by_usw"` + UseFixedIP FlexBool `json:"use_fixedip"` + UserGroupID string `json:"usergroup_id"` + UserID string `json:"user_id"` + Vlan FlexInt `json:"vlan"` + WifiTxAttempts int64 `json:"wifi_tx_attempts"` + WiredRxBytes int64 `json:"wired-rx_bytes"` + WiredRxBytesR int64 `json:"wired-rx_bytes-r"` + WiredRxPackets int64 `json:"wired-rx_packets"` + WiredTxBytes int64 `json:"wired-tx_bytes"` + WiredTxBytesR int64 `json:"wired-tx_bytes-r"` + WiredTxPackets int64 `json:"wired-tx_packets"` +} diff --git a/core/unifi/devices.go b/core/unifi/devices.go new file mode 100644 index 00000000..885a1844 --- /dev/null +++ b/core/unifi/devices.go @@ -0,0 +1,213 @@ +package unifi + +import ( + "encoding/json" + "fmt" + "strings" +) + +// GetDevices returns a response full of devices' data from the UniFi Controller. +func (u *Unifi) GetDevices(sites []*Site) (*Devices, error) { + devices := new(Devices) + + for _, site := range sites { + var response struct { + Data []json.RawMessage `json:"data"` + } + + devicePath := fmt.Sprintf(APIDevicePath, site.Name) + if err := u.GetData(devicePath, &response); err != nil { + return nil, err + } + + loopDevices := u.parseDevices(response.Data, site) + devices.UAPs = append(devices.UAPs, loopDevices.UAPs...) + devices.USGs = append(devices.USGs, loopDevices.USGs...) + devices.USWs = append(devices.USWs, loopDevices.USWs...) + devices.UDMs = append(devices.UDMs, loopDevices.UDMs...) + devices.UXGs = append(devices.UXGs, loopDevices.UXGs...) + } + + return devices, nil +} + +// GetUSWs returns all switches, an error, or nil if there are no switches. +func (u *Unifi) GetUSWs(site *Site) ([]*USW, error) { + var response struct { + Data []json.RawMessage `json:"data"` + } + + err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) + if err != nil { + return nil, err + } + + return u.parseDevices(response.Data, site).USWs, nil +} + +// GetUAPs returns all access points, an error, or nil if there are no APs. +func (u *Unifi) GetUAPs(site *Site) ([]*UAP, error) { + var response struct { + Data []json.RawMessage `json:"data"` + } + + err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) + if err != nil { + return nil, err + } + + return u.parseDevices(response.Data, site).UAPs, nil +} + +// GetUDMs returns all dream machines, an error, or nil if there are no UDMs. +func (u *Unifi) GetUDMs(site *Site) ([]*UDM, error) { + var response struct { + Data []json.RawMessage `json:"data"` + } + + err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) + if err != nil { + return nil, err + } + + return u.parseDevices(response.Data, site).UDMs, nil +} + +// GetUXGs returns all 10Gb gateways, an error, or nil if there are no UXGs. +func (u *Unifi) GetUXGs(site *Site) ([]*UXG, error) { + var response struct { + Data []json.RawMessage `json:"data"` + } + + err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) + if err != nil { + return nil, err + } + + return u.parseDevices(response.Data, site).UXGs, nil +} + +// GetUSGs returns all 1Gb gateways, an error, or nil if there are no USGs. +func (u *Unifi) GetUSGs(site *Site) ([]*USG, error) { + var response struct { + Data []json.RawMessage `json:"data"` + } + + err := u.GetData(fmt.Sprintf(APIDevicePath, site.Name), &response) + if err != nil { + return nil, err + } + + return u.parseDevices(response.Data, site).USGs, nil +} + +// parseDevices parses the raw JSON from the Unifi Controller into device structures. +func (u *Unifi) parseDevices(data []json.RawMessage, site *Site) *Devices { + devices := new(Devices) + + for _, r := range data { + // Loop each item in the raw JSON message, detect its type and unmarshal it. + o := make(map[string]interface{}) + if u.unmarshalDevice("map", r, &o) != nil { + u.ErrorLog("unknown asset type - cannot find asset type in payload - skipping") + continue + } + + assetType, _ := o["type"].(string) + u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, site.SiteName) + // Choose which type to unmarshal into based on the "type" json key. + + switch assetType { // Unmarshal again into the correct type.. + case "uap": + u.unmarshallUAP(site, r, devices) + case "ugw", "usg": // in case they ever fix the name in the api. + u.unmarshallUSG(site, r, devices) + case "usw": + u.unmarshallUSW(site, r, devices) + case "udm": + u.unmarshallUDM(site, r, devices) + case "uxg": + u.unmarshallUXG(site, r, devices) + default: + u.ErrorLog("unknown asset type - %v - skipping", assetType) + } + } + + return devices +} + +func (u *Unifi) unmarshallUAP(site *Site, payload json.RawMessage, devices *Devices) { + dev := &UAP{SiteName: site.SiteName, SourceName: u.URL} + if u.unmarshalDevice("uap", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + dev.site = site + devices.UAPs = append(devices.UAPs, dev) + } +} + +func (u *Unifi) unmarshallUSG(site *Site, payload json.RawMessage, devices *Devices) { + dev := &USG{SiteName: site.SiteName, SourceName: u.URL} + if u.unmarshalDevice("ugw", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + dev.site = site + devices.USGs = append(devices.USGs, dev) + } +} + +func (u *Unifi) unmarshallUSW(site *Site, payload json.RawMessage, devices *Devices) { + dev := &USW{SiteName: site.SiteName, SourceName: u.URL} + if u.unmarshalDevice("usw", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + dev.site = site + devices.USWs = append(devices.USWs, dev) + } +} + +func (u *Unifi) unmarshallUXG(site *Site, payload json.RawMessage, devices *Devices) { + dev := &UXG{SiteName: site.SiteName, SourceName: u.URL} + if u.unmarshalDevice("uxg", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + dev.site = site + devices.UXGs = append(devices.UXGs, dev) + } +} + +func (u *Unifi) unmarshallUDM(site *Site, payload json.RawMessage, devices *Devices) { + dev := &UDM{SiteName: site.SiteName, SourceName: u.URL} + if u.unmarshalDevice("udm", payload, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + dev.site = site + devices.UDMs = append(devices.UDMs, dev) + } +} + +// unmarshalDevice handles logging for the unmarshal operations in parseDevices(). +func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) { + if err = json.Unmarshal(data, v); err != nil { + u.ErrorLog("json.Unmarshal(%v): %v", dev, err) + u.ErrorLog("Enable Debug Logging to output the failed payload.") + + json, err := data.MarshalJSON() + u.DebugLog("Failed Payload: %s (marshal err: %v)", json, err) + u.DebugLog("The above payload can prove useful during torubleshooting when you open an Issue:") + u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==") + } + + if err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + return nil +} + +// pick returns the first non empty string in a list. +// used in a few places around this library. +func pick(strings ...string) string { + for _, s := range strings { + if s != "" { + return s + } + } + + return "" +} diff --git a/core/unifi/devmgr.go b/core/unifi/devmgr.go new file mode 100644 index 00000000..679f6648 --- /dev/null +++ b/core/unifi/devmgr.go @@ -0,0 +1,299 @@ +package unifi + +import ( + "encoding/json" + "fmt" +) + +// Known commands that can be sent to device manager. All of these are implemented. +//nolint:lll // https://ubntwiki.com/products/software/unifi-controller/api#callable +const ( + DevMgrPowerCycle = "power-cycle" // mac = switch mac (required), port_idx = PoE port to cycle (required) + DevMgrAdopt = "adopt" // mac = device mac (required) + DevMgrRestart = "restart" // mac = device mac (required) + DevMgrForceProvision = "force-provision" // mac = device mac (required) + DevMgrSpeedTest = "speedtest" // Start a speed test + DevMgrSpeedTestStatus = "speedtest-status" // Get current state of the speed test + DevMgrSetLocate = "set-locate" // mac = device mac (required): blink unit to locate + DevMgrUnsetLocate = "unset-locate" // mac = device mac (required): led to normal state + DevMgrUpgrade = "upgrade" // mac = device mac (required): upgrade firmware + DevMgrUpgradeExternal = "upgrade-external" // mac = device mac (required), url = firmware URL (required) + DevMgrMigrate = "migrate" // mac = device mac (required), inform_url = New Inform URL for device (required) + DevMgrCancelMigrate = "cancel-migrate" // mac = device mac (required) + DevMgrSpectrumScan = "spectrum-scan" // mac = AP mac (required): trigger RF scan +) + +// devMgrCmd is the type marshalled and sent to APIDevMgrPath. +type devMgrCmd struct { + Cmd string `json:"cmd"` // Required. + Mac string `json:"mac"` // Device MAC (required for most, but not all). + URL string `json:"url,omitempty"` // External Upgrade only. + Inform string `json:"inform_url,omitempty"` // Migration only. + Port int `json:"port_idx,omitempty"` // Power Cycle only. +} + +// devMgrCommandReply is for commands with a return value. +func (s *Site) devMgrCommandReply(cmd *devMgrCmd) ([]byte, error) { + data, err := json.Marshal(cmd) + if err != nil { + return nil, fmt.Errorf("json marshal: %w", err) + } + + b, err := s.controller.GetJSON(fmt.Sprintf(APIDevMgrPath, s.Name), string(data)) + if err != nil { + return nil, fmt.Errorf("controller: %w", err) + } + + return b, nil +} + +// devMgrCommandSimple is for commands with no return value. +func (s *Site) devMgrCommandSimple(cmd *devMgrCmd) error { + _, err := s.devMgrCommandReply(cmd) + return err +} + +// PowerCycle shuts off the PoE and turns it back on for a specific port. +// Get a USW from the device list to call this. +func (u *USW) PowerCycle(portIndex int) error { + return u.site.devMgrCommandSimple(&devMgrCmd{ + Cmd: DevMgrPowerCycle, + Mac: u.Mac, + Port: portIndex, + }) +} + +// ScanRF begins a spectrum scan on an access point. +func (u *UAP) ScanRF() error { + return u.site.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSpectrumScan, Mac: u.Mac}) +} + +// Restart a device by MAC address on your site. +func (s *Site) Restart(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrRestart, Mac: mac}) +} + +// Restart an access point. +func (u *UAP) Restart() error { + return u.site.Restart(u.Mac) +} + +// Restart a switch. +func (u *USW) Restart() error { + return u.site.Restart(u.Mac) +} + +// Restart a security gateway. +func (u *USG) Restart() error { + return u.site.Restart(u.Mac) +} + +// Restart a dream machine. +func (u *UDM) Restart() error { + return u.site.Restart(u.Mac) +} + +// Restart a 10Gb security gateway. +func (u *UXG) Restart() error { + return u.site.Restart(u.Mac) +} + +// Locate a device by MAC address on your site. This makes it blink. +func (s *Site) Locate(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSetLocate, Mac: mac}) +} + +// Locate an access point. +func (u *UAP) Locate() error { + return u.site.Locate(u.Mac) +} + +// Locate a switch. +func (u *USW) Locate() error { + return u.site.Locate(u.Mac) +} + +// Locate a security gateway. +func (u *USG) Locate() error { + return u.site.Locate(u.Mac) +} + +// Locate a dream machine. +func (u *UDM) Locate() error { + return u.site.Locate(u.Mac) +} + +// Locate a 10Gb security gateway. +func (u *UXG) Locate() error { + return u.site.Locate(u.Mac) +} + +// Unlocate a device by MAC address on your site. This makes it stop blinking. +func (s *Site) Unlocate(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUnsetLocate, Mac: mac}) +} + +// Unlocate an access point (stop blinking). +func (u *UAP) Unlocate() error { + return u.site.Unlocate(u.Mac) +} + +// Unlocate a switch (stop blinking). +func (u *USW) Unlocate() error { + return u.site.Unlocate(u.Mac) +} + +// Unlocate a security gateway (stop blinking). +func (u *USG) Unlocate() error { + return u.site.Unlocate(u.Mac) +} + +// Unlocate a dream machine (stop blinking). +func (u *UDM) Unlocate() error { + return u.site.Unlocate(u.Mac) +} + +// Unlocate a 10Gb security gateway (stop blinking). +func (u *UXG) Unlocate() error { + return u.site.Unlocate(u.Mac) +} + +// Provision force provisions a device by MAC address on your site. +func (s *Site) Provision(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrForceProvision, Mac: mac}) +} + +// Provision an access point forcefully. +func (u *UAP) Provision() error { + return u.site.Provision(u.Mac) +} + +// Provision a switch forcefully. +func (u *USW) Provision() error { + return u.site.Provision(u.Mac) +} + +// Provision a security gateway forcefully. +func (u *USG) Provision() error { + return u.site.Provision(u.Mac) +} + +// Provision a dream machine forcefully. +func (u *UDM) Provision() error { + return u.site.Provision(u.Mac) +} + +// Provision a 10Gb security gateway forcefully. +func (u *UXG) Provision() error { + return u.site.Provision(u.Mac) +} + +// Upgrade starts a firmware upgrade on a device by MAC address on your site. +// URL is optional. If URL is not "" an external upgrade is performed. +func (s *Site) Upgrade(mac string, url string) error { + if url == "" { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUpgrade, Mac: mac}) + } + + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrUpgradeExternal, Mac: mac, URL: url}) +} + +// Upgrade firmware on an access point. +// URL is optional. If URL is not "" an external upgrade is performed. +func (u *UAP) Upgrade(url string) error { + return u.site.Upgrade(u.Mac, url) +} + +// Upgrade firmware on a switch. +// URL is optional. If URL is not "" an external upgrade is performed. +func (u *USW) Upgrade(url string) error { + return u.site.Upgrade(u.Mac, url) +} + +// Upgrade firmware on a security gateway. +// URL is optional. If URL is not "" an external upgrade is performed. +func (u *USG) Upgrade(url string) error { + return u.site.Upgrade(u.Mac, url) +} + +// Upgrade firmware on a dream machine. +// URL is optional. If URL is not "" an external upgrade is performed. +func (u *UDM) Upgrade(url string) error { + return u.site.Upgrade(u.Mac, url) +} + +// Upgrade formware on a 10Gb security gateway. +// URL is optional. If URL is not "" an external upgrade is performed. +func (u *UXG) Upgrade(url string) error { + return u.site.Upgrade(u.Mac, url) +} + +// Migrate sends a device to another controller's URL. +// Probably does not work on devices with built-in controllers like UDM & UXG. +func (s *Site) Migrate(mac string, url string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrMigrate, Mac: mac, Inform: url}) +} + +// Migrate sends an access point to another controller's URL. +func (u *UAP) Migrate(url string) error { + return u.site.Migrate(u.Mac, url) +} + +// Migrate sends a switch to another controller's URL. +func (u *USW) Migrate(url string) error { + return u.site.Migrate(u.Mac, url) +} + +// Migrate sends a security gateway to another controller's URL. +func (u *USG) Migrate(url string) error { + return u.site.Migrate(u.Mac, url) +} + +// Migrate sends a 10Gb gateway to another controller's URL. +func (u *UXG) Migrate(url string) error { + return u.site.Migrate(u.Mac, url) +} + +// CancelMigrate stops a migration in progress. +// Probably does not work on devices with built-in controllers like UDM & UXG. +func (s *Site) CancelMigrate(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrCancelMigrate, Mac: mac}) +} + +// CancelMigrate stops an access point migration in progress. +func (u *UAP) CancelMigrate() error { + return u.site.CancelMigrate(u.Mac) +} + +// CancelMigrate stops a switch migration in progress. +func (u *USW) CancelMigrate() error { + return u.site.CancelMigrate(u.Mac) +} + +// CancelMigrate stops a security gateway migration in progress. +func (u *USG) CancelMigrate() error { + return u.site.CancelMigrate(u.Mac) +} + +// CancelMigrate stops 10Gb gateway a migration in progress. +func (u *UXG) CancelMigrate() error { + return u.site.CancelMigrate(u.Mac) +} + +// Adopt a device by MAC address to your site. +func (s *Site) Adopt(mac string) error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrAdopt, Mac: mac}) +} + +// SpeedTest begins a speed test on a site. +func (s *Site) SpeedTest() error { + return s.devMgrCommandSimple(&devMgrCmd{Cmd: DevMgrSpeedTest}) +} + +// SpeedTestStatus returns the raw response for the status of a speed test. +// XXX: marshal the response into a data structure. This method will change! +func (s *Site) SpeedTestStatus() ([]byte, error) { + body, err := s.devMgrCommandReply(&devMgrCmd{Cmd: DevMgrSpeedTestStatus}) + // marshal into struct here. + return body, err +} diff --git a/core/unifi/dpi.go b/core/unifi/dpi.go new file mode 100644 index 00000000..f487c877 --- /dev/null +++ b/core/unifi/dpi.go @@ -0,0 +1,2377 @@ +package unifi + +import "strconv" + +// DPITable contains DPI data for clients or sites, or .. things. +type DPITable struct { + SourceName string `json:"-"` + SiteName string `json:"-"` + Name string `json:"-"` + MAC string `json:"mac"` + ByCat []DPIData `json:"by_cat"` + ByApp []DPIData `json:"by_app"` + LastUpdated int64 `json:"last_updated"` +} + +// DPIData is the DPI data in the DPI table. +type DPIData struct { + Cat int `json:"cat"` + App int `json:"app"` + RxBytes int64 `json:"rx_bytes"` + TxBytes int64 `json:"tx_bytes"` + RxPackets int64 `json:"rx_packets"` + TxPackets int64 `json:"tx_packets"` + Clients []*DPIClient `json:"clients,omitempty"` + KnownClients FlexInt `json:"known_clients,omitempty"` +} + +// DPIClient data is sometimes included in ByApp output. +type DPIClient struct { + Mac string `json:"mac"` + RxBytes FlexInt `json:"rx_bytes"` + TxBytes FlexInt `json:"tx_bytes"` + RxPackets FlexInt `json:"rx_packets"` + TxPackets FlexInt `json:"tx_packets"` +} + +// DPIMap allows binding methods to the DPICat and DPIApps variables. +type DPIMap map[int]string + +// Get returns a value, or an unknown placeholder. +func (d DPIMap) Get(cat int) string { + if v, ok := d[cat]; ok { + return v + } + + return "Unknown_" + strconv.Itoa(cat) +} + +// GetApp returns an app value, or an unknown placeholder. +func (d DPIMap) GetApp(cat, app int) string { + if v, ok := d[cat<<16+app]; ok { + return v + } + + return "Unknown_" + strconv.Itoa(cat<<16+app) +} + +// Keys returns the map keys in a slice. +func (d DPIMap) Keys() []string { + out := []string{} + for k := range d { + out = append(out, strconv.Itoa(k)) + } + + return out +} + +// DPICats maps the categories to descriptions. +// From: https://fw-download.ubnt.com/data/usg-dpi/1628-debian-v1.442.0-05f5a57eaef344358bd5a8e84a184c18.tar +var DPICats = DPIMap{ // nolint: gochecknoglobals + 0: "Instant Messengers", + 1: "Peer-to-Peer Networks", + 3: "File Sharing", + 4: "Media Streaming", + 5: "Email Messaging", + 6: "VoIP Services", + 7: "Database Tools", + 8: "Online Games", + 9: "Management Protocols", + 10: "Remote Access", + 11: "Tunneling and Proxy", + 12: "Investment Platforms", + 13: "Web Services", + 14: "Security Updates", + 15: "Web IM", + 17: "Business Tools", + 18: "Network Protocols_18", + 19: "Network Protocols_19", + 20: "Network Protocols_20", + 23: "Private Protocols", + 24: "Social Networks", + 255: "Unknown_255", +} + +// DPIApps maps the applications to names. +// From: https://fw-download.ubnt.com/data/usg-dpi/1628-debian-v1.442.0-05f5a57eaef344358bd5a8e84a184c18.tar +var DPIApps = DPIMap{ // nolint: gochecknoglobals + 1: "MSN", + 2: "Yahoo Messenger", + 3: "AIM/ICQ/iIM", + 4: "QQ/TM", + 5: "DingTalk/Laiwang", + 6: "IRC", + 7: "Yoics", + 8: "Rediff BOL", + 9: "Google Talk", + 10: "Gadu-Gadu", + 11: "Yixin", + 12: "POPO", + 13: "Tlen", + 14: "Wlt", + 15: "RenRen", + 16: "Omegle", + 17: "IPMSG", + 18: "Aliww", + 19: "Mail.ru IM", + 20: "Kubao", + 21: "Lava-Lava", + 22: "PaltalkScene", + 23: "UcTalk", + 24: "WinpopupX", + 25: "BeeTalk", + 26: "Squiggle", + 27: "Apple iMessage", + 28: "Pidgin", + 29: "ISPQ", + 30: "Momo", + 31: "ChatON", + 32: "Caihong", + 33: "KC", + 34: "IMVU", + 35: "Instan-t", + 36: "PiIM", + 37: "Xfire", + 38: "Raidcall", + 39: "Slack", + 41: "WhatsApp", + 42: "Userplane", + 43: "24im", + 44: "Camfrog", + 45: "Snow", + 46: "Digsby", + 49: "Message Send Protocol", + 52: "SOMA", + 53: "Hike", + 54: "Fetion", + 55: "Heyyo", + 56: "Alicall", + 57: "Qeshow", + 58: "MissLee", + 59: "Jctrans", + 61: "BaiduHi", + 62: "TELTEL", + 64: "9158", + 65: "Kltx", + 66: "IM+", + 67: "Imi", + 68: "Netcall", + 69: "ECP", + 72: "Etnano", + 77: "ProvideSupport", + 78: "Dudu IM", + 80: "Weibo IM", + 81: "WO", + 82: "Guagua", + 83: "Hangouts", + 84: "ClubCooee", + 85: "Palringo", + 86: "KikMessenger", + 87: "Doshow", + 88: "Mibbit", + 89: "YY", + 90: "Ispeak", + 91: "VzoChat", + 92: "Trillian", + 93: "HipChat", + 94: "IntraMessenger", + 95: "BitWise", + 96: "Barablu", + 97: "Whoshere", + 98: "LiiHo", + 99: "Appme", + 100: "Verychat", + 101: "Voxer", + 102: "TextMe", + 103: "Bump", + 104: "CoolMessenger", + 105: "NateOn", + 106: "WeChat", + 107: "Snapchat", + 108: "Wangxin", + 65538: "BitTorrent Series", + 65540: "DirectConnect", + 65542: "eDonkey Series", + 65543: "FastTrack", + 65544: "Gnutella", + 65545: "WinMX", + 65546: "Foxy", + 65547: "Winny", + 65548: "POCO", + 65549: "iMesh/Lphant", + 65550: "ClubBox", + 65551: "Vagaa", + 65553: "Thunder", + 65554: "myMusic", + 65555: "QQDownload", + 65556: "WebTorrent", + 65557: "easyMule", + 65559: "Fileguri", + 65563: "Soulseek", + 65565: "GNUnet", + 65566: "XNap", + 65567: "Avicora", + 65568: "Kceasy", + 65569: "Aria2", + 65570: "Arctic", + 65572: "Bitflu", + 65573: "BTG", + 65574: "Pando", + 65577: "Deepnet Explorer", + 65578: "aMule", + 65580: "Ares", + 65581: "Azureus", + 65582: "BCDC++", + 65583: "BitBuddy", + 65584: "BitComet", + 65585: "BitTornado", + 65587: "ApexDC++", + 65588: "Bearshare", + 65590: "BitLord", + 65591: "BitSpirit", + 65594: "Shareaza", + 65598: "eMule", + 65600: "eMule Plus", + 65604: "FileScope", + 65609: "GoGoBox", + 65612: "Hydranode", + 65617: "Kazaa Lite Tools K++", + 65620: "BitRocket", + 65621: "MlDonkey", + 65622: "MooPolice", + 65630: "Phex", + 65633: "RevConnect", + 65634: "Rufus", + 65635: "SababaDC", + 65636: "Shareaza Plus", + 65640: "BTSlave", + 65642: "TorrentStorm", + 65648: "uTorrent", + 65652: "ZipTorrent", + 65655: "BitPump", + 65665: "Tuotu", + 65685: "Vuze", + 65686: "Enhanced CTorrent", + 65688: "Bittorrent X", + 65689: "DelugeTorrent", + 65690: "CTorrent", + 65691: "Propagate Data Client", + 65692: "EBit", + 65693: "Electric Sheep", + 65695: "FoxTorrent", + 65696: "GSTorrent", + 65698: "Halite", + 65700: "KGet", + 65701: "KTorrent", + 65703: "LH-ABC", + 65704: "libTorrent", + 65705: "LimeWire", + 65707: "MonoTorrent", + 65708: "MoonlightTorrent", + 65709: "Net Transport", + 65714: "qBittorrent", + 65715: "Qt 4 Torrent example", + 65716: "Retriever", + 65718: "Swiftbit", + 65720: "SwarmScope", + 65721: "SymTorrent", + 65722: "Sharktorrent", + 65724: "TorrentDotNET", + 65725: "Transmission", + 65726: "uLeecher", + 65727: "BitLet", + 65728: "FireTorrent", + 65730: "XanTorrent", + 65731: "Xtorrent", + 65732: "Pruna", + 65733: "Soribada", + 65734: "Gample", + 65735: "DIYHARD", + 65736: "LottoFile", + 65737: "ShareBox", + 65738: "Bondisk", + 65739: "Filei", + 65740: "KDISK", + 65741: "Ondisk", + 65742: "FILEJO", + 65743: "FILEDOK", + 65744: "Tomatopang/Santa25", + 65745: "Webhard", + 65746: "TPLE", + 65747: "DiskPump", + 65748: "NETFOLDER", + 65749: "QFILE", + 65750: "DISKMAN", + 65751: "DBGO", + 65752: "Congaltan", + 65753: "Diskpot", + 65754: "Ipopclub", + 65755: "Yesfile", + 65756: "Nedisk", + 65757: "Me2disk", + 65758: "Odisk", + 65759: "Tomfile", + 65760: "Adrive.co.kr", + 65761: "ZIOfile", + 65762: "APPLEFILE", + 65763: "SUPERDOWN", + 65764: "Hidisk", + 65765: "Downs", + 65766: "DownDay", + 65767: "BOMULBOX", + 65768: "FILEHAM", + 65769: "Tdisk", + 65770: "Filehon", + 65771: "Jjangfile", + 65772: "Onehard.com", + 65773: "Pdpop", + 65774: "AirFile", + 65775: "FILEZZIM", + 65776: "Atomfile.co.kr", + 65777: "QDOWN.com", + 65778: "Alfile.net", + 65779: "Bigfile.co.kr", + 65780: "Hardmoa.com", + 65781: "Redfile.co.kr", + 65782: "FILETV.co.kr", + 65783: "Now.co.kr", + 65784: "JustBeamIt", + 65785: "reep.io", + 65786: "GnucDNA/Gimme", + 65787: "MyNapster", + 196609: "FTP Applications", + 196610: "GetRight", + 196611: "FlashGet", + 196612: "AsianDVDClub", + 196613: "Web File Transfer", + 196614: "FileZilla", + 196615: "Kuaipan", + 196616: "DBank", + 196617: "115.com", + 196618: "Weiyun", + 196619: "Rayfile", + 196620: "0zz0", + 196621: "Herosh", + 196622: "2Shared", + 196624: "BIZHARD", + 196626: "UPlusBox", + 196627: "Filebox.ro", + 196628: "Qnext", + 196629: "OneDrive", + 196630: "YunFile", + 196631: "Filehosting", + 196632: "Dev-Host", + 196633: "Solidfiles", + 196634: "IBackup", + 196635: "FileSwap", + 196637: "Temp-Share", + 196638: "WikiUpload", + 196640: "MEGA", + 196641: "Copy.com", + 196642: "4Shared", + 196643: "HiCloud", + 196644: "Depositfiles", + 196645: "Docstoc", + 196646: "360 Cloud", + 196647: "Symantec Nomdb", + 196648: "Baidu Cloud", + 196649: "GitHub", + 196650: "FileDropper", + 196651: "CrashPlan", + 196652: "Net2FTP", + 196653: "Mediafire", + 196655: "Carbonite", + 196656: "Mozy", + 196657: "SOS Online Backup", + 196670: "NFS", + 196672: "WD My Cloud", + 196676: "Box", + 196678: "Scribd", + 196680: "Rapidshare", + 196681: "Sendspace", + 196683: "Hightail", + 196684: "Diino", + 196686: "Fluxiom", + 196689: "Nomadesk", + 196692: "Dropbox", + 196693: "Filesend.to", + 196694: "Firestorage", + 196695: "Naver Cloud", + 196696: "Filesend.net", + 196697: "Crocko", + 196700: "Fileserve", + 196701: "Netload", + 196702: "Megashares", + 196703: "TransferBigFiles", + 196705: "Filemail", + 196706: "Zamzar", + 196708: "Divshare", + 196709: "DL Free", + 196711: "Nakido", + 196713: "Gigaup", + 196714: "Filestube", + 196716: "Filer.cx", + 196717: "Cx.com", + 196718: "Elephantdrive", + 196722: "Zshare", + 196723: "Freakshare", + 196724: "Uploading", + 196725: "Bitshare", + 196726: "Letitbit.net", + 196727: "Extabit", + 196728: "Filefactory", + 196729: "Furk", + 196731: "GoldFile", + 196732: "GigaSize", + 196733: "Turbobit", + 196735: "Hitfile", + 196737: "Zippyshare", + 196738: "SoundCloud", + 196739: "SpeedyShare", + 196741: "WinSCP", + 196742: "FilePost.net", + 196743: "GlumboUploads", + 196744: "RapidGator.net", + 196745: "GoZilla", + 196746: "Clip2net", + 196747: "Datei.to", + 196748: "Totodisk", + 196749: "LeapFile", + 196750: "BigUpload", + 196751: "OnlineFileFolder", + 196752: "ASUSWebStorage", + 196753: "File-Upload.net", + 196754: "File-Works", + 196755: "Zumodrive", + 196756: "PutLocker", + 196757: "Wetransfer", + 196758: "iCloud", + 196759: "CloudMe", + 196760: "Beanywhere", + 196761: "Sugarsync", + 196762: "DriveHQ", + 196763: "Yandex.Disk", + 196764: "Backblaze", + 196765: "AirSet", + 196766: "SpiderOak", + 196767: "1337X", + 196768: "MailBigFile", + 196769: "GoldCoupon.co.kr", + 196770: "Egnyte", + 196771: "SmugMug", + 196772: "SlideShare.net", + 196773: "4Sync", + 196774: "IDrive", + 196775: "Mendeley", + 196777: "Daum-cloud", + 196778: "TeamBeam", + 262145: "Windows Media Player", + 262146: "RealPlayer", + 262147: "Winamp", + 262148: "QuickTime", + 262149: "Weather Channel", + 262150: "PPTV (PPLive)", + 262151: "QQLive", + 262152: "LOVEFiLM", + 262153: "ITV", + 262154: "iTunes", + 262155: "Adobe Flash", + 262156: "Channel 5", + 262157: "iQIYI/PPS", + 262158: "Headweb", + 262159: "Viaplay", + 262160: "KKBox", + 262161: "WATCHEVER", + 262162: "Maxdome", + 262163: "Twitch.tv", + 262164: "TED", + 262165: "RTP", + 262166: "SBS", + 262167: "UUSee", + 262168: "SopCast", + 262169: "KeyHoleTV", + 262170: "Sina Video", + 262171: "Metacafe", + 262172: "Wuaki.tv", + 262173: "SHOUTcast", + 262174: "BBC-iplayer", + 262175: "Live365", + 262176: "Dailymotion", + 262177: "Filmin", + 262178: "Flixster", + 262179: "Hulu", + 262180: "GuaGua", + 262181: "NUBEOX", + 262182: "Kugou", + 262183: "MoveNetworks", + 262184: "Babelgum", + 262185: "Livestation", + 262186: "Apple Music", + 262187: "Miro", + 262188: "Smithsonian Channel", + 262189: "NHL", + 262190: "NicoNico", + 262191: "Ooyala", + 262192: "Photobucket", + 262193: "MLSsoccer", + 262194: "Channel 4", + 262195: "VideoDetective", + 262196: "Ustream.tv", + 262197: "Veetle", + 262198: "VeohTV", + 262199: "iTunes Festival", + 262200: "SiriusXM", + 262201: "Break.com", + 262202: "CinemaNow/FilmOn", + 262203: "Letv", + 262204: "RTSP", + 262205: "Funshion", + 262206: "17", + 262207: "MTV.com", + 262208: "Sohu TV", + 262209: "MP4", + 262210: "MMS/WMSP", + 262211: "FLV", + 262212: "PIPI", + 262213: "Hulkshare", + 262214: "Tudou", + 262215: "Ifeng Video ", + 262216: "WSJ Live", + 262217: "Cradio", + 262218: "Roku", + 262219: "Amazon Prime Music", + 262220: "Crackle", + 262221: "Blip.tv", + 262223: "Audible", + 262224: "Web Streaming", + 262225: "DIRECTV", + 262226: "Vyclone", + 262227: "China Streaming Video", + 262228: "Crunchyroll", + 262229: "EmpFlix", + 262230: "Porn.com", + 262231: "EskimoTube", + 262232: "NewBigTube", + 262233: "Madbitties", + 262234: "RTMP", + 262235: "Hustlertube", + 262236: "TnaFlix", + 262237: "Xtube", + 262238: "Yobt.tv", + 262239: "Youjizz", + 262240: "v.163.com", + 262241: "Yahoo Video", + 262245: "Pandora", + 262246: "Deezer", + 262247: "VLC", + 262250: "Livesearch.tv/CoolStreaming", + 262251: "Qello", + 262252: "CNTV", + 262254: "Thunderkankan", + 262256: "Youtube", + 262258: "56.com", + 262259: "RMVB", + 262260: "Youku.com", + 262261: "SWF", + 262262: "AVI", + 262263: "MP3", + 262264: "WMA", + 262265: "MOV", + 262266: "WMV", + 262267: "ASF", + 262268: "Vudu", + 262270: "PBS Video", + 262271: "Freecast", + 262272: "Ku6", + 262274: "Spotify", + 262275: "LastFM", + 262276: "Netflix", + 262277: "Uitzendinggemist", + 262278: "RTL.nl", + 262279: "TudouVa", + 262280: "GYAO", + 262281: "BARKS", + 262283: "Baofeng", + 262284: "Qvod/Bobohu", + 262285: "Grooveshark", + 262286: "Microsoft Silverlight", + 262287: "6.cn", + 262288: "Rhapsody", + 262289: "Kideos", + 262290: "Imgo TV", + 262291: "Joy.cn", + 262292: "Yinyuetai", + 262293: "Hichannel", + 262294: "ADNstream", + 262295: "Livestream", + 262296: "YoukuVa ", + 262297: "Kodi", + 262298: "Voddler", + 262299: "National Geographic Kids", + 262301: "Flixwagon", + 262302: "M4V", + 262303: "Podcast", + 262305: "Shazam", + 262306: "TuneIn", + 262307: "PBS Kids", + 262308: "BaiduMusic", + 262310: "DoubanFM", + 262311: "IMDb.com", + 262312: "XVideos.com", + 262313: "xHamster.com", + 262314: "PornHub.com", + 262315: "LiveJasmin.com", + 262316: "XNXX.com", + 262317: "YouPorn.com", + 262318: "MajorLeagueGaming", + 262319: "Wowtv.co.kr", + 262320: "iMBC", + 262321: "AfreecaTV", + 262322: "Arirang", + 262323: "KCTVjeju", + 262324: "CJB.co.kr", + 262325: "MBN", + 262326: "MYSolive", + 262327: "KBS", + 262328: "Mwave", + 262329: "YTN", + 262330: "Musicsoda", + 262331: "FreeOnes.com", + 262332: "Streamate.com", + 262333: "Airplay", + 262334: "DAAP", + 262335: "M1905", + 262336: "VEVO", + 262337: "Amazon Instant Video", + 262338: "MixBit", + 262339: "Baomihua", + 262340: "FORA.tv", + 262341: "Vimeo", + 262342: "Vube", + 262343: "RedTube.com", + 262344: "Tube8", + 262345: "Mgoon", + 262346: "Trailers", + 262347: "HBOGO", + 262348: "MLB.com", + 262349: "Kaltura.com", + 262350: "Plex.tv", + 262351: "DouyuTV", + 262358: "Kids.gov", + 262367: "Periscope", + 262373: "HBO NOW", + 262374: "MiaoPai", + 262389: "UniFi Video Camera", + 327681: "SMTP", + 327682: "POP3", + 327683: "IMAP4", + 327684: "NNTP", + 327685: "Twig", + 327686: "GroupWise", + 327687: "au one net", + 327688: "Virtru", + 327689: "PChome", + 327690: "DTI MyMail", + 327691: "Ymail", + 327692: "IIJ MailViewer", + 327693: "Telenet Mail", + 327694: "Open Mail", + 327695: "InfoSphere Webmail", + 327696: "Goo Mail", + 327697: "Nifty", + 327698: "QQ Mail", + 327699: "Roundcubemail", + 327700: "Zenno", + 327701: "Itm-asp", + 327702: "Biglobe", + 327703: "SquirrelMail", + 327704: "Zoho Mail", + 327705: "Inter7", + 327706: "TOK2", + 327707: "Smoug", + 327708: "1und1", + 327709: "Plala", + 327710: "WAKWAK", + 327711: "Eyejot", + 327712: "AsahiNet", + 327713: "Aikq", + 327714: "Yandex.Mail", + 327715: "Arcor", + 327716: "Bluewin", + 327717: "Directbox", + 327718: "Freenet", + 327720: "Smart Mail", + 327722: "WEB.DE", + 327723: "MS Exchange Server", + 327732: "Webmail.de", + 327742: "NETEASE Mail", + 327743: "Gmx Mail", + 327744: "Excite", + 327745: "InfoSeek Mail", + 327746: "Livedoor", + 327747: "Nate Mail", + 327749: "Optimum", + 327751: "Secureserver", + 327753: "Sina Mail", + 327755: "Rambler", + 327760: "Daum Mail", + 327761: "Mail.com", + 327762: "OCN", + 327763: "MailChimp", + 327764: "Rediff Mail", + 327770: "Korea Mail", + 327772: "MyEmail", + 327773: "JumboMail", + 327775: "Gmail", + 327776: "AOL Mail", + 327777: "hiBox", + 327778: "COX", + 327779: "Hushmail", + 327780: "Mail.ru", + 327781: "HiNet Mail", + 327782: "Horde", + 327783: "Fastmail", + 327784: "Comcast", + 327785: "Laposte", + 327786: "Yahoo Mail", + 327787: "Usermin Mail", + 327788: "Tistory", + 327789: "Orange", + 327790: "012mail", + 327791: "T-Online", + 327792: "Jubii Mail", + 327793: "Whalemail", + 327794: "Lavabit", + 327795: "Tiscali", + 393217: "Skype", + 393218: "H.323", + 393220: "Facetime", + 393221: "Juiker", + 393222: "Sqwiggle", + 393223: "ooVoo", + 393225: "TeamSpeak", + 393226: "Ventrilo", + 393228: "SIP", + 393229: "NetMeeting", + 393230: "Inter-Asterisk", + 393231: "Net2Phone", + 393232: "MSRP", + 393234: "LINE", + 393235: "Fring", + 393236: "Goober", + 393238: "Viber", + 393239: "Kakao", + 393240: "iCall", + 393242: "Nimbuzz", + 393243: "Bobsled", + 393244: "indoona", + 393245: "Wi-Fi Calling", + 393246: "Tango", + 393247: "Ooma", + 458753: "MSSQL", + 458754: "MySQL", + 458755: "Oracle", + 458756: "PostgreSQL", + 458757: "SAP", + 458760: "Etelos", + 458761: "Centriccrm", + 458766: "MongoDB", + 458767: "Salesforce", + 458768: "MariaDB", + 524289: "QQ Game", + 524290: "Our Game", + 524291: "Cga.com", + 524292: "FIFA", + 524293: "PopKart", + 524294: "Archlord", + 524295: "AddictingGames.com", + 524296: "Realgame", + 524297: "Audition", + 524298: "Koramgame", + 524299: "BnB Game", + 524300: "Chinagame", + 524301: "CS Game", + 524302: "Diablo", + 524303: "Legend", + 524304: "Lineage", + 524306: "Quake Game", + 524307: "Diablo3", + 524308: "Sina Web Game", + 524310: "WOW Game", + 524311: "Ispeakgame", + 524312: "Torchlight2", + 524313: "MapleStory", + 524314: "TowerOfSaviors", + 524315: "Wolfenstein", + 524316: "Second Life", + 524317: "Kimi", + 524318: "Pokemon Go", + 524319: "PartyPoker", + 524320: "Pogo", + 524321: "PokerStars", + 524322: "Zango", + 524323: "Little Fighter 2", + 524324: "BomberClone", + 524325: "Doom", + 524326: "FSJOY", + 524327: "175pt", + 524328: "Zhuxian", + 524329: "GameTea/GameABC", + 524330: "Talesrunner", + 524331: "PK Game", + 524332: "Concerto Gate", + 524333: "TLBB", + 524334: "YBOnline", + 524335: "Xunyou", + 524336: "Mwo", + 524337: "Mobile Strike", + 524338: "WuLin", + 524339: "DNF Game", + 524340: "Bo Game", + 524341: "Gran Turismo", + 524343: "Electronic Arts", + 524344: "ZhengTu", + 524345: "SGOL", + 524346: "XY2Online", + 524347: "Asherons Call", + 524348: "Kali", + 524349: "EverQuest", + 524350: "XBOX", + 524351: "BrettspielWelt", + 524352: "Bet-at-Home", + 524353: "City of Heroes", + 524354: "ClubPenguin", + 524355: "StepMania", + 524356: "Battle.net", + 524358: "Apprentice", + 524359: "Monster Hunter Frontier Z", + 524360: "FreeLotto Game", + 524361: "Halo", + 524362: "iSketch", + 524363: "RuneScape", + 524364: "FUNMILY", + 524365: "Yeapgame", + 524366: "Grand Theft Auto", + 524367: "Lineage2", + 524368: "GM99 Game", + 524369: "RayCity", + 524370: "Rockstar Games", + 524371: "Aleph One", + 524372: "Wayi", + 524373: "CMWEBGAME", + 524374: "Call of Duty", + 524375: "CAPTAN", + 524376: "Supercell", + 524377: "Need for Speed", + 524379: "Madden NFL", + 524380: "Half-Life", + 524381: "Team Fortress", + 524383: "Final Fantasy", + 524384: "Mythic", + 524385: "NetPanzer", + 524386: "Sdo.com", + 524388: "Pokemon Netbattle", + 524389: "RunUO-Ultima", + 524390: "Soldat Dedicated", + 524391: "Blizzard Entertainment", + 524392: "RIFT", + 524393: "TetriNET", + 524394: "Tibia", + 524395: "PlanetSide", + 524396: "TripleA", + 524398: "Unreal", + 524399: "Valve Steam", + 524400: "WesNOth", + 524401: "Xpilot", + 524402: "Swtor", + 524403: "EVEOnline", + 524404: "Hearthstone", + 524405: "Guild Wars", + 524406: "Zhong Hua Hero", + 524407: "Wizard101", + 524408: "SD Gundam", + 524409: "Prius", + 524410: "Age of Conan", + 524411: "RF Returns", + 524412: "AION", + 524413: "POPO Game", + 524414: "War-Rock", + 524415: "TEN Game", + 524416: "LUNA2", + 524417: "Karos", + 524418: "SPOnline", + 524419: "RO Game", + 524420: "StarCraft2", + 524421: "Itaiwanmj", + 524422: "CMWEBGAME Game", + 524423: "Beanfun Game", + 524424: "JXW", + 524425: "Nobol", + 524426: "DragonNest", + 524427: "BBonline", + 524428: "Hangame", + 524429: "Homygame", + 524430: "Sony PlayStation", + 524431: "Garena", + 524432: "91555", + 524433: "JJ Game", + 524434: "YHgame", + 524435: "Mdm365", + 524436: "7fgame", + 524437: "Dokee", + 524438: "VSA", + 524439: "Funtown", + 524440: "SF Game", + 524441: "173kh", + 524442: "Boyaapoker", + 524443: "GameCenter", + 524444: "Minecraft", + 524445: "Dark Souls", + 524446: "The Secret World", + 524447: "World2", + 524448: "CrossFire", + 524449: "XYQ", + 524450: "Nexon", + 524451: "Vindictus", + 524452: "DotA", + 524453: "PAYDAY", + 524454: "Wayi Game", + 524455: "War Thunder", + 524456: "Warframe", + 524457: "TT-Play Game", + 524458: "TT-Play", + 524459: "Robocraft", + 524460: "World of Tanks", + 524461: "Divinity", + 524462: "Left 4 Dead 2", + 524463: "DayZ", + 524464: "Heroes of the Storm", + 524466: "TXWY Game", + 524476: "Smite", + 524478: "FreeStyle 2 Street Basketball", + 524479: "Yeapgame Game", + 524483: "BlackShot", + 524486: "Combat Arms", + 524490: "Blade and Soul", + 524491: "FUNMILY Game", + 524500: "Elsword", + 524501: "Echo of Soul", + 524502: "Aura Kingdom", + 524503: "Aeria Games", + 524504: "9-yin", + 524505: "Tera", + 524506: "PSO2", + 524507: "Mabinogi", + 524510: "Ubisoft", + 524512: "Sony Entertainment Network", + 524513: "WSOP", + 524514: "TexasHoldemPoker", + 524515: "DarkSummoner", + 524516: "AjaxPlay", + 524517: "AirlineMogul", + 524518: "Evony", + 524519: "BasketBallZone", + 524520: "Y8 Game", + 524521: "Y8-Y8", + 524522: "KIZI-GAMES", + 524523: "Ibibo", + 524524: "Hattrick Game", + 524525: "Godgame", + 524526: "Aswordtw", + 524527: "Qme RO", + 524529: "THE WORLD", + 524530: "Qme JH", + 524531: "Qme COS", + 524532: "Qme SG", + 524533: "Origin", + 524534: "LoL", + 524535: "THISISGAME", + 524536: "Miniclip Game", + 524537: "888games", + 524538: "WilliamHill", + 524539: "Betfair Game", + 524540: "Kongregate Game", + 524541: "Roblox Game", + 524542: "King Game", + 524543: "Chess Game", + 524593: "Overwatch", + 524632: "Battlefield", + 524633: "Star Wars Battlefront", + 524639: "Rainbow Six Siege", + 524640: "ARK Survival Evolved", + 524641: "The Division", + 524648: "Super Mario Run", + 524649: "Nintendo", + 524651: "Clash of Clans", + 524652: "Clash Royale", + 524736: "Grand Theft Auto: San Andreas", + 524782: "Destiny 2", + 524790: "NBA 2K18", + 524794: "Uncharted: The Lost Legacy", + 524795: "NHL 18", + 524796: "NBA Live 18", + 524801: "Wargaming.net", + 589828: "IGMP", + 589829: "SNMP", + 589885: "DNS", + 589888: "Multicast DNS", + 589890: "Finger protocol", + 589916: "DCE-RPC", + 589933: "SSDP", + 589934: "SMB", + 589936: "SMB2", + 589942: "ICMP", + 589951: "UPnP", + 655361: "pcAnywhere", + 655362: "VNC", + 655363: "TeamViewer", + 655364: "MS Remote Desktop Protocol (RDP)", + 655365: "Chrome Remote Desktop", + 655366: "NTRglobal", + 655367: "RemoteCall", + 655368: "LiveCare", + 655369: "GoToMyPC", + 655370: "Pulseway", + 655371: "Radmin", + 655372: "Beinsync", + 655373: "Fastviewer", + 655374: "CrossTec Remote Control", + 655375: "GoToMeeting", + 655376: "ShowMyPC", + 655377: "Join.me", + 655378: "Telnet", + 655379: "Techinline", + 655380: "ISL Online", + 655381: "Secure Shell (SSH)", + 655385: "IBM Remote monitoring and Control", + 655395: "Netviewer", + 655396: "VT100", + 655397: "AnyDesk", + 655398: "X11", + 655399: "Alpemix", + 655402: "Instanthousecall", + 655403: "Ammyy", + 655404: "Anyplace Control", + 655405: "BeamYourScreen", + 655406: "Laplink Everywhere", + 655407: "GoToAssist", + 655408: "MSP Anywhere", + 720898: "VNN", + 720899: "Spotflux", + 720900: "SoftEther/PacketiX", + 720901: "TinyVPN", + 720902: "HTTP-Tunnel", + 720903: "Tor", + 720904: "Ping Tunnel", + 720905: "Wujie/UltraSurf", + 720906: "Freegate", + 720907: "Hidemyass", + 720909: "Vedivi", + 720910: "ZenMate", + 720911: "Hamachi", + 720912: "Disconnect.me", + 720914: "Asproxy", + 720915: "OpenDoor", + 720916: "NSTX DNS Tunnel", + 720917: "Coralcdn", + 720918: "Glype", + 720919: "GPass", + 720920: "Kproxy", + 720921: "Megaproxy", + 720922: "FreeSafeIP", + 720924: "GreenVPN", + 720925: "Surrogafier", + 720926: "Vtunnel", + 720927: "GomVPN", + 720928: "BypassThat", + 720929: "GetPrivate", + 720930: "JAP/JonDo", + 720933: "SofaWare", + 720934: "FlyProxy", + 720936: "Kerberos", + 720939: "EasyHideIP", + 720942: "CPROXY", + 720943: "AnonyMouse", + 720945: "Avoidr", + 720946: "Hidedoor", + 720948: "CGIProxy", + 720949: "ProxyTopSite", + 720950: "Phproxy", + 720951: "OpenVPN", + 720952: "CCProxy", + 720953: "Proxy Rental", + 720954: "PD-Proxy", + 720955: "Proxy4Free", + 720957: "Hideman", + 720959: "Rtmpt", + 720960: "LogMeIn", + 720961: "HotspotShield", + 720962: "ExpressVPN", + 720963: "GogoNET", + 720964: "HTTP Proxy Server", + 720965: "Hola", + 720966: "Texasproxy", + 720967: "Ourproxy", + 720968: "Proxify", + 720969: "Fast Proxy", + 720970: "Zalmos", + 720971: "Easy Proxy", + 720972: "Proxy Era", + 720973: "DotVPN", + 720974: "BrowSec", + 720976: "Unblock Proxy", + 720977: "Air-Proxy", + 720978: "Suresome", + 720979: "Defilter", + 720980: "SSLunblock", + 720983: "K12History", + 720984: "SurfEasy", + 720985: "Frozenway", + 720986: "CyberGhostVPN", + 720987: "SecurityKISS", + 720988: "WebWarper", + 720989: "Guardster", + 720990: "ProxFree", + 720991: "TunnelBear", + 720992: "AstrillVPN", + 720993: "Hide ALL IP", + 720994: "ZfreeZ", + 720995: "IPVanish", + 720996: "PrivateTunnel", + 720997: "SaferSurf", + 720998: "SecureLine VPN", + 720999: "Steganos VPN", + 721000: "StrongVPN", + 721001: "ZeroTier", + 721002: "Ngrok", + 721003: "Pagekite", + 721004: "Goproxing", + 721005: "VPN.HT", + 721006: "Betternet", + 721007: "Hide My IP", + 721008: "Stay Invisible", + 721009: "Zapyo", + 721010: "NordVPN", + 721011: "Avast SecureLine", + 721013: "Tunnello", + 721014: "Opera VPN", + 786434: "DZH", + 786435: "10JQKA", + 786437: "Qianlong", + 786438: "Compass.cn", + 786439: "Huaan", + 786440: "StockStar ", + 786441: "TDX", + 786443: "Hexun", + 786444: "Hypwise", + 786449: "Kiwoom", + 786450: "Windin", + 786451: "SamsungPoP", + 786453: "StockTrace", + 786454: "JRJ", + 786455: "TradeFields", + 786456: "Bloomberg", + 786457: "Netdania", + 786458: "TradeInterceptor", + 851969: "WhiteHat Aviator", + 851970: "HTC Widget", + 851971: "Doodle", + 851972: "Level3", + 851973: "FuzeMeeting", + 851974: "Mobile01", + 851975: "Speedtest.net", + 851976: "Google Chrome", + 851977: "Babelfish", + 851978: "Google Translate", + 851980: "Mozilla Firefox", + 851981: "Apple Safari", + 851982: "Opera browser", + 851984: "Google Books", + 851985: "eBay", + 851986: "hao123", + 851987: "WebSocket", + 851988: "Tmall", + 851989: "PayPal.com", + 851990: "Ask.com", + 851991: "BBC", + 851992: "Alibaba.com", + 851993: "CNN.com", + 851995: "Sogou.com", + 851996: "Evernote", + 851997: "About.com", + 851998: "Alipay.com", + 851999: "Imgur", + 852000: "Adcash", + 852001: "Huffington Post", + 852002: "360buy", + 852003: "ESPN", + 852004: "Books", + 852005: "Craigslist.org", + 852006: "Google Analytics", + 852007: "Bing Maps", + 852008: "ETtoday ", + 852009: "104 Job Bank", + 852010: "NOWnews", + 852011: "518 Job Bank", + 852012: "Chinatimes.com", + 852013: "GOHAPPY", + 852014: "591", + 852015: "8591", + 852016: "Chinatrust", + 852017: "Donga.com", + 852018: "Gmarket", + 852019: "Chosun.com", + 852020: "Cafe24.com", + 852021: "11st", + 852022: "MK.co.kr", + 852023: "Auction", + 852024: "Hankyung", + 852025: "Ppomppu", + 852026: "MT.co.kr", + 852027: "Zum.com", + 852028: "Hankooki", + 852029: "JOBKOREA", + 852031: "Khan.co.kr", + 852032: "Incruit", + 852033: "YES24", + 852034: "Amazon CloudFront", + 852035: "Pcstore", + 852036: "Myfreshnet.com", + 852037: "Microsoft.com", + 852038: "Life.com.tw", + 852039: "Libertytimes", + 852040: "Lativ", + 852041: "Inven", + 852042: "cnYES", + 852043: "Babyhome", + 852044: "8comic.com", + 852045: "Ck101.com", + 852046: "Taiwanlottery", + 852047: "Momoshop", + 852048: "Eyny.com", + 852049: "Yam.com", + 852050: "PChome.com", + 852051: "Gamme", + 852052: "Apple.com", + 852053: "Hinet.net", + 852054: "Google Earth", + 852055: "Saramin", + 852056: "KoreaHerald", + 852057: "Plus28.com", + 852058: "ChunghwaPost ", + 852059: "Gomaji ", + 852060: "NewSen", + 852061: "Etnews.com", + 852062: "Seoul.co.kr", + 852063: "YONHAPNEWS", + 852064: "Etoday.co.kr", + 852065: "Yesky.com", + 852066: "1111 Job Bank", + 852067: "Emart", + 852068: "KBstar", + 852069: "HERALDCORP", + 852070: "ActiveX", + 852071: "MSN.com", + 852072: "Edaily", + 852073: "Segye", + 852074: "Bobaedream", + 852075: "Nocutnews", + 852076: "MONETA.co.kr", + 852077: "Kukinews", + 852078: "Java Applet", + 852079: "Todayhumor", + 852080: "Inews24", + 852081: "KoreaTimes", + 852082: "OhmyNews", + 852083: "Aladin.co.kr", + 852084: "SK Encar", + 852085: "eTorrent", + 852086: "TVREPORT", + 852087: "Mydaily", + 852088: "Microsoft Live.com", + 852089: "News1.kr", + 852090: "Munhwa", + 852091: "Dreamwiz", + 852092: "Dailian.co.kr", + 852093: "Rediff.com", + 852094: "Akamai.net", + 852096: "Microsoft Edge", + 852097: "Yugma", + 852098: "TPB PirateBrowser", + 852099: "Android browser", + 852100: "Wikispaces", + 852101: "Wikidot", + 852102: "Google Play", + 852103: "Wetpaint", + 852104: "Windows Store", + 852105: "Webshots", + 852106: "Kindle Cloud Reader", + 852107: "Nice264", + 852108: "Symbian browser", + 852109: "Vyew", + 852110: "TikiWiki", + 852111: "Castfire", + 852112: "Mercari", + 852113: "SugarCRM", + 852115: "Stumbleupon", + 852116: "Yahoo Shopping", + 852117: "Clothes Aoyama", + 852118: "Rakuten Shopping", + 852119: "Spark", + 852120: "Socialtext", + 852121: "CacaoWeb", + 852122: "PBworks", + 852123: "Fool", + 852124: "Showbie", + 852125: "MorningStar", + 852126: "Screaming Frog SEO Spider", + 852127: "MoinMoin", + 852128: "AppStore", + 852129: "Ragingbull", + 852130: "Daum", + 852131: "Google Docs", + 852133: "Naver", + 852134: "Editgrid", + 852135: "Jaspersoft", + 852136: "Clarizen", + 852139: "Interpark", + 852140: "Hyundaihmall", + 852141: "Groupon", + 852142: "Gsshop", + 852143: "Wemakeprice", + 852144: "Lotte.com", + 852145: "Coupang", + 852147: "Google Alerts", + 852149: "Dnshop.com", + 852150: "ZoomSpider crawler", + 852151: "Win Web Crawler", + 852152: "HTTrack crawler", + 852153: "Abot crawler", + 852154: "Googlebot crawler", + 852155: "Microsoft bingbot crawler", + 852156: "Yahoo Slurp crawler", + 852157: "Beanfun", + 852158: "QUIC", + 852159: "ifeng.com", + 852160: "Conduit Mobile", + 852161: "Rakuten Point", + 852162: "Gamebase", + 852163: "Kingstone", + 852164: "Udn.com", + 852165: "Fril", + 852166: "Sportsseoul", + 852167: "Babylon ", + 852168: "Yahoo Finance", + 852170: "Creative Cloud", + 852171: "Jira", + 852172: "PHPwiki", + 852173: "Rakuten Edy", + 852174: "WebCT", + 852175: "Youseemore", + 852176: "Zwiki-editing", + 852177: "Adobe.com", + 852178: "Backpackit/Campfire", + 852180: "ERoom-net", + 852182: "DiDiTaxi", + 852184: "Glide", + 852186: "Mediawiki", + 852187: "fitbit", + 852188: "LastPass", + 852189: "National Geographic", + 852190: "HTTP", + 852191: "AOL Toolbar", + 852192: "Yandex.Browser", + 852193: "Uber", + 852194: "Web-crawler", + 852195: "RSS", + 852196: "WeatherBug", + 852197: "Yahoo Toolbar", + 852198: "Alexa Toolbar", + 852199: "Internet Archive", + 852200: "Wikipedia", + 852201: "Wiktionary", + 852202: "Amazon", + 852203: "Google Toolbar", + 852205: "Zoho", + 852206: "Microsoft Internet Explorer", + 852207: "Localmind", + 852208: "LinkedIn Pulse", + 852209: "BookU", + 852210: "Zappos", + 852211: "Expedia", + 852212: "AdF.ly", + 852213: "Baidu", + 852214: "Yahoo", + 852215: "Taobao", + 852216: "163.com", + 852217: "Sina.com", + 852218: "Bing.com", + 852219: "Ruten", + 852220: "Shop.com", + 852221: "Appledaily", + 852222: "CWB", + 852223: "CNA", + 852224: "Harvey Norman", + 852225: "Hackpad", + 852226: "JB Hi-Fi", + 852227: "MyDeal.com.au", + 852228: "AUSHOP", + 852229: "CrazySales", + 852230: "Giphy", + 852231: "Riffsy", + 852232: "Gumtree", + 852233: "Priceline", + 852234: "Carousell", + 852235: "Wish", + 852236: "Shein Shopping", + 852237: "Romwe", + 852238: "The Iconic", + 852239: "Boohoo", + 852240: "Aliexpress", + 852241: "ASOS", + 852242: "Catch of the Day", + 852273: "Amazon AppStream", + 917505: "TrendMicro Titanium-6-ICRC", + 917506: "TrendMicro Titanium-7-ICRC", + 917507: "TrendMicro Titanium-8-ICRC", + 917508: "BitDefender", + 917509: "360Safe", + 917510: "Rising", + 917511: "TortoiseSVN", + 917513: "Microsoft Windows Update", + 917514: "Norton", + 917515: "Sophos", + 917516: "Yum", + 917517: "MIUI", + 917518: "Adobe", + 917519: "InstallAnyWhere", + 917520: "Kaspersky", + 917521: "McAfee", + 917522: "TrendMicro", + 917523: "F-Secure", + 917524: "NOD32", + 917525: "Avast", + 917526: "Jiangmin", + 917527: "Avira", + 917528: "Emsisoft", + 917529: "Panda", + 917530: "AVG", + 917531: "PCTools", + 917532: "TrendMicro Titanium-10-ICRC", + 917533: "Outpost", + 917534: "Spybot", + 917535: "Duba", + 917536: "Apple", + 917538: "Google Update", + 917539: "TrendMicro Titanium-6-WTP", + 917540: "JAVA Update", + 917541: "SONY PC/Xperia Companion", + 917542: "SketchUp", + 917543: "Webroot", + 917544: "TrendMicro Titanium-7-WTP", + 917545: "TrendMicro Titanium-8-WTP", + 917546: "TrendMicro Titanium-10-WTP", + 917547: "TrendMicro Titanium-11-ICRC", + 917548: "TrendMicro Titanium-11-WTP", + 917549: "TrendMicro Titanium-12-ICRC", + 917550: "TrendMicro Titanium-12-WTP", + 983043: "eBuddy.com", + 983044: "iLoveIM.com", + 983047: "imo.im", + 983048: "Chikka", + 983050: "QQ Web Messenger", + 983051: "AOL Web Messenger", + 983054: "ICQ Web Messenger", + 983057: "AirAim", + 983058: "Instan-t Web Messenger", + 983065: "TaoBao AliWW", + 983069: "Gadu-Gadu Web Messenger", + 983070: "Karoo Lark", + 983072: "Web IM+", + 1114113: "WatchGuard WSM Management", + 1114114: "WatchGuard Web Management UI", + 1114115: "WatchGuard Authentication Access", + 1114117: "WatchGuard external Webblocker database fetch", + 1114118: "Livelink", + 1114119: "Altiris", + 1114120: "AMS", + 1114121: "Apache Synapse", + 1114122: "WatchGuard CLI ", + 1114124: "Webex", + 1114125: "Webex-WebOffice", + 1114128: "Avamar", + 1114129: "Avaya", + 1114130: "BackupExec", + 1114131: "Bitcoin Core", + 1114133: "Microsoft OS license", + 1114134: "Microsoft Office 2013 license", + 1114138: "BZFlag", + 1114140: "CAJO", + 1114141: "Cisco HSRP", + 1114142: "SkyDesk", + 1114144: "Microsoft Office", + 1114150: "openQRM", + 1114151: "Citrix", + 1114152: "CodeMeter", + 1114155: "Corba", + 1114158: "Cups", + 1114160: "Cvsup", + 1114161: "DameWare", + 1114167: "Db2", + 1114168: "Docker", + 1114169: "Dclink", + 1114170: "Urchin Web Analytics", + 1114172: "Applications Manager", + 1114174: "Zoom", + 1114176: "EForward-document transport system", + 1114177: "EMWIN", + 1114179: "Adobe Connect", + 1114182: "Big Brother", + 1114185: "Fuze Meeting", + 1114187: "FritzBox", + 1114188: "Skype for Business", + 1114191: "Websense", + 1114195: "Whisker", + 1114201: "HP-JetDirect", + 1114203: "VMWare", + 1114205: "IBM HTTP", + 1114206: "IBM SmartCloud", + 1114212: "IMS", + 1114213: "Informix", + 1114222: "Limelight", + 1114229: "Lawson-m3", + 1114238: "Meeting-maker", + 1114239: "Zendesk", + 1114246: "Microsoft DTC", + 1114248: "Microsoft Netlogon", + 1114250: "Microsoft Remote Web Workplace", + 1114251: "Office Sway", + 1114252: "Sharepoint-wiki", + 1114253: "Microsoft SSDP", + 1114255: "GatherPlace", + 1114269: "Xgrid", + 1114272: "Backweb", + 1114273: "Bugzilla", + 1114274: "NCube", + 1114275: "WinboxRouterOS", + 1114277: "WSO2", + 1114279: "NetFlow", + 1114289: "concur", + 1114290: "NetSupport", + 1114308: "DirectAdmin", + 1114309: "EasyBits", + 1114310: "Eiq-sec-analyzer", + 1114311: "Netbotz", + 1114312: "Aspera FASP", + 1114318: "Perforce", + 1114320: "TiVoConnect", + 1114321: "Polycom", + 1114322: "WebSphere", + 1114330: "Radacct RADIUS", + 1114334: "Securemeeting", + 1114337: "SANE", + 1114339: "WebHost", + 1114340: "CPanel", + 1114342: "Sibelius", + 1114343: "Siebel-crm", + 1114347: "SMS", + 1114350: "Spirent", + 1114351: "SPSS", + 1114352: "Subversion", + 1114355: "Tripwire", + 1114359: "WatchGuard Webblocker database transfer", + 1114361: "WatchGuard Security Event Processor logging", + 1114363: "Genesys Meeting Center", + 1114365: "Nagios", + 1114366: "Microsoft Office 365", + 1114396: "ChatWork", + 1179649: "TCP Port Service Multiplexer", + 1179650: "Management Utility", + 1179651: "Compression Process", + 1179652: "Zeroconf", + 1179653: "Echo", + 1179654: "Discard", + 1179655: "Active Users", + 1179656: "L2TP", + 1179657: "puparp", + 1179658: "vsinet", + 1179659: "maitrd", + 1179660: "Character Generator", + 1179663: "applix", + 1179664: "Net Assistant", + 1179665: "any private mail system", + 1179666: "BackOrifice", + 1179667: "AltaVista Firewall97", + 1179668: "NSW User System FE", + 1179669: "MSG ICP", + 1179670: "MSG Authentication", + 1179671: "Display Support Protocol", + 1179672: "any private printer server", + 1179673: "Time", + 1179674: "Route Access Protocol", + 1179675: "Resource Location Protocol", + 1179676: "graphics", + 1179677: "Host Name Server", + 1179678: "NIC Name", + 1179679: "MPM FLAGS Protocol", + 1179680: "Message Processing Module [recv]", + 1179681: "MPM [default send]", + 1179682: "NI FTP", + 1179683: "Digital Audit Daemon", + 1179684: "Login Host Protocol (TACACS)", + 1179685: "Remote Mail Checking Protocol", + 1179686: "IMP Logical Address Maintenance", + 1179687: "XNS Time Protocol", + 1179688: "Domain Name Server", + 1179689: "XNS Clearinghouse", + 1179690: "ISI Graphics Language", + 1179691: "XNS Authentication", + 1179692: "Mail Transfer Protocol (MTP)", + 1179693: "XNS Mail", + 1179694: "any private file service", + 1179695: "NI MAIL", + 1179696: "ACA Services", + 1179697: "VIA Systems - FTP whois++", + 1179698: "Communications Integrator (CI)", + 1179699: "TACACS-Database Service", + 1179700: "Oracle SQL-NET", + 1179701: "Bootstrap Protocol Server", + 1179702: "Bootstrap Protocol Client", + 1179703: "profile", + 1179704: "Gopher", + 1179705: "Remote Job Service", + 1179706: "any private dial out service", + 1179707: "Distributed External Object Store", + 1179708: "any private RJE service netrjs", + 1179709: "Vet TCP", + 1179710: "Finger", + 1179711: "World Wide Web HTTP", + 1179712: "Torpark", + 1179713: "XFER Utility", + 1179714: "MIT ML Device", + 1179715: "Common Trace Facility", + 1179716: "Micro Focus Cobol", + 1179717: "any private terminal link ttylink", + 1179718: "Kerberos", + 1179719: "SU MIT Telnet Gateway", + 1179720: "DNSIX Securit Attribute Token Map", + 1179721: "MIT Dover Spooler", + 1179722: "Network Printing Protocol", + 1179723: "Device Control Protocol", + 1179724: "Tivoli Object Dispatcher", + 1179725: "BSD supdupd(8)", + 1179726: "DIXIE Protocol Specification", + 1179727: "Swift Remote Virtural File Protocol", + 1179728: "linuxconf", + 1179729: "Metagram Relay", + 1179731: "NIC Host Name Server", + 1179732: "ISO-TSAP Class 0", + 1179733: "Genesis Point-to-Point Trans Net", + 1179734: "ACR-NEMA Digital Imag. & Comm. 300", + 1179735: "Mailbox Name Nameserver", + 1179736: "msantipiracy", + 1179737: "Eudora compatible PW changer", + 1179739: "SNA Gateway Access Server", + 1179740: "PostOffice V.2", + 1179742: "Portmapper RPC Bind", + 1179743: "McIDAS Data Transmission Protocol", + 1179744: "Ident Tap Authentication Service", + 1179745: "Audio News Multicast", + 1179746: "Simple File Transfer Protocol", + 1179747: "ANSA REX Notify", + 1179748: "UUCP Path Service", + 1179749: "SQL Services", + 1179751: "blackjack", + 1179752: "Encore Expedited Remote Pro.Call", + 1179753: "Smakynet", + 1179754: "Network Time Protocol", + 1179755: "ANSA REX Trader", + 1179756: "Locus PC-Interface Net Map Ser", + 1179757: "Unisys Unitary Login", + 1179758: "Locus PC-Interface Conn Server", + 1179759: "GSS X License Verification", + 1179760: "Password Generator Protocol", + 1179761: "Cisco FNATIVE", + 1179762: "Cisco TNATIVE", + 1179763: "Cisco SYSMAINT", + 1179764: "Statistics Service", + 1179765: "INGRES-NET Service", + 1179766: "NCS local location broker", + 1179767: "PROFILE Naming System", + 1179768: "NetBIOS Name Service", + 1179769: "NetBIOS Datagram Service", + 1179770: "NetBIOS Session Service", + 1179771: "EMFIS Data Service", + 1179772: "EMFIS Control Service", + 1179773: "Britton-Lee IDM", + 1179774: "Internet Message Access Protocol", + 1179775: "Universal Management Architecture", + 1179776: "UAAC Protocol", + 1179777: "iso-ip0", + 1179778: "iso-ip", + 1179779: "Jargon", + 1179780: "AED 512 Emulation Service", + 1179781: "SQL-net", + 1179782: "HEMS", + 1179783: "Background File Transfer Program (BFTP)", + 1179784: "SGMP", + 1179785: "NetSC-prod", + 1179786: "NetSC-dev", + 1179787: "SQL Service", + 1179788: "KNET VM Command Message Protocol", + 1179789: "PCMail Server", + 1179790: "NSS-Routing", + 1179791: "SGMP-traps", + 1179793: "SNMPTRAP", + 1179794: "CMIP TCP Manager", + 1179795: "CMIP TCP Agent", + 1179796: "Xerox", + 1179797: "Sirius Systems", + 1179798: "namp", + 1179799: "rsvd", + 1179800: "send", + 1179801: "Network PostScript", + 1179802: "Network Innovations Multiplex", + 1179803: "Network Innovations CL 1", + 1179804: "xyplex-mux", + 1179805: "mailq", + 1179806: "vmnet", + 1179807: "genrad-mux", + 1179808: "X Display Manager Control Protocol", + 1179809: "NextStep Window Server", + 1179810: "Border Gateway Protocol", + 1179811: "Intergraph", + 1179812: "unify", + 1179813: "Unisys Audit SITP", + 1179814: "ocbinder", + 1179815: "ocserver", + 1179816: "remote-kis", + 1179817: "KIS Protocol", + 1179818: "Application Communication Interface", + 1179819: "Plus Fives MUMPS", + 1179820: "Queued File Transport", + 1179821: "Gateway Access Control Protocol", + 1179822: "Prospero Directory Service", + 1179823: "OSU Network Monitoring System", + 1179824: "Spider Remote Monitoring Protocol", + 1179825: "Internet Relay Chat", + 1179826: "DNSIX Network Level Module Audit", + 1179827: "DNSIX Session Mgt Module Audit Redir", + 1179828: "Directory Location Service", + 1179829: "Directory Location Service Monitor", + 1179830: "SMUX", + 1179831: "IBM System Resource Controller", + 1179832: "AppleTalk Routing Maintenance", + 1179833: "AppleTalk Name Binding", + 1179834: "AppleTalk Unused", + 1179835: "AppleTalk Echo", + 1179836: "AppleTalk Zone Information", + 1179838: "Trivial Authenticated Mail Protocol", + 1179839: "ANSI Z39.50", + 1179840: "Texas Instruments", + 1179841: "ATEXSSTR", + 1179842: "IPX", + 1179843: "vmpwscs", + 1179844: "Insignia Solutions", + 1179845: "Computer Associates Intl License Server", + 1179846: "dBASE Unix", + 1179847: "Netix Message Posting Protocol", + 1179848: "Unisys ARPs", + 1179849: "Interactive Mail Access Protocol v3", + 1179850: "Berkeley rlogind with SPX auth", + 1179851: "Berkeley rshd with SPX auth", + 1179852: "Certificate Distribution Center", + 1179853: "masqdialer", + 1179854: "direct", + 1179855: "Survey Measurement", + 1179856: "inbusiness", + 1179857: "link", + 1179858: "Display Systems Protocol", + 1179859: "VAT", + 1179860: "bhfhs", + 1179862: "RAP (Route Access Protocol)", + 1179863: "Checkpoint Firewall-1", + 1179864: "Efficient Short Remote Operations", + 1179865: "openport", + 1179866: "Checkpoint Firewall-1 Management", + 1179867: "arcisdms", + 1179868: "hdap", + 1179869: "Border Gateway Multicast Protocol (BGMP)", + 1179870: "X-Bone CTL", + 1179871: "SCSI on ST", + 1179872: "Tobit David Service Layer", + 1179873: "Tobit David Replica", + 1179874: "http-mgmt", + 1179875: "personal-link", + 1179876: "Cable Port A X", + 1179877: "rescap", + 1179878: "corerjd", + 1179879: "FXP-1", + 1179880: "K-BLOCK", + 1179881: "Novastor Backup", + 1179882: "entrusttime", + 1179883: "bhmds", + 1179884: "AppleShare IP WebAdmin", + 1179885: "VSLMP", + 1179886: "magenta-logic", + 1179887: "opalis-robot", + 1179888: "DPSI", + 1179889: "decAuth", + 1179890: "zannet", + 1179891: "PKIX TimeStamp", + 1179892: "PTP Event", + 1179893: "PTP General", + 1179894: "Programmable Interconnect Point (PIP)", + 1179895: "RTSPS", + 1179896: "Texar Security Port", + 1179897: "Prospero Data Access Protocol", + 1179898: "Perf Analysis Workbench", + 1179899: "Zebra server", + 1179900: "Fatmen Server", + 1179901: "Cabletron Management Protocol", + 1179902: "mftp", + 1179903: "MATIP Type A", + 1245185: "PPTP", + 1245186: "BakBone NetVault", + 1245187: "DTAG or bhoedap4", + 1245188: "ndsauth", + 1245189: "bh611", + 1245190: "datex-asn", + 1245191: "Cloanto Net 1", + 1245192: "bhevent", + 1245193: "shrinkwrap", + 1245194: "Windows RPC", + 1245195: "Tenebris Network Trace Service", + 1245196: "scoi2odialog", + 1245197: "semantix", + 1245198: "SRS Send", + 1245200: "aurora-cmgr", + 1245201: "DTK", + 1245202: "odmr", + 1245203: "mortgageware", + 1245204: "qbikgdp", + 1245205: "rpc2portmap", + 1245206: "Coda authentication server (codaauth2)", + 1245207: "ClearCase", + 1245208: "ListProcessor", + 1245209: "Legent Corporation", + 1245210: "hassle", + 1245211: "Amiga Envoy Network Inquiry Proto", + 1245212: "NEC Corporation", + 1245213: "TIA EIA IS-99 modem client", + 1245214: "TIA EIA IS-99 modem server", + 1245215: "HP Performance data collector", + 1245216: "HP Performance data managed node", + 1245217: "HP Performance data alarm manager", + 1245218: "A Remote Network Server System", + 1245219: "IBM Application", + 1245220: "ASA Message Router Object Def.", + 1245221: "Appletalk Update-Based Routing Pro.", + 1245222: "Unidata LDM", + 1245223: "Lightweight Directory Access Protocol", + 1245224: "uis", + 1245225: "SynOptics SNMP Relay Port", + 1245226: "SynOptics Port Broker Port", + 1245228: "Meta5", + 1245229: "EMBL Nucleic Data Transfer", + 1245230: "NETscout Control Protocol", + 1245231: "Novell Netware over IP", + 1245232: "Multi Protocol Trans. Net.", + 1245233: "kryptolan", + 1245234: "ISO Transport Class 2 Non-Control over TCP", + 1245235: "Workstation Solutions", + 1245236: "Uninterruptible Power Supply", + 1245237: "Genie Protocol", + 1245238: "decap", + 1245239: "nced", + 1245240: "ncld", + 1245241: "Interactive Mail Support Protocol", + 1245242: "timbuktu", + 1245243: "Prospero Resource Manager Sys. Man.", + 1245244: "Prospero Resource Manager Node Man.", + 1245245: "DECLadebug Remote Debug Protocol", + 1245246: "Remote MT Protocol", + 1245247: "Trap Convention Port", + 1245248: "smsp", + 1245249: "infoseek", + 1245250: "bnet", + 1245251: "silverplatter", + 1245252: "onmux", + 1245253: "hyper-g", + 1245254: "ariel1", + 1245255: "smpte", + 1245256: "ariel2", + 1245257: "ariel3", + 1245258: "IBM Operations Planning and Control Start", + 1245259: "IBM Operations Planning and Control Track", + 1245260: "icad-el", + 1245261: "smartsdp", + 1245262: "Server Location", + 1245263: "ocs_cmu", + 1245264: "ocs_amu", + 1245265: "utmpsd", + 1245266: "utmpcd", + 1245267: "iasd", + 1245268: "Usenet Network News Transfer", + 1245269: "mobileip-agent", + 1245270: "mobilip-mn", + 1245271: "dna-cml", + 1245272: "comscm", + 1245273: "dsfgw", + 1245274: "dasp", + 1245275: "sgcp", + 1245276: "decvms-sysmgt", + 1245277: "cvc_hostd", + 1245278: "HTTP Protocol over TLS SSL", + 1245279: "Simple Network Paging Protocol", + 1245280: "Win2k+ Server Message Block", + 1245281: "ddm-rdb", + 1245282: "ddm-dfm", + 1245283: "DDM-SSL", + 1245284: "AS Server Mapper", + 1245285: "tserver", + 1245286: "Cray Network Semaphore server", + 1245287: "Cray SFS config server", + 1245288: "creativeserver", + 1245289: "contentserver", + 1245290: "creativepartnr", + 1245291: "macon-tcp", + 1245292: "scohelp", + 1245294: "ampr-rcmd", + 1245295: "skronk", + 1245296: "datasurfsrv", + 1245297: "datasurfsrvsec", + 1245298: "Alpes", + 1245299: "kpasswd", + 1245300: "SMTP Protocol over TLS SSL (was SSMTP)", + 1245301: "digital-vrc", + 1245302: "mylex-mapd", + 1245303: "Photuris Key Management", + 1245304: "Radio Control Protocol", + 1245305: "scx-proxy", + 1245306: "mondex", + 1245307: "ljk-login", + 1245308: "hybrid-pop", + 1245309: "tn-tl-w1", + 1245310: "Tcpnethaspsrv Protocol", + 1245311: "tn-tl-fd1", + 1245312: "ss7ns", + 1245313: "spsc", + 1245314: "iafserver", + 1245315: "WCCP", + 1245316: "loadsrv", + 1245317: "serialnumberd", + 1245318: "dvs", + 1245319: "bgs-nsi", + 1245320: "ulpnet", + 1245321: "Integra Software Management Environment", + 1245322: "Air Soft Power Burst", + 1245324: "sstats", + 1245325: "saft Simple Asynchronous File Transfer", + 1245326: "gss-http", + 1245327: "nest-protocol", + 1245328: "micom-pfs", + 1245329: "go-login", + 1245330: "Transport Independent Convergence for FNA", + 1245331: "pov-ray", + 1245332: "intecourier", + 1245333: "pim-rp-disc", + 1245334: "dantz", + 1245335: "siam", + 1245336: "ISO ILL Protocol", + 1245337: "VPN Key Exchange", + 1245338: "Simple Transportation Management Framework (STMF)", + 1245339: "asa-appl-proto", + 1245340: "intrinsa", + 1245341: "Citadel", + 1245342: "mailbox-lm", + 1245343: "ohimsrv", + 1245344: "crs", + 1245345: "xvttp", + 1245346: "snare", + 1245347: "FirstClass Protocol", + 1245348: "passgo", + 1245349: "BSD rexecd(8)", + 1245350: "BSD rlogind(8)", + 1245351: "BSD rshd(8)", + 1245352: "spooler", + 1245353: "videotex", + 1245354: "like tenex link but across", + 1245355: "ntalk", + 1245356: "unixtime", + 1245357: "Routing Information Protocol (RIP)", + 1245358: "ripng", + 1245359: "ulp", + 1245360: "ibm-db2", + 1245361: "NetWare Core Protocol (NCP)", + 1245362: "Timeserver", + 1245363: "newdate", + 1245364: "Stock IXChange", + 1245365: "Customer IXChange", + 1245366: "irc-serv", + 1245370: "readnews", + 1245371: "netwall for emergency broadcasts", + 1245372: "MegaMedia Admin", + 1245373: "iiop", + 1245374: "opalis-rdv", + 1245375: "Networked Media Streaming Protocol", + 1245376: "gdomap", + 1245377: "Apertus Technologies Load Determination", + 1245378: "uucpd", + 1245379: "uucp-rlogin", + 1245380: "Commerce", + 1245381: "klogin", + 1245382: "krcmd", + 1245383: "Kerberos encrypted remote shell", + 1245384: "DHCPv6 Client", + 1245385: "DHCPv6 Server", + 1245386: "AFP over TCP", + 1245387: "idfp", + 1245388: "new-who", + 1245389: "cybercash", + 1245390: "deviceshare", + 1245391: "pirp", + 1245392: "Real Time Stream Control Protocol", + 1245393: "dsf", + 1245394: "Remote File System (RFS)", + 1245395: "openvms-sysipc", + 1245396: "sdnskmp", + 1245397: "teedtap", + 1245398: "rmonitord", + 1245399: "monitor", + 1245400: "chcmd", + 1245402: "snews", + 1245403: "plan 9 file service", + 1245404: "whoami", + 1245405: "streettalk", + 1245406: "banyan-rpc", + 1245407: "Microsoft shuttle", + 1245408: "Microsoft rome", + 1245409: "demon", + 1245410: "udemon", + 1245411: "sonar", + 1245412: "banyan-vip", + 1245413: "FTP Software Agent System", + 1245414: "vemmi", + 1245415: "ipcd", + 1245416: "vnas", + 1245417: "ipdd", + 1245418: "decbsrv", + 1245419: "sntp-heartbeat", + 1245420: "Bundle Discovery Protocol", + 1245421: "scc-security", + 1245422: "Philips Video-Conferencing", + 1245423: "keyserver", + 1245424: "IMAP4+SSL", + 1245425: "password-chg", + 1245426: "submission", + 1245427: "cal", + 1245428: "eyelink", + 1245429: "tns-cml", + 1245430: "FileMaker Pro", + 1245431: "eudora-set", + 1245432: "HTTP RPC Ep Map", + 1245433: "tpip", + 1245434: "cab-protocol", + 1245435: "smsd", + 1245436: "PTC Name Service", + 1245437: "SCO Web Server Manager 3", + 1245438: "Aeolon Core Protocol", + 1245439: "Sun IPC server", + 1310721: "nqs", + 1310722: "Sender-Initiated Unsolicited File Transfer", + 1310723: "npmp-trap", + 1310724: "npmp-local", + 1310725: "npmp-gui", + 1310726: "HMMP Indication", + 1310727: "HMMP Operation", + 1310728: "SSLshell", + 1310729: "Internet Configuration Manager", + 1310730: "SCO System Administration Server", + 1310731: "SCO Desktop Administration Server", + 1310732: "DEI-ICDA", + 1310733: "Digital EVM", + 1310734: "SCO WebServer Manager", + 1310735: "ESCP", + 1310736: "Collaborator", + 1310737: "Aux Bus Shunt", + 1310738: "Crypto Admin", + 1310739: "DEC DLM", + 1310740: "ASIA", + 1310741: "PassGo Tivoli", + 1310742: "QMQP (qmail)", + 1310743: "3Com AMP3", + 1310744: "RDA", + 1310745: "IPP (Internet Printing Protocol)", + 1310746: "bmpp", + 1310747: "Service Status update (Sterling Software)", + 1310748: "ginad", + 1310749: "RLZ DBase", + 1310750: "LDAP Protocol over TLS SSL (was SLDAP)", + 1310751: "lanserver", + 1310752: "mcns-sec", + 1310753: "Multicast Source Discovery Protocol (MSDP)", + 1310754: "entrust-sps", + 1310755: "repcmd", + 1310756: "ESRO-EMSDP V1.3", + 1310757: "SANity", + 1310758: "dwr", + 1310759: "PSSC", + 1310760: "Label Distribution Protocol (LDP)", + 1310761: "DHCP Failover", + 1310762: "Registry Registrar Protocol (RRP)", + 1310763: "Aminet", + 1310764: "OBEX", + 1310765: "IEEE MMS", + 1310766: "HELLO_PORT", + 1310767: "AODV", + 1310768: "TINC", + 1310769: "SPMP", + 1310770: "RMC", + 1310771: "TenFold", + 1310772: "URL Rendezvous", + 1310773: "MacOS Server Admin", + 1310774: "HAP", + 1310775: "PFTP", + 1310776: "PureNoise", + 1310777: "Secure Aux Bus", + 1310778: "Sun DR", + 1310779: "doom Id Software", + 1310780: "campaign contribution disclosures - SDR Technologies", + 1310781: "MeComm", + 1310782: "MeRegister", + 1310783: "VACDSM-SWS", + 1310784: "VACDSM-APP", + 1310785: "VPPS-QUA", + 1310786: "CIMPLEX", + 1310787: "ACAP", + 1310788: "DCTP", + 1310789: "VPPS Via", + 1310790: "Virtual Presence Protocol", + 1310791: "GNU Gereration Foundation NCP", + 1310792: "MRM", + 1310793: "entrust-aaas", + 1310794: "entrust-aams", + 1310795: "XFR", + 1310796: "CORBA IIOP", + 1310797: "CORBA IIOP SSL", + 1310798: "MDC Port Mapper", + 1310799: "Hardware Control Protocol Wismar", + 1310800: "asipregistry", + 1310801: "REALM-RUSD", + 1310802: "NMAP", + 1310803: "VATP", + 1310804: "MS Exchange Routing", + 1310805: "Hyperwave-ISP", + 1310806: "connendp", + 1310807: "Linux-HA (High-Availability Linux)", + 1310808: "IEEE-MMS-SSL", + 1310809: "RUSHD", + 1310810: "UUIDGEN", + 1310811: "OLSR", + 1310812: "Access Network", + 1310813: "errlog copy server daemon", + 1310814: "AgentX", + 1310815: "Secure Internet Live Conferencing (SILC)", + 1310816: "Borland DSJ", + 1310817: "Entrust Key Management Service Handler", + 1310818: "Entrust Administration Service Handler", + 1310819: "Cisco TDP", + 1310820: "IBM NetView DM 6000 Server Client", + 1310821: "IBM NetView DM 6000 send tcp", + 1310822: "IBM NetView DM 6000 receive tcp", + 1310823: "netGW", + 1310824: "Network based Rev. Cont. Sys.", + 1310825: "Flexible License Manager", + 1310826: "Fujitsu Device Control", + 1310827: "Russell Info Sci Calendar Manager", + 1310828: "Kerberos 5 admin changepw", + 1310830: "rfile", + 1310832: "pump", + 1310833: "qrh", + 1310834: "rrh", + 1310835: "kerberos v5 server propagation", + 1310836: "nlogin", + 1310837: "con", + 1310839: "ns", + 1310840: "kpwd Kerberos (v4) passwd", + 1310841: "quotad", + 1310842: "cycleserv", + 1310843: "omserv", + 1310844: "webster", + 1310845: "phone", + 1310846: "vid", + 1310847: "cadlock", + 1310848: "rtip", + 1310849: "cycleserv2", + 1310850: "submit", + 1310851: "rpasswd", + 1310852: "entomb", + 1310853: "wpages", + 1310854: "Hummingbird Exceed jconfig", + 1310855: "wpgs", + 1310856: "concert", + 1310857: "QSC", + 1310858: "controlit", + 1310859: "mdbs_daemon", + 1310860: "Device", + 1310861: "FCP", + 1310862: "itm-mcell-s", + 1310863: "PKIX-3 CA RA", + 1310864: "DHCP Failover 2", + 1310865: "SUP server", + 1310866: "rsync", + 1310867: "ICL coNETion locate server", + 1310868: "ICL coNETion server info", + 1310869: "AccessBuilder", + 1310870: "OMG Initial Refs", + 1310871: "Samba SWAT Tool", + 1310872: "IDEAFARM-CHAT", + 1310873: "IDEAFARM-CATCH", + 1310874: "xact-backup", + 1310875: "SecureNet Pro sensor", + 1310878: "Netnews Administration System", + 1310879: "Telnet Protocol over TLS SSL", + 1310880: "IMAP4 Protocol over TLS SSL", + 1310881: "ICP Protocol over TLS SSL", + 1310882: "POP3 Protocol over TLS SSL", + 1310883: "bhoetty", + 1310884: "Cray Unified Resource Manager", + 1310887: "Microsoft Authentication via SSL", + 1310888: "Google(SSL)", + 1310889: "Yahoo Authentication via SSL", + 1310890: "AOL Authentication via SSL", + 1310891: "FIX", + 1310892: "STUN", + 1310893: "Dynamic Host Configuration Protocol (DHCP)", + 1310894: "Megaco", + 1310895: "Rstatd", + 1310896: "RSVP", + 1310897: "SOAP", + 1310898: "Ess Apple Authentication via SSL", + 1310899: "TFTP", + 1310900: "Daytime", + 1310902: "MicrosoftOnline Authentication via SSL", + 1310903: "Microsoft WINS", + 1310904: "Remote Procedure Call (RPC)", + 1310905: "SSL/TLS", + 1310906: "Google APIs(SSL)", + 1310907: "Sina Authentication via SSL", + 1310908: "Google App Engine(SSL)", + 1310909: "Google User Content(SSL)", + 1310910: "Blackberry Authentication via SSL", + 1310912: "Adobe Authentication via SSL", + 1310914: "Lets Encrypt", + 1507329: "QQ Private Protocol", + 1507330: "Thunder Private Protocol", + 1507333: "Jabber Private Protocol", + 1572865: "Classmates", + 1572866: "Yik Yak", + 1572867: "Facebook", + 1572868: "Flickr", + 1572870: "Friendfeed", + 1572871: "Hi5", + 1572872: "LinkedIn", + 1572873: "Livejournal", + 1572874: "Twitter", + 1572875: "Plurk", + 1572876: "MySpace", + 1572880: "Khan Academy", + 1572881: "Pinterest", + 1572882: "Tumblr", + 1572883: "MeetMe", + 1572884: "VKontakte", + 1572885: "Odnoklassniki", + 1572886: "Niwota", + 1572887: "Tagged", + 1572889: "PerfSpot", + 1572890: "Me2day", + 1572891: "Mekusharim", + 1572892: "Draugiem", + 1572893: "Badoo", + 1572894: "Meetup", + 1572895: "Foursquare", + 1572896: "Ning", + 1572897: "i-Part/iPair", + 1572898: "Wretch", + 1572899: "Dudu", + 1572900: "Mig33", + 1572901: "Hatena", + 1572902: "eHarmony", + 1572903: "Fotolog ", + 1572905: "Tencent QQ", + 1572906: "Pixnet", + 1572907: "Nk.Pl", + 1572909: "Twoo", + 1572910: "Plaxo", + 1572911: "Cyworld", + 1572912: "Jivesoftware", + 1572913: "WordPress", + 1572914: "FMyLife", + 1572915: "Dcinside", + 1572916: "Class Chinaren", + 1572917: "Bai Sohu", + 1572918: "Yammer", + 1572919: "Douban", + 1572920: "Gamer", + 1572921: "Xuite", + 1572922: "ChatMe", + 1572923: "Clien.net", + 1572927: "AdultFriendFinder", + 1572928: "Fling.com", + 1572929: "Delicious", + 1572930: "Mei.fm", + 1572931: "Streetlife", + 1572967: "Daum-blog", + 1572968: "Naver-blog", + 1572970: "Panoramio", + 1572974: "Blogger", + 1572975: "FC2", + 1572976: "Yahoo Blog", + 1572977: "Friendster", + 1572978: "Ameba", + 1572980: "Bebo social network", + 1572981: "Kaixin", + 1572983: "Orkut", + 1572985: "Aol-Answers", + 1572987: "CoolTalk social network", + 1572988: "RenRen.com", + 1572989: "TweetDeck", + 1572990: "Hootsuite", + 1572998: "Xing", + 1572999: "Lokalisten", + 1573000: "meinVZ/studiVZ", + 1573004: "Viadeo", + 1573005: "Tuenti", + 1573006: "Hyves", + 1573007: "Mixi.jp", + 1573008: "Yahoo-mbga.jp", + 1573009: "GREE", + 1573010: "Netlog", + 1573011: "2ch", + 1573013: "Reddit", + 1573014: "LoveTheseCurves", + 1573015: "Weibo", + 1573016: "Google+", + 1573017: "Skyrock", + 1573018: "51.com", + 1573019: "Jackd", + 1573020: "Touch", + 1573021: "Skout", + 1573022: "Instagram", + 1573023: "Jiayuan", + 1573024: "Zoosk", + 1573025: "DatingDNA", + 1573026: "500px", + 1573028: "iAround", + 1573029: "pairs", + 1573030: "Path", + 1573031: "WeHeartIt", + 1573032: "Fancy", + 1573033: "Vine", + 1573034: "SnappyTV", + 1573035: "Miliao", + 1573036: "After School", + 1573074: "Weico", + 16777215: "Unknown_Other", +} diff --git a/core/unifi/events.go b/core/unifi/events.go new file mode 100644 index 00000000..4340c670 --- /dev/null +++ b/core/unifi/events.go @@ -0,0 +1,204 @@ +package unifi + +import ( + "encoding/json" + "fmt" + "sort" + "time" +) + +var ( + ErrNoSiteProvided = fmt.Errorf("site must not be nil or empty") + ErrInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange") +) + +const ( + eventLimit = 50000 +) + +// GetEvents returns a response full of UniFi Events for the last 1 hour from multiple sites. +func (u *Unifi) GetEvents(sites []*Site, hours time.Duration) ([]*Event, error) { + data := make([]*Event, 0) + + for _, site := range sites { + response, err := u.GetSiteEvents(site, hours) + if err != nil { + return data, err + } + + data = append(data, response...) + } + + return data, nil +} + +// GetSiteEvents retrieves the last 1 hour's worth of events from a single site. +func (u *Unifi) GetSiteEvents(site *Site, hours time.Duration) ([]*Event, error) { + if site == nil || site.Name == "" { + return nil, ErrNoSiteProvided + } + + if hours < time.Hour { + hours = time.Hour + } + + u.DebugLog("Polling Controller, retreiving UniFi Events, site %s", site.SiteName) + + var ( + path = fmt.Sprintf(APIEventPath, site.Name) + params = fmt.Sprintf(`{"_limit":%d,"within":%d,"_sort":"-time"}}`, + eventLimit, int(hours.Round(time.Hour).Hours())) + event struct { + Data events `json:"data"` + } + ) + + if err := u.GetData(path, &event, params); err != nil { + return event.Data, err + } + + for i := range event.Data { + // Add special SourceName value. + event.Data[i].SourceName = u.URL + // Add the special "Site Name" to each event. This becomes a Grafana filter somewhere. + event.Data[i].SiteName = site.SiteName + } + + sort.Sort(event.Data) + + return event.Data, nil +} + +// Event describes a UniFi Event. +// API Path: /api/s/default/stat/event. +type Event struct { + IsAdmin FlexBool `json:"is_admin"` + DestPort int `json:"dest_port"` + SrcPort int `json:"src_port"` + Bytes FlexInt `json:"bytes"` + Duration FlexInt `json:"duration"` + FlowID FlexInt `json:"flow_id"` + InnerAlertGID FlexInt `json:"inner_alert_gid"` + InnerAlertRev FlexInt `json:"inner_alert_rev"` + InnerAlertSeverity FlexInt `json:"inner_alert_severity"` + InnerAlertSignatureID FlexInt `json:"inner_alert_signature_id"` + Channel FlexInt `json:"channel"` + ChannelFrom FlexInt `json:"channel_from"` + ChannelTo FlexInt `json:"channel_to"` + Time int64 `json:"time"` + Timestamp int64 `json:"timestamp"` + Datetime time.Time `json:"datetime"` + Admin string `json:"admin"` + Ap string `json:"ap"` + ApFrom string `json:"ap_from"` + ApName string `json:"ap_name"` + ApTo string `json:"ap_to"` + AppProto string `json:"app_proto"` + Catname string `json:"catname"` + DestIP string `json:"dest_ip"` + DstMAC string `json:"dst_mac"` + EventType string `json:"event_type"` + Guest string `json:"guest"` + Gw string `json:"gw"` + GwName string `json:"gw_name"` + Host string `json:"host"` + Hostname string `json:"hostname"` + ID string `json:"_id"` + IP string `json:"ip"` + InIface string `json:"in_iface"` + InnerAlertAction string `json:"inner_alert_action"` + InnerAlertCategory string `json:"inner_alert_category"` + InnerAlertSignature string `json:"inner_alert_signature"` + Key string `json:"key"` + Msg string `json:"msg"` + Network string `json:"network"` + Proto string `json:"proto"` + Radio string `json:"radio"` + RadioFrom string `json:"radio_from"` + RadioTo string `json:"radio_to"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + SourceName string `json:"-"` + SrcIP string `json:"src_ip"` + SrcMAC string `json:"src_mac"` + SrcIPASN string `json:"srcipASN"` + SrcIPCountry string `json:"srcipCountry"` + SSID string `json:"ssid"` + Subsystem string `json:"subsystem"` + Sw string `json:"sw"` + SwName string `json:"sw_name"` + UniqueAlertID string `json:"unique_alertid"` + User string `json:"user"` + USGIP string `json:"usgip"` + USGIPASN string `json:"usgipASN"` + USGIPCountry string `json:"usgipCountry"` + DestIPGeo IPGeo `json:"dstipGeo"` + SourceIPGeo IPGeo `json:"srcipGeo"` + USGIPGeo IPGeo `json:"usgipGeo"` +} + +// IPGeo is part of the UniFi Event data. Each event may have up to three of these. +// One for source, one for dest and one for the USG location. +type IPGeo struct { + Asn int64 `json:"asn"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + City string `json:"city"` + ContinentCode string `json:"continent_code"` + CountryCode string `json:"country_code"` + CountryName string `json:"country_name"` + Organization string `json:"organization"` +} + +// Events satisfied the sort.Interface. +type events []*Event + +// Len satisfies sort.Interface. +func (e events) Len() int { + return len(e) +} + +// Swap satisfies sort.Interface. +func (e events) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +// Less satisfies sort.Interface. Sort our list by date/time. +func (e events) Less(i, j int) bool { + return e[i].Datetime.Before(e[j].Datetime) +} + +// UnmarshalJSON is required because sometimes the unifi api returns +// an empty array instead of a struct filled with data. +func (v *IPGeo) UnmarshalJSON(data []byte) error { + if string(data) == "[]" { + return nil // it's empty + } + + g := struct { + Asn int64 `json:"asn"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + City string `json:"city"` + ContinentCode string `json:"continent_code"` + CountryCode string `json:"country_code"` + CountryName string `json:"country_name"` + Organization string `json:"organization"` + }{} + + err := json.Unmarshal(data, &g) + v.Asn = g.Asn + v.Latitude = g.Latitude + v.Longitude = g.Longitude + v.City = g.City + v.ContinentCode = g.ContinentCode + v.CountryCode = g.CountryCode + v.CountryName = g.CountryName + v.Organization = g.Organization + + if err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + return nil +} diff --git a/core/unifi/events_test.go b/core/unifi/events_test.go new file mode 100644 index 00000000..270d0a95 --- /dev/null +++ b/core/unifi/events_test.go @@ -0,0 +1,20 @@ +package unifi_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/unpoller/unifi" +) + +func TestIPGeoUnmarshalJSON(t *testing.T) { + t.Parallel() + + a := assert.New(t) + i := &unifi.IPGeo{} + + a.Nil(i.UnmarshalJSON([]byte(`[]`))) + a.EqualValues(0, i.Asn) + a.Nil(i.UnmarshalJSON([]byte(`{"asn": 123}`))) + a.EqualValues(123, i.Asn) +} diff --git a/core/unifi/examples/convert.sh b/core/unifi/examples/convert.sh new file mode 100755 index 00000000..de5b2975 --- /dev/null +++ b/core/unifi/examples/convert.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Usage: +# ./convert.sh [prefix] +# should contain a go struct, like uap_type.go +# It converts the go struct to an influx thing, like you see in uap_influx.go. +# [prefix] is optional. I used it to do all the stat_ uap metrics. +# Very crude, just helps skip a lot of copy/paste. +# +path=$1 +pre=$2 + +# Reads in the file one line at a time. +while IFS='' read -r line; do + # Split each piece of the file out. + name=$(echo "${line}" | awk '{print $1}') + type=$(echo "${line}" | awk '{print $2}') + json=$(echo "${line}" | awk '{print $3}') + json=$(echo "${json}" | cut -d\" -f2) + + # Don't print junk lines. (it still prints some junk lines) + if [ "$json" != "" ]; then + # Add a .Val suffix if this is a FlexInt or FlexBool. + [[ "$type" = Flex* ]] && suf=.Val + echo "\"${pre}${json}\": u.Stat.${name}${suf}," + fi +done < ${path} diff --git a/core/unifi/examples/events.json b/core/unifi/examples/events.json new file mode 100644 index 00000000..1fec2a99 --- /dev/null +++ b/core/unifi/examples/events.json @@ -0,0 +1,86 @@ +{ + "_id": "5ee9e572453d4e00f3c04a7c", + "user": "d8:4c:90:9f:82:5a", + "ssid": "Extra Fast", + "ap": "b4:fb:e4:d2:74:39", + "radio": "na", + "channel": "36", + "key": "EVT_WU_Connected", + "subsystem": "wlan", + "site_id": "574e86994566ffb914a2683c", + "time": 1592386923851, + "datetime": "2020-06-17T09:42:03Z", + "msg": "User[d8:4c:90:9f:82:5a] has connected to AP[b4:fb:e4:d2:74:39] with SSID \"Extra Fast\" on \"channel 36(na)\"" +}, +{ + "_id": "5ee9e56b453d4e00f3c04a7a", + "user": "d8:4c:90:9f:82:5a", + "ssid": "Extra Fast", + "hostname": "dns-ipp", + "ap": "74:83:c2:d4:11:3d", + "duration": 1084, + "bytes": 846171, + "key": "EVT_WU_Disconnected", + "subsystem": "wlan", + "site_id": "574e86994566ffb914a2683c", + "time": 1592386923000, + "datetime": "2020-06-17T09:42:03Z", + "msg": "User[d8:4c:90:9f:82:5a] disconnected from \"Extra Fast\" (18m 4s connected, 826.34K bytes, last AP[74:83:c2:d4:11:3d])" +}, +{ + "_id": "5ee9f7ca453d4e00f3c04b57", + "timestamp": 1592391625, + "flow_id": 1510453960799559, + "in_iface": "eth0", + "event_type": "alert", + "src_ip": "192.168.1.199", + "src_mac": "00:50:b6:96:76:6e", + "src_port": 50447, + "dest_ip": "54.36.xxx.xxx", + "dst_mac": "74:83:c2:1a:35:39", + "dest_port": 80, + "proto": "TCP", + "tx_id": 0, + "app_proto": "http", + "host": "usg-sensor", + "usgip": "67.181.75.120", + "unique_alertid": "1603112333-2020-06-17T04:00:25.225809-0700", + "srcipGeo": [], + "dstipGeo": { + "continent_code": "EU", + "country_code": "FR", + "country_name": "France", + "latitude": 48.8582, + "longitude": 2.3387, + "asn": 16276, + "organization": "OVH SAS" + }, + "dstipCountry": "FR", + "dstipASN": "16276 OVH SAS", + "usgipGeo": { + "continent_code": "NA", + "country_code": "US", + "country_name": "United States", + "city": "Lodi", + "latitude": 38.1228, + "longitude": -121.2543, + "asn": 7922, + "organization": "COMCAST-7922" + }, + "usgipCountry": "US", + "usgipASN": "7922 COMCAST-7922", + "catname": "emerging-malware", + "inner_alert_action": "allowed", + "inner_alert_gid": 1, + "inner_alert_signature_id": 2003337, + "inner_alert_rev": 21, + "inner_alert_signature": "ET MALWARE Suspicious User Agent (Autoupdate)", + "inner_alert_category": "A Network Trojan was Detected", + "inner_alert_severity": 1, + "key": "EVT_IPS_IpsAlert", + "subsystem": "www", + "site_id": "574e86994566ffb914a2683c", + "time": 1592391625000, + "datetime": "2020-06-17T11:00:25Z", + "msg": "IPS Alert 1: A Network Trojan was Detected. Signature ET MALWARE Suspicious User Agent (Autoupdate). From: 192.168.1.199:50447, to: 54.36.xxx.xxx:80, protocol: TCP" +}, diff --git a/core/unifi/examples/ids.json b/core/unifi/examples/ids.json new file mode 100644 index 00000000..5dbb0fe2 --- /dev/null +++ b/core/unifi/examples/ids.json @@ -0,0 +1,66 @@ +{ + "_id": "5d2416c78f0385ccf1c6df44", + "archived": false, + "timestamp": 1562646211, + "flow_id": 1591464006222389, + "in_iface": "eth1", + "event_type": "alert", + "src_ip": "196.196.244.84", + "src_mac": "f0:9f:c2:c4:bb:f1", + "src_port": 51413, + "dest_ip": "192.168.3.2", + "dst_mac": "40:a8:f0:68:c3:58", + "dest_port": 36881, + "proto": "UDP", + "app_proto": "failed", + "host": "f0:22:22:22:22:22", + "usgip": "11.22.33.44", + "unique_alertid": "1341902566-2019-07-08T21:23:31.229941-0700", + "srcipCountry": "SE", + "dstipCountry": false, + "usgipCountry": "US", + "srcipGeo": { + "continent_code": "EU", + "country_code": "SE", + "country_code3": "SWE", + "country_name": "Sweden", + "region": "26", + "city": "Stockholm", + "postal_code": "168 65", + "latitude": 59.349998474121094, + "longitude": 17.91670036315918, + "dma_code": 0, + "area_code": 0 + }, + "dstipGeo": false, + "usgipGeo": { + "continent_code": "NA", + "country_code": "US", + "country_code3": "USA", + "country_name": "United States", + "region": "CA", + "city": "Other", + "postal_code": "99999", + "latitude": 99.139400482177734, + "longitude": -99.39669799804688, + "dma_code": 862, + "area_code": 999 + }, + "srcipASN": "AS42607 Internet Carrier Limited", + "dstipASN": "", + "usgipASN": "AS7922 Comcast Cable Communications, LLC", + "catname": "spamhaus", + "inner_alert_action": "allowed", + "inner_alert_gid": 1, + "inner_alert_signature_id": 2400022, + "inner_alert_rev": 2673, + "inner_alert_signature": "ET DROP Spamhaus DROP Listed Traffic Inbound group 23", + "inner_alert_category": "Misc Attack", + "inner_alert_severity": 2, + "key": "EVT_IPS_IpsAlert", + "subsystem": "www", + "site_id": "574e86994566ffb914a2683c", + "time": 1562646211000, + "datetime": "2019-07-09T04:23:31Z", + "msg": "IPS Alert 2: Misc Attack. Signature ET DROP Spamhaus DROP Listed Traffic Inbound group 23. From: 196.196.244.84:51413, to: 192.168.3.2:36881, protocol: UDP" +}, diff --git a/core/unifi/examples/uap.json b/core/unifi/examples/uap.json new file mode 100644 index 00000000..dbc6c4ae --- /dev/null +++ b/core/unifi/examples/uap.json @@ -0,0 +1,738 @@ +{ + "_id": "574e8bde4566ffb914a26853", + "adopted": true, + "antenna_table": [ + { + "default": true, + "id": 4, + "name": "Combined", + "wifi0_gain": 3, + "wifi1_gain": 3 + } + ], + "bandsteering_mode": "prefer_5g", + "board_rev": 18, + "cfgversion": "08cd8d6b71ebe82f", + "config_network": { + "type": "dhcp", + "ip": "10.1.10.67" + }, + "countrycode_table": [ + 840, + 124, + 630 + ], + "ethernet_table": [ + { + "mac": "80:22:a8:22:ae:22", + "num_port": 2, + "name": "eth0" + } + ], + "fw_caps": 4128063, + "has_eth1": false, + "has_speaker": false, + "inform_ip": "192.168.3.1", + "inform_url": "http://unifi:8080/inform", + "ip": "192.168.1.8", + "led_override": "default", + "mac": "80:22:22:22:22:22", + "mesh_sta_vap_enabled": true, + "model": "U7PG2", + "name": "wap-lower", + "outdoor_mode_override": "default", + "port_table": [ + { + "port_idx": 1, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "attr_no_edit": true, + "media": "GE", + "name": "Main", + "poe_caps": 0, + "port_poe": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "port_delta": { + "time_delta": 21 + }, + "enable": true, + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 2, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "media": "GE", + "name": "Secondary", + "poe_caps": 0, + "port_poe": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "port_delta": { + "time_delta": 21 + }, + "enable": true, + "masked": false, + "aggregated_by": false + } + ], + "radio_table": [ + { + "radio": "ng", + "name": "wifi0", + "channel": "auto", + "ht": "40", + "tx_power_mode": "auto", + "tx_power": "0", + "max_txpower": 22, + "min_txpower": 6, + "nss": 3, + "min_rssi_enabled": false, + "builtin_antenna": true, + "builtin_ant_gain": 3, + "current_antenna_gain": 0, + "radio_caps": 16420, + "wlangroup_id": "574e869d4566ffb914a26843" + }, + { + "radio": "na", + "name": "wifi1", + "channel": "auto", + "ht": "80", + "tx_power_mode": "high", + "tx_power": "0", + "max_txpower": 22, + "min_txpower": 6, + "nss": 3, + "min_rssi_enabled": false, + "is_11ac": true, + "has_dfs": true, + "has_fccdfs": true, + "builtin_antenna": true, + "builtin_ant_gain": 3, + "current_antenna_gain": 0, + "radio_caps": 50479140, + "wlangroup_id": "574e869d4566ffb914a26843" + } + ], + "scan_radio_table": [], + "serial": "xxxyyyzzz", + "site_id": "574e86994566ffb914a2683c", + "type": "uap", + "version": "4.0.42.10433", + "vwire_table": [], + "wifi_caps": 49141, + "wlangroup_id_na": "574e869d4566ffb914a26843", + "wlangroup_id_ng": "574e869d4566ffb914a26843", + "required_version": "3.4.1", + "hw_caps": 0, + "unsupported": false, + "unsupported_reason": 0, + "sys_error_caps": 0, + "has_fan": false, + "has_temperature": false, + "device_id": "574e8bde4566ffb914a26853", + "state": 1, + "last_seen": 1562225381, + "upgradable": false, + "adoptable_when_upgraded": false, + "rollupgrade": false, + "known_cfgversion": "08cd8d6b71ebe82f", + "uptime": 3105845, + "_uptime": 3105845, + "locating": false, + "connect_request_ip": "192.168.1.8", + "connect_request_port": "43913", + "sys_stats": { + "loadavg_1": "0.04", + "loadavg_15": "0.14", + "loadavg_5": "0.10", + "mem_buffer": 0, + "mem_total": 128622592, + "mem_used": 67178496 + }, + "system-stats": { + "cpu": "24.7", + "mem": "52.2", + "uptime": "3105845" + }, + "ssh_session_table": [], + "scanning": false, + "spectrum_scanning": false, + "guest_token": "4D630D2A1AF84771FCBB8EEB4C47E030", + "meshv3_peer_mac": "", + "satisfaction": 95, + "isolated": false, + "radio_table_stats": [ + { + "name": "wifi0", + "channel": 6, + "radio": "ng", + "ast_txto": null, + "ast_cst": null, + "ast_be_xmit": 398, + "cu_total": 20, + "cu_self_rx": 14, + "cu_self_tx": 4, + "gain": 3, + "state": "RUN", + "extchannel": 1, + "tx_power": 22, + "tx_packets": 183, + "tx_retries": 108, + "num_sta": 2, + "guest-num_sta": 0, + "user-num_sta": 2 + }, + { + "name": "wifi1", + "channel": 36, + "radio": "na", + "ast_txto": null, + "ast_cst": null, + "ast_be_xmit": 398, + "cu_total": 12, + "cu_self_rx": 10, + "cu_self_tx": 2, + "gain": 3, + "state": "RUN", + "extchannel": 1, + "tx_power": 22, + "tx_packets": 22466, + "tx_retries": 858, + "num_sta": 4, + "guest-num_sta": 0, + "user-num_sta": 4 + } + ], + "uplink": { + "full_duplex": true, + "ip": "0.0.0.0", + "mac": "80:22:22:22:22:22", + "max_vlan": 96, + "name": "eth0", + "netmask": "0.0.0.0", + "num_port": 2, + "rx_bytes": 3752803309, + "rx_dropped": 102338, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 245302304, + "speed": 1000, + "tx_bytes": 1604707458, + "tx_dropped": 341, + "tx_errors": 0, + "tx_packets": 194278357, + "up": true, + "max_speed": 1000, + "type": "wire", + "tx_bytes-r": 9693222, + "rx_bytes-r": 92418, + "uplink_mac": "f0:22:22:22:22:22", + "uplink_remote_port": 15 + }, + "vap_table": [ + { + "avg_client_signal": 0, + "bssid": "82:22:22:22:22:22", + "ccq": 0, + "channel": 36, + "essid": "Extra Free", + "extchannel": 1, + "id": "574e96834566ffb914a26875", + "mac_filter_rejections": 0, + "name": "ath3", + "num_satisfaction_sta": 0, + "num_sta": 0, + "radio": "na", + "radio_name": "wifi1", + "rx_bytes": 61253, + "rx_crypts": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_frags": 0, + "rx_nwids": 47658, + "rx_packets": 576, + "rx_tcp_stats": { + "goodbytes": 0, + "lat_avg": 0, + "lat_max": 0, + "lat_min": 4294967295, + "stalls": 0 + }, + "satisfaction": -1, + "satisfaction_now": -1, + "state": "RUN", + "tx_bytes": 922841, + "tx_combined_retries": 0, + "tx_data_mpdu_bytes": 0, + "tx_dropped": 18128, + "tx_errors": 7, + "tx_packets": 736, + "tx_power": 22, + "tx_retries": 0, + "tx_rts_retries": 0, + "tx_success": 0, + "tx_tcp_stats": { + "goodbytes": 0, + "lat_avg": 0, + "lat_max": 0, + "lat_min": 4294967295, + "stalls": 0 + }, + "tx_total": 0, + "up": true, + "usage": "guest", + "wifi_tx_attempts": 0, + "wifi_tx_dropped": 0, + "wifi_tx_latency_mov": { + "avg": 0, + "max": 0, + "min": 4294967295, + "total": 0, + "total_count": 0 + }, + "t": "vap", + "wlanconf_id": "574e96834566ffb914a26875", + "is_guest": true, + "is_wep": false, + "ap_mac": "80:22:a8:22:22:22", + "map_id": null, + "site_id": "574e86994566ffb914a2683c" + }, + { + "avg_client_signal": -65, + "bssid": "80:22:a8:22:22:22", + "ccq": 333, + "channel": 36, + "essid": "Extra Fast", + "extchannel": 1, + "id": "574e96614566ffb914a26874", + "mac_filter_rejections": 0, + "name": "ath2", + "num_satisfaction_sta": 4, + "num_sta": 4, + "radio": "na", + "radio_name": "wifi1", + "rx_bytes": 3358763522, + "rx_crypts": 161639, + "rx_dropped": 161639, + "rx_errors": 161639, + "rx_frags": 0, + "rx_nwids": 37605, + "rx_packets": 99128603, + "rx_tcp_stats": { + "goodbytes": 16243150352, + "lat_avg": 7, + "lat_max": 100, + "lat_min": 10, + "stalls": 0 + }, + "satisfaction": 96, + "satisfaction_now": 97, + "state": "RUN", + "tx_bytes": 834859686, + "tx_combined_retries": 9067488, + "tx_data_mpdu_bytes": 48117828355, + "tx_dropped": 109, + "tx_errors": 4076905, + "tx_packets": 111980588, + "tx_power": 22, + "tx_retries": 9067488, + "tx_rts_retries": 0, + "tx_success": 62855481, + "tx_tcp_stats": { + "goodbytes": 2154118473, + "lat_avg": 18, + "lat_max": 120, + "lat_min": 10, + "stalls": 0 + }, + "tx_total": 62855552, + "up": true, + "usage": "user", + "wifi_tx_attempts": 71923040, + "wifi_tx_dropped": 71, + "wifi_tx_latency_mov": { + "avg": 565, + "max": 31757, + "min": 98, + "total": 10183098, + "total_count": 18019 + }, + "t": "vap", + "wlanconf_id": "574e96614566ffb914a26874", + "is_guest": false, + "is_wep": false, + "ap_mac": "80:22:22:22:22:22", + "map_id": null, + "site_id": "574e86994566ffb914a2683c" + }, + { + "avg_client_signal": 0, + "bssid": "82:22:a8:22:22:22", + "ccq": 0, + "channel": 6, + "essid": "Extra Free", + "extchannel": 1, + "id": "574e96834566ffb914a26875", + "mac_filter_rejections": 0, + "name": "ath1", + "num_satisfaction_sta": 0, + "num_sta": 0, + "radio": "ng", + "radio_name": "wifi0", + "reasons_bar_chart": { + "phy_rate": 0, + "signal": 0, + "sleepy_client": 0, + "sta_arp_timeout": 0, + "sta_dns_timeout": 0, + "sta_ip_timeout": 0, + "stream_eff": 0, + "tcp_latency": 0, + "tcp_packet_loss": 0, + "wifi_latency": 0, + "wifi_retries": 0 + }, + "reasons_bar_chart_now": { + "phy_rate": 0, + "signal": 0, + "sleepy_client": 0, + "sta_arp_timeout": 0, + "sta_dns_timeout": 0, + "sta_ip_timeout": 0, + "stream_eff": 0, + "tcp_latency": 0, + "tcp_packet_loss": 0, + "wifi_latency": 0, + "wifi_retries": 0 + }, + "rx_bytes": 0, + "rx_crypts": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_frags": 0, + "rx_nwids": 120385, + "rx_packets": 0, + "rx_tcp_stats": { + "goodbytes": 0, + "lat_avg": 0, + "lat_max": 0, + "lat_min": 4294967295, + "stalls": 0 + }, + "satisfaction": -1, + "satisfaction_now": -1, + "state": "RUN", + "tx_bytes": 0, + "tx_combined_retries": 0, + "tx_data_mpdu_bytes": 0, + "tx_dropped": 20262, + "tx_errors": 0, + "tx_packets": 0, + "tx_power": 22, + "tx_retries": 0, + "tx_rts_retries": 0, + "tx_success": 0, + "tx_tcp_stats": { + "goodbytes": 0, + "lat_avg": 0, + "lat_max": 0, + "lat_min": 4294967295, + "stalls": 0 + }, + "tx_total": 0, + "up": true, + "usage": "guest", + "wifi_tx_attempts": 0, + "wifi_tx_dropped": 0, + "wifi_tx_latency_mov": { + "avg": 0, + "max": 0, + "min": 4294967295, + "total": 0, + "total_count": 0 + }, + "t": "vap", + "wlanconf_id": "574e96834566ffb914a26875", + "is_guest": true, + "is_wep": false, + "ap_mac": "80:22:a8:22:22:22", + "map_id": null, + "site_id": "574e86994566ffb914a2683c" + }, + { + "avg_client_signal": -68, + "bssid": "80:22:22:22:22:22", + "ccq": 966, + "channel": 6, + "essid": "Extra Fast", + "extchannel": 1, + "id": "574e96614566ffb914a26874", + "mac_filter_rejections": 0, + "name": "ath0", + "num_satisfaction_sta": 2, + "num_sta": 2, + "radio": "ng", + "radio_name": "wifi0", + "reasons_bar_chart": { + "phy_rate": 0, + "signal": 0, + "sleepy_client": 0, + "sta_arp_timeout": 0, + "sta_dns_timeout": 0, + "sta_ip_timeout": 0, + "stream_eff": 0, + "tcp_latency": 0, + "tcp_packet_loss": 0, + "wifi_latency": 0, + "wifi_retries": 1 + }, + "reasons_bar_chart_now": { + "phy_rate": 0, + "signal": 0, + "sleepy_client": 0, + "sta_arp_timeout": 0, + "sta_dns_timeout": 0, + "sta_ip_timeout": 0, + "stream_eff": 0, + "tcp_latency": 0, + "tcp_packet_loss": 0, + "wifi_latency": 0, + "wifi_retries": 1 + }, + "rx_bytes": 1017366419, + "rx_crypts": 11013, + "rx_dropped": 12764, + "rx_errors": 12764, + "rx_frags": 0, + "rx_nwids": 177145, + "rx_packets": 22542668, + "rx_tcp_stats": { + "goodbytes": 114220296, + "lat_avg": 5, + "lat_max": 10, + "lat_min": 10, + "stalls": 0 + }, + "satisfaction": 93, + "satisfaction_now": 94, + "state": "RUN", + "tx_bytes": 965488630, + "tx_combined_retries": 8202639, + "tx_data_mpdu_bytes": 1145631754, + "tx_dropped": 43, + "tx_errors": 0, + "tx_packets": 22623798, + "tx_power": 22, + "tx_retries": 7194267, + "tx_rts_retries": 1008372, + "tx_success": 9545999, + "tx_tcp_stats": { + "goodbytes": 182912739, + "lat_avg": 5, + "lat_max": 10, + "lat_min": 10, + "stalls": 0 + }, + "tx_total": 9547096, + "up": true, + "usage": "user", + "wifi_tx_attempts": 16740276, + "wifi_tx_dropped": 1095, + "wifi_tx_latency_mov": { + "avg": 673, + "max": 13612, + "min": 0, + "total": 263176, + "total_count": 391 + }, + "t": "vap", + "wlanconf_id": "574e96614566ffb914a26874", + "is_guest": false, + "is_wep": false, + "ap_mac": "80:22:22:22:22:22", + "map_id": null, + "site_id": "574e86994566ffb914a2683c" + } + ], + "downlink_table": [], + "vwire_vap_table": [], + "bytes-d": 204913232, + "tx_bytes-d": 1921569, + "rx_bytes-d": 202991663, + "bytes-r": 9757772, + "last_uplink": { + "uplink_mac": "f0:22:22:22:22:22", + "uplink_remote_port": 15 + }, + "stat": { + "site_id": "574e86994566ffb914a2683c", + "o": "ap", + "oid": "80:22:a8:22:22:22", + "ap": "80:22:a8:22:22:22", + "time": 1562207100000, + "datetime": "2019-07-04T02:25:00Z", + "guest-wifi0-rx_packets": 0, + "guest-wifi1-rx_packets": 0, + "user-wifi1-rx_packets": 31373230, + "user-wifi0-rx_packets": 169790, + "user-rx_packets": 31543020, + "guest-rx_packets": 0, + "wifi0-rx_packets": 169790, + "wifi1-rx_packets": 31373230, + "rx_packets": 31543020, + "guest-wifi0-rx_bytes": 0, + "guest-wifi1-rx_bytes": 0, + "user-wifi1-rx_bytes": 42049645434, + "user-wifi0-rx_bytes": 16755639, + "user-rx_bytes": 42066401073, + "guest-rx_bytes": 0, + "wifi0-rx_bytes": 16755639, + "wifi1-rx_bytes": 42049645434, + "rx_bytes": 42066401073, + "guest-wifi0-rx_errors": 0, + "guest-wifi1-rx_errors": 0, + "user-wifi1-rx_errors": 150651, + "user-wifi0-rx_errors": 0, + "user-rx_errors": 150651, + "guest-rx_errors": 0, + "wifi0-rx_errors": 0, + "wifi1-rx_errors": 150651, + "rx_errors": 150651, + "guest-wifi0-rx_dropped": 0, + "guest-wifi1-rx_dropped": 0, + "user-wifi1-rx_dropped": 150651, + "user-wifi0-rx_dropped": 0, + "user-rx_dropped": 150651, + "guest-rx_dropped": 0, + "wifi0-rx_dropped": 0, + "wifi1-rx_dropped": 150651, + "rx_dropped": 150651, + "guest-wifi0-rx_crypts": 0, + "guest-wifi1-rx_crypts": 0, + "user-wifi1-rx_crypts": 150651, + "user-wifi0-rx_crypts": 0, + "user-rx_crypts": 150651, + "guest-rx_crypts": 0, + "wifi0-rx_crypts": 0, + "wifi1-rx_crypts": 150651, + "rx_crypts": 150651, + "guest-wifi0-rx_frags": 0, + "guest-wifi1-rx_frags": 0, + "user-wifi1-rx_frags": 0, + "user-wifi0-rx_frags": 0, + "user-rx_frags": 0, + "guest-rx_frags": 0, + "wifi0-rx_frags": 0, + "wifi1-rx_frags": 0, + "rx_frags": 0, + "guest-wifi0-tx_packets": 0, + "guest-wifi1-tx_packets": 0, + "user-wifi1-tx_packets": 7125589, + "user-wifi0-tx_packets": 210389, + "user-tx_packets": 7335978, + "guest-tx_packets": 0, + "wifi0-tx_packets": 210389, + "wifi1-tx_packets": 7125589, + "tx_packets": 7335978, + "guest-wifi0-tx_bytes": 0, + "guest-wifi1-tx_bytes": 0, + "user-wifi1-tx_bytes": 3011293823, + "user-wifi0-tx_bytes": 25966558, + "user-tx_bytes": 3037260381, + "guest-tx_bytes": 0, + "wifi0-tx_bytes": 25966558, + "wifi1-tx_bytes": 3011293823, + "tx_bytes": 3037260381, + "guest-wifi0-tx_errors": 0, + "guest-wifi1-tx_errors": 0, + "user-wifi1-tx_errors": 102193, + "user-wifi0-tx_errors": 0, + "user-tx_errors": 102193, + "guest-tx_errors": 0, + "wifi0-tx_errors": 0, + "wifi1-tx_errors": 102193, + "tx_errors": 102193, + "guest-wifi0-tx_dropped": 296, + "guest-wifi1-tx_dropped": 296, + "user-wifi1-tx_dropped": 0, + "user-wifi0-tx_dropped": 0, + "user-tx_dropped": 0, + "guest-tx_dropped": 592, + "wifi0-tx_dropped": 296, + "wifi1-tx_dropped": 296, + "tx_dropped": 592, + "guest-wifi0-tx_retries": 0, + "guest-wifi1-tx_retries": 0, + "user-wifi1-tx_retries": 519734, + "user-wifi0-tx_retries": 90225, + "user-tx_retries": 609959, + "guest-tx_retries": 0, + "wifi0-tx_retries": 90225, + "wifi1-tx_retries": 519734, + "tx_retries": 609959, + "guest-wifi0-mac_filter_rejections": 0, + "guest-wifi1-mac_filter_rejections": 0, + "user-wifi1-mac_filter_rejections": 0, + "user-wifi0-mac_filter_rejections": 0, + "user-mac_filter_rejections": 0, + "guest-mac_filter_rejections": 0, + "wifi0-mac_filter_rejections": 0, + "wifi1-mac_filter_rejections": 0, + "mac_filter_rejections": 0, + "guest-wifi0-wifi_tx_attempts": 0, + "guest-wifi1-wifi_tx_attempts": 0, + "user-wifi1-wifi_tx_attempts": 4419026, + "user-wifi0-wifi_tx_attempts": 255999, + "user-wifi_tx_attempts": 4675025, + "guest-wifi_tx_attempts": 0, + "wifi0-wifi_tx_attempts": 255999, + "wifi1-wifi_tx_attempts": 4419026, + "wifi_tx_attempts": 4675025, + "guest-wifi0-wifi_tx_dropped": 0, + "guest-wifi1-wifi_tx_dropped": 0, + "user-wifi1-wifi_tx_dropped": 25, + "user-wifi0-wifi_tx_dropped": 2, + "user-wifi_tx_dropped": 27, + "guest-wifi_tx_dropped": 0, + "wifi0-wifi_tx_dropped": 2, + "wifi1-wifi_tx_dropped": 25, + "wifi_tx_dropped": 27, + "bytes": 45103661454, + "duration": 17988000, + "user-wifi1-ath2-574e96614566ffb914a26874-rx_packets": 31373230, + "user-wifi1-ath2-574e96614566ffb914a26874-rx_bytes": 42049645434, + "user-wifi1-ath2-574e96614566ffb914a26874-tx_packets": 7125589, + "user-wifi1-ath2-574e96614566ffb914a26874-tx_bytes": 3011293823, + "user-wifi1-ath2-574e96614566ffb914a26874-tx_errors": 102193, + "user-wifi1-ath2-574e96614566ffb914a26874-tx_retries": 519734, + "user-wifi1-ath2-574e96614566ffb914a26874-wifi_tx_attempts": 4419026, + "user-wifi0-ath0-574e96614566ffb914a26874-rx_packets": 169790, + "user-wifi0-ath0-574e96614566ffb914a26874-rx_bytes": 16755639, + "user-wifi0-ath0-574e96614566ffb914a26874-tx_packets": 210389, + "user-wifi0-ath0-574e96614566ffb914a26874-tx_bytes": 25966558, + "user-wifi0-ath0-574e96614566ffb914a26874-tx_retries": 90225, + "user-wifi0-ath0-574e96614566ffb914a26874-wifi_tx_attempts": 255999, + "guest-wifi1-ath3-574e96834566ffb914a26875-tx_dropped": 296, + "guest-wifi0-ath1-574e96834566ffb914a26875-tx_dropped": 296, + "user-wifi1-ath2-574e96614566ffb914a26874-rx_errors": 150651, + "user-wifi1-ath2-574e96614566ffb914a26874-rx_dropped": 150651, + "user-wifi1-ath2-574e96614566ffb914a26874-rx_crypts": 150651, + "user-wifi0-ath0-574e96614566ffb914a26874-wifi_tx_dropped": 2, + "user-wifi1-ath2-574e96614566ffb914a26874-wifi_tx_dropped": 25 + }, + "tx_bytes": 3037260381, + "rx_bytes": 42066401073, + "bytes": 45103661454, + "vwireEnabled": false, + "uplink_table": [], + "num_sta": 6, + "user-num_sta": 6, + "guest-num_sta": 0 +} diff --git a/core/unifi/examples/ugw.json b/core/unifi/examples/ugw.json new file mode 100644 index 00000000..117b14f7 --- /dev/null +++ b/core/unifi/examples/ugw.json @@ -0,0 +1,371 @@ +{ + "_id": "59a35da745663e6cc82600f6", + "adopted": true, + "cfgversion": "bf9f0335063fe6ea", + "config_network": { + "type": "dhcp", + "ip": "192.168.2.0" + }, + "ethernet_table": [ + { + "mac": "22:22:00:22:22:00", + "num_port": 1, + "name": "eth0" + }, + { + "mac": "22:22:00:22:22:00", + "num_port": 1, + "name": "eth1" + }, + { + "mac": "22:22:00:22:22:00", + "num_port": 1, + "name": "eth2" + } + ], + "fw_caps": 184323, + "inform_ip": "192.168.3.1", + "inform_url": "http://security:8080/inform", + "ip": "3.1.33.7", + "led_override": "default", + "license_state": "registered", + "mac": "22:22:00:22:22:00", + "model": "UGW3", + "name": "gateway", + "outdoor_mode_override": "default", + "serial": "xxxyyyzzz", + "site_id": "574e86994566ffb914a2683c", + "type": "ugw", + "usg_caps": 786431, + "version": "4.4.41.5193700", + "required_version": "4.0.0", + "ethernet_overrides": [ + { + "ifname": "eth1", + "networkgroup": "LAN" + }, + { + "ifname": "eth0", + "networkgroup": "WAN" + } + ], + "hw_caps": 0, + "board_rev": 16, + "unsupported": false, + "unsupported_reason": 0, + "device_id": "59a35da745663e6cc82600f6", + "state": 1, + "last_seen": 1562311857, + "upgradable": false, + "adoptable_when_upgraded": false, + "rollupgrade": false, + "known_cfgversion": "bf9f0335063fe6ea", + "uptime": 3191626, + "_uptime": 3191626, + "locating": false, + "connect_request_ip": "192.168.2.1", + "connect_request_port": "35615", + "sys_stats": { + "loadavg_1": "0.01", + "loadavg_15": "0.12", + "loadavg_5": "0.06", + "mem_buffer": 62406656, + "mem_total": 507412480, + "mem_used": 397500416 + }, + "system-stats": { + "cpu": "14", + "mem": "30", + "uptime": "3191066" + }, + "guest_token": "83342830AE9C0641DC39DD2759C122A1", + "speedtest-status": { + "latency": 14, + "rundate": 1562310531, + "runtime": 172, + "status_download": 2, + "status_ping": 2, + "status_summary": 2, + "status_upload": 2, + "xput_download": 157.36776733398438, + "xput_upload": 37.90521240234375 + }, + "speedtest-status-saved": true, + "wan1": { + "tx_bytes-r": 2852355, + "rx_bytes-r": 1224743, + "bytes-r": 4077098, + "max_speed": 1000, + "type": "wire", + "name": "wan", + "ifname": "eth0", + "ip": "3.1.33.7", + "netmask": "255.255.254.0", + "mac": "22:22:00:22:22:00", + "up": true, + "speed": 1000, + "full_duplex": true, + "rx_bytes": 2648236513108, + "rx_dropped": 34030, + "rx_errors": 0, + "rx_packets": 3068347172, + "tx_bytes": 3009601283006, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 2859713220, + "rx_multicast": 939111, + "enable": true, + "dns": [ + "1.1.1.1", + "8.8.8.8" + ], + "gateway": "3.1.33.8" + }, + "port_table": [ + { + "name": "wan", + "ifname": "eth0", + "ip": "3.1.33.7", + "netmask": "255.255.254.0", + "mac": "22:22:00:22:22:00", + "up": true, + "speed": 1000, + "full_duplex": true, + "rx_bytes": 2648236513108, + "rx_dropped": 34030, + "rx_errors": 0, + "rx_packets": 3068347172, + "tx_bytes": 3009601283006, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 2859713220, + "rx_multicast": 939111, + "enable": true, + "dns": [ + "216.146.35.35", + "216.146.36.36" + ], + "gateway": "3.1.33.8" + }, + { + "name": "lan", + "ifname": "eth1", + "ip": "192.168.2.1", + "netmask": "255.255.252.0", + "mac": "22:22:00:22:22:00", + "up": true, + "speed": 1000, + "full_duplex": true, + "rx_bytes": 2911311797255, + "rx_dropped": 3438, + "rx_errors": 0, + "rx_packets": 2659342049, + "tx_bytes": 2140222188895, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 2734245088, + "rx_multicast": 11929365, + "enable": true + }, + { + "name": "lan2", + "ifname": "eth2", + "ip": "0.0.0.0", + "netmask": "0.0.0.0", + "mac": "22:22:00:22:22:00", + "up": false, + "speed": 0, + "full_duplex": false, + "rx_bytes": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_packets": 0, + "tx_bytes": 0, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 0, + "rx_multicast": 0, + "enable": false + } + ], + "network_table": [ + { + "_id": "574e8de34566ffb914a26862", + "is_nat": true, + "dhcpd_dns_enabled": false, + "purpose": "guest", + "dhcpd_leasetime": "86400", + "igmp_snooping": true, + "dhcpguard_enabled": false, + "dhcpd_start": "192.168.5.1", + "enabled": true, + "dhcpd_stop": "192.168.5.254", + "dhcpd_wins_enabled": false, + "domain_name": "guest.lan", + "dhcpd_enabled": true, + "ip_subnet": "192.168.5.0/23", + "vlan": "5", + "networkgroup": "LAN", + "name": "Public Wireless", + "site_id": "574e86994566ffb914a2683c", + "dhcpd_ip_1": "", + "vlan_enabled": true, + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "ipv6_interface_type": "none", + "dhcp_relay_enabled": false, + "mac": "22:22:00:22:22:00", + "is_guest": true, + "ip": "192.168.5.0", + "up": "true", + "num_sta": 1, + "rx_bytes": 578602537, + "rx_packets": 471151, + "tx_bytes": 182318948, + "tx_packets": 239651 + }, + { + "_id": "59a362f645663e6cc8260133", + "is_nat": true, + "dhcpd_dns_enabled": false, + "purpose": "corporate", + "dhcpd_leasetime": 86400, + "dhcpd_start": "192.168.68.2", + "dhcpd_stop": "192.168.68.250", + "enabled": true, + "domain_name": "secure.lan", + "dhcpd_enabled": true, + "vlan": "69", + "ip_subnet": "192.168.69.1/23", + "networkgroup": "LAN", + "name": "Security Network", + "site_id": "574e86994566ffb914a2683c", + "vlan_enabled": true, + "dhcpd_ntp_1": "192.168.69.1", + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_ntp_enabled": true, + "ipv6_interface_type": "none", + "dhcpd_unifi_controller": "192.168.3.1", + "igmp_snooping": true, + "mac": "22:22:00:22:22:00", + "is_guest": false, + "ip": "192.168.69.1", + "up": "true", + "num_sta": 11, + "rx_bytes": 5221725, + "rx_packets": 70663, + "tx_bytes": 0, + "tx_packets": 0 + }, + { + "_id": "574e869d4566ffb914a26841", + "purpose": "corporate", + "dhcpd_leasetime": "86400", + "igmp_snooping": false, + "dhcpd_ntp_1": "192.168.2.1", + "dhcpguard_enabled": false, + "dhcpd_gateway_enabled": false, + "dhcpd_time_offset_enabled": false, + "dhcpd_start": "192.168.1.1", + "dhcpd_unifi_controller": "192.168.3.1", + "dhcpd_stop": "192.168.1.254", + "enabled": true, + "domain_name": "home.lan", + "dhcpd_enabled": true, + "ip_subnet": "192.168.2.1/22", + "networkgroup": "LAN", + "dhcpd_ip_1": "", + "vlan_enabled": false, + "is_nat": true, + "dhcpd_dns_enabled": false, + "dhcp_relay_enabled": false, + "dhcpd_wins_enabled": false, + "upnp_lan_enabled": true, + "dhcpd_ntp_enabled": true, + "name": "Home Network", + "site_id": "574e86994566ffb914a2683c", + "attr_no_delete": true, + "attr_hidden_id": "LAN", + "ipv6_interface_type": "none", + "mac": "22:22:00:22:22:00", + "is_guest": false, + "ip": "192.168.2.1", + "up": "true", + "num_sta": 30, + "rx_bytes": 2099754971983, + "rx_packets": 2689749160, + "tx_bytes": 2877873632166, + "tx_packets": 2579198457 + } + ], + "uplink": { + "drops": 40, + "enable": true, + "full_duplex": true, + "gateways": [ + "3.1.33.8" + ], + "ip": "3.1.33.7", + "latency": 103, + "mac": "22:22:00:22:22:00", + "name": "eth0", + "nameservers": [ + "1.1.1.1", + "8.8.8.8" + ], + "netmask": "255.255.254.0", + "num_port": 1, + "rx_bytes": 2648236513108, + "rx_dropped": 34030, + "rx_errors": 0, + "rx_multicast": 939111, + "rx_packets": 3068347172, + "speed": 1000, + "speedtest_lastrun": 1562310531, + "speedtest_ping": 14, + "speedtest_status": "Success", + "tx_bytes": 3009601283006, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 2859713220, + "up": true, + "uptime": 559088, + "xput_down": 157.368, + "xput_up": 37.905, + "tx_bytes-r": 2852355, + "rx_bytes-r": 1224743, + "bytes-r": 4077098, + "max_speed": 1000, + "type": "wire" + }, + "stat": { + "site_id": "574e86994566ffb914a2683c", + "o": "gw", + "oid": "22:22:00:22:22:00", + "gw": "22:22:00:22:22:00", + "time": 1562207100000, + "datetime": "2019-07-04T02:25:00Z", + "duration": 104466000, + "wan-rx_packets": 151387924, + "wan-rx_bytes": 111251311739, + "wan-tx_packets": 182985900, + "wan-tx_bytes": 230372237709, + "lan-rx_packets": 173953163, + "lan-rx_bytes": 226862410885, + "lan-tx_packets": 137029474, + "lan-tx_bytes": 89478206254, + "wan-rx_dropped": 561, + "lan-rx_dropped": 29 + }, + "tx_bytes": 2648236513108, + "rx_bytes": 3009601283006, + "bytes": 5657837796114, + "num_sta": 41, + "user-num_sta": 41, + "guest-num_sta": 0, + "num_desktop": 7, + "num_mobile": 2, + "num_handheld": 8 +} diff --git a/core/unifi/examples/usw.json b/core/unifi/examples/usw.json new file mode 100644 index 00000000..d70d6ab3 --- /dev/null +++ b/core/unifi/examples/usw.json @@ -0,0 +1,1720 @@ +{ + "_id": "59a35cee45663e6cc82600f0", + "adopted": true, + "board_rev": 7, + "cfgversion": "669564dd04994088", + "config_network": { + "type": "dhcp", + "ip": "192.168.1.6" + }, + "dot1x_portctrl_enabled": false, + "ethernet_table": [ + { + "mac": "22:22:00:22:22:00", + "num_port": 26, + "name": "eth0" + }, + { + "mac": "22:22:00:22:22:00", + "name": "srv0" + } + ], + "flowctrl_enabled": false, + "fw_caps": 712229, + "has_fan": true, + "has_temperature": true, + "inform_ip": "192.168.3.1", + "inform_url": "http://security:8080/inform", + "ip": "192.168.1.7", + "jumboframe_enabled": false, + "led_override": "default", + "license_state": "registered", + "mac": "22:22:00:22:22:00", + "model": "US24P250", + "name": "switch", + "outdoor_mode_override": "default", + "port_overrides": [ + { + "name": "APC UPS", + "poe_mode": "off", + "port_idx": 1, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "poe_mode": "auto", + "port_idx": 2, + "portconf_id": "574e869d4566ffb914a26847" + }, + { + "name": "Camera: Gate", + "poe_mode": "auto", + "port_idx": 3, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Bubba's Desktop", + "poe_mode": "auto", + "port_idx": 4, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "Lexi's Desktop", + "poe_mode": "off", + "port_idx": 5, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "Camera: Car", + "poe_mode": "auto", + "port_idx": 6, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Camera: Garage", + "poe_mode": "auto", + "port_idx": 7, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Office Tertiary Desk PD14", + "poe_mode": "off", + "port_idx": 8, + "portconf_id": "574e869d4566ffb914a26847" + }, + { + "name": "Camera: Pool", + "poe_mode": "auto", + "port_idx": 9, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Security: Home Port", + "poe_mode": "off", + "port_idx": 10, + "portconf_id": "574e869d4566ffb914a26847" + }, + { + "name": "8ch Relay Board", + "poe_mode": "auto", + "port_idx": 11, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Security: Secure Port", + "poe_mode": "off", + "port_idx": 12, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Upstairs WAP", + "poe_mode": "auto", + "port_idx": 13, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "Downstairs WAP", + "poe_mode": "auto", + "port_idx": 15, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "David's Desk", + "poe_mode": "off", + "port_idx": 16, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "Camera: Door", + "poe_mode": "auto", + "port_idx": 17, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Camera: Porch", + "poe_mode": "auto", + "port_idx": 19, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Camera: Road", + "port_idx": 21, + "portconf_id": "59a362f645663e6cc8260134" + }, + { + "name": "Livingroom Stereo", + "poe_mode": "off", + "port_idx": 22, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "Homerun Prime", + "poe_mode": "off", + "port_idx": 23, + "portconf_id": "574e869d4566ffb914a26845" + }, + { + "name": "gateway", + "poe_mode": "off", + "port_idx": 24, + "portconf_id": "574e869d4566ffb914a26845" + } + ], + "port_table": [ + { + "port_idx": 1, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 1202, + "rx_bytes": 227167212, + "rx_dropped": 0, + "rx_errors": 12, + "rx_multicast": 28260, + "rx_packets": 1153365, + "satisfaction": 85, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 417327, + "tx_bytes": 507571083, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2284158, + "tx_packets": 4465717, + "up": true, + "tx_bytes-r": 45, + "rx_bytes-r": 5, + "bytes-r": 50, + "name": "APC UPS", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 2, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26847", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 1175, + "rx_bytes": 1068032535, + "rx_dropped": 150, + "rx_errors": 0, + "rx_multicast": 3489, + "rx_packets": 11624994, + "satisfaction": 90, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 62176, + "tx_bytes": 41832520833, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 302928, + "tx_packets": 28160117, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Port 2", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 3, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "91.55", + "poe_enable": true, + "poe_good": true, + "poe_power": "4.84", + "poe_voltage": "52.88", + "rx_broadcast": 65216, + "rx_bytes": 3456851429405, + "rx_dropped": 157638, + "rx_errors": 0, + "rx_multicast": 164814, + "rx_packets": 2410688372, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 353417, + "tx_bytes": 40005007443, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2419630, + "tx_packets": 564763412, + "up": true, + "tx_bytes-r": 12085, + "rx_bytes-r": 888603, + "bytes-r": 900689, + "name": "Camera: Gate", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 4, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 646153, + "rx_bytes": 4581272050, + "rx_dropped": 15683, + "rx_errors": 0, + "rx_multicast": 36698, + "rx_packets": 53602118, + "satisfaction": 90, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 452081, + "tx_bytes": 166341729228, + "tx_dropped": 10704730, + "tx_errors": 0, + "tx_multicast": 3414231, + "tx_packets": 122098881, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Bubba's Desktop", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 5, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 1186, + "rx_bytes": 913565372, + "rx_dropped": 2079, + "rx_errors": 0, + "rx_multicast": 9915, + "rx_packets": 8040327, + "satisfaction": 90, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 99303, + "tx_bytes": 13691997722, + "tx_dropped": 126800, + "tx_errors": 0, + "tx_multicast": 619240, + "tx_packets": 11897488, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Lexi's Desktop", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 6, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 2", + "poe_current": "75.07", + "poe_enable": true, + "poe_good": true, + "poe_power": "3.95", + "poe_voltage": "52.63", + "rx_broadcast": 15135, + "rx_bytes": 1683544880532, + "rx_dropped": 105097, + "rx_errors": 0, + "rx_multicast": 140206, + "rx_packets": 1145100603, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 403490, + "tx_bytes": 25109031755, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2363833, + "tx_packets": 356126846, + "up": true, + "tx_bytes-r": 7672, + "rx_bytes-r": 504473, + "bytes-r": 512145, + "name": "Camera: Car", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 7, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "92.28", + "poe_enable": true, + "poe_good": true, + "poe_power": "4.85", + "poe_voltage": "52.56", + "rx_broadcast": 65316, + "rx_bytes": 2610602428826, + "rx_dropped": 157634, + "rx_errors": 0, + "rx_multicast": 164757, + "rx_packets": 1825440143, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 353314, + "tx_bytes": 30130225938, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2419669, + "tx_packets": 424070512, + "up": true, + "tx_bytes-r": 9976, + "rx_bytes-r": 786139, + "bytes-r": 796116, + "name": "Camera: Garage", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 8, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26847", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 1970, + "rx_bytes": 57788865632, + "rx_dropped": 49, + "rx_errors": 1, + "rx_multicast": 5397, + "rx_packets": 81214364, + "satisfaction": 75, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 243126, + "tx_bytes": 106910126818, + "tx_dropped": 343060, + "tx_errors": 0, + "tx_multicast": 1620633, + "tx_packets": 96321547, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Office Tertiary Desk PD14", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 9, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 4", + "poe_current": "185.42", + "poe_enable": true, + "poe_good": true, + "poe_power": "9.81", + "poe_voltage": "52.88", + "rx_broadcast": 63647, + "rx_bytes": 2607385367644, + "rx_dropped": 157620, + "rx_errors": 0, + "rx_multicast": 164740, + "rx_packets": 1811240949, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 354985, + "tx_bytes": 29502203517, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2419671, + "tx_packets": 414986830, + "up": true, + "tx_bytes-r": 9281, + "rx_bytes-r": 781308, + "bytes-r": 790589, + "name": "Camera: Pool", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 10, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26847", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 133159, + "rx_bytes": 2980426813696, + "rx_dropped": 86, + "rx_errors": 0, + "rx_multicast": 1194903, + "rx_packets": 2315914383, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2154128, + "tx_bytes": 1464180769133, + "tx_dropped": 250473, + "tx_errors": 0, + "tx_multicast": 13471964, + "tx_packets": 2230137488, + "up": true, + "tx_bytes-r": 1250938, + "rx_bytes-r": 2919503, + "bytes-r": 4170441, + "name": "Security: Home Port", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 11, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "12.93", + "poe_enable": true, + "poe_good": true, + "poe_power": "0.69", + "poe_voltage": "53.01", + "rx_broadcast": 10728, + "rx_bytes": 252244504, + "rx_dropped": 0, + "rx_errors": 2, + "rx_multicast": 0, + "rx_packets": 2540659, + "satisfaction": 85, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 407892, + "tx_bytes": 496402591, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2312237, + "tx_packets": 6542398, + "up": true, + "tx_bytes-r": 120, + "rx_bytes-r": 79, + "bytes-r": 199, + "name": "8ch Relay Board", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 12, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 19342, + "rx_bytes": 190236042211, + "rx_dropped": 174281, + "rx_errors": 0, + "rx_multicast": 649832, + "rx_packets": 2704715442, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 395664, + "tx_bytes": 18974306738758, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 1886642, + "tx_packets": 13093826470, + "up": true, + "tx_bytes-r": 5653872, + "rx_bytes-r": 67700, + "bytes-r": 5721573, + "name": "Security: Secure Port", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 13, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "88.13", + "poe_enable": true, + "poe_good": true, + "poe_power": "4.65", + "poe_voltage": "52.76", + "rx_broadcast": 383577, + "rx_bytes": 550646722109, + "rx_dropped": 116302, + "rx_errors": 0, + "rx_multicast": 2950663, + "rx_packets": 892359442, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2351476, + "tx_bytes": 1374251273399, + "tx_dropped": 614, + "tx_errors": 0, + "tx_multicast": 12430218, + "tx_packets": 1140545051, + "up": true, + "tx_bytes-r": 1098062, + "rx_bytes-r": 81853, + "bytes-r": 1179915, + "name": "Upstairs WAP", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 14, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 0, + "rx_bytes": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 0, + "satisfaction": 100, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 0, + "tx_bytes": 0, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 0, + "tx_packets": 0, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Port 14", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 15, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "71.65", + "poe_enable": true, + "poe_good": true, + "poe_power": "3.77", + "poe_voltage": "52.63", + "rx_broadcast": 196313, + "rx_bytes": 155746936125, + "rx_dropped": 19700, + "rx_errors": 0, + "rx_multicast": 2064123, + "rx_packets": 254212324, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2538787, + "tx_bytes": 229690921008, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 13311081, + "tx_packets": 260289095, + "up": true, + "tx_bytes-r": 20824, + "rx_bytes-r": 4227, + "bytes-r": 25051, + "name": "Downstairs WAP", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 16, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 11925, + "rx_bytes": 300038087723, + "rx_dropped": 6, + "rx_errors": 0, + "rx_multicast": 105090, + "rx_packets": 487779140, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2698267, + "tx_bytes": 1161896194876, + "tx_dropped": 2542, + "tx_errors": 0, + "tx_multicast": 15097190, + "tx_packets": 936474098, + "up": true, + "tx_bytes-r": 792886, + "rx_bytes-r": 1134268, + "bytes-r": 1927154, + "name": "David's Desk", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 17, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "52.24", + "poe_enable": true, + "poe_good": true, + "poe_power": "2.75", + "poe_voltage": "52.69", + "rx_broadcast": 57982, + "rx_bytes": 3736764408104, + "rx_dropped": 157644, + "rx_errors": 1, + "rx_multicast": 157809, + "rx_packets": 2553432414, + "satisfaction": 75, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 360666, + "tx_bytes": 37300635720, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2433676, + "tx_packets": 525589037, + "up": true, + "tx_bytes-r": 11826, + "rx_bytes-r": 1204374, + "bytes-r": 1216200, + "name": "Camera: Door", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 18, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 106288, + "rx_bytes": 2133344721406, + "rx_dropped": 25, + "rx_errors": 12, + "rx_multicast": 311666, + "rx_packets": 2159594753, + "satisfaction": 75, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2629049, + "tx_bytes": 1194122124612, + "tx_dropped": 7147, + "tx_errors": 0, + "tx_multicast": 15063781, + "tx_packets": 1677508931, + "up": true, + "tx_bytes-r": 37435, + "rx_bytes-r": 1009309, + "bytes-r": 1046744, + "name": "Port 18", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 19, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "84.22", + "poe_enable": true, + "poe_good": true, + "poe_power": "4.43", + "poe_voltage": "52.56", + "rx_broadcast": 59295, + "rx_bytes": 1565104688294, + "rx_dropped": 157648, + "rx_errors": 0, + "rx_multicast": 157775, + "rx_packets": 1148468555, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 359378, + "tx_bytes": 1645861732, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2433784, + "tx_packets": 21052239, + "up": true, + "tx_bytes-r": 8133, + "rx_bytes-r": 483735, + "bytes-r": 491868, + "name": "Camera: Porch", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 20, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 0, + "rx_bytes": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 0, + "satisfaction": 100, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 0, + "tx_bytes": 0, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 0, + "tx_packets": 0, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "Port 20", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 21, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "59a362f645663e6cc8260134", + "poe_mode": "auto", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Class 0", + "poe_current": "123.90", + "poe_enable": true, + "poe_good": true, + "poe_power": "6.56", + "poe_voltage": "52.95", + "rx_broadcast": 61014, + "rx_bytes": 3317256188719, + "rx_dropped": 157642, + "rx_errors": 0, + "rx_multicast": 157803, + "rx_packets": 2220162515, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 357637, + "tx_bytes": 30626674529, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 2433710, + "tx_packets": 428205094, + "up": true, + "tx_bytes-r": 9771, + "rx_bytes-r": 1006105, + "bytes-r": 1015877, + "name": "Camera: Road", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 22, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 14465, + "rx_bytes": 794839703, + "rx_dropped": 3, + "rx_errors": 0, + "rx_multicast": 1080600, + "rx_packets": 5377988, + "satisfaction": 90, + "speed": 100, + "stp_pathcost": 200000, + "stp_state": "forwarding", + "tx_broadcast": 2720299, + "tx_bytes": 9381221794, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 14294088, + "tx_packets": 37961870, + "up": true, + "tx_bytes-r": 4900, + "rx_bytes-r": 536, + "bytes-r": 5436, + "name": "Livingroom Stereo", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 23, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 532640, + "rx_bytes": 331010131889, + "rx_dropped": 35400, + "rx_errors": 0, + "rx_multicast": 576408, + "rx_packets": 261698587, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2202860, + "tx_bytes": 15811798849, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 14799244, + "tx_packets": 184788576, + "up": true, + "tx_bytes-r": 1611, + "rx_bytes-r": 3806, + "bytes-r": 5418, + "name": "Homerun Prime", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 24, + "media": "GE", + "port_poe": true, + "poe_caps": 7, + "speed_caps": 1048623, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "poe_mode": "off", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": true, + "is_uplink": true, + "jumbo": false, + "lldp_table": [ + { + "lldp_chassis_id": "22:22:00:22:22:00", + "lldp_port_id": "eth1", + "lldp_system_name": "gateway" + } + ], + "poe_class": "Unknown", + "poe_current": "0.00", + "poe_enable": false, + "poe_good": false, + "poe_power": "0.00", + "poe_voltage": "0.00", + "rx_broadcast": 186134, + "rx_bytes": 2149399426518, + "rx_dropped": 183, + "rx_errors": 0, + "rx_multicast": 4799002, + "rx_packets": 2730988091, + "satisfaction": 90, + "speed": 1000, + "stp_pathcost": 20000, + "stp_state": "forwarding", + "tx_broadcast": 2549473, + "tx_bytes": 2905955335082, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 11918890, + "tx_packets": 2655236464, + "up": true, + "tx_bytes-r": 2348698, + "rx_bytes-r": 393352, + "bytes-r": 2742050, + "name": "gateway", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 25, + "media": "SFP", + "port_poe": false, + "poe_caps": 0, + "speed_caps": 1048608, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "rx_broadcast": 0, + "rx_bytes": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 0, + "satisfaction": 100, + "sfp_found": false, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 0, + "tx_bytes": 0, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 0, + "tx_packets": 0, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "SFP 1", + "masked": false, + "aggregated_by": false + }, + { + "port_idx": 26, + "media": "SFP", + "port_poe": false, + "poe_caps": 0, + "speed_caps": 1048608, + "op_mode": "switch", + "portconf_id": "574e869d4566ffb914a26845", + "autoneg": true, + "dot1x_mode": "unknown", + "dot1x_status": "disabled", + "enable": true, + "flowctrl_rx": false, + "flowctrl_tx": false, + "full_duplex": false, + "is_uplink": false, + "jumbo": false, + "lldp_table": [], + "rx_broadcast": 0, + "rx_bytes": 0, + "rx_dropped": 0, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 0, + "satisfaction": 100, + "sfp_found": false, + "speed": 0, + "stp_pathcost": 0, + "stp_state": "disabled", + "tx_broadcast": 0, + "tx_bytes": 0, + "tx_dropped": 0, + "tx_errors": 0, + "tx_multicast": 0, + "tx_packets": 0, + "up": false, + "tx_bytes-r": 0, + "rx_bytes-r": 0, + "bytes-r": 0, + "name": "SFP 2", + "masked": false, + "aggregated_by": false + } + ], + "serial": "xxxyyyzzz", + "site_id": "574e86994566ffb914a2683c", + "stp_priority": "32768", + "stp_version": "rstp", + "type": "usw", + "version": "4.0.42.10433", + "required_version": "3.3.1", + "switch_caps": { + "feature_caps": 1022, + "max_mirror_sessions": 1, + "max_aggregate_sessions": 6 + }, + "hw_caps": 0, + "unsupported": false, + "unsupported_reason": 0, + "sys_error_caps": 0, + "device_id": "59a35cee45663e6cc82600f0", + "state": 1, + "last_seen": 1562309680, + "upgradable": false, + "adoptable_when_upgraded": false, + "rollupgrade": false, + "known_cfgversion": "669564dd04994088", + "uptime": 3188809, + "_uptime": 3188809, + "locating": false, + "connect_request_ip": "192.168.1.7", + "connect_request_port": "45941", + "sys_stats": { + "loadavg_1": "1.83", + "loadavg_15": "1.72", + "loadavg_5": "1.77", + "mem_buffer": 0, + "mem_total": 262397952, + "mem_used": 131473408 + }, + "system-stats": { + "cpu": "59.3", + "mem": "50.1", + "uptime": "3188809" + }, + "ssh_session_table": [], + "fan_level": 50, + "general_temperature": 43, + "overheating": false, + "total_max_power": 222, + "downlink_table": [ + { + "port_idx": 13, + "speed": 1000, + "full_duplex": true, + "mac": "22:22:00:22:22:00" + }, + { + "port_idx": 15, + "speed": 1000, + "full_duplex": true, + "mac": "22:22:00:22:22:00" + } + ], + "uplink": { + "full_duplex": true, + "ip": "192.168.1.7", + "mac": "22:22:00:22:22:00", + "name": "eth0", + "netmask": "255.255.252.0", + "num_port": 26, + "rx_bytes": 2149399426518, + "rx_dropped": 183, + "rx_errors": 0, + "rx_multicast": 0, + "rx_packets": 2730988091, + "speed": 1000, + "tx_bytes": 2905955335082, + "tx_dropped": 0, + "tx_errors": 0, + "tx_packets": 2655236464, + "up": true, + "port_idx": 24, + "media": "GE", + "max_speed": 1000, + "uplink_mac": "22:22:00:22:22:00", + "type": "wire", + "tx_bytes-r": 2348698, + "rx_bytes-r": 393352 + }, + "last_uplink": { + "uplink_mac": "22:22:00:22:22:00" + }, + "uplink_depth": 1, + "dhcp_server_table": [], + "stat": { + "site_id": "574e86994566ffb914a2683c", + "o": "sw", + "oid": "22:22:00:22:22:00", + "sw": "22:22:00:22:22:00", + "time": 1562207400000, + "datetime": "2019-07-04T02:30:00Z", + "rx_packets": 1521931792, + "rx_bytes": 1640634193054, + "rx_errors": 0, + "rx_dropped": 44998, + "rx_crypts": 0, + "rx_frags": 0, + "tx_packets": 1526591275, + "tx_bytes": 1641404646603, + "tx_errors": 0, + "tx_dropped": 230234, + "tx_retries": 0, + "rx_multicast": 477933, + "rx_broadcast": 134416, + "tx_multicast": 4372775, + "tx_broadcast": 1123817, + "bytes": 3282038839657, + "duration": 102277000, + "port_1-rx_packets": 37407, + "port_1-rx_bytes": 7345154, + "port_1-tx_packets": 142648, + "port_1-tx_bytes": 15425151, + "port_1-tx_multicast": 72854, + "port_1-tx_broadcast": 13075, + "port_3-rx_packets": 88198959, + "port_3-rx_bytes": 127291063971, + "port_3-tx_packets": 20230363, + "port_3-tx_bytes": 1436625813, + "port_3-rx_broadcast": 2037, + "port_3-tx_multicast": 78496, + "port_3-tx_broadcast": 11062, + "port_6-rx_packets": 37102295, + "port_6-rx_bytes": 54565186228, + "port_6-tx_packets": 11561353, + "port_6-tx_bytes": 814818610, + "port_6-rx_multicast": 4504, + "port_6-tx_multicast": 76688, + "port_6-tx_broadcast": 12634, + "port_7-rx_packets": 59149016, + "port_7-rx_bytes": 84615020479, + "port_7-tx_packets": 14033190, + "port_7-tx_bytes": 996438501, + "port_7-tx_multicast": 78496, + "port_7-tx_broadcast": 11058, + "port_9-rx_packets": 58736633, + "port_9-rx_bytes": 84549190443, + "port_9-tx_packets": 14151617, + "port_9-tx_bytes": 1004772261, + "port_9-tx_multicast": 78496, + "port_9-tx_broadcast": 11058, + "port_10-rx_packets": 267372091, + "port_10-rx_bytes": 281500610536, + "port_10-tx_packets": 563250041, + "port_10-tx_bytes": 567769155042, + "port_10-rx_multicast": 43302, + "port_10-tx_multicast": 424255, + "port_10-tx_broadcast": 119711, + "port_11-rx_packets": 81983, + "port_11-rx_bytes": 8144416, + "port_11-tx_packets": 209313, + "port_11-tx_bytes": 15405574, + "port_11-tx_multicast": 73730, + "port_11-tx_broadcast": 12758, + "port_12-rx_packets": 103277309, + "port_12-rx_bytes": 7258199289, + "port_12-tx_packets": 439910260, + "port_12-tx_bytes": 639760721999, + "port_12-tx_multicast": 60704, + "port_12-tx_broadcast": 12565, + "port_13-rx_packets": 246443105, + "port_13-rx_bytes": 198040237192, + "port_13-tx_packets": 78593344, + "port_13-tx_bytes": 57869606583, + "port_13-rx_multicast": 137299, + "port_13-rx_broadcast": 65040, + "port_13-tx_multicast": 355704, + "port_13-tx_broadcast": 72404, + "port_15-rx_packets": 91090086, + "port_15-rx_bytes": 129066815209, + "port_15-tx_packets": 21964312, + "port_15-tx_bytes": 8996022939, + "port_15-rx_broadcast": 6271, + "port_15-tx_multicast": 470685, + "port_15-tx_broadcast": 131171, + "port_16-rx_packets": 186203471, + "port_16-rx_bytes": 238718016683, + "port_16-tx_packets": 111909553, + "port_16-tx_bytes": 116205334442, + "port_16-tx_multicast": 484907, + "port_16-tx_broadcast": 137180, + "port_17-rx_packets": 88192775, + "port_17-rx_bytes": 129333707339, + "port_17-tx_packets": 18059043, + "port_17-tx_bytes": 1280177720, + "port_17-tx_multicast": 78951, + "port_17-tx_broadcast": 11288, + "port_18-rx_packets": 47274835, + "port_18-rx_bytes": 52416730761, + "port_18-tx_packets": 31288433, + "port_18-tx_bytes": 19604228682, + "port_18-rx_multicast": 10027, + "port_18-tx_multicast": 478207, + "port_18-tx_broadcast": 136852, + "port_19-rx_packets": 37113361, + "port_19-rx_bytes": 51866531433, + "port_19-tx_packets": 11858558, + "port_19-tx_bytes": 835844449, + "port_19-tx_multicast": 78952, + "port_19-tx_broadcast": 11216, + "port_21-rx_packets": 72039284, + "port_21-rx_bytes": 107639322810, + "port_21-tx_packets": 14367901, + "port_21-tx_bytes": 1026379138, + "port_21-rx_broadcast": 1926, + "port_21-tx_multicast": 78951, + "port_21-tx_broadcast": 11173, + "port_22-rx_packets": 176103, + "port_22-rx_bytes": 25390561, + "port_22-tx_packets": 1272735, + "port_22-tx_bytes": 294225324, + "port_22-rx_multicast": 35968, + "port_22-tx_multicast": 452266, + "port_22-tx_broadcast": 137038, + "port_23-rx_packets": 4246915, + "port_23-rx_bytes": 4976913808, + "port_23-rx_dropped": 1138, + "port_23-tx_packets": 2141027, + "port_23-tx_bytes": 251388436, + "port_23-rx_multicast": 18478, + "port_23-rx_broadcast": 17522, + "port_23-tx_multicast": 469756, + "port_23-tx_broadcast": 119922, + "port_24-rx_packets": 134576026, + "port_24-rx_bytes": 88659302916, + "port_24-tx_packets": 170136735, + "port_24-tx_bytes": 221668518676, + "port_24-rx_multicast": 153949, + "port_24-tx_multicast": 376773, + "port_24-tx_broadcast": 132998, + "port_1-rx_multicast": 881, + "port_3-rx_dropped": 5055, + "port_3-rx_multicast": 5283, + "port_6-rx_dropped": 3370, + "port_7-rx_dropped": 5052, + "port_7-rx_multicast": 5280, + "port_9-rx_dropped": 5052, + "port_9-rx_multicast": 5280, + "port_9-rx_broadcast": 2041, + "port_10-rx_broadcast": 4628, + "port_12-rx_dropped": 5055, + "port_12-rx_multicast": 19895, + "port_13-rx_dropped": 3759, + "port_17-rx_dropped": 5055, + "port_17-rx_multicast": 5055, + "port_17-rx_broadcast": 1811, + "port_19-rx_dropped": 5055, + "port_19-rx_multicast": 5055, + "port_19-rx_broadcast": 1883, + "port_21-rx_dropped": 5055, + "port_21-rx_multicast": 5055, + "port_7-rx_broadcast": 2041, + "port_18-rx_broadcast": 590, + "port_16-rx_multicast": 3329, + "port_15-rx_dropped": 800, + "port_15-rx_multicast": 17552, + "port_16-rx_broadcast": 262, + "port_11-rx_broadcast": 341, + "port_12-rx_broadcast": 534, + "port_6-rx_broadcast": 465, + "port_24-rx_broadcast": 4447, + "port_22-rx_broadcast": 404, + "port_10-tx_dropped": 195211, + "port_16-tx_dropped": 251, + "port_1-rx_broadcast": 20, + "port_4-rx_packets": 620138, + "port_4-rx_bytes": 96463826, + "port_4-rx_dropped": 552, + "port_4-tx_packets": 1510849, + "port_4-tx_bytes": 1559557263, + "port_4-tx_dropped": 34772, + "port_4-rx_multicast": 1741, + "port_4-rx_broadcast": 22153, + "port_4-tx_multicast": 103904, + "port_4-tx_broadcast": 18654 + }, + "tx_bytes": 2149399426518, + "rx_bytes": 2905955335082, + "bytes": 5055354761600, + "num_sta": 16, + "user-num_sta": 16, + "guest-num_sta": 0 +}, diff --git a/core/unifi/go.mod b/core/unifi/go.mod new file mode 100644 index 00000000..a1075ea2 --- /dev/null +++ b/core/unifi/go.mod @@ -0,0 +1,9 @@ +module github.com/unpoller/unifi + +go 1.16 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/stretchr/testify v1.4.0 + golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d +) diff --git a/core/unifi/go.sum b/core/unifi/go.sum new file mode 100644 index 00000000..125f26b4 --- /dev/null +++ b/core/unifi/go.sum @@ -0,0 +1,19 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs= +golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/core/unifi/ids.go b/core/unifi/ids.go new file mode 100644 index 00000000..2898506d --- /dev/null +++ b/core/unifi/ids.go @@ -0,0 +1,155 @@ +package unifi + +import ( + "encoding/json" + "fmt" + "sort" + "time" +) + +// IDS holds an Intrusion Prevention System Event. +type IDS struct { + Archived FlexBool `json:"archived"` + DestPort int `json:"dest_port,omitempty"` + SrcPort int `json:"src_port,omitempty"` + FlowID int64 `json:"flow_id"` + InnerAlertRev int64 `json:"inner_alert_rev"` + InnerAlertSeverity int64 `json:"inner_alert_severity"` + InnerAlertGID int64 `json:"inner_alert_gid"` + InnerAlertSignatureID int64 `json:"inner_alert_signature_id"` + Time int64 `json:"time"` + Timestamp int64 `json:"timestamp"` + Datetime time.Time `json:"datetime"` + AppProto string `json:"app_proto,omitempty"` + Catname string `json:"catname"` + DestIP string `json:"dest_ip"` + DstMAC string `json:"dst_mac"` + DstIPASN string `json:"dstipASN"` + DstIPCountry string `json:"dstipCountry"` + EventType string `json:"event_type"` + Host string `json:"host"` + ID string `json:"_id"` + InIface string `json:"in_iface"` + InnerAlertAction string `json:"inner_alert_action"` + InnerAlertCategory string `json:"inner_alert_category"` + InnerAlertSignature string `json:"inner_alert_signature"` + Key string `json:"key"` + Msg string `json:"msg"` + Proto string `json:"proto"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + SourceName string `json:"-"` + SrcIP string `json:"src_ip"` + SrcIPASN string `json:"srcipASN"` + SrcIPCountry string `json:"srcipCountry"` + SrcMAC string `json:"src_mac"` + Subsystem string `json:"subsystem"` + UniqueAlertID string `json:"unique_alertid"` + USGIP string `json:"usgip"` + USGIPASN string `json:"usgipASN"` + USGIPCountry string `json:"usgipCountry"` + DestIPGeo IPGeo `json:"dstipGeo"` + SourceIPGeo IPGeo `json:"srcipGeo"` + USGIPGeo IPGeo `json:"usgipGeo"` +} + +// GetIDS returns Intrusion Detection Systems events for a list of Sites. +// timeRange may have a length of 0, 1 or 2. The first time is Start, the second is End. +// Events between start and end are returned. End defaults to time.Now(). +func (u *Unifi) GetIDS(sites []*Site, timeRange ...time.Time) ([]*IDS, error) { + data := []*IDS{} + + for _, site := range sites { + response, err := u.GetIDSSite(site, timeRange...) + if err != nil { + return data, err + } + + data = append(data, response...) + } + + return data, nil +} + +// GetIDSSite retreives the Intrusion Detection System Data for a single Site. +// timeRange may have a length of 0, 1 or 2. The first time is Start, the second is End. +// Events between start and end are returned. End defaults to time.Now(). +func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) { + if site == nil || site.Name == "" { + return nil, ErrNoSiteProvided + } + + u.DebugLog("Polling Controller for IDS Events, site %s", site.SiteName) + + var ( + path = fmt.Sprintf(APIEventPathIDS, site.Name) + ids struct { + Data idsList `json:"data"` + } + ) + + if params, err := makeEventParams(timeRange...); err != nil { + return ids.Data, err + } else if err = u.GetData(path, &ids, params); err != nil { + return ids.Data, err + } + + for i := range ids.Data { + // Add special SourceName value. + ids.Data[i].SourceName = u.URL + // Add the special "Site Name" to each event. This becomes a Grafana filter somewhere. + ids.Data[i].SiteName = site.SiteName + } + + sort.Sort(ids.Data) + + return ids.Data, nil +} + +func makeEventParams(timeRange ...time.Time) (string, error) { + type eventReq struct { + Start int64 `json:"start,omitempty"` + End int64 `json:"end,omitempty"` + Limit int `json:"_limit,omitempty"` + Sort string `json:"_sort"` + } + + rp := eventReq{Limit: eventLimit, Sort: "-time"} + + switch len(timeRange) { + case 0: + rp.End = time.Now().Unix() * int64(time.Microsecond) + case 1: + rp.Start = timeRange[0].Unix() * int64(time.Microsecond) + rp.End = time.Now().Unix() * int64(time.Microsecond) + case 2: // nolint: gomnd + rp.Start = timeRange[0].Unix() * int64(time.Microsecond) + rp.End = timeRange[1].Unix() * int64(time.Microsecond) + default: + return "", ErrInvalidTimeRange + } + + params, err := json.Marshal(&rp) + if err != nil { + return "", fmt.Errorf("json marshal: %w", err) + } + + return string(params), nil +} + +type idsList []*IDS + +// Len satisfies sort.Interface. +func (e idsList) Len() int { + return len(e) +} + +// Swap satisfies sort.Interface. +func (e idsList) Swap(i, j int) { + e[i], e[j] = e[j], e[i] +} + +// Less satisfies sort.Interface. Sort our list by Datetime. +func (e idsList) Less(i, j int) bool { + return e[i].Datetime.Before(e[j].Datetime) +} diff --git a/core/unifi/networks.go b/core/unifi/networks.go new file mode 100644 index 00000000..f0e4a876 --- /dev/null +++ b/core/unifi/networks.go @@ -0,0 +1,62 @@ +package unifi + +import ( + "encoding/json" + "fmt" +) + +// GetNetworks returns a response full of network data from the UniFi Controller. +func (u *Unifi) GetNetworks(sites []*Site) ([]Network, error) { + networks := make([]Network, 0) + + for _, site := range sites { + var response struct { + Data []json.RawMessage `json:"data"` + } + + networkPath := fmt.Sprintf(APINetworkPath, site.Name) + if err := u.GetData(networkPath, &response); err != nil { + return nil, err + } + + for _, data := range response.Data { + network, err := u.parseNetwork(data, site.SiteName) + if err != nil { + return networks, err + } + + networks = append(networks, *network) + } + } + + return networks, nil +} + +// parseNetwork parses the raw JSON from the Unifi Controller into network structures. +func (u *Unifi) parseNetwork(data json.RawMessage, siteName string) (*Network, error) { + network := new(Network) + return network, u.unmarshalDevice(siteName, data, network) +} + +// Network is metadata about a network managed by a UniFi controller. +type Network struct { + DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` + DhcpdEnabled FlexBool `json:"dhcpd_enabled"` + DhcpdGatewayEnabled FlexBool `json:"dhcpd_gateway_enabled"` + DhcpdIP1 string `json:"dhcpd_ip_1"` + DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"` + DhcpRelayEnabled FlexBool `json:"dhcp_relay_enabled"` + DhcpdTimeOffsetEnabled FlexBool `json:"dhcpd_time_offset_enabled"` + DhcpGuardEnabled FlexBool `json:"dhcpguard_enabled"` + DomainName string `json:"domain_name"` + Enabled FlexBool `json:"enabled"` + ID string `json:"_id"` + IPSubnet string `json:"ip_subnet"` + IsNat FlexBool `json:"is_nat"` + Name string `json:"name"` + Networkgroup string `json:"networkgroup"` + Purpose string `json:"purpose"` + SiteID string `json:"site_id"` + Vlan FlexInt `json:"vlan"` + VlanEnabled FlexBool `json:"vlan_enabled"` +} diff --git a/core/unifi/site.go b/core/unifi/site.go new file mode 100644 index 00000000..4534a689 --- /dev/null +++ b/core/unifi/site.go @@ -0,0 +1,127 @@ +package unifi + +import ( + "fmt" + "strings" +) + +var ErrDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report") + +// GetSites returns a list of configured sites on the UniFi controller. +func (u *Unifi) GetSites() ([]*Site, error) { + var response struct { + Data []*Site `json:"data"` + } + + if err := u.GetData(APISiteList, &response); err != nil { + return nil, err + } + + sites := []string{} // used for debug log only + + for i, d := range response.Data { + // Add the unifi struct to the site. + response.Data[i].controller = u + // Add special SourceName value. + response.Data[i].SourceName = u.URL + // If the human name is missing (description), set it to the cryptic name. + response.Data[i].Desc = strings.TrimSpace(pick(d.Desc, d.Name)) + // Add the custom site name to each site. used as a Grafana filter somewhere. + response.Data[i].SiteName = d.Desc + " (" + d.Name + ")" + sites = append(sites, d.Name) // used for debug log only + } + + u.DebugLog("Found %d site(s): %s", len(sites), strings.Join(sites, ",")) + + return response.Data, nil +} + +// GetSiteDPI garners dpi data for sites. +func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) { + data := []*DPITable{} + + for _, site := range sites { + u.DebugLog("Polling Controller, retreiving Site DPI data, site %s", site.SiteName) + + var response struct { + Data []*DPITable `json:"data"` + } + + siteDPIpath := fmt.Sprintf(APISiteDPI, site.Name) + if err := u.GetData(siteDPIpath, &response, `{"type":"by_app"}`); err != nil { + return nil, err + } + + if l := len(response.Data); l > 1 { + return nil, ErrDPIDataBug + } else if l == 0 { + u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s", site.SiteName) + continue + } + + response.Data[0].SourceName = site.SourceName + response.Data[0].SiteName = site.SiteName + data = append(data, response.Data[0]) + } + + return data, nil +} + +// Site represents a site's data. +type Site struct { + controller *Unifi + SourceName string `json:"-"` + ID string `json:"_id"` + Name string `json:"name"` + Desc string `json:"desc"` + SiteName string `json:"-"` + AttrHiddenID string `json:"attr_hidden_id"` + AttrNoDelete FlexBool `json:"attr_no_delete"` + Health []struct { + Subsystem string `json:"subsystem"` + NumUser FlexInt `json:"num_user,omitempty"` + NumGuest FlexInt `json:"num_guest,omitempty"` + NumIot FlexInt `json:"num_iot,omitempty"` + TxBytesR FlexInt `json:"tx_bytes-r,omitempty"` + RxBytesR FlexInt `json:"rx_bytes-r,omitempty"` + Status string `json:"status"` + NumAp FlexInt `json:"num_ap,omitempty"` + NumAdopted FlexInt `json:"num_adopted,omitempty"` + NumDisabled FlexInt `json:"num_disabled,omitempty"` + NumDisconnected FlexInt `json:"num_disconnected,omitempty"` + NumPending FlexInt `json:"num_pending,omitempty"` + NumGw FlexInt `json:"num_gw,omitempty"` + WanIP string `json:"wan_ip,omitempty"` + Gateways []string `json:"gateways,omitempty"` + Netmask string `json:"netmask,omitempty"` + Nameservers []string `json:"nameservers,omitempty"` + NumSta FlexInt `json:"num_sta,omitempty"` + GwMac string `json:"gw_mac,omitempty"` + GwName string `json:"gw_name,omitempty"` + GwSystemStats struct { + CPU FlexInt `json:"cpu"` + Mem FlexInt `json:"mem"` + Uptime FlexInt `json:"uptime"` + } `json:"gw_system-stats,omitempty"` + GwVersion string `json:"gw_version,omitempty"` + Latency FlexInt `json:"latency,omitempty"` + Uptime FlexInt `json:"uptime,omitempty"` + Drops FlexInt `json:"drops,omitempty"` + XputUp FlexInt `json:"xput_up,omitempty"` + XputDown FlexInt `json:"xput_down,omitempty"` + SpeedtestStatus string `json:"speedtest_status,omitempty"` + SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"` + SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"` + LanIP string `json:"lan_ip,omitempty"` + NumSw FlexInt `json:"num_sw,omitempty"` + RemoteUserEnabled FlexBool `json:"remote_user_enabled,omitempty"` + RemoteUserNumActive FlexInt `json:"remote_user_num_active,omitempty"` + RemoteUserNumInactive FlexInt `json:"remote_user_num_inactive,omitempty"` + RemoteUserRxBytes FlexInt `json:"remote_user_rx_bytes,omitempty"` + RemoteUserTxBytes FlexInt `json:"remote_user_tx_bytes,omitempty"` + RemoteUserRxPackets FlexInt `json:"remote_user_rx_packets,omitempty"` + RemoteUserTxPackets FlexInt `json:"remote_user_tx_packets,omitempty"` + SiteToSiteEnabled FlexBool `json:"site_to_site_enabled,omitempty"` + } `json:"health"` + NumNewAlarms FlexInt `json:"num_new_alarms"` +} diff --git a/core/unifi/types.go b/core/unifi/types.go new file mode 100644 index 00000000..5ef5a569 --- /dev/null +++ b/core/unifi/types.go @@ -0,0 +1,195 @@ +package unifi + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +var ErrCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt") + +// This is a list of unifi API paths. +// The %s in each string must be replaced with a Site.Name. +const ( + // APIRogueAP shows your neighbors' wifis. + APIRogueAP string = "/api/s/%s/stat/rogueap" + // APIStatusPath shows Controller version. + APIStatusPath string = "/status" + // APIEventPath contains UniFi Event data. + APIEventPath string = "/api/s/%s/stat/event" + // APISiteList is the path to the api site list. + APISiteList string = "/api/stat/sites" + // APISiteDPI is site DPI data. + APISiteDPI string = "/api/s/%s/stat/sitedpi" + // APISiteDPI is site DPI data. + APIClientDPI string = "/api/s/%s/stat/stadpi" + // APIClientPath is Unifi Clients API Path. + APIClientPath string = "/api/s/%s/stat/sta" + // APIAllUserPath is Unifi Insight all previous Clients API Path. + APIAllUserPath string = "/api/s/%s/stat/alluser" + // APINetworkPath is where we get data about Unifi networks. + APINetworkPath string = "/api/s/%s/rest/networkconf" + // APIDevicePath is where we get data about Unifi devices. + APIDevicePath string = "/api/s/%s/stat/device" + // APILoginPath is Unifi Controller Login API Path. + APILoginPath string = "/api/login" + // APILoginPathNew is how we log into UDM 5.12.55+. + APILoginPathNew string = "/api/auth/login" + // APILogoutPath is how we logout from UDM. + APILogoutPath string = "/api/logout" + // APIEventPathIDS returns Intrusion Detection/Prevention Systems Events. + APIEventPathIDS string = "/api/s/%s/stat/ips/event" + // APIEventPathAlarms contains the site alarms. + APIEventPathAlarms string = "/api/s/%s/list/alarm" + // APIPrefixNew is the prefix added to the new API paths; except login. duh. + APIPrefixNew string = "/proxy/network" + // APIAnomaliesPath returns site anomalies. + APIAnomaliesPath string = "/api/s/%s/stat/anomalies" + APICommandPath string = "/api/s/%s/cmd" + APIDevMgrPath string = APICommandPath + "/devmgr" +) + +// path returns the correct api path based on the new variable. +// new is based on the unifi-controller output. is it new or old output? +func (u *Unifi) path(path string) string { + if u.new { + if path == APILoginPath { + return APILoginPathNew + } + + if !strings.HasPrefix(path, APIPrefixNew) && path != APILoginPathNew { + return APIPrefixNew + path + } + } + + return path +} + +// Logger is a base type to deal with changing log outputs. Create a logger +// that matches this interface to capture debug and error logs. +type Logger func(msg string, fmt ...interface{}) + +// discardLogs is the default debug logger. +func discardLogs(msg string, v ...interface{}) { + // do nothing. +} + +// Devices contains a list of all the unifi devices from a controller. +// Contains Access points, security gateways and switches. +type Devices struct { + UAPs []*UAP + USGs []*USG + USWs []*USW + UDMs []*UDM + UXGs []*UXG +} + +// Config is the data passed into our library. This configures things and allows +// us to connect to a controller and write log messages. Optional SSLCert is used +// for ssl cert pinning; provide the content of a PEM to validate the server's cert. +type Config struct { + User string + Pass string + URL string + SSLCert [][]byte + ErrorLog Logger + DebugLog Logger + Timeout time.Duration // how long to wait for replies, default: forever. + VerifySSL bool +} + +// Unifi is what you get in return for providing a password! Unifi represents +// a controller that you can make authenticated requests to. Use this to make +// additional requests for devices, clients or other custom data. Do not set +// the loggers to nil. Set them to DiscardLogs if you want no logs. +type Unifi struct { + *http.Client + *Config + *server + csrf string + fingerprints fingerprints + new bool +} + +type fingerprints []string + +// Contains returns true if the fingerprint is in the list. +func (f fingerprints) Contains(s string) bool { + for i := range f { + if s == f[i] { + return true + } + } + + return false +} + +// server is the /status endpoint from the Unifi controller. +type server struct { + Up FlexBool `json:"up"` + ServerVersion string `json:"server_version"` + UUID string `json:"uuid"` +} + +// FlexInt provides a container and unmarshalling for fields that may be +// numbers or strings in the Unifi API. +type FlexInt struct { + Val float64 + Txt string +} + +// UnmarshalJSON converts a string or number to an integer. +// Generally, do call this directly, it's used in the json interface. +func (f *FlexInt) UnmarshalJSON(b []byte) error { + var unk interface{} + + if err := json.Unmarshal(b, &unk); err != nil { + return fmt.Errorf("json unmarshal: %w", err) + } + + switch i := unk.(type) { + case float64: + f.Val = i + f.Txt = strconv.FormatFloat(i, 'f', -1, 64) + case string: + f.Txt = i + f.Val, _ = strconv.ParseFloat(i, 64) + case nil: + f.Txt = "0" + f.Val = 0 + default: + return fmt.Errorf("%v: %w", b, ErrCannotUnmarshalFlexInt) + } + + return nil +} + +func (f *FlexInt) String() string { + return f.Txt +} + +// FlexBool provides a container and unmarshalling for fields that may be +// boolean or strings in the Unifi API. +type FlexBool struct { + Val bool + Txt string +} + +// UnmarshalJSON method converts armed/disarmed, yes/no, active/inactive or 0/1 to true/false. +// Really it converts ready, ok, up, t, armed, yes, active, enabled, 1, true to true. Anything else is false. +func (f *FlexBool) UnmarshalJSON(b []byte) error { + f.Txt = strings.Trim(string(b), `"`) + f.Val = f.Txt == "1" || strings.EqualFold(f.Txt, "true") || strings.EqualFold(f.Txt, "yes") || + strings.EqualFold(f.Txt, "t") || strings.EqualFold(f.Txt, "armed") || strings.EqualFold(f.Txt, "active") || + strings.EqualFold(f.Txt, "enabled") || strings.EqualFold(f.Txt, "ready") || strings.EqualFold(f.Txt, "up") || + strings.EqualFold(f.Txt, "ok") + + return nil +} + +func (f *FlexBool) String() string { + return f.Txt +} diff --git a/core/unifi/types_test.go b/core/unifi/types_test.go new file mode 100644 index 00000000..90e66969 --- /dev/null +++ b/core/unifi/types_test.go @@ -0,0 +1,42 @@ +package unifi_test + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/unpoller/unifi" +) + +func TestFlexInt(t *testing.T) { + t.Parallel() + a := assert.New(t) + five, seven := 5, 7 + + var r struct { + Five unifi.FlexInt `json:"five"` + Seven unifi.FlexInt `json:"seven"` + Auto unifi.FlexInt `json:"auto"` + Channel unifi.FlexInt `json:"channel"` + Nil unifi.FlexInt `json:"nil"` + } + + // test unmarshalling the custom type three times with different values. + a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto", "nil": null}`), &r)) + // test number in string. + a.EqualValues(five, r.Five.Val) + a.EqualValues("5", r.Five.Txt) + // test number. + a.EqualValues(seven, r.Seven.Val) + a.EqualValues("7", r.Seven.Txt) + // test string. + a.EqualValues(0, r.Auto.Val) + a.EqualValues("auto", r.Auto.Txt) + // test (error) struct. + a.NotNil(json.Unmarshal([]byte(`{"channel": {}}`), &r), + "a non-string and non-number must produce an error.") + a.EqualValues(0, r.Channel.Val) + // test null. + a.EqualValues(0, r.Nil.Val) + a.EqualValues("0", r.Nil.Txt) +} diff --git a/core/unifi/uap.go b/core/unifi/uap.go new file mode 100644 index 00000000..9418c1cb --- /dev/null +++ b/core/unifi/uap.go @@ -0,0 +1,644 @@ +package unifi + +import ( + "encoding/json" + "fmt" + "time" +) + +// UAP represents all the data from the Ubiquiti Controller for a Unifi Access Point. +// This was auto generated then edited by hand to get all the data types right. +type UAP struct { + site *Site + SourceName string `json:"-"` + ID string `json:"_id"` + Adopted FlexBool `json:"adopted"` + AntennaTable []struct { + Default FlexBool `json:"default"` + ID FlexInt `json:"id"` + Name string `json:"name"` + Wifi0Gain FlexInt `json:"wifi0_gain"` + Wifi1Gain FlexInt `json:"wifi1_gain"` + } `json:"antenna_table"` + BandsteeringMode string `json:"bandsteering_mode,omitempty"` + BoardRev int `json:"board_rev"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork struct { + Type string `json:"type"` + IP string `json:"ip"` + } `json:"config_network"` + CountrycodeTable []int `json:"countrycode_table"` + EthernetTable []struct { + Mac string `json:"mac"` + NumPort FlexInt `json:"num_port"` + Name string `json:"name"` + } `json:"ethernet_table"` + FwCaps int `json:"fw_caps"` + HasEth1 FlexBool `json:"has_eth1"` + HasSpeaker FlexBool `json:"has_speaker"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + LedOverride string `json:"led_override"` + Mac string `json:"mac"` + MeshStaVapEnabled FlexBool `json:"mesh_sta_vap_enabled"` + Model string `json:"model"` + Name string `json:"name"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + PortTable []Port `json:"port_table"` + RadioTable RadioTable `json:"radio_table"` + ScanRadioTable []interface{} `json:"scan_radio_table"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + Type string `json:"type"` + Version string `json:"version"` + VwireTable []interface{} `json:"vwire_table"` + WifiCaps int `json:"wifi_caps"` + WlangroupIDNa string `json:"wlangroup_id_na"` + WlangroupIDNg string `json:"wlangroup_id_ng"` + RequiredVersion string `json:"required_version"` + HwCaps int `json:"hw_caps"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + SysErrorCaps int `json:"sys_error_caps"` + HasFan FlexBool `json:"has_fan"` + HasTemperature FlexBool `json:"has_temperature"` + DeviceID string `json:"device_id"` + State FlexInt `json:"state"` + LastSeen FlexInt `json:"last_seen"` + Upgradable FlexBool `json:"upgradable"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` + Rollupgrade FlexBool `json:"rollupgrade"` + KnownCfgversion string `json:"known_cfgversion"` + Uptime FlexInt `json:"uptime"` + UUptime FlexInt `json:"_uptime"` + Locating FlexBool `json:"locating"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + SysStats SysStats `json:"sys_stats"` + SystemStats SystemStats `json:"system-stats"` + SSHSessionTable []interface{} `json:"ssh_session_table"` + Scanning FlexBool `json:"scanning"` + SpectrumScanning FlexBool `json:"spectrum_scanning"` + GuestToken string `json:"guest_token"` + Meshv3PeerMac string `json:"meshv3_peer_mac"` + Satisfaction FlexInt `json:"satisfaction"` + Isolated FlexBool `json:"isolated"` + RadioTableStats RadioTableStats `json:"radio_table_stats"` + Uplink struct { + FullDuplex FlexBool `json:"full_duplex"` + IP string `json:"ip"` + Mac string `json:"mac"` + MaxVlan int `json:"max_vlan"` + Name string `json:"name"` + Netmask string `json:"netmask"` + NumPort int `json:"num_port"` + RxBytes FlexInt `json:"rx_bytes"` + RxDropped FlexInt `json:"rx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxMulticast FlexInt `json:"rx_multicast"` + RxPackets FlexInt `json:"rx_packets"` + Speed FlexInt `json:"speed"` + TxBytes FlexInt `json:"tx_bytes"` + TxDropped FlexInt `json:"tx_dropped"` + TxErrors FlexInt `json:"tx_errors"` + TxPackets FlexInt `json:"tx_packets"` + Up FlexBool `json:"up"` + MaxSpeed FlexInt `json:"max_speed"` + Type string `json:"type"` + TxBytesR FlexInt `json:"tx_bytes-r"` + RxBytesR FlexInt `json:"rx_bytes-r"` + UplinkMac string `json:"uplink_mac"` + UplinkRemotePort int `json:"uplink_remote_port"` + } `json:"uplink"` + VapTable VapTable `json:"vap_table"` + DownlinkTable []struct { + PortIdx int `json:"port_idx"` + Speed int `json:"speed"` + FullDuplex bool `json:"full_duplex"` + Mac string `json:"mac"` + } `json:"downlink_table,omitempty"` + VwireVapTable []interface{} `json:"vwire_vap_table"` + BytesD FlexInt `json:"bytes-d"` + TxBytesD FlexInt `json:"tx_bytes-d"` + RxBytesD FlexInt `json:"rx_bytes-d"` + BytesR FlexInt `json:"bytes-r"` + LastUplink struct { + UplinkMac string `json:"uplink_mac"` + UplinkRemotePort int `json:"uplink_remote_port"` + } `json:"last_uplink"` + Stat UAPStat `json:"stat"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + VwireEnabled FlexBool `json:"vwireEnabled"` + UplinkTable []interface{} `json:"uplink_table"` + NumSta FlexInt `json:"num_sta"` + UserNumSta FlexInt `json:"user-num_sta"` + GuestNumSta FlexInt `json:"guest-num_sta"` + TwoPhaseAdopt FlexBool `json:"two_phase_adopt,omitempty"` +} + +// UAPStat holds the "stat" data for an access point. +// This is split out because of a JSON data format change from 5.10 to 5.11. +type UAPStat struct { + *Ap +} + +// Ap is a subtype of UAPStat to make unmarshalling of different controller versions possible. +type Ap struct { + SiteID string `json:"site_id"` + O string `json:"o"` + Oid string `json:"oid"` + Ap string `json:"ap"` + Time FlexInt `json:"time"` + Datetime time.Time `json:"datetime"` + Bytes FlexInt `json:"bytes"` + Duration FlexInt `json:"duration"` + WifiTxDropped FlexInt `json:"wifi_tx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxDropped FlexInt `json:"rx_dropped"` + RxFrags FlexInt `json:"rx_frags"` + RxCrypts FlexInt `json:"rx_crypts"` + TxPackets FlexInt `json:"tx_packets"` + TxBytes FlexInt `json:"tx_bytes"` + TxErrors FlexInt `json:"tx_errors"` + TxDropped FlexInt `json:"tx_dropped"` + TxRetries FlexInt `json:"tx_retries"` + RxPackets FlexInt `json:"rx_packets"` + RxBytes FlexInt `json:"rx_bytes"` + UserRxDropped FlexInt `json:"user-rx_dropped"` + GuestRxDropped FlexInt `json:"guest-rx_dropped"` + UserRxErrors FlexInt `json:"user-rx_errors"` + GuestRxErrors FlexInt `json:"guest-rx_errors"` + UserRxPackets FlexInt `json:"user-rx_packets"` + GuestRxPackets FlexInt `json:"guest-rx_packets"` + UserRxBytes FlexInt `json:"user-rx_bytes"` + GuestRxBytes FlexInt `json:"guest-rx_bytes"` + UserRxCrypts FlexInt `json:"user-rx_crypts"` + GuestRxCrypts FlexInt `json:"guest-rx_crypts"` + UserRxFrags FlexInt `json:"user-rx_frags"` + GuestRxFrags FlexInt `json:"guest-rx_frags"` + UserTxPackets FlexInt `json:"user-tx_packets"` + GuestTxPackets FlexInt `json:"guest-tx_packets"` + UserTxBytes FlexInt `json:"user-tx_bytes"` + GuestTxBytes FlexInt `json:"guest-tx_bytes"` + UserTxErrors FlexInt `json:"user-tx_errors"` + GuestTxErrors FlexInt `json:"guest-tx_errors"` + UserTxDropped FlexInt `json:"user-tx_dropped"` + GuestTxDropped FlexInt `json:"guest-tx_dropped"` + UserTxRetries FlexInt `json:"user-tx_retries"` + GuestTxRetries FlexInt `json:"guest-tx_retries"` + MacFilterRejections FlexInt `json:"mac_filter_rejections"` + UserMacFilterRejections FlexInt `json:"user-mac_filter_rejections"` + GuestMacFilterRejections FlexInt `json:"guest-mac_filter_rejections"` + WifiTxAttempts FlexInt `json:"wifi_tx_attempts"` + UserWifiTxDropped FlexInt `json:"user-wifi_tx_dropped"` + GuestWifiTxDropped FlexInt `json:"guest-wifi_tx_dropped"` + UserWifiTxAttempts FlexInt `json:"user-wifi_tx_attempts"` + GuestWifiTxAttempts FlexInt `json:"guest-wifi_tx_attempts"` + + // UAP-AC-PRO names, others may differ. + /* These are all in VAP TABLE */ + /* + GuestWifi0RxPackets FlexInt `json:"guest-wifi0-rx_packets"` + GuestWifi1RxPackets FlexInt `json:"guest-wifi1-rx_packets"` + UserWifi1RxPackets FlexInt `json:"user-wifi1-rx_packets"` + UserWifi0RxPackets FlexInt `json:"user-wifi0-rx_packets"` + Wifi0RxPackets FlexInt `json:"wifi0-rx_packets"` + Wifi1RxPackets FlexInt `json:"wifi1-rx_packets"` + GuestWifi0RxBytes FlexInt `json:"guest-wifi0-rx_bytes"` + GuestWifi1RxBytes FlexInt `json:"guest-wifi1-rx_bytes"` + UserWifi1RxBytes FlexInt `json:"user-wifi1-rx_bytes"` + UserWifi0RxBytes FlexInt `json:"user-wifi0-rx_bytes"` + Wifi0RxBytes FlexInt `json:"wifi0-rx_bytes"` + Wifi1RxBytes FlexInt `json:"wifi1-rx_bytes"` + GuestWifi0RxErrors FlexInt `json:"guest-wifi0-rx_errors"` + GuestWifi1RxErrors FlexInt `json:"guest-wifi1-rx_errors"` + UserWifi1RxErrors FlexInt `json:"user-wifi1-rx_errors"` + UserWifi0RxErrors FlexInt `json:"user-wifi0-rx_errors"` + Wifi0RxErrors FlexInt `json:"wifi0-rx_errors"` + Wifi1RxErrors FlexInt `json:"wifi1-rx_errors"` + GuestWifi0RxDropped FlexInt `json:"guest-wifi0-rx_dropped"` + GuestWifi1RxDropped FlexInt `json:"guest-wifi1-rx_dropped"` + UserWifi1RxDropped FlexInt `json:"user-wifi1-rx_dropped"` + UserWifi0RxDropped FlexInt `json:"user-wifi0-rx_dropped"` + Wifi0RxDropped FlexInt `json:"wifi0-rx_dropped"` + Wifi1RxDropped FlexInt `json:"wifi1-rx_dropped"` + GuestWifi0RxCrypts FlexInt `json:"guest-wifi0-rx_crypts"` + GuestWifi1RxCrypts FlexInt `json:"guest-wifi1-rx_crypts"` + UserWifi1RxCrypts FlexInt `json:"user-wifi1-rx_crypts"` + UserWifi0RxCrypts FlexInt `json:"user-wifi0-rx_crypts"` + Wifi0RxCrypts FlexInt `json:"wifi0-rx_crypts"` + Wifi1RxCrypts FlexInt `json:"wifi1-rx_crypts"` + GuestWifi0RxFrags FlexInt `json:"guest-wifi0-rx_frags"` + GuestWifi1RxFrags FlexInt `json:"guest-wifi1-rx_frags"` + UserWifi1RxFrags FlexInt `json:"user-wifi1-rx_frags"` + UserWifi0RxFrags FlexInt `json:"user-wifi0-rx_frags"` + Wifi0RxFrags FlexInt `json:"wifi0-rx_frags"` + Wifi1RxFrags FlexInt `json:"wifi1-rx_frags"` + GuestWifi0TxPackets FlexInt `json:"guest-wifi0-tx_packets"` + GuestWifi1TxPackets FlexInt `json:"guest-wifi1-tx_packets"` + UserWifi1TxPackets FlexInt `json:"user-wifi1-tx_packets"` + UserWifi0TxPackets FlexInt `json:"user-wifi0-tx_packets"` + Wifi0TxPackets FlexInt `json:"wifi0-tx_packets"` + Wifi1TxPackets FlexInt `json:"wifi1-tx_packets"` + GuestWifi0TxBytes FlexInt `json:"guest-wifi0-tx_bytes"` + GuestWifi1TxBytes FlexInt `json:"guest-wifi1-tx_bytes"` + UserWifi1TxBytes FlexInt `json:"user-wifi1-tx_bytes"` + UserWifi0TxBytes FlexInt `json:"user-wifi0-tx_bytes"` + Wifi0TxBytes FlexInt `json:"wifi0-tx_bytes"` + Wifi1TxBytes FlexInt `json:"wifi1-tx_bytes"` + GuestWifi0TxErrors FlexInt `json:"guest-wifi0-tx_errors"` + GuestWifi1TxErrors FlexInt `json:"guest-wifi1-tx_errors"` + UserWifi1TxErrors FlexInt `json:"user-wifi1-tx_errors"` + UserWifi0TxErrors FlexInt `json:"user-wifi0-tx_errors"` + Wifi0TxErrors FlexInt `json:"wifi0-tx_errors"` + Wifi1TxErrors FlexInt `json:"wifi1-tx_errors"` + GuestWifi0TxDropped FlexInt `json:"guest-wifi0-tx_dropped"` + GuestWifi1TxDropped FlexInt `json:"guest-wifi1-tx_dropped"` + UserWifi1TxDropped FlexInt `json:"user-wifi1-tx_dropped"` + UserWifi0TxDropped FlexInt `json:"user-wifi0-tx_dropped"` + Wifi0TxDropped FlexInt `json:"wifi0-tx_dropped"` + Wifi1TxDropped FlexInt `json:"wifi1-tx_dropped"` + GuestWifi0TxRetries FlexInt `json:"guest-wifi0-tx_retries"` + GuestWifi1TxRetries FlexInt `json:"guest-wifi1-tx_retries"` + UserWifi1TxRetries FlexInt `json:"user-wifi1-tx_retries"` + UserWifi0TxRetries FlexInt `json:"user-wifi0-tx_retries"` + Wifi0TxRetries FlexInt `json:"wifi0-tx_retries"` + Wifi1TxRetries FlexInt `json:"wifi1-tx_retries"` + GuestWifi0MacFilterRejections FlexInt `json:"guest-wifi0-mac_filter_rejections"` + GuestWifi1MacFilterRejections FlexInt `json:"guest-wifi1-mac_filter_rejections"` + UserWifi1MacFilterRejections FlexInt `json:"user-wifi1-mac_filter_rejections"` + UserWifi0MacFilterRejections FlexInt `json:"user-wifi0-mac_filter_rejections"` + Wifi0MacFilterRejections FlexInt `json:"wifi0-mac_filter_rejections"` + Wifi1MacFilterRejections FlexInt `json:"wifi1-mac_filter_rejections"` + GuestWifi0WifiTxAttempts FlexInt `json:"guest-wifi0-wifi_tx_attempts"` + GuestWifi1WifiTxAttempts FlexInt `json:"guest-wifi1-wifi_tx_attempts"` + UserWifi1WifiTxAttempts FlexInt `json:"user-wifi1-wifi_tx_attempts"` + UserWifi0WifiTxAttempts FlexInt `json:"user-wifi0-wifi_tx_attempts"` + Wifi0WifiTxAttempts FlexInt `json:"wifi0-wifi_tx_attempts"` + Wifi1WifiTxAttempts FlexInt `json:"wifi1-wifi_tx_attempts"` + GuestWifi0WifiTxDropped FlexInt `json:"guest-wifi0-wifi_tx_dropped"` + GuestWifi1WifiTxDropped FlexInt `json:"guest-wifi1-wifi_tx_dropped"` + UserWifi1WifiTxDropped FlexInt `json:"user-wifi1-wifi_tx_dropped"` + UserWifi0WifiTxDropped FlexInt `json:"user-wifi0-wifi_tx_dropped"` + Wifi0WifiTxDropped FlexInt `json:"wifi0-wifi_tx_dropped"` + Wifi1WifiTxDropped FlexInt `json:"wifi1-wifi_tx_dropped"` + // UDM Names + GuestRa0RxPackets FlexInt `json:"guest-ra0-rx_packets"` + UserRa0RxPackets FlexInt `json:"user-ra0-rx_packets"` + Ra0RxPackets FlexInt `json:"ra0-rx_packets"` + GuestRa0RxBytes FlexInt `json:"guest-ra0-rx_bytes"` + UserRa0RxBytes FlexInt `json:"user-ra0-rx_bytes"` + Ra0RxBytes FlexInt `json:"ra0-rx_bytes"` + GuestRa0RxErrors FlexInt `json:"guest-ra0-rx_errors"` + UserRa0RxErrors FlexInt `json:"user-ra0-rx_errors"` + Ra0RxErrors FlexInt `json:"ra0-rx_errors"` + GuestRa0RxDropped FlexInt `json:"guest-ra0-rx_dropped"` + UserRa0RxDropped FlexInt `json:"user-ra0-rx_dropped"` + Ra0RxDropped FlexInt `json:"ra0-rx_dropped"` + GuestRa0RxCrypts FlexInt `json:"guest-ra0-rx_crypts"` + UserRa0RxCrypts FlexInt `json:"user-ra0-rx_crypts"` + Ra0RxCrypts FlexInt `json:"ra0-rx_crypts"` + GuestRa0RxFrags FlexInt `json:"guest-ra0-rx_frags"` + UserRa0RxFrags FlexInt `json:"user-ra0-rx_frags"` + Ra0RxFrags FlexInt `json:"ra0-rx_frags"` + GuestRa0TxPackets FlexInt `json:"guest-ra0-tx_packets"` + UserRa0TxPackets FlexInt `json:"user-ra0-tx_packets"` + Ra0TxPackets FlexInt `json:"ra0-tx_packets"` + GuestRa0TxBytes FlexInt `json:"guest-ra0-tx_bytes"` + UserRa0TxBytes FlexInt `json:"user-ra0-tx_bytes"` + Ra0TxBytes FlexInt `json:"ra0-tx_bytes"` + GuestRa0TxErrors FlexInt `json:"guest-ra0-tx_errors"` + UserRa0TxErrors FlexInt `json:"user-ra0-tx_errors"` + Ra0TxErrors FlexInt `json:"ra0-tx_errors"` + GuestRa0TxDropped FlexInt `json:"guest-ra0-tx_dropped"` + UserRa0TxDropped FlexInt `json:"user-ra0-tx_dropped"` + Ra0TxDropped FlexInt `json:"ra0-tx_dropped"` + GuestRa0TxRetries FlexInt `json:"guest-ra0-tx_retries"` + UserRa0TxRetries FlexInt `json:"user-ra0-tx_retries"` + Ra0TxRetries FlexInt `json:"ra0-tx_retries"` + GuestRa0MacFilterRejections FlexInt `json:"guest-ra0-mac_filter_rejections"` + UserRa0MacFilterRejections FlexInt `json:"user-ra0-mac_filter_rejections"` + Ra0MacFilterRejections FlexInt `json:"ra0-mac_filter_rejections"` + GuestRa0WifiTxAttempts FlexInt `json:"guest-ra0-wifi_tx_attempts"` + UserRa0WifiTxAttempts FlexInt `json:"user-ra0-wifi_tx_attempts"` + Ra0WifiTxAttempts FlexInt `json:"ra0-wifi_tx_attempts"` + GuestRa0WifiTxDropped FlexInt `json:"guest-ra0-wifi_tx_dropped"` + UserRa0WifiTxDropped FlexInt `json:"user-ra0-wifi_tx_dropped"` + Ra0WifiTxDropped FlexInt `json:"ra0-wifi_tx_dropped"` + GuestRai0RxPackets FlexInt `json:"guest-rai0-rx_packets"` + UserRai0RxPackets FlexInt `json:"user-rai0-rx_packets"` + Rai0RxPackets FlexInt `json:"rai0-rx_packets"` + GuestRai0RxBytes FlexInt `json:"guest-rai0-rx_bytes"` + UserRai0RxBytes FlexInt `json:"user-rai0-rx_bytes"` + Rai0RxBytes FlexInt `json:"rai0-rx_bytes"` + GuestRai0RxErrors FlexInt `json:"guest-rai0-rx_errors"` + UserRai0RxErrors FlexInt `json:"user-rai0-rx_errors"` + Rai0RxErrors FlexInt `json:"rai0-rx_errors"` + GuestRai0RxDropped FlexInt `json:"guest-rai0-rx_dropped"` + UserRai0RxDropped FlexInt `json:"user-rai0-rx_dropped"` + Rai0RxDropped FlexInt `json:"rai0-rx_dropped"` + GuestRai0RxCrypts FlexInt `json:"guest-rai0-rx_crypts"` + UserRai0RxCrypts FlexInt `json:"user-rai0-rx_crypts"` + Rai0RxCrypts FlexInt `json:"rai0-rx_crypts"` + GuestRai0RxFrags FlexInt `json:"guest-rai0-rx_frags"` + UserRai0RxFrags FlexInt `json:"user-rai0-rx_frags"` + Rai0RxFrags FlexInt `json:"rai0-rx_frags"` + GuestRai0TxPackets FlexInt `json:"guest-rai0-tx_packets"` + UserRai0TxPackets FlexInt `json:"user-rai0-tx_packets"` + Rai0TxPackets FlexInt `json:"rai0-tx_packets"` + GuestRai0TxBytes FlexInt `json:"guest-rai0-tx_bytes"` + UserRai0TxBytes FlexInt `json:"user-rai0-tx_bytes"` + Rai0TxBytes FlexInt `json:"rai0-tx_bytes"` + GuestRai0TxErrors FlexInt `json:"guest-rai0-tx_errors"` + UserRai0TxErrors FlexInt `json:"user-rai0-tx_errors"` + Rai0TxErrors FlexInt `json:"rai0-tx_errors"` + GuestRai0TxDropped FlexInt `json:"guest-rai0-tx_dropped"` + UserRai0TxDropped FlexInt `json:"user-rai0-tx_dropped"` + Rai0TxDropped FlexInt `json:"rai0-tx_dropped"` + GuestRai0TxRetries FlexInt `json:"guest-rai0-tx_retries"` + UserRai0TxRetries FlexInt `json:"user-rai0-tx_retries"` + Rai0TxRetries FlexInt `json:"rai0-tx_retries"` + GuestRai0MacFilterRejections FlexInt `json:"guest-rai0-mac_filter_rejections"` + UserRai0MacFilterRejections FlexInt `json:"user-rai0-mac_filter_rejections"` + Rai0MacFilterRejections FlexInt `json:"rai0-mac_filter_rejections"` + GuestRai0WifiTxAttempts FlexInt `json:"guest-rai0-wifi_tx_attempts"` + UserRai0WifiTxAttempts FlexInt `json:"user-rai0-wifi_tx_attempts"` + Rai0WifiTxAttempts FlexInt `json:"rai0-wifi_tx_attempts"` + GuestRai0WifiTxDropped FlexInt `json:"guest-rai0-wifi_tx_dropped"` + UserRai0WifiTxDropped FlexInt `json:"user-rai0-wifi_tx_dropped"` + Rai0WifiTxDropped FlexInt `json:"rai0-wifi_tx_dropped"` + */ +} + +// RadioTable is part of the data for UAPs and UDMs. +type RadioTable []struct { + AntennaGain FlexInt `json:"antenna_gain"` + BuiltinAntGain FlexInt `json:"builtin_ant_gain"` + BuiltinAntenna FlexBool `json:"builtin_antenna"` + Channel FlexInt `json:"channel"` + CurrentAntennaGain FlexInt `json:"current_antenna_gain"` + HasDfs FlexBool `json:"has_dfs"` + HasFccdfs FlexBool `json:"has_fccdfs"` + HasHt160 FlexBool `json:"has_ht160"` + Ht FlexInt `json:"ht"` + Is11Ac FlexBool `json:"is_11ac"` + MaxTxpower FlexInt `json:"max_txpower"` + MinRssi FlexInt `json:"min_rssi,omitempty"` + MinRssiEnabled FlexBool `json:"min_rssi_enabled"` + MinTxpower FlexInt `json:"min_txpower"` + Name string `json:"name"` + Nss FlexInt `json:"nss"` + Radio string `json:"radio"` + RadioCaps FlexInt `json:"radio_caps"` + SensLevelEnabled FlexBool `json:"sens_level_enabled"` + TxPower FlexInt `json:"tx_power"` + TxPowerMode string `json:"tx_power_mode"` + VwireEnabled FlexBool `json:"vwire_enabled"` + WlangroupID string `json:"wlangroup_id"` +} + +// RadioTableStats is part of the data shared between UAP and UDM. +type RadioTableStats []struct { + Name string `json:"name"` + Channel FlexInt `json:"channel"` + Radio string `json:"radio"` + AstTxto interface{} `json:"ast_txto"` + AstCst interface{} `json:"ast_cst"` + AstBeXmit FlexInt `json:"ast_be_xmit"` + CuTotal FlexInt `json:"cu_total"` + CuSelfRx FlexInt `json:"cu_self_rx"` + CuSelfTx FlexInt `json:"cu_self_tx"` + Gain FlexInt `json:"gain"` + Satisfaction FlexInt `json:"satisfaction"` + State string `json:"state"` + Extchannel FlexInt `json:"extchannel"` + TxPower FlexInt `json:"tx_power"` + TxPackets FlexInt `json:"tx_packets"` + TxRetries FlexInt `json:"tx_retries"` + NumSta FlexInt `json:"num_sta"` + GuestNumSta FlexInt `json:"guest-num_sta"` + UserNumSta FlexInt `json:"user-num_sta"` +} + +// VapTable holds much of the UAP wireless data. Shared by UDM. +type VapTable []struct { + AnomaliesBarChart struct { + HighDNSLatency FlexInt `json:"high_dns_latency"` + HighTCPLatency FlexInt `json:"high_tcp_latency"` + HighTCPPacketLoss FlexInt `json:"high_tcp_packet_loss"` + HighWifiLatency FlexInt `json:"high_wifi_latency"` + HighWifiRetries FlexInt `json:"high_wifi_retries"` + LowPhyRate FlexInt `json:"low_phy_rate"` + PoorStreamEff FlexInt `json:"poor_stream_eff"` + SleepyClient FlexInt `json:"sleepy_client"` + StaArpTimeout FlexInt `json:"sta_arp_timeout"` + StaDNSTimeout FlexInt `json:"sta_dns_timeout"` + StaIPTimeout FlexInt `json:"sta_ip_timeout"` + WeakSignal FlexInt `json:"weak_signal"` + } `json:"anomalies_bar_chart"` + AnomaliesBarChartNow struct { + HighDNSLatency FlexInt `json:"high_dns_latency"` + HighTCPLatency FlexInt `json:"high_tcp_latency"` + HighTCPPacketLoss FlexInt `json:"high_tcp_packet_loss"` + HighWifiLatency FlexInt `json:"high_wifi_latency"` + HighWifiRetries FlexInt `json:"high_wifi_retries"` + LowPhyRate FlexInt `json:"low_phy_rate"` + PoorStreamEff FlexInt `json:"poor_stream_eff"` + SleepyClient FlexInt `json:"sleepy_client"` + StaArpTimeout FlexInt `json:"sta_arp_timeout"` + StaDNSTimeout FlexInt `json:"sta_dns_timeout"` + StaIPTimeout FlexInt `json:"sta_ip_timeout"` + WeakSignal FlexInt `json:"weak_signal"` + } `json:"anomalies_bar_chart_now"` + ReasonsBarChart struct { + PhyRate FlexInt `json:"phy_rate"` + Signal FlexInt `json:"signal"` + SleepyClient FlexInt `json:"sleepy_client"` + StaArpTimeout FlexInt `json:"sta_arp_timeout"` + StaDNSLatency FlexInt `json:"sta_dns_latency"` + StaDNSTimeout FlexInt `json:"sta_dns_timeout"` + StaIPTimeout FlexInt `json:"sta_ip_timeout"` + StreamEff FlexInt `json:"stream_eff"` + TCPLatency FlexInt `json:"tcp_latency"` + TCPPacketLoss FlexInt `json:"tcp_packet_loss"` + WifiLatency FlexInt `json:"wifi_latency"` + WifiRetries FlexInt `json:"wifi_retries"` + } `json:"reasons_bar_chart"` + ReasonsBarChartNow struct { + PhyRate FlexInt `json:"phy_rate"` + Signal FlexInt `json:"signal"` + SleepyClient FlexInt `json:"sleepy_client"` + StaArpTimeout FlexInt `json:"sta_arp_timeout"` + StaDNSLatency FlexInt `json:"sta_dns_latency"` + StaDNSTimeout FlexInt `json:"sta_dns_timeout"` + StaIPTimeout FlexInt `json:"sta_ip_timeout"` + StreamEff FlexInt `json:"stream_eff"` + TCPLatency FlexInt `json:"tcp_latency"` + TCPPacketLoss FlexInt `json:"tcp_packet_loss"` + WifiLatency FlexInt `json:"wifi_latency"` + WifiRetries FlexInt `json:"wifi_retries"` + } `json:"reasons_bar_chart_now"` + RxTCPStats struct { + Goodbytes FlexInt `json:"goodbytes"` + LatAvg FlexInt `json:"lat_avg"` + LatMax FlexInt `json:"lat_max"` + LatMin FlexInt `json:"lat_min"` + Stalls FlexInt `json:"stalls"` + } `json:"rx_tcp_stats"` + TxTCPStats struct { + Goodbytes FlexInt `json:"goodbytes"` + LatAvg FlexInt `json:"lat_avg"` + LatMax FlexInt `json:"lat_max"` + LatMin FlexInt `json:"lat_min"` + Stalls FlexInt `json:"stalls"` + } `json:"tx_tcp_stats"` + WifiTxLatencyMov struct { + Avg FlexInt `json:"avg"` + Max FlexInt `json:"max"` + Min FlexInt `json:"min"` + Total FlexInt `json:"total"` + TotalCount FlexInt `json:"total_count"` + } `json:"wifi_tx_latency_mov"` + ApMac string `json:"ap_mac"` + AvgClientSignal FlexInt `json:"avg_client_signal"` + Bssid string `json:"bssid"` + Ccq int `json:"ccq"` + Channel FlexInt `json:"channel"` + DNSAvgLatency FlexInt `json:"dns_avg_latency"` + Essid string `json:"essid"` + Extchannel int `json:"extchannel"` + ID string `json:"id"` + IsGuest FlexBool `json:"is_guest"` + IsWep FlexBool `json:"is_wep"` + MacFilterRejections int `json:"mac_filter_rejections"` + MapID interface{} `json:"map_id"` + Name string `json:"name"` + NumSatisfactionSta FlexInt `json:"num_satisfaction_sta"` + NumSta int `json:"num_sta"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + RxBytes FlexInt `json:"rx_bytes"` + RxCrypts FlexInt `json:"rx_crypts"` + RxDropped FlexInt `json:"rx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxFrags FlexInt `json:"rx_frags"` + RxNwids FlexInt `json:"rx_nwids"` + RxPackets FlexInt `json:"rx_packets"` + Satisfaction FlexInt `json:"satisfaction"` + SatisfactionNow FlexInt `json:"satisfaction_now"` + SiteID string `json:"site_id"` + State string `json:"state"` + T string `json:"t"` + TxBytes FlexInt `json:"tx_bytes"` + TxCombinedRetries FlexInt `json:"tx_combined_retries"` + TxDataMpduBytes FlexInt `json:"tx_data_mpdu_bytes"` + TxDropped FlexInt `json:"tx_dropped"` + TxErrors FlexInt `json:"tx_errors"` + TxPackets FlexInt `json:"tx_packets"` + TxPower FlexInt `json:"tx_power"` + TxRetries FlexInt `json:"tx_retries"` + TxRtsRetries FlexInt `json:"tx_rts_retries"` + TxSuccess FlexInt `json:"tx_success"` + TxTotal FlexInt `json:"tx_total"` + Up FlexBool `json:"up"` + Usage string `json:"usage"` + WifiTxAttempts FlexInt `json:"wifi_tx_attempts"` + WifiTxDropped FlexInt `json:"wifi_tx_dropped"` + WlanconfID string `json:"wlanconf_id"` +} + +// RogueAP are your neighbors access points. +type RogueAP struct { + SourceName string `json:"-"` + SiteName string `json:"-"` + ID string `json:"_id"` + ApMac string `json:"ap_mac"` + Bssid string `json:"bssid"` + SiteID string `json:"site_id"` + Age FlexInt `json:"age"` + Band string `json:"band"` + Bw FlexInt `json:"bw"` + CenterFreq FlexInt `json:"center_freq"` + Channel int `json:"channel"` + Essid string `json:"essid"` + Freq FlexInt `json:"freq"` + IsAdhoc FlexBool `json:"is_adhoc"` + IsRogue FlexBool `json:"is_rogue"` + IsUbnt FlexBool `json:"is_ubnt"` + LastSeen FlexInt `json:"last_seen"` + Noise FlexInt `json:"noise"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + ReportTime FlexInt `json:"report_time"` + Rssi FlexInt `json:"rssi"` + RssiAge FlexInt `json:"rssi_age"` + Security string `json:"security"` + Signal FlexInt `json:"signal"` + Oui string `json:"oui"` +} + +// GetRogueAPs returns RogueAPs for a list of Sites. +// Use GetRogueAPsSite if you want more control. +func (u *Unifi) GetRogueAPs(sites []*Site) ([]*RogueAP, error) { + data := []*RogueAP{} + + for _, site := range sites { + response, err := u.GetRogueAPsSite(site) + if err != nil { + return data, err + } + + data = append(data, response...) + } + + return data, nil +} + +// GetRogueAPsSite returns RogueAPs for a single Site. +func (u *Unifi) GetRogueAPsSite(site *Site) ([]*RogueAP, error) { + if site == nil || site.Name == "" { + return nil, ErrNoSiteProvided + } + + u.DebugLog("Polling Controller for RogueAPs, site %s (%s)", site.SiteName, site.Desc) + + var ( + path = fmt.Sprintf(APIRogueAP, site.Name) + rogueaps struct { + Data []*RogueAP `json:"data"` + } + ) + + if err := u.GetData(path, &rogueaps, ""); err != nil { + return rogueaps.Data, err + } + + for i := range rogueaps.Data { + // Add special SourceName value. + rogueaps.Data[i].SourceName = u.URL + // Add the special "Site Name" to each event. This becomes a Grafana filter somewhere. + rogueaps.Data[i].SiteName = site.SiteName + } + + return rogueaps.Data, nil +} + +// UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Access Point Stat data. +func (v *UAPStat) UnmarshalJSON(data []byte) error { + var n struct { + Ap `json:"ap"` + } + + v.Ap = &n.Ap + + err := json.Unmarshal(data, v.Ap) // controller version 5.10. + if err != nil { + return json.Unmarshal(data, &n) // controller version 5.11. + } + + return nil +} diff --git a/core/unifi/uap_test.go b/core/unifi/uap_test.go new file mode 100644 index 00000000..3c6c28a1 --- /dev/null +++ b/core/unifi/uap_test.go @@ -0,0 +1,53 @@ +package unifi // nolint: testpackage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUAPUnmarshalJSON(t *testing.T) { + testcontroller511 := `{ + "ap": { + "site_id": "mySite", + "o": "ap", + "oid": "00:00:00:00:00:00", + "ap": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:50:00Z", + "user-wifi1-rx_packets": 6596670, + "user-wifi0-rx_packets": 42659527, + "user-rx_packets": 49294197, + "guest-rx_packets": 0, + "wifi0-rx_packets": 42639527, + "wifi1-rx_packets": 6591670, + "rx_packets": 49299197}}` + + testcontroller510 := `{ + "site_id": "mySite", + "o": "ap", + "oid": "00:00:00:00:00:00", + "ap": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:50:00Z", + "user-wifi1-rx_packets": 6596670, + "user-wifi0-rx_packets": 42659527, + "user-rx_packets": 49294197, + "guest-rx_packets": 0, + "wifi0-rx_packets": 42639527, + "wifi1-rx_packets": 6591670, + "rx_packets": 49299197}` + + t.Parallel() + a := assert.New(t) + rxPakcets := 49299197 + u := &UAPStat{} + err := u.UnmarshalJSON([]byte(testcontroller510)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(rxPakcets), u.RxPackets.Val, "data was not properly unmarshaled") + + u = &UAPStat{} // reset + err = u.UnmarshalJSON([]byte(testcontroller511)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(rxPakcets), u.RxPackets.Val, "data was not properly unmarshaled") +} diff --git a/core/unifi/udm.go b/core/unifi/udm.go new file mode 100644 index 00000000..547c5f0e --- /dev/null +++ b/core/unifi/udm.go @@ -0,0 +1,206 @@ +package unifi + +// UDM represents all the data from the Ubiquiti Controller for a Unifi Dream Machine. +// The UDM shares several structs/type-data with USW and USG. +type UDM struct { + site *Site + SourceName string `json:"-"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + Mac string `json:"mac"` + Adopted FlexBool `json:"adopted"` + Serial string `json:"serial"` + IP string `json:"ip"` + Uptime FlexInt `json:"uptime"` + Model string `json:"model"` + Version string `json:"version"` + Name string `json:"name"` + Default FlexBool `json:"default"` + Locating FlexBool `json:"locating"` + Type string `json:"type"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + DiscoveredVia string `json:"discovered_via"` + AdoptIP string `json:"adopt_ip"` + AdoptURL string `json:"adopt_url"` + State FlexInt `json:"state"` + AdoptStatus FlexInt `json:"adopt_status"` + UpgradeState FlexInt `json:"upgrade_state"` + LastSeen FlexInt `json:"last_seen"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + VwireTable []interface{} `json:"vwire_table"` + Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` + JumboframeEnabled FlexBool `json:"jumboframe_enabled"` + FlowctrlEnabled FlexBool `json:"flowctrl_enabled"` + StpVersion string `json:"stp_version"` + StpPriority FlexInt `json:"stp_priority"` + PowerSourceCtrlEnabled FlexBool `json:"power_source_ctrl_enabled"` + LicenseState string `json:"license_state"` + ID string `json:"_id"` + DeviceID string `json:"device_id"` + AdoptState FlexInt `json:"adopt_state"` + AdoptTries FlexInt `json:"adopt_tries"` + AdoptManual FlexBool `json:"adopt_manual"` + InformURL string `json:"inform_url"` + InformIP string `json:"inform_ip"` + RequiredVersion string `json:"required_version"` + BoardRev FlexInt `json:"board_rev"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + PortTable []Port `json:"port_table"` + EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"` + UsgCaps FlexInt `json:"usg_caps"` + HasSpeaker FlexBool `json:"has_speaker"` + HasEth1 FlexBool `json:"has_eth1"` + FwCaps FlexInt `json:"fw_caps"` + HwCaps FlexInt `json:"hw_caps"` + WifiCaps FlexInt `json:"wifi_caps"` + SwitchCaps struct { + MaxMirrorSessions FlexInt `json:"max_mirror_sessions"` + MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` + } `json:"switch_caps"` + HasFan FlexBool `json:"has_fan"` + Temperatures []Temperature `json:"temperatures,omitempty"` + RulesetInterfaces interface{} `json:"ruleset_interfaces"` + /* struct { + Br0 string `json:"br0"` + Eth0 string `json:"eth0"` + Eth1 string `json:"eth1"` + Eth2 string `json:"eth2"` + Eth3 string `json:"eth3"` + Eth4 string `json:"eth4"` + Eth5 string `json:"eth5"` + Eth6 string `json:"eth6"` + Eth7 string `json:"eth7"` + Eth8 string `json:"eth8"` + } */ + KnownCfgversion string `json:"known_cfgversion"` + SysStats SysStats `json:"sys_stats"` + SystemStats SystemStats `json:"system-stats"` + GuestToken string `json:"guest_token"` + Overheating FlexBool `json:"overheating"` + SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` + SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` + Wan1 Wan `json:"wan1"` + Wan2 Wan `json:"wan2"` + Uplink Uplink `json:"uplink"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + DownlinkTable []*DownlinkTable `json:"downlink_table"` + WlangroupIDNa string `json:"wlangroup_id_na"` + WlangroupIDNg string `json:"wlangroup_id_ng"` + BandsteeringMode string `json:"bandsteering_mode"` + RadioTable *RadioTable `json:"radio_table,omitempty"` + RadioTableStats *RadioTableStats `json:"radio_table_stats,omitempty"` + VapTable *VapTable `json:"vap_table"` + XInformAuthkey string `json:"x_inform_authkey"` + NetworkTable NetworkTable `json:"network_table"` + PortOverrides []struct { + PortIdx FlexInt `json:"port_idx"` + PortconfID string `json:"portconf_id"` + } `json:"port_overrides"` + Stat UDMStat `json:"stat"` + Storage []*Storage `json:"storage"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + BytesD FlexInt `json:"bytes-d"` + TxBytesD FlexInt `json:"tx_bytes-d"` + RxBytesD FlexInt `json:"rx_bytes-d"` + BytesR FlexInt `json:"bytes-r"` + NumSta FlexInt `json:"num_sta"` // USG + WlanNumSta FlexInt `json:"wlan-num_sta"` // UAP + LanNumSta FlexInt `json:"lan-num_sta"` // USW + UserWlanNumSta FlexInt `json:"user-wlan-num_sta"` // UAP + UserLanNumSta FlexInt `json:"user-lan-num_sta"` // USW + UserNumSta FlexInt `json:"user-num_sta"` // USG + GuestWlanNumSta FlexInt `json:"guest-wlan-num_sta"` // UAP + GuestLanNumSta FlexInt `json:"guest-lan-num_sta"` // USW + GuestNumSta FlexInt `json:"guest-num_sta"` // USG + NumDesktop FlexInt `json:"num_desktop"` // USG + NumMobile FlexInt `json:"num_mobile"` // USG + NumHandheld FlexInt `json:"num_handheld"` // USG +} + +type EthernetOverrides struct { + Ifname string `json:"ifname"` + Networkgroup string `json:"networkgroup"` +} + +type EthernetTable struct { + Mac string `json:"mac"` + NumPort FlexInt `json:"num_port"` + Name string `json:"name"` +} + +// NetworkTable is the list of networks on a gateway. +// Not all gateways have all features. +type NetworkTable []struct { + ID string `json:"_id"` + AttrNoDelete FlexBool `json:"attr_no_delete"` + AttrHiddenID string `json:"attr_hidden_id"` + Name string `json:"name"` + SiteID string `json:"site_id"` + VlanEnabled FlexBool `json:"vlan_enabled"` + Purpose string `json:"purpose"` + IPSubnet string `json:"ip_subnet"` + Ipv6InterfaceType string `json:"ipv6_interface_type"` + DomainName string `json:"domain_name"` + IsNat FlexBool `json:"is_nat"` + DhcpdEnabled FlexBool `json:"dhcpd_enabled"` + DhcpdStart string `json:"dhcpd_start"` + DhcpdStop string `json:"dhcpd_stop"` + Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"` + Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"` + LteLanEnabled FlexBool `json:"lte_lan_enabled"` + AutoScaleEnabled FlexBool `json:"auto_scale_enabled"` + Networkgroup string `json:"networkgroup"` + DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"` + DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` + DhcpdGatewayEnabled FlexBool `json:"dhcpd_gateway_enabled"` + DhcpdTimeOffsetEnabled FlexBool `json:"dhcpd_time_offset_enabled"` + Ipv6PdStart string `json:"ipv6_pd_start"` + Ipv6PdStop string `json:"ipv6_pd_stop"` + DhcpdDNS1 string `json:"dhcpd_dns_1"` + DhcpdDNS2 string `json:"dhcpd_dns_2"` + DhcpdDNS3 string `json:"dhcpd_dns_3"` + DhcpdDNS4 string `json:"dhcpd_dns_4"` + Enabled FlexBool `json:"enabled"` + DhcpRelayEnabled FlexBool `json:"dhcp_relay_enabled"` + Mac string `json:"mac"` + IsGuest FlexBool `json:"is_guest"` + IP string `json:"ip"` + Up FlexBool `json:"up"` + ActiveDhcpLeaseCount int `json:"active_dhcp_lease_count"` + GatewayInterfaceName string `json:"gateway_interface_name"` + DPIStatsTable *DPITable `json:"dpistats_table"` + NumSta FlexInt `json:"num_sta"` + RxBytes FlexInt `json:"rx_bytes"` + RxPackets FlexInt `json:"rx_packets"` + TxBytes FlexInt `json:"tx_bytes"` + TxPackets FlexInt `json:"tx_packets"` +} + +// Storage is hard drive into for a device with storage. +type Storage struct { + MountPoint string `json:"mount_point"` + Name string `json:"name"` + Size FlexInt `json:"size"` + Type string `json:"type"` + Used FlexInt `json:"used"` +} + +type Temperature struct { + Name string `json:"name"` + Type string `json:"type"` + Value float64 `json:"value"` +} + +// UDMStat holds the "stat" data for a dream machine. +// A dream machine is a USG + USW + Controller. +type UDMStat struct { + *Gw `json:"gw"` + *Sw `json:"sw"` + *Ap `json:"ap,omitempty"` +} diff --git a/core/unifi/unifi.go b/core/unifi/unifi.go new file mode 100644 index 00000000..cd1bf76c --- /dev/null +++ b/core/unifi/unifi.go @@ -0,0 +1,382 @@ +// Package unifi provides a set of types to unload (unmarshal) Ubiquiti UniFi +// controller data. Also provided are methods to easily get data for devices - +// things like access points and switches, and for clients - the things +// connected to those access points and switches. As a bonus, each device and +// client type provided has an attached method to create InfluxDB datapoints. +package unifi + +import ( + "bytes" + "context" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "strings" + "time" + + "golang.org/x/net/publicsuffix" +) + +var ( + ErrAuthenticationFailed = fmt.Errorf("authentication failed") + ErrInvalidStatusCode = fmt.Errorf("invalid status code from server") + ErrNoParams = fmt.Errorf("requested PUT with no parameters") + ErrInvalidSignature = fmt.Errorf("certificate signature does not match") +) + +// NewUnifi creates a http.Client with authenticated cookies. +// Used to make additional, authenticated requests to the APIs. +// Start here. +func NewUnifi(config *Config) (*Unifi, error) { + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return nil, fmt.Errorf("creating cookiejar: %w", err) + } + + u := newUnifi(config, jar) + + for i, cert := range config.SSLCert { + p, _ := pem.Decode(cert) + u.fingerprints[i] = fmt.Sprintf("%x", sha256.Sum256(p.Bytes)) + } + + if err := u.checkNewStyleAPI(); err != nil { + return u, err + } + + if err := u.Login(); err != nil { + return u, err + } + + if err := u.GetServerData(); err != nil { + return u, fmt.Errorf("unable to get server version: %w", err) + } + + return u, nil +} + +func newUnifi(config *Config, jar http.CookieJar) *Unifi { + config.URL = strings.TrimRight(config.URL, "/") + + if config.ErrorLog == nil { + config.ErrorLog = discardLogs + } + + if config.DebugLog == nil { + config.DebugLog = discardLogs + } + + u := &Unifi{ + Config: config, + Client: &http.Client{ + Timeout: config.Timeout, + Jar: jar, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: !config.VerifySSL, // nolint: gosec + }, + }, + }, + } + + if len(config.SSLCert) > 0 { + u.fingerprints = make(fingerprints, len(config.SSLCert)) + u.Client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, // nolint: gosec + VerifyPeerCertificate: u.verifyPeerCertificate, + }, + } + } + + return u +} + +func (u *Unifi) verifyPeerCertificate(certs [][]byte, chains [][]*x509.Certificate) error { + if len(u.fingerprints) == 0 { + return nil + } + + for _, cert := range certs { + if u.fingerprints.Contains(fmt.Sprintf("%x", sha256.Sum256(cert))) { + return nil + } + } + + return ErrInvalidSignature +} + +// Login is a helper method. It can be called to grab a new authentication cookie. +func (u *Unifi) Login() error { + start := time.Now() + + // magic login. + req, err := u.UniReq(APILoginPath, fmt.Sprintf(`{"username":"%s","password":"%s"}`, u.User, u.Pass)) + if err != nil { + return err + } + + resp, err := u.Do(req) + if err != nil { + return fmt.Errorf("making request: %w", err) + } + + defer resp.Body.Close() // we need no data here. + _, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking. + u.DebugLog("Requested %s: elapsed %v, returned %d bytes", + req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("(user: %s): %s (status: %s): %w", + u.User, req.URL, resp.Status, ErrAuthenticationFailed) + } + + return nil +} + +// Logout closes the current session. +func (u *Unifi) Logout() error { + // a post is needed for logout + _, err := u.PostJSON(APILogoutPath) + return err +} + +// with the release of controller version 5.12.55 on UDM in Jan 2020 the api paths +// changed and broke this library. This function runs when `NewUnifi()` is called to +// check if this is a newer controller or not. If it is, we set new to true. +// Setting new to true makes the path() method return different (new) paths. +func (u *Unifi) checkNewStyleAPI() error { + var ( + ctx = context.Background() + cancel func() + ) + + if u.Config.Timeout != 0 { + ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout) + defer cancel() + } + + u.DebugLog("Requesting %s/ to determine API paths", u.URL) + + req, err := http.NewRequestWithContext(ctx, "GET", u.URL+"/", nil) + if err != nil { + return fmt.Errorf("creating request: %w", err) + } + + // We can't share these cookies with other requests, so make a new client. + // Checking the return code on the first request so don't follow a redirect. + client := &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: !u.VerifySSL}, // nolint: gosec + }, + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("making request: %w", err) + } + + defer resp.Body.Close() // we need no data here. + _, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking. + + if resp.StatusCode == http.StatusOK { + // The new version returns a "200" for a / request. + u.new = true + u.DebugLog("Using NEW UniFi controller API paths for %s", req.URL) + } + + // The old version returns a "302" (to /manage) for a / request + return nil +} + +// GetServerData sets the controller's version and UUID. Only call this if you +// previously called Login and suspect the controller version has changed. +func (u *Unifi) GetServerData() error { + var response struct { + Data server `json:"meta"` + } + + u.server = &response.Data + + return u.GetData(APIStatusPath, &response) +} + +// GetData makes a unifi request and unmarshals the response into a provided pointer. +func (u *Unifi) GetData(apiPath string, v interface{}, params ...string) error { + start := time.Now() + + body, err := u.GetJSON(apiPath, params...) + if err != nil { + return err + } + + u.DebugLog("Requested %s: elapsed %v, returned %d bytes", + u.URL+u.path(apiPath), time.Since(start).Round(time.Millisecond), len(body)) + + return json.Unmarshal(body, v) +} + +// PutData makes a unifi request and unmarshals the response into a provided pointer. +func (u *Unifi) PutData(apiPath string, v interface{}, params ...string) error { + start := time.Now() + + body, err := u.PutJSON(apiPath, params...) + if err != nil { + return err + } + + u.DebugLog("Requested %s: elapsed %v, returned %d bytes", + u.URL+u.path(apiPath), time.Since(start).Round(time.Millisecond), len(body)) + + return json.Unmarshal(body, v) +} + +// UniReq is a small helper function that adds an Accept header. +// Use this if you're unmarshalling UniFi data into custom types. +// And if you're doing that... sumbut a pull request with your new struct. :) +// This is a helper method that is exposed for convenience. +func (u *Unifi) UniReq(apiPath string, params string) (*http.Request, error) { + var ( + req *http.Request + err error + ) + + switch apiPath = u.path(apiPath); params { + case "": + req, err = http.NewRequest(http.MethodGet, u.URL+apiPath, nil) + default: + req, err = http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params)) + } + + if err != nil { + return nil, fmt.Errorf("creating request: %w", err) + } + + u.setHeaders(req, params) + + return req, nil +} + +// UniReqPut is the Put call equivalent to UniReq. +func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) { + if params == "" { + return nil, ErrNoParams + } + + apiPath = u.path(apiPath) + + req, err := http.NewRequest(http.MethodPut, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx + if err != nil { + return nil, fmt.Errorf("creating request: %w", err) + } + + u.setHeaders(req, params) + + return req, nil +} + +// UniReqPost is the Post call equivalent to UniReq. +func (u *Unifi) UniReqPost(apiPath string, params string) (*http.Request, error) { + apiPath = u.path(apiPath) + + req, err := http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx + if err != nil { + return nil, fmt.Errorf("creating request: %w", err) + } + + u.setHeaders(req, params) + + return req, nil +} + +// GetJSON returns the raw JSON from a path. This is useful for debugging. +func (u *Unifi) GetJSON(apiPath string, params ...string) ([]byte, error) { + req, err := u.UniReq(apiPath, strings.Join(params, " ")) + if err != nil { + return []byte{}, err + } + + return u.do(req) +} + +// PutJSON uses a PUT call and returns the raw JSON in the same way as GetData +// Use this if you want to change data via the REST API. +func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) { + req, err := u.UniReqPut(apiPath, strings.Join(params, " ")) + if err != nil { + return []byte{}, err + } + + return u.do(req) +} + +// PostJSON uses a POST call and returns the raw JSON in the same way as GetData +// Use this if you want to change data via the REST API. +func (u *Unifi) PostJSON(apiPath string, params ...string) ([]byte, error) { + req, err := u.UniReqPost(apiPath, strings.Join(params, " ")) + if err != nil { + return []byte{}, err + } + + return u.do(req) +} + +func (u *Unifi) do(req *http.Request) ([]byte, error) { + var ( + cancel func() + ctx = context.Background() + ) + + if u.Config.Timeout != 0 { + ctx, cancel = context.WithTimeout(ctx, u.Config.Timeout) + defer cancel() + } + + resp, err := u.Do(req.WithContext(ctx)) + if err != nil { + return []byte{}, fmt.Errorf("making request: %w", err) + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return body, fmt.Errorf("reading response: %w", err) + } + + // Save the returned CSRF header. + if csrf := resp.Header.Get("x-csrf-token"); csrf != "" { + u.csrf = resp.Header.Get("x-csrf-token") + } + + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf("%s: %s: %w", req.URL, resp.Status, ErrInvalidStatusCode) + } + + return body, err +} + +func (u *Unifi) setHeaders(req *http.Request, params string) { + // Add the saved CSRF header. + req.Header.Set("X-CSRF-Token", u.csrf) + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json; charset=utf-8") + + if u.Client.Jar != nil { + parsedURL, _ := url.Parse(req.URL.String()) + u.DebugLog("Requesting %s, with params: %v, cookies: %d", req.URL, params != "", len(u.Client.Jar.Cookies(parsedURL))) + } else { + u.DebugLog("Requesting %s, with params: %v,", req.URL, params != "") + } +} diff --git a/core/unifi/unifi_test.go b/core/unifi/unifi_test.go new file mode 100644 index 00000000..f466a950 --- /dev/null +++ b/core/unifi/unifi_test.go @@ -0,0 +1,95 @@ +package unifi // nolint: testpackage + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewUnifi(t *testing.T) { + t.Parallel() + a := assert.New(t) + u := "http://127.0.0.1:64431" + c := &Config{ + User: "user1", + Pass: "pass2", + URL: u, + VerifySSL: false, + DebugLog: discardLogs, + } + authReq, err := NewUnifi(c) + a.NotNil(err) + a.EqualValues(u, authReq.URL) + a.Contains(err.Error(), "connection refused", "an invalid destination should produce a connection error.") +} + +func TestUniReq(t *testing.T) { + t.Parallel() + a := assert.New(t) + p := "/test/path" + u := "http://some.url:8443" + + // Test empty parameters. + authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} + r, err := authReq.UniReq(p, "") + + a.Nil(err, "newrequest must not produce an error") + a.EqualValues(p, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") + a.EqualValues("GET", r.Method, "without parameters the method must be GET") + a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") + + // Test with parameters + k := "key1=value9&key2=value7" + authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} + r, err = authReq.UniReq(p, k) + a.Nil(err, "newrequest must not produce an error") + + a.EqualValues(p, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") + a.EqualValues("POST", r.Method, "with parameters the method must be POST") + a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") + + // Check the parameters. + d, err := ioutil.ReadAll(r.Body) + a.Nil(err, "problem reading request body, POST parameters may be malformed") + a.EqualValues(k, string(d), "POST parameters improperly encoded") +} + +func TestUniReqPut(t *testing.T) { + t.Parallel() + a := assert.New(t) + p := "/test/path" + u := "http://some.url:8443" + + // Test empty parameters. + authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} + _, err := authReq.UniReqPut(p, "") + a.NotNil(err, "empty params must produce an error") + + // Test with parameters + k := "key1=value9&key2=value7" + authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} + r, err := authReq.UniReqPut(p, k) + a.Nil(err, "newrequest must not produce an error") + + a.EqualValues(p, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") + a.EqualValues("PUT", r.Method, "with parameters the method must be POST") + a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json") + + // Check the parameters. + d, err := ioutil.ReadAll(r.Body) + a.Nil(err, "problem reading request body, PUT parameters may be malformed") + a.EqualValues(k, string(d), "PUT parameters improperly encoded") +} + +/* NOT DONE: OPEN web server, check parameters posted, more. These tests are incomplete. +a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), + "user/pass json parameters improperly encoded") +*/ diff --git a/core/unifi/users.go b/core/unifi/users.go new file mode 100644 index 00000000..995e2908 --- /dev/null +++ b/core/unifi/users.go @@ -0,0 +1,71 @@ +package unifi + +import ( + "fmt" + "strings" +) + +// GetUsers returns a response full of clients that connected to the UDM within the provided amount of time +// using the insight historical connection data set. +func (u *Unifi) GetUsers(sites []*Site, hours int) ([]*User, error) { + data := make([]*User, 0) + + for _, site := range sites { + var ( + response struct { + Data []*User `json:"data"` + } + params = fmt.Sprintf(`{ "type": "all:", "conn": "all", "within":%d }`, hours) + ) + + u.DebugLog("Polling Controller, retrieving UniFi Users, site %s ", site.SiteName) + + clientPath := fmt.Sprintf(APIAllUserPath, site.Name) + if err := u.GetData(clientPath, &response, params); err != nil { + return nil, err + } + + for i, d := range response.Data { + // Add special SourceName value. + response.Data[i].SourceName = u.URL + // Add the special "Site Name" to each client. This becomes a Grafana filter somewhere. + response.Data[i].SiteName = site.SiteName + // Fix name and hostname fields. Sometimes one or the other is blank. + response.Data[i].Hostname = strings.TrimSpace(pick(d.Hostname, d.Name, d.Mac)) + response.Data[i].Name = strings.TrimSpace(pick(d.Name, d.Hostname)) + } + + data = append(data, response.Data...) + } + + return data, nil +} + +// User defines the metadata available for previously connected clients. +type User struct { + SourceName string `json:"-"` + SiteName string `json:"-"` + ID string `json:"_id"` + Mac string `json:"mac"` + SiteID string `json:"site_id"` + Oui string `json:"oui,omitempty"` + IsGuest bool `json:"is_guest"` + FirstSeen FlexInt `json:"first_seen,omitempty"` + LastSeen FlexInt `json:"last_seen,omitempty"` + IsWired bool `json:"is_wired,omitempty"` + Hostname string `json:"hostname,omitempty"` + Duration FlexInt `json:"duration,omitempty"` + TxBytes FlexInt `json:"tx_bytes,omitempty"` + TxPackets FlexInt `json:"tx_packets,omitempty"` + RxBytes FlexInt `json:"rx_bytes,omitempty"` + RxPackets FlexInt `json:"rx_packets,omitempty"` + WifiTxAttempts FlexInt `json:"wifi_tx_attempts,omitempty"` + TxRetries FlexInt `json:"tx_retries,omitempty"` + UsergroupID string `json:"usergroup_id,omitempty"` + Name string `json:"name,omitempty"` + Note string `json:"note,omitempty"` + Noted FlexBool `json:"noted,omitempty"` + Blocked FlexBool `json:"blocked,omitempty"` + DevIDOverride FlexInt `json:"dev_id_override,omitempty"` + FingerprintOverride FlexBool `json:"fingerprint_override,omitempty"` +} diff --git a/core/unifi/usg.go b/core/unifi/usg.go new file mode 100644 index 00000000..9cdc653e --- /dev/null +++ b/core/unifi/usg.go @@ -0,0 +1,252 @@ +package unifi + +import ( + "encoding/json" + "time" +) + +// USG represents all the data from the Ubiquiti Controller for a Unifi Security Gateway. +type USG struct { + site *Site + SourceName string `json:"-"` + ID string `json:"_id"` + Adopted FlexBool `json:"adopted"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + FwCaps FlexInt `json:"fw_caps"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + LedOverride string `json:"led_override"` + LicenseState string `json:"license_state"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + Type string `json:"type"` + UsgCaps FlexInt `json:"usg_caps"` + Version string `json:"version"` + RequiredVersion string `json:"required_version"` + EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"` + HwCaps FlexInt `json:"hw_caps"` + BoardRev FlexInt `json:"board_rev"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + DeviceID string `json:"device_id"` + State FlexInt `json:"state"` + LastSeen FlexInt `json:"last_seen"` + Upgradable FlexBool `json:"upgradable"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` + Rollupgrade FlexBool `json:"rollupgrade"` + KnownCfgversion string `json:"known_cfgversion"` + Uptime FlexInt `json:"uptime"` + Locating FlexBool `json:"locating"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + SysStats SysStats `json:"sys_stats"` + Temperatures []Temperature `json:"temperatures,omitempty"` + SystemStats SystemStats `json:"system-stats"` + GuestToken string `json:"guest_token"` + SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` + SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` + Wan1 Wan `json:"wan1"` + Wan2 Wan `json:"wan2"` + PortTable []*Port `json:"port_table"` + NetworkTable NetworkTable `json:"network_table"` + Uplink Uplink `json:"uplink"` + Stat USGStat `json:"stat"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + NumSta FlexInt `json:"num_sta"` + UserNumSta FlexInt `json:"user-num_sta"` + GuestNumSta FlexInt `json:"guest-num_sta"` + NumDesktop FlexInt `json:"num_desktop"` + NumMobile FlexInt `json:"num_mobile"` + NumHandheld FlexInt `json:"num_handheld"` +} + +// Uplink is the Internet connection (or uplink) on a UniFi device. +type Uplink struct { + BytesR FlexInt `json:"bytes-r"` + Drops FlexInt `json:"drops"` + Enable FlexBool `json:"enable,omitempty"` + FullDuplex FlexBool `json:"full_duplex"` + Gateways []string `json:"gateways,omitempty"` + IP string `json:"ip"` + Latency FlexInt `json:"latency"` + Mac string `json:"mac,omitempty"` + MaxSpeed FlexInt `json:"max_speed"` + Name string `json:"name"` + Nameservers []string `json:"nameservers"` + Netmask string `json:"netmask"` + NumPort FlexInt `json:"num_port"` + Media string `json:"media"` + PortIdx FlexInt `json:"port_idx"` + RxBytes FlexInt `json:"rx_bytes"` + RxBytesR FlexInt `json:"rx_bytes-r"` + RxDropped FlexInt `json:"rx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxMulticast FlexInt `json:"rx_multicast"` + RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate"` + Speed FlexInt `json:"speed"` + SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"` + SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"` + SpeedtestStatus string `json:"speedtest_status,omitempty"` + TxBytes FlexInt `json:"tx_bytes"` + TxBytesR FlexInt `json:"tx_bytes-r"` + TxDropped FlexInt `json:"tx_dropped"` + TxErrors FlexInt `json:"tx_errors"` + TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate"` + Type string `json:"type"` + Up FlexBool `json:"up"` + Uptime FlexInt `json:"uptime"` + XputDown FlexInt `json:"xput_down,omitempty"` + XputUp FlexInt `json:"xput_up,omitempty"` +} + +// Wan is a Wan interface on a USG or UDM. +type Wan struct { + Autoneg FlexBool `json:"autoneg"` + BytesR FlexInt `json:"bytes-r"` + DNS []string `json:"dns"` // may be deprecated + Enable FlexBool `json:"enable"` + FlowctrlRx FlexBool `json:"flowctrl_rx"` + FlowctrlTx FlexBool `json:"flowctrl_tx"` + FullDuplex FlexBool `json:"full_duplex"` + Gateway string `json:"gateway"` // may be deprecated + IP string `json:"ip"` + Ifname string `json:"ifname"` + IsUplink FlexBool `json:"is_uplink"` + Mac string `json:"mac"` + MaxSpeed FlexInt `json:"max_speed"` + Media string `json:"media"` + Name string `json:"name"` + Netmask string `json:"netmask"` // may be deprecated + NumPort int `json:"num_port"` + PortIdx int `json:"port_idx"` + PortPoe FlexBool `json:"port_poe"` + RxBroadcast FlexInt `json:"rx_broadcast"` + RxBytes FlexInt `json:"rx_bytes"` + RxBytesR FlexInt `json:"rx_bytes-r"` + RxDropped FlexInt `json:"rx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxMulticast FlexInt `json:"rx_multicast"` + RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate"` + Speed FlexInt `json:"speed"` + SpeedCaps FlexInt `json:"speed_caps"` + TxBroadcast FlexInt `json:"tx_broadcast"` + TxBytes FlexInt `json:"tx_bytes"` + TxBytesR FlexInt `json:"tx_bytes-r"` + TxDropped FlexInt `json:"tx_dropped"` + TxErrors FlexInt `json:"tx_errors"` + TxMulticast FlexInt `json:"tx_multicast"` + TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate"` + Type string `json:"type"` + Up FlexBool `json:"up"` +} + +// SpeedtestStatus is the speed test info on a USG or UDM. +type SpeedtestStatus struct { + Latency FlexInt `json:"latency"` + Rundate FlexInt `json:"rundate"` + Runtime FlexInt `json:"runtime"` + ServerDesc string `json:"server_desc,omitempty"` + Server *SpeedtestServer `json:"server"` + SourceInterface string `json:"source_interface"` + StatusDownload FlexInt `json:"status_download"` + StatusPing FlexInt `json:"status_ping"` + StatusSummary FlexInt `json:"status_summary"` + StatusUpload FlexInt `json:"status_upload"` + XputDownload FlexInt `json:"xput_download"` + XputUpload FlexInt `json:"xput_upload"` +} + +type SpeedtestServer struct { + Cc string `json:"cc"` + City string `json:"city"` + Country string `json:"country"` + Lat FlexInt `json:"lat"` + Lon FlexInt `json:"lon"` + Provider string `json:"provider"` + ProviderURL string `json:"provider_url"` +} + +// ConfigNetwork comes from gateways. +type ConfigNetwork struct { + Type string `json:"type"` + IP string `json:"ip"` +} + +// SystemStats is system info for a UDM, USG, USW. +type SystemStats struct { + CPU FlexInt `json:"cpu"` + Mem FlexInt `json:"mem"` + Uptime FlexInt `json:"uptime"` + // This exists on at least USG4, may others, maybe not. + // {"Board (CPU)":"51 C","Board (PHY)":"51 C","CPU":"72 C","PHY":"77 C"} + Temps map[string]string `json:"temps,omitempty"` +} + +// SysStats is load info for a UDM, USG, USW. +type SysStats struct { + Loadavg1 FlexInt `json:"loadavg_1"` + Loadavg15 FlexInt `json:"loadavg_15"` + Loadavg5 FlexInt `json:"loadavg_5"` + MemBuffer FlexInt `json:"mem_buffer"` + MemTotal FlexInt `json:"mem_total"` + MemUsed FlexInt `json:"mem_used"` +} + +// USGStat holds the "stat" data for a gateway. +// This is split out because of a JSON data format change from 5.10 to 5.11. +type USGStat struct { + *Gw +} + +// Gw is a subtype of USGStat to make unmarshalling of different controller versions possible. +type Gw struct { + SiteID string `json:"site_id"` + O string `json:"o"` + Oid string `json:"oid"` + Gw string `json:"gw"` + Time FlexInt `json:"time"` + Datetime time.Time `json:"datetime"` + Duration FlexInt `json:"duration"` + WanRxPackets FlexInt `json:"wan-rx_packets"` + WanRxBytes FlexInt `json:"wan-rx_bytes"` + WanRxDropped FlexInt `json:"wan-rx_dropped"` + WanTxPackets FlexInt `json:"wan-tx_packets"` + WanTxBytes FlexInt `json:"wan-tx_bytes"` + LanRxPackets FlexInt `json:"lan-rx_packets"` + LanRxBytes FlexInt `json:"lan-rx_bytes"` + LanTxPackets FlexInt `json:"lan-tx_packets"` + LanTxBytes FlexInt `json:"lan-tx_bytes"` + LanRxDropped FlexInt `json:"lan-rx_dropped"` + WanRxErrors FlexInt `json:"wan-rx_errors,omitempty"` + LanRxErrors FlexInt `json:"lan-rx_errors,omitempty"` +} + +// UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Gateway Stat data. +func (v *USGStat) UnmarshalJSON(data []byte) error { + var n struct { + Gw `json:"gw"` + } + + v.Gw = &n.Gw + + err := json.Unmarshal(data, v.Gw) // controller version 5.10. + if err != nil { + return json.Unmarshal(data, &n) // controller version 5.11. + } + + return nil +} diff --git a/core/unifi/usg_test.go b/core/unifi/usg_test.go new file mode 100644 index 00000000..2905d066 --- /dev/null +++ b/core/unifi/usg_test.go @@ -0,0 +1,60 @@ +package unifi // nolint: testpackage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUSGUnmarshalJSON(t *testing.T) { + testcontroller511 := `{ + "gw": { + "site_id": "mySite", + "o": "gw", + "oid": "00:00:00:00:00:00", + "gw": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:50:00Z", + "bytes": 0, + "duration": 3590568000, + "wan-rx_packets": 299729434558, + "wan-rx_bytes": 299882768958208, + "wan-tx_packets": 249639259523, + "wan-tx_bytes": 169183252492369, + "lan-rx_packets": 78912349453, + "lan-rx_bytes": 37599596992669, + "lan-tx_packets": 12991234992, + "lan-tx_bytes": 11794664098210}}` + + testcontroller510 := `{ + "site_id": "mySite", + "o": "gw", + "oid": "00:00:00:00:00:00", + "gw": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:50:00Z", + "bytes": 0, + "duration": 3590568000, + "wan-rx_packets": 299729434558, + "wan-rx_bytes": 299882768958208, + "wan-tx_packets": 249639259523, + "wan-tx_bytes": 169183252492369, + "lan-rx_packets": 78912349453, + "lan-rx_bytes": 37599596992669, + "lan-tx_packets": 12991234992, + "lan-tx_bytes": 11794664098210}` + + t.Parallel() + a := assert.New(t) + + u := &USGStat{} + lanRx := 37599596992669 + err := u.UnmarshalJSON([]byte(testcontroller510)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(lanRx), u.LanRxBytes.Val, "data was not properly unmarshaled") + + u = &USGStat{} // reset + err = u.UnmarshalJSON([]byte(testcontroller511)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(lanRx), u.LanRxBytes.Val, "data was not properly unmarshaled") +} diff --git a/core/unifi/usw.go b/core/unifi/usw.go new file mode 100644 index 00000000..b1533467 --- /dev/null +++ b/core/unifi/usw.go @@ -0,0 +1,400 @@ +package unifi + +import ( + "encoding/json" + "time" +) + +// USW represents all the data from the Ubiquiti Controller for a Unifi Switch. +type USW struct { + site *Site + SourceName string `json:"-"` + SiteName string `json:"-"` + ID string `json:"_id"` + Adopted FlexBool `json:"adopted"` + BoardRev FlexInt `json:"board_rev"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + FlowctrlEnabled FlexBool `json:"flowctrl_enabled"` + FwCaps FlexInt `json:"fw_caps"` + HasFan FlexBool `json:"has_fan"` + HasTemperature FlexBool `json:"has_temperature"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + JumboframeEnabled FlexBool `json:"jumboframe_enabled"` + LedOverride string `json:"led_override"` + LicenseState string `json:"license_state"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + PortOverrides []struct { + Name string `json:"name,omitempty"` + PoeMode string `json:"poe_mode,omitempty"` + PortIdx FlexInt `json:"port_idx"` + PortconfID string `json:"portconf_id"` + } `json:"port_overrides"` + PortTable []Port `json:"port_table"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + StpPriority FlexInt `json:"stp_priority"` + StpVersion string `json:"stp_version"` + Type string `json:"type"` + Version string `json:"version"` + RequiredVersion string `json:"required_version"` + SwitchCaps *SwitchCaps `json:"switch_caps"` + HwCaps FlexInt `json:"hw_caps"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + SysErrorCaps FlexInt `json:"sys_error_caps"` + DeviceID string `json:"device_id"` + State FlexInt `json:"state"` + LastSeen FlexInt `json:"last_seen"` + Upgradable FlexBool `json:"upgradable,omitempty"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded,omitempty"` + Rollupgrade FlexBool `json:"rollupgrade,omitempty"` + KnownCfgversion string `json:"known_cfgversion"` + Uptime FlexInt `json:"uptime"` + Locating FlexBool `json:"locating"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + SysStats SysStats `json:"sys_stats"` + SystemStats SystemStats `json:"system-stats"` + FanLevel FlexInt `json:"fan_level"` + GeneralTemperature FlexInt `json:"general_temperature"` + Overheating FlexBool `json:"overheating"` + TotalMaxPower FlexInt `json:"total_max_power"` + DownlinkTable []*DownlinkTable `json:"downlink_table"` + Uplink Uplink `json:"uplink"` + LastUplink struct { + UplinkMac string `json:"uplink_mac"` + } `json:"last_uplink"` + UplinkDepth FlexInt `json:"uplink_depth"` + Stat USWStat `json:"stat"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + NumSta FlexInt `json:"num_sta"` + UserNumSta FlexInt `json:"user-num_sta"` + GuestNumSta FlexInt `json:"guest-num_sta"` +} + +type SwitchCaps struct { + FeatureCaps FlexInt `json:"feature_caps"` + MaxMirrorSessions FlexInt `json:"max_mirror_sessions"` + MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` +} + +// MacTable is a newer feature on some switched ports. +type MacTable struct { + Age int64 `json:"age"` + Authorized FlexBool `json:"authorized"` + Hostname string `json:"hostname"` + IP string `json:"ip"` + LastReachable int64 `json:"lastReachable"` + Mac string `json:"mac"` +} + +// Port is a physical connection on a USW or Gateway. +// Not every port has the same capabilities. +type Port struct { + AggregatedBy FlexBool `json:"aggregated_by"` + Autoneg FlexBool `json:"autoneg,omitempty"` + BytesR FlexInt `json:"bytes-r"` + DNS []string `json:"dns,omitempty"` + Dot1XMode string `json:"dot1x_mode"` + Dot1XStatus string `json:"dot1x_status"` + Enable FlexBool `json:"enable"` + FlowctrlRx FlexBool `json:"flowctrl_rx"` + FlowctrlTx FlexBool `json:"flowctrl_tx"` + FullDuplex FlexBool `json:"full_duplex"` + IP string `json:"ip,omitempty"` + Ifname string `json:"ifname,omitempty"` + IsUplink FlexBool `json:"is_uplink"` + Mac string `json:"mac,omitempty"` + MacTable []MacTable `json:"mac_table,omitempty"` + Jumbo FlexBool `json:"jumbo,omitempty"` + Masked FlexBool `json:"masked"` + Media string `json:"media"` + Name string `json:"name"` + NetworkName string `json:"network_name,omitempty"` + Netmask string `json:"netmask,omitempty"` + NumPort int `json:"num_port,omitempty"` + OpMode string `json:"op_mode"` + PoeCaps FlexInt `json:"poe_caps"` + PoeClass string `json:"poe_class,omitempty"` + PoeCurrent FlexInt `json:"poe_current,omitempty"` + PoeEnable FlexBool `json:"poe_enable,omitempty"` + PoeGood FlexBool `json:"poe_good,omitempty"` + PoeMode string `json:"poe_mode,omitempty"` + PoePower FlexInt `json:"poe_power,omitempty"` + PoeVoltage FlexInt `json:"poe_voltage,omitempty"` + PortDelta PortDelta `json:"port_delta,omitempty"` + PortIdx FlexInt `json:"port_idx"` + PortPoe FlexBool `json:"port_poe"` + PortconfID string `json:"portconf_id"` + RxBroadcast FlexInt `json:"rx_broadcast"` + RxBytes FlexInt `json:"rx_bytes"` + RxBytesR FlexInt `json:"rx_bytes-r"` + RxDropped FlexInt `json:"rx_dropped"` + RxErrors FlexInt `json:"rx_errors"` + RxMulticast FlexInt `json:"rx_multicast"` + RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate,omitempty"` + Satisfaction FlexInt `json:"satisfaction,omitempty"` + SatisfactionReason FlexInt `json:"satisfaction_reason"` + SFPCompliance string `json:"sfp_compliance"` + SFPCurrent FlexInt `json:"sfp_current"` + SFPFound FlexBool `json:"sfp_found"` + SFPPart string `json:"sfp_part"` + SFPRev string `json:"sfp_rev"` + SFPRxfault FlexBool `json:"sfp_rxfault"` + SFPRxpower FlexInt `json:"sfp_rxpower"` + SFPSerial string `json:"sfp_serial"` + SFPTemperature FlexInt `json:"sfp_temperature"` + SFPTxfault FlexBool `json:"sfp_txfault"` + SFPTxpower FlexInt `json:"sfp_txpower"` + SFPVendor string `json:"sfp_vendor"` + SFPVoltage FlexInt `json:"sfp_voltage"` + Speed FlexInt `json:"speed"` + SpeedCaps FlexInt `json:"speed_caps"` + StpPathcost FlexInt `json:"stp_pathcost"` + StpState string `json:"stp_state"` + TxBroadcast FlexInt `json:"tx_broadcast"` + TxBytes FlexInt `json:"tx_bytes"` + TxBytesR FlexInt `json:"tx_bytes-r"` + TxDropped FlexInt `json:"tx_dropped"` + TxErrors FlexInt `json:"tx_errors"` + TxMulticast FlexInt `json:"tx_multicast"` + TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate,omitempty"` + Type string `json:"type,omitempty"` + Up FlexBool `json:"up"` +} + +// PortDelta is part of a Port. +type PortDelta struct { + TimeDelta FlexInt `json:"time_delta"` + TimeDeltaActivity FlexInt `json:"time_delta_activity"` +} + +// USWStat holds the "stat" data for a switch. +// This is split out because of a JSON data format change from 5.10 to 5.11. +type USWStat struct { + *Sw +} + +// Sw is a subtype of USWStat to make unmarshalling of different controller versions possible. +type Sw struct { + SiteID string `json:"site_id"` + O string `json:"o"` + Oid string `json:"oid"` + Sw string `json:"sw"` + Time FlexInt `json:"time"` + Datetime time.Time `json:"datetime"` + RxPackets FlexInt `json:"rx_packets"` + RxBytes FlexInt `json:"rx_bytes"` + RxErrors FlexInt `json:"rx_errors"` + RxDropped FlexInt `json:"rx_dropped"` + RxCrypts FlexInt `json:"rx_crypts"` + RxFrags FlexInt `json:"rx_frags"` + TxPackets FlexInt `json:"tx_packets"` + TxBytes FlexInt `json:"tx_bytes"` + TxErrors FlexInt `json:"tx_errors"` + TxDropped FlexInt `json:"tx_dropped"` + TxRetries FlexInt `json:"tx_retries"` + RxMulticast FlexInt `json:"rx_multicast"` + RxBroadcast FlexInt `json:"rx_broadcast"` + TxMulticast FlexInt `json:"tx_multicast"` + TxBroadcast FlexInt `json:"tx_broadcast"` + Bytes FlexInt `json:"bytes"` + Duration FlexInt `json:"duration"` + /* These are all in port table */ + /* + Port1RxPackets FlexInt `json:"port_1-rx_packets,omitempty"` + Port1RxBytes FlexInt `json:"port_1-rx_bytes,omitempty"` + Port1TxPackets FlexInt `json:"port_1-tx_packets,omitempty"` + Port1TxBytes FlexInt `json:"port_1-tx_bytes,omitempty"` + Port1TxMulticast FlexInt `json:"port_1-tx_multicast"` + Port1TxBroadcast FlexInt `json:"port_1-tx_broadcast"` + Port3RxPackets FlexInt `json:"port_3-rx_packets,omitempty"` + Port3RxBytes FlexInt `json:"port_3-rx_bytes,omitempty"` + Port3TxPackets FlexInt `json:"port_3-tx_packets,omitempty"` + Port3TxBytes FlexInt `json:"port_3-tx_bytes,omitempty"` + Port3RxBroadcast FlexInt `json:"port_3-rx_broadcast"` + Port3TxMulticast FlexInt `json:"port_3-tx_multicast"` + Port3TxBroadcast FlexInt `json:"port_3-tx_broadcast"` + Port6RxPackets FlexInt `json:"port_6-rx_packets,omitempty"` + Port6RxBytes FlexInt `json:"port_6-rx_bytes,omitempty"` + Port6TxPackets FlexInt `json:"port_6-tx_packets,omitempty"` + Port6TxBytes FlexInt `json:"port_6-tx_bytes,omitempty"` + Port6RxMulticast FlexInt `json:"port_6-rx_multicast"` + Port6TxMulticast FlexInt `json:"port_6-tx_multicast"` + Port6TxBroadcast FlexInt `json:"port_6-tx_broadcast"` + Port7RxPackets FlexInt `json:"port_7-rx_packets,omitempty"` + Port7RxBytes FlexInt `json:"port_7-rx_bytes,omitempty"` + Port7TxPackets FlexInt `json:"port_7-tx_packets,omitempty"` + Port7TxBytes FlexInt `json:"port_7-tx_bytes,omitempty"` + Port7TxMulticast FlexInt `json:"port_7-tx_multicast"` + Port7TxBroadcast FlexInt `json:"port_7-tx_broadcast"` + Port9RxPackets FlexInt `json:"port_9-rx_packets,omitempty"` + Port9RxBytes FlexInt `json:"port_9-rx_bytes,omitempty"` + Port9TxPackets FlexInt `json:"port_9-tx_packets,omitempty"` + Port9TxBytes FlexInt `json:"port_9-tx_bytes,omitempty"` + Port9TxMulticast FlexInt `json:"port_9-tx_multicast"` + Port9TxBroadcast FlexInt `json:"port_9-tx_broadcast"` + Port10RxPackets FlexInt `json:"port_10-rx_packets,omitempty"` + Port10RxBytes FlexInt `json:"port_10-rx_bytes,omitempty"` + Port10TxPackets FlexInt `json:"port_10-tx_packets,omitempty"` + Port10TxBytes FlexInt `json:"port_10-tx_bytes,omitempty"` + Port10RxMulticast FlexInt `json:"port_10-rx_multicast"` + Port10TxMulticast FlexInt `json:"port_10-tx_multicast"` + Port10TxBroadcast FlexInt `json:"port_10-tx_broadcast"` + Port11RxPackets FlexInt `json:"port_11-rx_packets,omitempty"` + Port11RxBytes FlexInt `json:"port_11-rx_bytes,omitempty"` + Port11TxPackets FlexInt `json:"port_11-tx_packets,omitempty"` + Port11TxBytes FlexInt `json:"port_11-tx_bytes,omitempty"` + Port11TxMulticast FlexInt `json:"port_11-tx_multicast"` + Port11TxBroadcast FlexInt `json:"port_11-tx_broadcast"` + Port12RxPackets FlexInt `json:"port_12-rx_packets,omitempty"` + Port12RxBytes FlexInt `json:"port_12-rx_bytes,omitempty"` + Port12TxPackets FlexInt `json:"port_12-tx_packets,omitempty"` + Port12TxBytes FlexInt `json:"port_12-tx_bytes,omitempty"` + Port12TxMulticast FlexInt `json:"port_12-tx_multicast"` + Port12TxBroadcast FlexInt `json:"port_12-tx_broadcast"` + Port13RxPackets FlexInt `json:"port_13-rx_packets,omitempty"` + Port13RxBytes FlexInt `json:"port_13-rx_bytes,omitempty"` + Port13TxPackets FlexInt `json:"port_13-tx_packets,omitempty"` + Port13TxBytes FlexInt `json:"port_13-tx_bytes,omitempty"` + Port13RxMulticast FlexInt `json:"port_13-rx_multicast"` + Port13RxBroadcast FlexInt `json:"port_13-rx_broadcast"` + Port13TxMulticast FlexInt `json:"port_13-tx_multicast"` + Port13TxBroadcast FlexInt `json:"port_13-tx_broadcast"` + Port15RxPackets FlexInt `json:"port_15-rx_packets,omitempty"` + Port15RxBytes FlexInt `json:"port_15-rx_bytes,omitempty"` + Port15TxPackets FlexInt `json:"port_15-tx_packets,omitempty"` + Port15TxBytes FlexInt `json:"port_15-tx_bytes,omitempty"` + Port15RxBroadcast FlexInt `json:"port_15-rx_broadcast"` + Port15TxMulticast FlexInt `json:"port_15-tx_multicast"` + Port15TxBroadcast FlexInt `json:"port_15-tx_broadcast"` + Port16RxPackets FlexInt `json:"port_16-rx_packets,omitempty"` + Port16RxBytes FlexInt `json:"port_16-rx_bytes,omitempty"` + Port16TxPackets FlexInt `json:"port_16-tx_packets,omitempty"` + Port16TxBytes FlexInt `json:"port_16-tx_bytes,omitempty"` + Port16TxMulticast FlexInt `json:"port_16-tx_multicast"` + Port16TxBroadcast FlexInt `json:"port_16-tx_broadcast"` + Port17RxPackets FlexInt `json:"port_17-rx_packets,omitempty"` + Port17RxBytes FlexInt `json:"port_17-rx_bytes,omitempty"` + Port17TxPackets FlexInt `json:"port_17-tx_packets,omitempty"` + Port17TxBytes FlexInt `json:"port_17-tx_bytes,omitempty"` + Port17TxMulticast FlexInt `json:"port_17-tx_multicast"` + Port17TxBroadcast FlexInt `json:"port_17-tx_broadcast"` + Port18RxPackets FlexInt `json:"port_18-rx_packets,omitempty"` + Port18RxBytes FlexInt `json:"port_18-rx_bytes,omitempty"` + Port18TxPackets FlexInt `json:"port_18-tx_packets,omitempty"` + Port18TxBytes FlexInt `json:"port_18-tx_bytes,omitempty"` + Port18RxMulticast FlexInt `json:"port_18-rx_multicast"` + Port18TxMulticast FlexInt `json:"port_18-tx_multicast"` + Port18TxBroadcast FlexInt `json:"port_18-tx_broadcast"` + Port19RxPackets FlexInt `json:"port_19-rx_packets,omitempty"` + Port19RxBytes FlexInt `json:"port_19-rx_bytes,omitempty"` + Port19TxPackets FlexInt `json:"port_19-tx_packets,omitempty"` + Port19TxBytes FlexInt `json:"port_19-tx_bytes,omitempty"` + Port19TxMulticast FlexInt `json:"port_19-tx_multicast"` + Port19TxBroadcast FlexInt `json:"port_19-tx_broadcast"` + Port21RxPackets FlexInt `json:"port_21-rx_packets,omitempty"` + Port21RxBytes FlexInt `json:"port_21-rx_bytes,omitempty"` + Port21TxPackets FlexInt `json:"port_21-tx_packets,omitempty"` + Port21TxBytes FlexInt `json:"port_21-tx_bytes,omitempty"` + Port21RxBroadcast FlexInt `json:"port_21-rx_broadcast"` + Port21TxMulticast FlexInt `json:"port_21-tx_multicast"` + Port21TxBroadcast FlexInt `json:"port_21-tx_broadcast"` + Port22RxPackets FlexInt `json:"port_22-rx_packets,omitempty"` + Port22RxBytes FlexInt `json:"port_22-rx_bytes,omitempty"` + Port22TxPackets FlexInt `json:"port_22-tx_packets,omitempty"` + Port22TxBytes FlexInt `json:"port_22-tx_bytes,omitempty"` + Port22RxMulticast FlexInt `json:"port_22-rx_multicast"` + Port22TxMulticast FlexInt `json:"port_22-tx_multicast"` + Port22TxBroadcast FlexInt `json:"port_22-tx_broadcast"` + Port23RxPackets FlexInt `json:"port_23-rx_packets,omitempty"` + Port23RxBytes FlexInt `json:"port_23-rx_bytes,omitempty"` + Port23RxDropped FlexInt `json:"port_23-rx_dropped"` + Port23TxPackets FlexInt `json:"port_23-tx_packets,omitempty"` + Port23TxBytes FlexInt `json:"port_23-tx_bytes,omitempty"` + Port23RxMulticast FlexInt `json:"port_23-rx_multicast"` + Port23RxBroadcast FlexInt `json:"port_23-rx_broadcast"` + Port23TxMulticast FlexInt `json:"port_23-tx_multicast"` + Port23TxBroadcast FlexInt `json:"port_23-tx_broadcast"` + Port24RxPackets FlexInt `json:"port_24-rx_packets,omitempty"` + Port24RxBytes FlexInt `json:"port_24-rx_bytes,omitempty"` + Port24TxPackets FlexInt `json:"port_24-tx_packets,omitempty"` + Port24TxBytes FlexInt `json:"port_24-tx_bytes,omitempty"` + Port24RxMulticast FlexInt `json:"port_24-rx_multicast"` + Port24TxMulticast FlexInt `json:"port_24-tx_multicast"` + Port24TxBroadcast FlexInt `json:"port_24-tx_broadcast"` + Port1RxMulticast FlexInt `json:"port_1-rx_multicast"` + Port3RxDropped FlexInt `json:"port_3-rx_dropped"` + Port3RxMulticast FlexInt `json:"port_3-rx_multicast"` + Port6RxDropped FlexInt `json:"port_6-rx_dropped"` + Port7RxDropped FlexInt `json:"port_7-rx_dropped"` + Port7RxMulticast FlexInt `json:"port_7-rx_multicast"` + Port9RxDropped FlexInt `json:"port_9-rx_dropped"` + Port9RxMulticast FlexInt `json:"port_9-rx_multicast"` + Port9RxBroadcast FlexInt `json:"port_9-rx_broadcast"` + Port10RxBroadcast FlexInt `json:"port_10-rx_broadcast"` + Port12RxDropped FlexInt `json:"port_12-rx_dropped"` + Port12RxMulticast FlexInt `json:"port_12-rx_multicast"` + Port13RxDropped FlexInt `json:"port_13-rx_dropped"` + Port17RxDropped FlexInt `json:"port_17-rx_dropped"` + Port17RxMulticast FlexInt `json:"port_17-rx_multicast"` + Port17RxBroadcast FlexInt `json:"port_17-rx_broadcast"` + Port19RxDropped FlexInt `json:"port_19-rx_dropped"` + Port19RxMulticast FlexInt `json:"port_19-rx_multicast"` + Port19RxBroadcast FlexInt `json:"port_19-rx_broadcast"` + Port21RxDropped FlexInt `json:"port_21-rx_dropped"` + Port21RxMulticast FlexInt `json:"port_21-rx_multicast"` + Port7RxBroadcast FlexInt `json:"port_7-rx_broadcast"` + Port18RxBroadcast FlexInt `json:"port_18-rx_broadcast"` + Port16RxMulticast FlexInt `json:"port_16-rx_multicast"` + Port15RxDropped FlexInt `json:"port_15-rx_dropped"` + Port15RxMulticast FlexInt `json:"port_15-rx_multicast"` + Port16RxBroadcast FlexInt `json:"port_16-rx_broadcast"` + Port11RxBroadcast FlexInt `json:"port_11-rx_broadcast"` + Port12RxBroadcast FlexInt `json:"port_12-rx_broadcast"` + Port6RxBroadcast FlexInt `json:"port_6-rx_broadcast"` + Port24RxBroadcast FlexInt `json:"port_24-rx_broadcast"` + Port22RxBroadcast FlexInt `json:"port_22-rx_broadcast"` + Port10TxDropped FlexInt `json:"port_10-tx_dropped"` + Port16TxDropped FlexInt `json:"port_16-tx_dropped"` + Port1RxBroadcast FlexInt `json:"port_1-rx_broadcast"` + Port4RxPackets FlexInt `json:"port_4-rx_packets,omitempty"` + Port4RxBytes FlexInt `json:"port_4-rx_bytes,omitempty"` + Port4RxDropped FlexInt `json:"port_4-rx_dropped"` + Port4TxPackets FlexInt `json:"port_4-tx_packets,omitempty"` + Port4TxBytes FlexInt `json:"port_4-tx_bytes,omitempty"` + Port4TxDropped FlexInt `json:"port_4-tx_dropped"` + Port4RxMulticast FlexInt `json:"port_4-rx_multicast"` + Port4RxBroadcast FlexInt `json:"port_4-rx_broadcast"` + Port4TxMulticast FlexInt `json:"port_4-tx_multicast"` + Port4TxBroadcast FlexInt `json:"port_4-tx_broadcast"` + */ +} + +// UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Switch Stat data. +func (v *USWStat) UnmarshalJSON(data []byte) error { + var n struct { + Sw `json:"sw"` + } + + v.Sw = &n.Sw + + err := json.Unmarshal(data, v.Sw) // controller version 5.10. + if err != nil { + return json.Unmarshal(data, &n) // controller version 5.11. + } + + return nil +} diff --git a/core/unifi/usw_test.go b/core/unifi/usw_test.go new file mode 100644 index 00000000..4ba1eb5e --- /dev/null +++ b/core/unifi/usw_test.go @@ -0,0 +1,75 @@ +package unifi // nolint: testpackage + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func testGetControllerJSON() (string, string) { + return `{ + "sw": { + "site_id": "mySite", + "o": "sw", + "oid": "00:00:00:00:00:00", + "sw": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:40:00Z", + "rx_packets": 321, + "rx_bytes": 321, + "rx_errors": 123, + "rx_dropped": 123, + "rx_crypts": 123, + "rx_frags": 123, + "tx_packets": 123, + "tx_bytes": 123, + "tx_errors": 0, + "tx_dropped": 0, + "tx_retries": 0, + "rx_multicast": 123, + "rx_broadcast": 123, + "tx_multicast": 123, + "tx_broadcast": 123, + "bytes": 123, + "duration": 123}}`, + `{ + "site_id": "mySite", + "o": "sw", + "oid": "00:00:00:00:00:00", + "sw": "00:00:00:00:00:00", + "time": 1577742600000, + "datetime": "2019-12-30T09:40:00Z", + "rx_packets": 321, + "rx_bytes": 321, + "rx_errors": 123, + "rx_dropped": 123, + "rx_crypts": 123, + "rx_frags": 123, + "tx_packets": 123, + "tx_bytes": 123, + "tx_errors": 0, + "tx_dropped": 0, + "tx_retries": 0, + "rx_multicast": 123, + "rx_broadcast": 123, + "tx_multicast": 123, + "tx_broadcast": 123, + "bytes": 123, + "duration": 123}` +} + +func TestUSWUnmarshalJSON(t *testing.T) { + t.Parallel() + a := assert.New(t) + testcontroller511, testcontroller510 := testGetControllerJSON() + rxMulticast := 123 + u := &USWStat{} + err := u.UnmarshalJSON([]byte(testcontroller510)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(rxMulticast), u.RxMulticast.Val, "data was not properly unmarshaled") + + u = &USWStat{} // reset + err = u.UnmarshalJSON([]byte(testcontroller511)) + a.Nil(err, "must be no error unmarshaling test strings") + a.Equal(float64(rxMulticast), u.RxMulticast.Val, "data was not properly unmarshaled") +} diff --git a/core/unifi/uxg.go b/core/unifi/uxg.go new file mode 100644 index 00000000..ed688718 --- /dev/null +++ b/core/unifi/uxg.go @@ -0,0 +1,158 @@ +package unifi + +// UXG represents all the data from the Ubiquiti Controller for a UniFi 10Gb Gateway. +// The UDM shares several structs/type-data with USW and USG. +type UXG struct { + site *Site + SourceName string `json:"-"` + SiteName string `json:"-"` + ID string `json:"_id"` + IP string `json:"ip"` + Mac string `json:"mac"` + Model string `json:"model"` + ModelInLts FlexBool `json:"model_in_lts"` + ModelInEol FlexBool `json:"model_in_eol"` + Type string `json:"type"` + Version string `json:"version"` + Adopted FlexBool `json:"adopted"` + SiteID string `json:"site_id"` + Cfgversion string `json:"cfgversion"` + SyslogKey string `json:"syslog_key"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + SetupID string `json:"setup_id"` + LicenseState string `json:"license_state"` + ConfigNetworkLan *ConfigNetworkLan `json:"config_network_lan"` + InformURL string `json:"inform_url"` + InformIP string `json:"inform_ip"` + RequiredVersion string `json:"required_version"` + KernelVersion string `json:"kernel_version"` + Architecture string `json:"architecture"` + BoardRev FlexInt `json:"board_rev"` + ManufacturerID FlexInt `json:"manufacturer_id"` + Internet FlexBool `json:"internet"` + ModelIncompatible FlexBool `json:"model_incompatible"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + PortTable []Port `json:"port_table"` + EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"` + UsgCaps FlexInt `json:"usg_caps"` + HasSpeaker FlexBool `json:"has_speaker"` + HasEth1 FlexBool `json:"has_eth1"` + FwCaps FlexInt `json:"fw_caps"` + HwCaps FlexInt `json:"hw_caps"` + WifiCaps FlexInt `json:"wifi_caps"` + SwitchCaps *SwitchCaps `json:"switch_caps"` + HasFan FlexBool `json:"has_fan"` + HasTemperature FlexBool `json:"has_temperature"` + Temperatures []Temperature `json:"temperatures"` + Storage []*Storage `json:"storage"` + RulesetInterfaces interface{} `json:"ruleset_interfaces"` + ConnectedAt FlexInt `json:"connected_at"` + ProvisionedAt FlexInt `json:"provisioned_at"` + LedOverride string `json:"led_override"` + LedOverrideColor string `json:"led_override_color"` + LedOverrideColorBrightness FlexInt `json:"led_override_color_brightness"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + LcmBrightnessOverride FlexBool `json:"lcm_brightness_override"` + LcmIdleTimeoutOverride FlexBool `json:"lcm_idle_timeout_override"` + Name string `json:"name"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + Serial string `json:"serial"` + HashID string `json:"hash_id"` + TwoPhaseAdopt FlexBool `json:"two_phase_adopt"` + DeviceID string `json:"device_id"` + State FlexInt `json:"state"` + StartDisconnectedMillis FlexInt `json:"start_disconnected_millis"` + UpgradeState FlexInt `json:"upgrade_state"` + StartConnectedMillis FlexInt `json:"start_connected_millis"` + LastSeen FlexInt `json:"last_seen"` + Uptime FlexInt `json:"uptime"` + UnderscoreUptime FlexInt `json:"_uptime"` + Locating FlexBool `json:"locating"` + SysStats SysStats `json:"sys_stats"` + SystemStats SystemStats `json:"system-stats"` + GuestKicks FlexInt `json:"guest_kicks"` + GuestToken string `json:"guest_token"` + UptimeStats map[string]*UptimeStats `json:"uptime_stats"` + Overheating FlexBool `json:"overheating"` + GeoInfo map[string]*GeoInfo `json:"geo_info"` + LedState *LedState `json:"led_state"` + SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` + SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` + Wan1 Wan `json:"wan1"` + Wan2 Wan `json:"wan2"` + Uplink Uplink `json:"uplink"` + DownlinkTable []*DownlinkTable `json:"downlink_table"` + NetworkTable NetworkTable `json:"network_table"` + KnownCfgversion string `json:"known_cfgversion"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + NextInterval FlexInt `json:"next_interval"` + NextHeartbeatAt FlexInt `json:"next_heartbeat_at"` + ConsideredLostAt FlexInt `json:"considered_lost_at"` + Stat *UXGStat `json:"stat"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + NumSta FlexInt `json:"num_sta"` + WlanNumSta FlexInt `json:"wlan-num_sta"` + LanNumSta FlexInt `json:"lan-num_sta"` + UserWlanNumSta FlexInt `json:"user-wlan-num_sta"` + UserLanNumSta FlexInt `json:"user-lan-num_sta"` + UserNumSta FlexInt `json:"user-num_sta"` + GuestWlanNumSta FlexInt `json:"guest-wlan-num_sta"` + GuestLanNumSta FlexInt `json:"guest-lan-num_sta"` + GuestNumSta FlexInt `json:"guest-num_sta"` + NumDesktop FlexInt `json:"num_desktop"` + NumMobile FlexInt `json:"num_mobile"` + NumHandheld FlexInt `json:"num_handheld"` +} + +// ConfigNetworkLan is part of a UXG, maybe others. +type ConfigNetworkLan struct { + DhcpEnabled FlexBool `json:"dhcp_enabled"` + Vlan int `json:"vlan"` +} + +// DownlinkTable is part of a UXG and UDM output. +type DownlinkTable struct { + PortIdx FlexInt `json:"port_idx"` + Speed FlexInt `json:"speed"` + FullDuplex FlexBool `json:"full_duplex"` + Mac string `json:"mac"` +} + +// LedState is incuded with newer devices. +type LedState struct { + Pattern string `json:"pattern"` + Tempo FlexInt `json:"tempo"` +} + +// GeoInfo is incuded with certain devices. +type GeoInfo struct { + Accuracy FlexInt `json:"accuracy"` + Address string `json:"address"` + Asn FlexInt `json:"asn"` + City string `json:"city"` + ContinentCode string `json:"continent_code"` + CountryCode string `json:"country_code"` + CountryName string `json:"country_name"` + IspName string `json:"isp_name"` + IspOrganization string `json:"isp_organization"` + Latitude FlexInt `json:"latitude"` + Longitude FlexInt `json:"longitude"` + Timezone string `json:"timezone"` +} + +// UptimeStats is incuded with certain devices. +type UptimeStats struct { + Availability FlexInt `json:"availability"` + LatencyAverage FlexInt `json:"latency_average"` + TimePeriod FlexInt `json:"time_period"` +} + +// UXGStat holds the "stat" data for a 10Gb gateway. +type UXGStat struct { + *Gw `json:"gw"` + *Sw `json:"sw"` +}