From 7f57cae94e88618354974a3b258331f13cb1e6f7 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Wed, 23 Jan 2019 18:14:23 -0800 Subject: [PATCH] Initial library import. --- core/unifi/README.md | 12 ++ core/unifi/clients.go | 104 ++++++++++ core/unifi/clients_type.go | 88 ++++++++ core/unifi/uap.go | 266 ++++++++++++++++++++++++ core/unifi/uap_type.go | 361 ++++++++++++++++++++++++++++++++ core/unifi/unidev.go | 105 ++++++++++ core/unifi/unidev_test.go | 82 ++++++++ core/unifi/unifi.go | 154 ++++++++++++++ core/unifi/usg.go | 186 +++++++++++++++++ core/unifi/usg_type.go | 260 +++++++++++++++++++++++ core/unifi/usw.go | 118 +++++++++++ core/unifi/usw_type.go | 415 +++++++++++++++++++++++++++++++++++++ 12 files changed, 2151 insertions(+) create mode 100644 core/unifi/README.md create mode 100644 core/unifi/clients.go create mode 100644 core/unifi/clients_type.go create mode 100644 core/unifi/uap.go create mode 100644 core/unifi/uap_type.go create mode 100644 core/unifi/unidev.go create mode 100644 core/unifi/unidev_test.go create mode 100644 core/unifi/unifi.go create mode 100644 core/unifi/usg.go create mode 100644 core/unifi/usg_type.go create mode 100644 core/unifi/usw.go create mode 100644 core/unifi/usw_type.go diff --git a/core/unifi/README.md b/core/unifi/README.md new file mode 100644 index 00000000..20c723db --- /dev/null +++ b/core/unifi/README.md @@ -0,0 +1,12 @@ +# 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 included asset interface currently only works for InfluxDB but could +probably be modified to support other output mechanisms; not sure really. + +Pull requests and feedback are welcomed! + +This lib is rudimentary and gets a job done for the tool at hand. It could be +used to base your own library. Good luck! diff --git a/core/unifi/clients.go b/core/unifi/clients.go new file mode 100644 index 00000000..f4694537 --- /dev/null +++ b/core/unifi/clients.go @@ -0,0 +1,104 @@ +package unidev + +import ( + "strconv" + "time" + + influx "github.com/influxdata/influxdb/client/v2" +) + +// Points generates a client's datapoints for InfluxDB. +func (u UCL) Points() ([]*influx.Point, error) { + var points []*influx.Point + if u.Name == "" && u.Hostname != "" { + u.Name = u.Hostname + } else if u.Hostname == "" && u.Name != "" { + u.Hostname = u.Name + } else if u.Hostname == "" && u.Name == "" { + u.Hostname = "-no-name-" + u.Name = "-no-name-" + } + tags := map[string]string{ + "id": u.ID, + "mac": u.Mac, + "user_id": u.UserID, + "site_id": u.SiteID, + "network_id": u.NetworkID, + "usergroup_id": u.UserGroupID, + "ap_mac": u.ApMac, + "gw_mac": u.GwMac, + "sw_mac": u.SwMac, + "oui": u.Oui, + "radio_name": u.RadioName, + "radio": u.Radio, + "radio_proto": u.RadioProto, + "name": u.Name, + "fixed_ip": u.FixedIP, + "sw_port": strconv.Itoa(u.SwPort), + "os_class": strconv.Itoa(u.OsClass), + "os_name": strconv.Itoa(u.OsName), + "dev_cat": strconv.Itoa(u.DevCat), + "dev_id": strconv.Itoa(u.DevID), + "dev_family": strconv.Itoa(u.DevFamily), + "authorized": strconv.FormatBool(u.Authorized), + "is_11r": strconv.FormatBool(u.Is11R), + "is_wired": strconv.FormatBool(u.IsWired), + "is_guest": strconv.FormatBool(u.IsGuest), + "is_guest_by_uap": strconv.FormatBool(u.IsGuestByUAP), + "is_guest_by_ugw": strconv.FormatBool(u.IsGuestByUGW), + "is_guest_by_usw": strconv.FormatBool(u.IsGuestByUSW), + "noted": strconv.FormatBool(u.Noted), + "powersave_enabled": strconv.FormatBool(u.PowersaveEnabled), + "qos_policy_applied": strconv.FormatBool(u.QosPolicyApplied), + "use_fixedip": strconv.FormatBool(u.UseFixedIP), + "channel": strconv.Itoa(u.Channel), + "vlan": strconv.Itoa(u.Vlan), + } + fields := map[string]interface{}{ + "ip": u.IP, + "essid": u.Essid, + "bssid": u.Bssid, + "hostname": u.Hostname, + "dpi_stats_last_updated": u.DpiStatsLastUpdated, + "last_seen_by_uap": u.LastSeenByUAP, + "last_seen_by_ugw": u.LastSeenByUGW, + "last_seen_by_usw": u.LastSeenByUSW, + "uptime_by_uap": u.UptimeByUAP, + "uptime_by_ugw": u.UptimeByUGW, + "uptime_by_usw": u.UptimeByUSW, + "assoc_time": u.AssocTime, + "bytes_r": u.BytesR, + "ccq": u.Ccq, + "first_seen": u.FirstSeen, + "idle_time": u.IdleTime, + "last_seen": u.LastSeen, + "latest_assoc_time": u.LatestAssocTime, + "network": u.Network, + "noise": u.Noise, + "note": u.Note, + "roam_count": u.RoamCount, + "rssi": u.Rssi, + "rx_bytes": u.RxBytes, + "rx_bytes_r": u.RxBytesR, + "rx_packets": u.RxPackets, + "rx_rate": u.RxRate, + "signal": u.Signal, + "tx_bytes": u.TxBytes, + "tx_bytes_r": u.TxBytesR, + "tx_packets": u.TxPackets, + "tx_power": u.TxPower, + "tx_rate": u.TxRate, + "uptime": u.Uptime, + "wired-rx_bytes": u.WiredRxBytes, + "wired-rx_bytes-r": u.WiredRxBytesR, + "wired-rx_packets": u.WiredRxPackets, + "wired-tx_bytes": u.WiredTxBytes, + "wired-tx_bytes-r": u.WiredTxBytesR, + "wired-tx_packets": u.WiredTxPackets, + } + pt, err := influx.NewPoint("clients", tags, fields, time.Now()) + if err == nil { + points = append(points, pt) + } + return points, err +} diff --git a/core/unifi/clients_type.go b/core/unifi/clients_type.go new file mode 100644 index 00000000..45ddf1dd --- /dev/null +++ b/core/unifi/clients_type.go @@ -0,0 +1,88 @@ +package unidev + +// UCL defines all the data a connected-network client contains. +type UCL struct { + ID string `json:"_id"` + IsGuestByUAP bool `json:"_is_guest_by_uap"` + IsGuestByUGW bool `json:"_is_guest_by_ugw"` + IsGuestByUSW bool `json:"_is_guest_by_usw"` + LastSeenByUAP int64 `json:"_last_seen_by_uap"` + LastSeenByUGW int64 `json:"_last_seen_by_ugw"` + LastSeenByUSW int64 `json:"_last_seen_by_usw"` + UptimeByUAP int64 `json:"_uptime_by_uap"` + UptimeByUGW int64 `json:"_uptime_by_ugw"` + UptimeByUSW int64 `json:"_uptime_by_usw"` + ApMac string `json:"ap_mac"` + AssocTime int64 `json:"assoc_time"` + Authorized bool `json:"authorized"` + Bssid string `json:"bssid"` + BytesR int64 `json:"bytes-r"` + Ccq int64 `json:"ccq"` + Channel int `json:"channel"` + DevCat int `json:"dev_cat"` + DevFamily int `json:"dev_family"` + DevID int `json:"dev_id"` + DpiStats struct { + App int64 + Cat int64 + RxBytes int64 + RxPackets int64 + TxBytes int64 + TxPackets int64 + } `json:"dpi_stats"` + DpiStatsLastUpdated int64 `json:"dpi_stats_last_updated"` + Essid string `json:"essid"` + FirstSeen int64 `json:"first_seen"` + FixedIP string `json:"fixed_ip"` + Hostname string `json:"hostname"` + GwMac string `json:"gw_mac"` + IdleTime int64 `json:"idle_time"` + IP string `json:"ip"` + Is11R bool `json:"is_11r"` + IsGuest bool `json:"is_guest"` + IsWired bool `json:"is_wired"` + LastSeen int64 `json:"last_seen"` + LatestAssocTime int64 `json:"latest_assoc_time"` + Mac string `json:"mac"` + Name string `json:"name"` + Network string `json:"network"` + NetworkID string `json:"network_id"` + Noise int64 `json:"noise"` + Note string `json:"note"` + Noted bool `json:"noted"` + OsClass int `json:"os_class"` + OsName int `json:"os_name"` + Oui string `json:"oui"` + PowersaveEnabled bool `json:"powersave_enabled"` + QosPolicyApplied bool `json:"qos_policy_applied"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + RadioProto string `json:"radio_proto"` + RoamCount int64 `json:"roam_count"` + Rssi int64 `json:"rssi"` + RxBytes int64 `json:"rx_bytes"` + RxBytesR int64 `json:"rx_bytes-r"` + RxPackets int64 `json:"rx_packets"` + RxRate int64 `json:"rx_rate"` + Signal int64 `json:"signal"` + SiteID string `json:"site_id"` + SwDepth int `json:"sw_depth"` + SwMac string `json:"sw_mac"` + SwPort int `json:"sw_port"` + TxBytes int64 `json:"tx_bytes"` + TxBytesR int64 `json:"tx_bytes-r"` + TxPackets int64 `json:"tx_packets"` + TxPower int64 `json:"tx_power"` + TxRate int64 `json:"tx_rate"` + Uptime int64 `json:"uptime"` + UserID string `json:"user_id"` + UserGroupID string `json:"usergroup_id"` + UseFixedIP bool `json:"use_fixedip"` + Vlan int `json:"vlan"` + 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/uap.go b/core/unifi/uap.go new file mode 100644 index 00000000..d388506c --- /dev/null +++ b/core/unifi/uap.go @@ -0,0 +1,266 @@ +package unidev + +import ( + "strconv" + "time" + + influx "github.com/influxdata/influxdb/client/v2" +) + +// Points generates a device's datapoints for InfluxDB. +func (u UAP) Points() ([]*influx.Point, error) { + /* I generally suck at InfluxDB, so if I got the tags/fields wrong, + please send me a PR or open an Issue to address my faults. Thanks! + */ + var points []*influx.Point + tags := map[string]string{ + "id": u.ID, + "mac": u.Mac, + "device_type": u.Stat.O, + "device_oid": u.Stat.Oid, + "device_ap": u.Stat.Ap, + "site_id": u.SiteID, + "name": u.Name, + "addopted": strconv.FormatBool(u.Adopted), + "bandsteering_mode": u.BandsteeringMode, + "board_rev": strconv.Itoa(u.BoardRev), + "cfgversion": u.Cfgversion, + "config_network_ip": u.ConfigNetwork.IP, + "config_network_type": u.ConfigNetwork.Type, + "connect_request_ip": u.ConnectRequestIP, + "connect_request_port": u.ConnectRequestPort, + "default": strconv.FormatBool(u.Default), + "device_id": u.DeviceID, + "discovered_via": u.DiscoveredVia, + "fw_caps": strconv.Itoa(u.FwCaps), + "guest-num_sta": strconv.Itoa(u.GuestNumSta), + "guest_token": u.GuestToken, + "has_eth1": strconv.FormatBool(u.HasEth1), + "has_speaker": strconv.FormatBool(u.HasSpeaker), + "inform_ip": u.InformIP, + "isolated": strconv.FormatBool(u.Isolated), + "last_uplink_mac": u.LastUplink.UplinkMac, + "last_uplink_remote_port": strconv.Itoa(u.LastUplink.UplinkRemotePort), + "known_cfgversion": u.KnownCfgversion, + "led_override": u.LedOverride, + "locating": strconv.FormatBool(u.Locating), + "model": u.Model, + "outdoor_mode_override": u.OutdoorModeOverride, + "serial": u.Serial, + "type": u.Type, + "version_incompatible": strconv.FormatBool(u.VersionIncompatible), + "vwireEnabled": strconv.FormatBool(u.VwireEnabled), + "wifi_caps": strconv.Itoa(u.WifiCaps), + } + fields := map[string]interface{}{ + "ip": u.IP, + "bytes": u.Bytes, + "bytes_d": u.BytesD, + "bytes_r": u.BytesR, + "last_seen": u.LastSeen, + "rx_bytes": u.RxBytes, + "rx_bytes-d": u.RxBytesD, + "tx_bytes": u.TxBytes, + "tx_bytes-d": u.TxBytesD, + "uptime": u.Uptime.Number, + "considered_lost_at": u.ConsideredLostAt, + "next_heartbeat_at": u.NextHeartbeatAt, + "scanning": u.Scanning, + "spectrum_scanning": u.SpectrumScanning, + "roll_upgrade": u.Rollupgrade, + "state": u.State, + "upgradable": u.Upgradable, + "user-num_sta": u.UserNumSta, + "version": u.Version, + "loadavg_1": u.SysStats.Loadavg1, + "loadavg_5": u.SysStats.Loadavg5, + "loadavg_15": u.SysStats.Loadavg15, + "mem_buffer": u.SysStats.MemBuffer, + "mem_total": u.SysStats.MemTotal, + "mem_used": u.SysStats.MemUsed, + "cpu": u.SystemStats.CPU, + "mem": u.SystemStats.Mem, + "system_uptime": u.SystemStats.Uptime, + "stat_bytes": u.Stat.Bytes, + "stat_duration": u.Stat.Duration, + "stat_guest-rx_bytes": u.Stat.RxBytes, + "stat_guest-rx_crypts": u.Stat.RxCrypts, + "stat_guest-rx_dropped": u.Stat.RxDropped, + "stat_guest-rx_errors": u.Stat.RxErrors, + "stat_guest-rx_frags": u.Stat.RxFrags, + "stat_guest-rx_packets": u.Stat.RxPackets, + "stat_guest-tx_bytes": u.Stat.TxBytes, + "stat_guest-tx_dropped": u.Stat.TxDropped, + "stat_guest-tx_errors": u.Stat.TxErrors, + "stat_guest-tx_packets": u.Stat.TxPackets, + "stat_guest-tx_retries": u.Stat.TxRetries, + "stat_port_1-rx_broadcast": u.Stat.Port1RxBroadcast, + "stat_port_1-rx_bytes": u.Stat.Port1RxBytes, + "stat_port_1-rx_multicast": u.Stat.Port1RxMulticast, + "stat_port_1-rx_packets": u.Stat.Port1RxPackets, + "stat_port_1-tx_broadcast": u.Stat.Port1TxBroadcast, + "stat_port_1-tx_bytes": u.Stat.Port1TxBytes, + "stat_port_1-tx_multicast": u.Stat.Port1TxMulticast, + "stat_port_1-tx_packets": u.Stat.Port1TxPackets, + "stat_rx_bytes": u.Stat.RxBytes, + "stat_rx_crypts": u.Stat.RxCrypts, + "stat_rx_dropped": u.Stat.RxDropped, + "stat_rx_errors": u.Stat.RxErrors, + "stat_rx_frags": u.Stat.RxFrags, + "stat_rx_packets": u.Stat.TxPackets, + "stat_tx_bytes": u.Stat.TxBytes, + "stat_tx_dropped": u.Stat.TxDropped, + "stat_tx_errors": u.Stat.TxErrors, + "stat_tx_packets": u.Stat.TxPackets, + "stat_tx_retries": u.Stat.TxRetries, + "stat_user-rx_bytes": u.Stat.UserRxBytes, + "stat_user-rx_crypts": u.Stat.UserRxCrypts, + "stat_user-rx_dropped": u.Stat.UserRxDropped, + "stat_user-rx_errors": u.Stat.UserRxErrors, + "stat_user-rx_frags": u.Stat.UserRxFrags, + "stat_user-rx_packets": u.Stat.UserRxPackets, + "stat_user-tx_bytes": u.Stat.UserTxBytes, + "stat_user-tx_dropped": u.Stat.UserTxDropped, + "stat_user-tx_errors": u.Stat.UserTxErrors, + "stat_user-tx_packets": u.Stat.UserTxPackets, + "stat_user-tx_retries": u.Stat.UserTxRetries, + "stat_user-wifi0-rx_bytes": u.Stat.UserWifi0RxBytes, + "stat_user-wifi0-rx_crypts": u.Stat.UserWifi0RxCrypts, + "stat_user-wifi0-rx_dropped": u.Stat.UserWifi0RxDropped, + "stat_user-wifi0-rx_errors": u.Stat.UserWifi0RxErrors, + "stat_user-wifi0-rx_frags": u.Stat.UserWifi0RxFrags, + "stat_user-wifi0-rx_packets": u.Stat.UserWifi0RxPackets, + "stat_user-wifi0-tx_bytes": u.Stat.UserWifi0TxBytes, + "stat_user-wifi0-tx_dropped": u.Stat.UserWifi0TxDropped, + "stat_user-wifi0-tx_errors": u.Stat.UserWifi0TxErrors, + "stat_user-wifi0-tx_packets": u.Stat.UserWifi0TxPackets, + "stat_user-wifi0-tx_retries": u.Stat.UserWifi0TxRetries, + "stat_user-wifi1-rx_bytes": u.Stat.UserWifi1RxBytes, + "stat_user-wifi1-rx_crypts": u.Stat.UserWifi1RxCrypts, + "stat_user-wifi1-rx_dropped": u.Stat.UserWifi1RxDropped, + "stat_user-wifi1-rx_errors": u.Stat.UserWifi1RxErrors, + "stat_user-wifi1-rx_frags": u.Stat.UserWifi1RxFrags, + "stat_user-wifi1-rx_packets": u.Stat.UserWifi1RxPackets, + "stat_user-wifi1-tx_bytes": u.Stat.UserWifi1TxBytes, + "stat_user-wifi1-tx_dropped": u.Stat.UserWifi1TxDropped, + "stat_user-wifi1-tx_errors": u.Stat.UserWifi1TxErrors, + "stat_user-wifi1-tx_packets": u.Stat.UserWifi1TxPackets, + "stat_user-wifi1-tx_retries": u.Stat.UserWifi1TxRetries, + "stat_wifi0-rx_bytes": u.Stat.Wifi0RxBytes, + "stat_wifi0-rx_crypts": u.Stat.Wifi0RxCrypts, + "stat_wifi0-rx_dropped": u.Stat.Wifi0RxDropped, + "stat_wifi0-rx_errors": u.Stat.Wifi0RxErrors, + "stat_wifi0-rx_frags": u.Stat.Wifi0RxFrags, + "stat_wifi0-rx_packets": u.Stat.Wifi0RxPackets, + "stat_wifi0-tx_bytes": u.Stat.Wifi0TxBytes, + "stat_wifi0-tx_dropped": u.Stat.Wifi0TxDropped, + "stat_wifi0-tx_errors": u.Stat.Wifi0TxErrors, + "stat_wifi0-tx_packets": u.Stat.Wifi0TxPackets, + "stat_wifi0-tx_retries": u.Stat.Wifi0TxRetries, + "stat_wifi1-rx_bytes": u.Stat.Wifi1RxBytes, + "stat_wifi1-rx_crypts": u.Stat.Wifi1RxCrypts, + "stat_wifi1-rx_dropped": u.Stat.Wifi1RxDropped, + "stat_wifi1-rx_errors": u.Stat.Wifi1RxErrors, + "stat_wifi1-rx_frags": u.Stat.Wifi1RxFrags, + "stat_wifi1-rx_packets": u.Stat.Wifi1RxPackets, + "stat_wifi1-tx_bytes": u.Stat.Wifi1TxBytes, + "stat_wifi1-tx_dropped": u.Stat.Wifi1TxDropped, + "stat_wifi1-tx_errors": u.Stat.Wifi1TxErrors, + "stat_wifi1-tx_packets": u.Stat.Wifi1TxPackets, + "stat_wifi1-tx_retries": u.Stat.Wifi1TxRetries, + } + pt, err := influx.NewPoint("uap", tags, fields, time.Now()) + if err != nil { + return nil, err + } + points = append(points, pt) + for _, p := range u.RadioTable { + tags := map[string]string{ + "device_name": u.Name, + "device_id": u.ID, + "device_mac": u.Mac, + "name": p.Name, + "wlangroup_id": p.WlangroupID, + "channel": p.Channel.String, + "radio": p.Radio, + } + fields := map[string]interface{}{ + "builtin_ant_gain": p.BuiltinAntGain, + "current_antenna_gain": p.CurrentAntennaGain, + "has_dfs": p.HasDfs, + "has_fccdfs": p.HasFccdfs, + "ht": p.Ht, + "is_11ac": p.Is11Ac, + "max_txpower": p.MaxTxpower, + "min_rssi_enabled": p.MinRssiEnabled, + "min_txpower": p.MinTxpower, + "nss": p.Nss, + "radio_caps": p.RadioCaps, + "tx_power": p.TxPower.Number, + "tx_power_mode": p.TxPowerMode, + } + + for _, s := range u.RadioTableStats { + // This may be a tad slower but it allows putting + // all the radio stats into one table. + if p.Name == s.Name { + fields["ast_be_xmit"] = s.AstBeXmit + fields["ast_cst"] = s.AstCst + fields["channel"] = s.Channel + fields["ast_txto"] = s.AstTxto + fields["cu_self_rx"] = s.CuSelfRx + fields["cu_self_tx"] = s.CuSelfTx + fields["cu_total"] = s.CuTotal + fields["extchannel"] = s.Extchannel + fields["gain"] = s.Gain + fields["guest-num_sta"] = s.GuestNumSta + fields["num_sta"] = s.NumSta + fields["radio"] = s.Radio + fields["state"] = s.State + fields["radio_tx_packets"] = s.TxPackets + fields["radio_tx_power"] = s.TxPower + fields["radio_tx_retries"] = s.TxRetries + fields["user-num_sta"] = s.UserNumSta + } + } + for _, s := range u.VapTable { + if p.Name == s.RadioName { + tags["ap_mac"] = s.ApMac + tags["bssid"] = s.Bssid + fields["ccq"] = s.Ccq + fields["essid"] = s.Essid + fields["extchannel"] = s.Extchannel + tags["vap_id"] = s.ID + fields["is_guest"] = s.IsGuest + fields["is_wep"] = s.IsWep + fields["mac_filter_rejections"] = s.MacFilterRejections + fields["map_id"] = s.MapID + tags["vap_name"] = s.Name + fields["rx_bytes"] = s.RxBytes + fields["rx_crypts"] = s.RxCrypts + fields["rx_dropped"] = s.RxDropped + fields["rx_errors"] = s.RxErrors + fields["rx_frags"] = s.RxFrags + fields["rx_nwids"] = s.RxNwids + fields["rx_packets"] = s.RxPackets + fields["tx_bytes"] = s.TxBytes + fields["tx_dropped"] = s.TxDropped + fields["tx_errors"] = s.TxErrors + fields["tx_latency_avg"] = s.TxLatencyAvg + fields["tx_latency_max"] = s.TxLatencyMax + fields["tx_latency_min"] = s.TxLatencyMin + fields["tx_packets"] = s.TxPackets + fields["tx_power"] = s.TxPower + fields["tx_retries"] = s.TxRetries + fields["usage"] = s.Usage + tags["wlanconf_id"] = s.WlanconfID + } + } + pt, err := influx.NewPoint("uap_radios", tags, fields, time.Now()) + if err != nil { + return points, err + } + points = append(points, pt) + } + return points, nil +} diff --git a/core/unifi/uap_type.go b/core/unifi/uap_type.go new file mode 100644 index 00000000..053a83e1 --- /dev/null +++ b/core/unifi/uap_type.go @@ -0,0 +1,361 @@ +package unidev + +// UAP is a Unifi Access Point. +type UAP struct { + /* This was auto generated and then slowly edited by hand + to get all the data types right and graphable. + No ones feelings will be hurt if you want to break this + up into multiple structs, and/or make it better in general. + */ + ID string `json:"_id"` + UUptime float64 `json:"_uptime"` + AdoptIP string `json:"adopt_ip,omitempty"` + AdoptURL string `json:"adopt_url,omitempty"` + Adopted bool `json:"adopted"` + AntennaTable []struct { + ID float64 `json:"id"` + Name string `json:"name"` + Wifi0Gain float64 `json:"wifi0_gain"` + Wifi1Gain float64 `json:"wifi1_gain"` + } `json:"antenna_table"` + BandsteeringMode string `json:"bandsteering_mode,omitempty"` + BoardRev int `json:"board_rev"` + Bytes float64 `json:"bytes"` + BytesD float64 `json:"bytes-d"` + BytesR float64 `json:"bytes-r"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork struct { + IP string `json:"ip"` + Type string `json:"type"` + } `json:"config_network"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + ConsideredLostAt float64 `json:"considered_lost_at"` + CountrycodeTable []float64 `json:"countrycode_table"` + Default bool `json:"default,omitempty"` + DeviceID string `json:"device_id"` + DiscoveredVia string `json:"discovered_via,omitempty"` + DownlinkTable []interface{} `json:"downlink_table"` + EthernetTable []struct { + Mac string `json:"mac"` + Name string `json:"name"` + NumPort float64 `json:"num_port"` + } `json:"ethernet_table"` + FwCaps int `json:"fw_caps"` + GuestNumSta int `json:"guest-num_sta"` + GuestToken string `json:"guest_token"` + HasEth1 bool `json:"has_eth1"` + HasSpeaker bool `json:"has_speaker"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + Isolated bool `json:"isolated"` + KnownCfgversion string `json:"known_cfgversion"` + LastSeen float64 `json:"last_seen"` + LastUplink struct { + UplinkMac string `json:"uplink_mac"` + UplinkRemotePort int `json:"uplink_remote_port"` + } `json:"last_uplink"` + LedOverride string `json:"led_override"` + Locating bool `json:"locating"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + NextHeartbeatAt float64 `json:"next_heartbeat_at"` + NumSta float64 `json:"num_sta"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + PortTable []struct { + AggregatedBy bool `json:"aggregated_by"` + AttrNoEdit bool `json:"attr_no_edit,omitempty"` + Autoneg bool `json:"autoneg"` + BytesR float64 `json:"bytes-r"` + Enable bool `json:"enable"` + FlowctrlRx bool `json:"flowctrl_rx"` + FlowctrlTx bool `json:"flowctrl_tx"` + FullDuplex bool `json:"full_duplex"` + IsUplink bool `json:"is_uplink"` + Jumbo bool `json:"jumbo"` + MacTable []struct { + Age float64 `json:"age"` + Mac string `json:"mac"` + Static bool `json:"static"` + Uptime float64 `json:"uptime"` + Vlan float64 `json:"vlan"` + } `json:"mac_table"` + Masked bool `json:"masked"` + Media string `json:"media"` + Name string `json:"name"` + OpMode string `json:"op_mode"` + PoeCaps float64 `json:"poe_caps"` + PortDelta struct { + RxBytes float64 `json:"rx_bytes"` + RxPackets float64 `json:"rx_packets"` + TimeDelta float64 `json:"time_delta"` + TxBytes float64 `json:"tx_bytes"` + TxPackets float64 `json:"tx_packets"` + } `json:"port_delta"` + PortIdx float64 `json:"port_idx"` + PortPoe bool `json:"port_poe"` + PortconfID string `json:"portconf_id"` + RxBroadcast float64 `json:"rx_broadcast"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + StpPathcost float64 `json:"stp_pathcost"` + StpState string `json:"stp_state"` + TxBroadcast float64 `json:"tx_broadcast"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxMulticast float64 `json:"tx_multicast"` + TxPackets float64 `json:"tx_packets"` + Up bool `json:"up"` + } `json:"port_table"` + RadioTable []struct { + BuiltinAntGain float64 `json:"builtin_ant_gain"` + BuiltinAntenna bool `json:"builtin_antenna"` + Channel FlexInt `json:"channel"` + CurrentAntennaGain float64 `json:"current_antenna_gain"` + Ht string `json:"ht"` + MaxTxpower float64 `json:"max_txpower"` + MinRssiEnabled bool `json:"min_rssi_enabled"` + MinTxpower float64 `json:"min_txpower"` + Name string `json:"name"` + Nss float64 `json:"nss"` + Radio string `json:"radio"` + RadioCaps float64 `json:"radio_caps"` + TxPower FlexInt `json:"tx_power"` + TxPowerMode string `json:"tx_power_mode"` + WlangroupID string `json:"wlangroup_id"` + HasDfs bool `json:"has_dfs,omitempty"` + HasFccdfs bool `json:"has_fccdfs,omitempty"` + Is11Ac bool `json:"is_11ac,omitempty"` + } `json:"radio_table"` + RadioTableStats []struct { + AstBeXmit float64 `json:"ast_be_xmit"` + AstCst float64 `json:"ast_cst"` + AstTxto float64 `json:"ast_txto"` + Channel float64 `json:"channel"` + CuSelfRx float64 `json:"cu_self_rx"` + CuSelfTx float64 `json:"cu_self_tx"` + CuTotal float64 `json:"cu_total"` + Extchannel float64 `json:"extchannel"` + Gain float64 `json:"gain"` + GuestNumSta float64 `json:"guest-num_sta"` + Name string `json:"name"` + NumSta float64 `json:"num_sta"` + Radio string `json:"radio"` + State string `json:"state"` + TxPackets float64 `json:"tx_packets"` + TxPower float64 `json:"tx_power"` + TxRetries float64 `json:"tx_retries"` + UserNumSta float64 `json:"user-num_sta"` + } `json:"radio_table_stats"` + Rollupgrade bool `json:"rollupgrade"` + RxBytes float64 `json:"rx_bytes"` + RxBytesD float64 `json:"rx_bytes-d"` + ScanRadioTable []interface{} `json:"scan_radio_table"` + Scanning bool `json:"scanning"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SpectrumScanning bool `json:"spectrum_scanning"` + SSHSessionTable []interface{} `json:"ssh_session_table"` + Stat struct { + Ap string `json:"ap"` + Bytes float64 `json:"bytes"` + Datetime string `json:"datetime"` + Duration float64 `json:"duration"` + GuestRxBytes float64 `json:"guest-rx_bytes"` + GuestRxCrypts float64 `json:"guest-rx_crypts"` + GuestRxDropped float64 `json:"guest-rx_dropped"` + GuestRxErrors float64 `json:"guest-rx_errors"` + GuestRxFrags float64 `json:"guest-rx_frags"` + GuestRxPackets float64 `json:"guest-rx_packets"` + GuestTxBytes float64 `json:"guest-tx_bytes"` + GuestTxDropped float64 `json:"guest-tx_dropped"` + GuestTxErrors float64 `json:"guest-tx_errors"` + GuestTxPackets float64 `json:"guest-tx_packets"` + GuestTxRetries float64 `json:"guest-tx_retries"` + O string `json:"o"` + Oid string `json:"oid"` + Port1RxBroadcast float64 `json:"port_1-rx_broadcast"` + Port1RxBytes float64 `json:"port_1-rx_bytes"` + Port1RxMulticast float64 `json:"port_1-rx_multicast"` + Port1RxPackets float64 `json:"port_1-rx_packets"` + Port1TxBroadcast float64 `json:"port_1-tx_broadcast"` + Port1TxBytes float64 `json:"port_1-tx_bytes"` + Port1TxMulticast float64 `json:"port_1-tx_multicast"` + Port1TxPackets float64 `json:"port_1-tx_packets"` + RxBytes float64 `json:"rx_bytes"` + RxCrypts float64 `json:"rx_crypts"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxFrags float64 `json:"rx_frags"` + RxPackets float64 `json:"rx_packets"` + SiteID string `json:"site_id"` + Time float64 `json:"time"` + TxBytes float64 `json:"tx_bytes"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + TxRetries float64 `json:"tx_retries"` + UserRxBytes float64 `json:"user-rx_bytes"` + UserRxCrypts float64 `json:"user-rx_crypts"` + UserRxDropped float64 `json:"user-rx_dropped"` + UserRxErrors float64 `json:"user-rx_errors"` + UserRxFrags float64 `json:"user-rx_frags"` + UserRxPackets float64 `json:"user-rx_packets"` + UserTxBytes float64 `json:"user-tx_bytes"` + UserTxDropped float64 `json:"user-tx_dropped"` + UserTxErrors float64 `json:"user-tx_errors"` + UserTxPackets float64 `json:"user-tx_packets"` + UserTxRetries float64 `json:"user-tx_retries"` + UserWifi0RxBytes float64 `json:"user-wifi0-rx_bytes"` + UserWifi0RxCrypts float64 `json:"user-wifi0-rx_crypts"` + UserWifi0RxDropped float64 `json:"user-wifi0-rx_dropped"` + UserWifi0RxErrors float64 `json:"user-wifi0-rx_errors"` + UserWifi0RxFrags float64 `json:"user-wifi0-rx_frags"` + UserWifi0RxPackets float64 `json:"user-wifi0-rx_packets"` + UserWifi0TxBytes float64 `json:"user-wifi0-tx_bytes"` + UserWifi0TxDropped float64 `json:"user-wifi0-tx_dropped"` + UserWifi0TxErrors float64 `json:"user-wifi0-tx_errors"` + UserWifi0TxPackets float64 `json:"user-wifi0-tx_packets"` + UserWifi0TxRetries float64 `json:"user-wifi0-tx_retries"` + UserWifi1RxBytes float64 `json:"user-wifi1-rx_bytes"` + UserWifi1RxCrypts float64 `json:"user-wifi1-rx_crypts"` + UserWifi1RxDropped float64 `json:"user-wifi1-rx_dropped"` + UserWifi1RxErrors float64 `json:"user-wifi1-rx_errors"` + UserWifi1RxFrags float64 `json:"user-wifi1-rx_frags"` + UserWifi1RxPackets float64 `json:"user-wifi1-rx_packets"` + UserWifi1TxBytes float64 `json:"user-wifi1-tx_bytes"` + UserWifi1TxDropped float64 `json:"user-wifi1-tx_dropped"` + UserWifi1TxErrors float64 `json:"user-wifi1-tx_errors"` + UserWifi1TxPackets float64 `json:"user-wifi1-tx_packets"` + UserWifi1TxRetries float64 `json:"user-wifi1-tx_retries"` + Wifi0RxBytes float64 `json:"wifi0-rx_bytes"` + Wifi0RxCrypts float64 `json:"wifi0-rx_crypts"` + Wifi0RxDropped float64 `json:"wifi0-rx_dropped"` + Wifi0RxErrors float64 `json:"wifi0-rx_errors"` + Wifi0RxFrags float64 `json:"wifi0-rx_frags"` + Wifi0RxPackets float64 `json:"wifi0-rx_packets"` + Wifi0TxBytes float64 `json:"wifi0-tx_bytes"` + Wifi0TxDropped float64 `json:"wifi0-tx_dropped"` + Wifi0TxErrors float64 `json:"wifi0-tx_errors"` + Wifi0TxPackets float64 `json:"wifi0-tx_packets"` + Wifi0TxRetries float64 `json:"wifi0-tx_retries"` + Wifi1RxBytes float64 `json:"wifi1-rx_bytes"` + Wifi1RxCrypts float64 `json:"wifi1-rx_crypts"` + Wifi1RxDropped float64 `json:"wifi1-rx_dropped"` + Wifi1RxErrors float64 `json:"wifi1-rx_errors"` + Wifi1RxFrags float64 `json:"wifi1-rx_frags"` + Wifi1RxPackets float64 `json:"wifi1-rx_packets"` + Wifi1TxBytes float64 `json:"wifi1-tx_bytes"` + Wifi1TxDropped float64 `json:"wifi1-tx_dropped"` + Wifi1TxErrors float64 `json:"wifi1-tx_errors"` + Wifi1TxPackets float64 `json:"wifi1-tx_packets"` + Wifi1TxRetries float64 `json:"wifi1-tx_retries"` + } `json:"stat"` + State int `json:"state"` + SysStats struct { + Loadavg1 float64 `json:"loadavg_1,string"` + Loadavg15 float64 `json:"loadavg_15,string"` + Loadavg5 float64 `json:"loadavg_5,string"` + MemBuffer float64 `json:"mem_buffer"` + MemTotal float64 `json:"mem_total"` + MemUsed float64 `json:"mem_used"` + } `json:"sys_stats"` + SystemStats struct { + CPU float64 `json:"cpu,string"` + Mem float64 `json:"mem,string"` + Uptime float64 `json:"uptime,string"` + } `json:"system-stats"` + TxBytes float64 `json:"tx_bytes"` + TxBytesD float64 `json:"tx_bytes-d"` + Type string `json:"type"` + Upgradable bool `json:"upgradable"` + Uplink struct { + FullDuplex bool `json:"full_duplex"` + IP string `json:"ip"` + Mac string `json:"mac"` + MaxSpeed int `json:"max_speed"` + MaxVlan int `json:"max_vlan"` + Media string `json:"media"` + Name string `json:"name"` + Netmask string `json:"netmask"` + NumPort int `json:"num_port"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Type string `json:"type"` + Up bool `json:"up"` + UplinkMac string `json:"uplink_mac"` + UplinkRemotePort int `json:"uplink_remote_port"` + } `json:"uplink"` + UplinkTable []interface{} `json:"uplink_table"` + Uptime FlexInt `json:"uptime"` + UserNumSta int `json:"user-num_sta"` + VapTable []struct { + ApMac string `json:"ap_mac"` + Bssid string `json:"bssid"` + Ccq int `json:"ccq"` + Channel int `json:"channel"` + Essid string `json:"essid"` + Extchannel int `json:"extchannel"` + ID string `json:"id"` + IsGuest bool `json:"is_guest"` + IsWep bool `json:"is_wep"` + MacFilterRejections int `json:"mac_filter_rejections"` + MapID string `json:"map_id"` + Name string `json:"name"` + NumSta int `json:"num_sta"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + RxBytes float64 `json:"rx_bytes"` + RxCrypts float64 `json:"rx_crypts"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxFrags float64 `json:"rx_frags"` + RxNwids float64 `json:"rx_nwids"` + RxPackets float64 `json:"rx_packets"` + SiteID string `json:"site_id"` + State string `json:"state"` + T string `json:"t"` + TxBytes float64 `json:"tx_bytes"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxLatencyAvg float64 `json:"tx_latency_avg"` + TxLatencyMax float64 `json:"tx_latency_max"` + TxLatencyMin float64 `json:"tx_latency_min"` + TxPackets float64 `json:"tx_packets"` + TxPower int `json:"tx_power"` + TxRetries int `json:"tx_retries"` + Up bool `json:"up"` + Usage string `json:"usage"` + WlanconfID string `json:"wlanconf_id"` + } `json:"vap_table"` + Version string `json:"version"` + VersionIncompatible bool `json:"version_incompatible"` + VwireEnabled bool `json:"vwireEnabled"` + VwireTable []interface{} `json:"vwire_table"` + VwireVapTable []struct { + Bssid string `json:"bssid"` + Radio string `json:"radio"` + RadioName string `json:"radio_name"` + State string `json:"state"` + } `json:"vwire_vap_table"` + WifiCaps int `json:"wifi_caps"` +} diff --git a/core/unifi/unidev.go b/core/unifi/unidev.go new file mode 100644 index 00000000..34f08254 --- /dev/null +++ b/core/unifi/unidev.go @@ -0,0 +1,105 @@ +package unidev + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "log" + "net/http" + "net/http/cookiejar" + "strconv" + + influx "github.com/influxdata/influxdb/client/v2" + "github.com/pkg/errors" +) + +// LoginPath is Unifi Controller Login API Path +const LoginPath = "/api/login" + +// Asset provides a common interface to retreive metrics from a device or client. +// It currently only supports InfluxDB, but could be amended to support other +// libraries that have a similar interface. +// This app only uses the .AddPoint/s() methods with the Asset type. +type Asset interface { + // Point() means this is useful to influxdb.. + Points() ([]*influx.Point, error) + // Add more methods to achieve more usefulness from this library. +} + +// AuthedReq is what you get in return for providing a password! +type AuthedReq struct { + *http.Client + baseURL string +} + +// FlexInt provides a container and unmarshalling for fields that may be +// numbers or strings in the Unifi API +type FlexInt struct { + Number float64 + String string +} + +// UnmarshalJSON converts a string or number to an integer. +func (f *FlexInt) UnmarshalJSON(b []byte) error { + var unk interface{} + if err := json.Unmarshal(b, &unk); err != nil { + return err + } + switch i := unk.(type) { + case float64: + f.Number = i + f.String = strconv.FormatFloat(i, 'f', -1, 64) + return nil + case string: + f.String = i + f.Number, _ = strconv.ParseFloat(i, 64) + return nil + default: + return errors.New("Cannot unmarshal to FlexInt") + } +} + +// AuthController creates a http.Client with authenticated cookies. +// Used to make additional, authenticated requests to the APIs. +func AuthController(user, pass, url string, verifySSL bool) (*AuthedReq, error) { + json := `{"username": "` + user + `","password": "` + pass + `"}` + jar, err := cookiejar.New(nil) + if err != nil { + return nil, errors.Wrap(err, "cookiejar.New(nil)") + } + a := &AuthedReq{&http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}}, + Jar: jar, + }, url} + req, err := a.UniReq(LoginPath, json) + if err != nil { + return a, errors.Wrap(err, "UniReq(LoginPath, json)") + } + resp, err := a.Do(req) + if err != nil { + return a, errors.Wrap(err, "authReq.Do(req)") + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("resp.Body.Close():", err) // Not fatal. Just log it. + } + }() + if resp.StatusCode != http.StatusOK { + return a, errors.Errorf("authentication failed (%v): %v (status: %v/%v)", + user, url+LoginPath, resp.StatusCode, resp.Status) + } + return a, nil +} + +// UniReq is a small helper function that adds an Accept header. +func (a AuthedReq) UniReq(apiPath string, params string) (req *http.Request, err error) { + if params != "" { + req, err = http.NewRequest("POST", a.baseURL+apiPath, bytes.NewBufferString(params)) + } else { + req, err = http.NewRequest("GET", a.baseURL+apiPath, nil) + } + if err == nil { + req.Header.Add("Accept", "application/json") + } + return +} diff --git a/core/unifi/unidev_test.go b/core/unifi/unidev_test.go new file mode 100644 index 00000000..0557934a --- /dev/null +++ b/core/unifi/unidev_test.go @@ -0,0 +1,82 @@ +package unidev + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlexInt(t *testing.T) { + t.Parallel() + a := assert.New(t) + type testReply struct { + Five FlexInt `json:"five"` + Seven FlexInt `json:"seven"` + Auto FlexInt `json:"auto"` + Channel FlexInt `json:"channel"` + } + var r testReply + // test unmarshalling the custom type three times with different values. + a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto"}`), &r)) + + // test number in string. + a.EqualValues(5, r.Five.Number) + a.EqualValues("5", r.Five.String) + // test number. + a.EqualValues(7, r.Seven.Number) + a.EqualValues("7", r.Seven.String) + // test string. + a.EqualValues(0, r.Auto.Number) + a.EqualValues("auto", r.Auto.String) + // 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.Number) +} + +func TestUniReq(t *testing.T) { + t.Parallel() + a := assert.New(t) + u := "/test/path" + url := "http://some.url:8443" + // Test empty parameters. + authReq := &AuthedReq{&http.Client{}, url} + r, err := authReq.UniReq(u, "") + a.Nil(err, "newrequest must not produce an error") + a.EqualValues(u, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(url, 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 + p := "key1=value9&key2=value7" + authReq = &AuthedReq{&http.Client{}, "http://some.url:8443"} + r, err = authReq.UniReq(u, p) + a.Nil(err, "newrequest must not produce an error") + a.EqualValues(u, r.URL.Path, + "the provided apiPath was not added to http request") + a.EqualValues(url, 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(p, string(d), "POST parameters improperly encoded") +} + +func TestAuthController(t *testing.T) { + t.Parallel() + a := assert.New(t) + url := "http://127.0.0.1:64431" + authReq, err := AuthController("user1", "pass2", url, false) + a.NotNil(err) + a.EqualValues(url, authReq.baseURL) + a.Contains(err.Error(), "authReq.Do(req):", "an invalid destination should product a .Do(req) error.") + /* TODO: OPEN web server, check parameters posted, more. This test is incomplete. + a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), "user/pass json parameters improperly encoded") + */ +} diff --git a/core/unifi/unifi.go b/core/unifi/unifi.go new file mode 100644 index 00000000..e1f1e6f2 --- /dev/null +++ b/core/unifi/unifi.go @@ -0,0 +1,154 @@ +package unidev + +import ( + "encoding/json" + "io/ioutil" + "log" + + "github.com/pkg/errors" +) + +// Debug .... +var Debug = false + +const ( + // ClientPath is Unifi Clients API Path + ClientPath = "/api/s/default/stat/sta" + // DevicePath is where we get data about Unifi devices. + DevicePath = "/api/s/default/stat/device" + // NetworkPath contains network-configuration data. Not really graphable. + NetworkPath = "/api/s/default/rest/networkconf" + // UserGroupPath contains usergroup configurations. + UserGroupPath = "/api/s/default/rest/usergroup" +) + +// GetUnifiClients returns a response full of clients' data from the Unifi Controller. +func (c *AuthedReq) GetUnifiClients() ([]UCL, error) { + var response struct { + Clients []UCL `json:"data"` + Meta struct { + Rc string `json:"rc"` + } `json:"meta"` + } + req, err := c.UniReq(ClientPath, "") + if err != nil { + return nil, errors.Wrap(err, "c.UniReq(ClientPath)") + } + resp, err := c.Do(req) + if err != nil { + return nil, errors.Wrap(err, "c.Do(req)") + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + } + }() + if body, err := ioutil.ReadAll(resp.Body); err != nil { + return nil, errors.Wrap(err, "ioutil.ReadAll(resp.Body)") + } else if err = json.Unmarshal(body, &response); err != nil { + return nil, errors.Wrap(err, "json.Unmarshal([]UCL)") + } + return response.Clients, nil +} + +// GetUnifiClientAssets provides an interface to return common asset types. +func (c *AuthedReq) GetUnifiClientAssets() ([]Asset, error) { + clients, err := c.GetUnifiClients() + assets := []Asset{} + if err == nil { + for _, r := range clients { + assets = append(assets, r) + } + } + return assets, err +} + +// GetUnifiDevices returns a response full of devices' data from the Unifi Controller. +func (c *AuthedReq) GetUnifiDevices() ([]USG, []USW, []UAP, error) { + var parsed struct { + Data []json.RawMessage `json:"data"` + Meta struct { + Rc string `json:"rc"` + } `json:"meta"` + } + req, err := c.UniReq(DevicePath, "") + if err != nil { + return nil, nil, nil, errors.Wrap(err, "c.UniReq(DevicePath)") + } + resp, err := c.Do(req) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "c.Do(req)") + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + } + }() + if body, err := ioutil.ReadAll(resp.Body); err != nil { + return nil, nil, nil, errors.Wrap(err, "ioutil.ReadAll(resp.Body)") + } else if err = json.Unmarshal(body, &parsed); err != nil { + return nil, nil, nil, errors.Wrap(err, "json.Unmarshal([]json.RawMessage)") + } + + var usgs []USG + var usws []USW + var uaps []UAP + // Loop each item in the raw JSON message, detect its type and unmarshal it. + for i, r := range parsed.Data { + var usg USG + var usw USW + var uap UAP + // Unamrshal into a map and check "type" + var obj map[string]interface{} + if err := json.Unmarshal(r, &obj); err != nil { + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal(interfce{})", i) + } + assetType := "" + if t, ok := obj["type"].(string); ok { + assetType = t + } + if Debug { + log.Println("Unmarshalling Device Type:", assetType) + } + // Unmarshal again into the correct type.. + switch assetType { + case "uap": + if err := json.Unmarshal(r, &uap); err != nil { + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]UAP)", i) + } + uaps = append(uaps, uap) + case "ugw", "usg": // in case they ever fix the name in the api. + if err := json.Unmarshal(r, &usg); err != nil { + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]USG)", i) + } + usgs = append(usgs, usg) + case "usw": + if err := json.Unmarshal(r, &usw); err != nil { + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]USW)", i) + } + usws = append(usws, usw) + default: + log.Println("unknown asset type -", assetType, "- skipping") + continue + } + } + return usgs, usws, uaps, nil +} + +// GetUnifiDeviceAssets provides an interface to return common asset types. +func (c *AuthedReq) GetUnifiDeviceAssets() ([]Asset, error) { + usgs, usws, uaps, err := c.GetUnifiDevices() + assets := []Asset{} + if err == nil { + for _, r := range usgs { + assets = append(assets, r) + } + for _, r := range usws { + assets = append(assets, r) + } + for _, r := range uaps { + assets = append(assets, r) + } + } + return assets, err +} diff --git a/core/unifi/usg.go b/core/unifi/usg.go new file mode 100644 index 00000000..a8123705 --- /dev/null +++ b/core/unifi/usg.go @@ -0,0 +1,186 @@ +package unidev + +import ( + "strconv" + "time" + + influx "github.com/influxdata/influxdb/client/v2" +) + +// Points generates a device's datapoints for InfluxDB. +func (u USG) Points() ([]*influx.Point, error) { + var points []*influx.Point + tags := map[string]string{ + "id": u.ID, + "mac": u.Mac, + "device_type": u.Stat.O, + "device_oid": u.Stat.Oid, + "site_id": u.SiteID, + "addopted": strconv.FormatBool(u.Adopted), + "name": u.Name, + "adopt_ip": u.AdoptIP, + "adopt_url": u.AdoptURL, + "cfgversion": u.Cfgversion, + "config_network_ip": u.ConfigNetwork.IP, + "config_network_type": u.ConfigNetwork.Type, + "connect_request_ip": u.ConnectRequestIP, + "connect_request_port": u.ConnectRequestPort, + "default": strconv.FormatBool(u.Default), + "device_id": u.DeviceID, + "discovered_via": u.DiscoveredVia, + "guest_token": u.GuestToken, + "inform_ip": u.InformIP, + "known_cfgversion": u.KnownCfgversion, + "led_override": u.LedOverride, + "locating": strconv.FormatBool(u.Locating), + "model": u.Model, + "outdoor_mode_override": u.OutdoorModeOverride, + "serial": u.Serial, + "type": u.Type, + "version_incompatible": strconv.FormatBool(u.VersionIncompatible), + "usg_caps": strconv.FormatFloat(u.UsgCaps, 'f', 6, 64), + "speedtest-status-saved": strconv.FormatBool(u.SpeedtestStatusSaved), + } + fields := map[string]interface{}{ + "ip": u.IP, + "bytes": u.Bytes, + "last_seen": u.LastSeen, + "license_state": u.LicenseState, + "fw_caps": u.FwCaps, + "guest-num_sta": u.GuestNumSta, + "rx_bytes": u.RxBytes, + "tx_bytes": u.TxBytes, + "uptime": u.Uptime, + "considered_lost_at": u.ConsideredLostAt, + "next_heartbeat_at": u.NextHeartbeatAt, + "roll_upgrade": u.Rollupgrade, + "state": u.State, + "upgradable": u.Upgradable, + "user-num_sta": u.UserNumSta, + "version": u.Version, + "num_desktop": u.NumDesktop, + "num_handheld": u.NumHandheld, + "num_mobile": u.NumMobile, + "speedtest-status_latency": u.SpeedtestStatus.Latency, + "speedtest-status_rundate": u.SpeedtestStatus.Rundate, + "speedtest-status_runtime": u.SpeedtestStatus.Runtime, + "speedtest-status_download": u.SpeedtestStatus.StatusDownload, + "speedtest-status_ping": u.SpeedtestStatus.StatusPing, + "speedtest-status_summary": u.SpeedtestStatus.StatusSummary, + "speedtest-status_upload": u.SpeedtestStatus.StatusUpload, + "speedtest-status_xput_download": u.SpeedtestStatus.XputDownload, + "speedtest-status_xput_upload": u.SpeedtestStatus.XputUpload, + // have two WANs? mmmm, go ahead and add it. ;) + "config_network_wan_type": u.ConfigNetworkWan.Type, + "wan1_bytes-r": u.Wan1.BytesR, + "wan1_enable": u.Wan1.Enable, + "wan1_full_duplex": u.Wan1.FullDuplex, + "wan1_purpose": "uplink", // because it should have a purpose. + "wan1_gateway": u.Wan1.Gateway, + "wan1_ifname": u.Wan1.Ifname, + "wan1_ip": u.Wan1.IP, + "wan1_mac": u.Wan1.Mac, + "wan1_max_speed": u.Wan1.MaxSpeed, + "wan1_name": u.Wan1.Name, + "wan1_netmask": u.Wan1.Netmask, + "wan1_rx_bytes": u.Wan1.RxBytes, + "wan1_rx_bytes-r": u.Wan1.RxBytesR, + "wan1_rx_dropped": u.Wan1.RxDropped, + "wan1_rx_errors": u.Wan1.RxErrors, + "wan1_rx_multicast": u.Wan1.RxMulticast, + "wan1_rx_packets": u.Wan1.RxPackets, + "wan1_type": u.Wan1.Type, + "wan1_speed": u.Wan1.Speed, + "wan1_up": u.Wan1.Up, + "wan1_tx_bytes": u.Wan1.TxBytes, + "wan1_tx_bytes-r": u.Wan1.TxBytesR, + "wan1_tx_dropped": u.Wan1.TxDropped, + "wan1_tx_errors": u.Wan1.TxErrors, + "wan1_tx_packets": u.Wan1.TxPackets, + "loadavg_1": u.SysStats.Loadavg1, + "loadavg_5": u.SysStats.Loadavg5, + "loadavg_15": u.SysStats.Loadavg15, + "mem_used": u.SysStats.MemUsed, + "mem_buffer": u.SysStats.MemBuffer, + "mem_total": u.SysStats.MemTotal, + "cpu": u.SystemStats.CPU, + "mem": u.SystemStats.Mem, + "system_uptime": u.SystemStats.Uptime, + "stat_duration": u.Stat.Duration, + "stat_datetime": u.Stat.Datetime, + "gw": u.Stat.Gw, + "false": "false", // to fill holes in graphs. + "lan-rx_bytes": u.Stat.LanRxBytes, + "lan-rx_packets": u.Stat.LanRxPackets, + "lan-tx_bytes": u.Stat.LanTxBytes, + "lan-tx_packets": u.Stat.LanTxPackets, + "wan-rx_bytes": u.Stat.WanRxBytes, + "wan-rx_dropped": u.Stat.WanRxDropped, + "wan-rx_packets": u.Stat.WanRxPackets, + "wan-tx_bytes": u.Stat.WanTxBytes, + "wan-tx_packets": u.Stat.WanTxPackets, + "uplink_name": u.Uplink.Name, + "uplink_latency": u.Uplink.Latency, + "uplink_speed": u.Uplink.Speed, + "uplink_num_ports": u.Uplink.NumPort, + "uplink_max_speed": u.Uplink.MaxSpeed, + } + pt, err := influx.NewPoint("usg", tags, fields, time.Now()) + if err != nil { + return nil, err + } + points = append(points, pt) + for _, p := range u.NetworkTable { + tags := map[string]string{ + "device_name": u.Name, + "device_id": u.ID, + "device_mac": u.Mac, + "name": p.Name, + "dhcpd_dns_enabled": strconv.FormatBool(p.DhcpdDNSEnabled), + "dhcpd_enabled": strconv.FormatBool(p.DhcpdEnabled), + "dhcpd_ntp_enabled": strconv.FormatBool(p.DhcpdNtpEnabled), + "dhcpd_time_offset_enabled": strconv.FormatBool(p.DhcpdTimeOffsetEnabled), + "dhcp_relay_enabledy": strconv.FormatBool(p.DhcpRelayEnabled), + "dhcpd_gateway_enabled": strconv.FormatBool(p.DhcpdGatewayEnabled), + "dhcpd_wins_enabled": strconv.FormatBool(p.DhcpdWinsEnabled), + "dhcpguard_enabled": strconv.FormatBool(p.DhcpguardEnabled), + "enabled": strconv.FormatBool(p.Enabled), + "vlan_enabled": strconv.FormatBool(p.VlanEnabled), + "attr_no_delete": strconv.FormatBool(p.AttrNoDelete), + "upnp_lan_enabled": strconv.FormatBool(p.UpnpLanEnabled), + "igmp_snooping": strconv.FormatBool(p.IgmpSnooping), + "is_guest": strconv.FormatBool(p.IsGuest), + "is_nat": strconv.FormatBool(p.IsNat), + "networkgroup": p.Networkgroup, + "site_id": p.SiteID, + } + fields := map[string]interface{}{ + "dhcpd_ip_1": p.DhcpdIP1, + "domain_name": p.DomainName, + "dhcpd_start": p.DhcpdStart, + "dhcpd_stop": p.DhcpdStop, + "ip": p.IP, + "ip_subnet": p.IPSubnet, + "mac": p.Mac, + "name": p.Name, + "num_sta": p.NumSta, + "purpose": p.Purpose, + "rx_bytes": p.RxBytes, + "rx_packets": p.RxPackets, + "tx_bytes": p.TxBytes, + "tx_packets": p.TxPackets, + "up": p.Up, + "vlan": p.Vlan, + "dhcpd_ntp_1": p.DhcpdNtp1, + "dhcpd_unifi_controller": p.DhcpdUnifiController, + "ipv6_interface_type": p.Ipv6InterfaceType, + "attr_hidden_id": p.AttrHiddenID, + } + pt, err = influx.NewPoint("usg_networks", tags, fields, time.Now()) + if err != nil { + return points, err + } + points = append(points, pt) + } + return points, err +} diff --git a/core/unifi/usg_type.go b/core/unifi/usg_type.go new file mode 100644 index 00000000..d5a7d15a --- /dev/null +++ b/core/unifi/usg_type.go @@ -0,0 +1,260 @@ +package unidev + +import "encoding/json" + +// USG is a Unifi Security Gateway +type USG struct { + ID string `json:"_id"` + UUptime float64 `json:"_uptime"` + AdoptIP string `json:"adopt_ip"` + AdoptURL string `json:"adopt_url"` + Adopted bool `json:"adopted"` + Bytes float64 `json:"bytes"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork struct { + IP string `json:"ip"` + Type string `json:"type"` + } `json:"config_network"` + ConfigNetworkWan struct { + Type string `json:"type"` + } `json:"config_network_wan"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + ConsideredLostAt float64 `json:"considered_lost_at"` + Default bool `json:"default"` + DeviceID string `json:"device_id"` + DiscoveredVia string `json:"discovered_via"` + EthernetTable []struct { + Mac string `json:"mac"` + Name string `json:"name"` + NumPort float64 `json:"num_port"` + } `json:"ethernet_table"` + FwCaps float64 `json:"fw_caps"` + GuestNumSta float64 `json:"guest-num_sta"` + GuestToken string `json:"guest_token"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + KnownCfgversion string `json:"known_cfgversion"` + LastSeen float64 `json:"last_seen"` + LedOverride string `json:"led_override"` + LicenseState string `json:"license_state"` + Locating bool `json:"locating"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + NetworkTable []struct { + ID string `json:"_id"` + DhcpdDNSEnabled bool `json:"dhcpd_dns_enabled"` + DhcpdEnabled bool `json:"dhcpd_enabled"` + DhcpdIP1 string `json:"dhcpd_ip_1,omitempty"` + DhcpdLeasetime json.Number `json:"dhcpd_leasetime,Number"` + DhcpdStart string `json:"dhcpd_start"` + DhcpdStop string `json:"dhcpd_stop"` + DhcpdWinsEnabled bool `json:"dhcpd_wins_enabled,omitempty"` + DhcpguardEnabled bool `json:"dhcpguard_enabled,omitempty"` + DomainName string `json:"domain_name"` + Enabled bool `json:"enabled"` + IgmpSnooping bool `json:"igmp_snooping,omitempty"` + IP string `json:"ip"` + IPSubnet string `json:"ip_subnet"` + IsGuest bool `json:"is_guest"` + IsNat bool `json:"is_nat"` + Mac string `json:"mac"` + Name string `json:"name"` + Networkgroup string `json:"networkgroup"` + NumSta float64 `json:"num_sta"` + Purpose string `json:"purpose"` + RxBytes float64 `json:"rx_bytes"` + RxPackets float64 `json:"rx_packets"` + SiteID string `json:"site_id"` + TxBytes float64 `json:"tx_bytes"` + TxPackets float64 `json:"tx_packets"` + Up string `json:"up"` + Vlan string `json:"vlan,omitempty"` + VlanEnabled bool `json:"vlan_enabled"` + DhcpRelayEnabled bool `json:"dhcp_relay_enabled,omitempty"` + DhcpdGatewayEnabled bool `json:"dhcpd_gateway_enabled,omitempty"` + DhcpdNtp1 string `json:"dhcpd_ntp_1,omitempty"` + DhcpdNtpEnabled bool `json:"dhcpd_ntp_enabled,omitempty"` + DhcpdTimeOffsetEnabled bool `json:"dhcpd_time_offset_enabled,omitempty"` + DhcpdUnifiController string `json:"dhcpd_unifi_controller,omitempty"` + Ipv6InterfaceType string `json:"ipv6_interface_type,omitempty"` + AttrHiddenID string `json:"attr_hidden_id,omitempty"` + AttrNoDelete bool `json:"attr_no_delete,omitempty"` + UpnpLanEnabled bool `json:"upnp_lan_enabled,omitempty"` + } `json:"network_table"` + NextHeartbeatAt float64 `json:"next_heartbeat_at"` + NumDesktop float64 `json:"num_desktop"` + NumHandheld float64 `json:"num_handheld"` + NumMobile float64 `json:"num_mobile"` + NumSta float64 `json:"num_sta"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + PortTable []struct { + DNS []string `json:"dns,omitempty"` + Enable bool `json:"enable"` + FullDuplex bool `json:"full_duplex"` + Gateway string `json:"gateway,omitempty"` + Ifname string `json:"ifname"` + IP string `json:"ip"` + Mac string `json:"mac"` + Name string `json:"name"` + Netmask string `json:"netmask"` + RxBytes float64 `json:"rx_bytes"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + TxBytes float64 `json:"tx_bytes"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Up bool `json:"up"` + } `json:"port_table"` + Rollupgrade bool `json:"rollupgrade"` + RxBytes float64 `json:"rx_bytes"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SpeedtestStatus struct { + Latency float64 `json:"latency"` + Rundate float64 `json:"rundate"` + Runtime float64 `json:"runtime"` + StatusDownload float64 `json:"status_download"` + StatusPing float64 `json:"status_ping"` + StatusSummary float64 `json:"status_summary"` + StatusUpload float64 `json:"status_upload"` + XputDownload float64 `json:"xput_download"` + XputUpload float64 `json:"xput_upload"` + } `json:"speedtest-status"` + SpeedtestStatusSaved bool `json:"speedtest-status-saved"` + Stat struct { + Datetime string `json:"datetime"` + Duration float64 `json:"duration"` + Gw string `json:"gw"` + LanRxBytes float64 `json:"lan-rx_bytes"` + LanRxPackets float64 `json:"lan-rx_packets"` + LanTxBytes float64 `json:"lan-tx_bytes"` + LanTxPackets float64 `json:"lan-tx_packets"` + O string `json:"o"` + Oid string `json:"oid"` + SiteID string `json:"site_id"` + Time float64 `json:"time"` + WanRxBytes float64 `json:"wan-rx_bytes"` + WanRxDropped float64 `json:"wan-rx_dropped"` + WanRxPackets float64 `json:"wan-rx_packets"` + WanTxBytes float64 `json:"wan-tx_bytes"` + WanTxPackets float64 `json:"wan-tx_packets"` + } `json:"stat"` + State float64 `json:"state"` + SysStats struct { + Loadavg1 float64 `json:"loadavg_1,string"` + Loadavg15 float64 `json:"loadavg_15,string"` + Loadavg5 float64 `json:"loadavg_5,string"` + MemBuffer float64 `json:"mem_buffer"` + MemTotal float64 `json:"mem_total"` + MemUsed float64 `json:"mem_used"` + } `json:"sys_stats"` + SystemStats struct { + CPU float64 `json:"cpu,string"` + Mem float64 `json:"mem,string"` + Uptime float64 `json:"uptime,string"` + } `json:"system-stats"` + TxBytes float64 `json:"tx_bytes"` + Type string `json:"type"` + Upgradable bool `json:"upgradable"` + Uplink struct { + BytesR float64 `json:"bytes-r"` + Drops float64 `json:"drops"` + Enable bool `json:"enable"` + FullDuplex bool `json:"full_duplex"` + Gateways []string `json:"gateways"` + IP string `json:"ip"` + Latency float64 `json:"latency"` + Mac string `json:"mac"` + MaxSpeed float64 `json:"max_speed"` + Name string `json:"name"` + Nameservers []string `json:"nameservers"` + Netmask string `json:"netmask"` + NumPort float64 `json:"num_port"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + SpeedtestLastrun float64 `json:"speedtest_lastrun"` + SpeedtestPing float64 `json:"speedtest_ping"` + SpeedtestStatus string `json:"speedtest_status"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Type string `json:"type"` + Up bool `json:"up"` + Uptime float64 `json:"uptime"` + XputDown float64 `json:"xput_down"` + XputUp float64 `json:"xput_up"` + } `json:"uplink"` + Uptime float64 `json:"uptime"` + UserNumSta float64 `json:"user-num_sta"` + UsgCaps float64 `json:"usg_caps"` + Version string `json:"version"` + VersionIncompatible bool `json:"version_incompatible"` + Wan1 struct { + BytesR float64 `json:"bytes-r"` + DNS []string `json:"dns"` + Enable bool `json:"enable"` + FullDuplex bool `json:"full_duplex"` + Gateway string `json:"gateway"` + Ifname string `json:"ifname"` + IP string `json:"ip"` + Mac string `json:"mac"` + MaxSpeed float64 `json:"max_speed"` + Name string `json:"name"` + Netmask string `json:"netmask"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Type string `json:"type"` + Up bool `json:"up"` + } `json:"wan1"` + Wan2 struct { + BytesR float64 `json:"bytes-r"` + DNS []string `json:"dns"` + Enable bool `json:"enable"` + FullDuplex bool `json:"full_duplex"` + Gateway string `json:"gateway"` + Ifname string `json:"ifname"` + IP string `json:"ip"` + Mac string `json:"mac"` + MaxSpeed float64 `json:"max_speed"` + Name string `json:"name"` + Netmask string `json:"netmask"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Type string `json:"type"` + Up bool `json:"up"` + } `json:"wan2"` +} diff --git a/core/unifi/usw.go b/core/unifi/usw.go new file mode 100644 index 00000000..5a2e491e --- /dev/null +++ b/core/unifi/usw.go @@ -0,0 +1,118 @@ +package unidev + +import ( + "strconv" + "time" + + influx "github.com/influxdata/influxdb/client/v2" +) + +// Points generates a device's datapoints for InfluxDB. +func (u USW) Points() ([]*influx.Point, error) { + var points []*influx.Point + tags := map[string]string{ + "id": u.ID, + "mac": u.Mac, + "device_type": u.Stat.O, + "device_oid": u.Stat.Oid, + "site_id": u.SiteID, + "name": u.Name, + "addopted": strconv.FormatBool(u.Adopted), + "adopt_ip": u.AdoptIP, + "adopt_url": u.AdoptURL, + "cfgversion": u.Cfgversion, + "config_network_ip": u.ConfigNetwork.IP, + "config_network_type": u.ConfigNetwork.Type, + "connect_request_ip": u.ConnectRequestIP, + "connect_request_port": u.ConnectRequestPort, + "default": strconv.FormatBool(u.Default), + "device_id": u.DeviceID, + "discovered_via": u.DiscoveredVia, + "inform_ip": u.InformIP, + "last_uplink_mac": u.LastUplink.UplinkMac, + "known_cfgversion": u.KnownCfgversion, + "led_override": u.LedOverride, + "locating": strconv.FormatBool(u.Locating), + "model": u.Model, + "outdoor_mode_override": u.OutdoorModeOverride, + "serial": u.Serial, + "type": u.Type, + "version_incompatible": strconv.FormatBool(u.VersionIncompatible), + "dot1x_portctrl_enabled": strconv.FormatBool(u.Dot1XPortctrlEnabled), + "flowctrl_enabled": strconv.FormatBool(u.FlowctrlEnabled), + "has_fan": strconv.FormatBool(u.HasFan), + "has_temperature": strconv.FormatBool(u.HasTemperature), + "jumboframe_enabled": strconv.FormatBool(u.JumboframeEnabled), + "stp_priority": u.StpPriority, + "stp_version": u.StpVersion, + } + fields := map[string]interface{}{ + "fw_caps": u.FwCaps, + "guest-num_sta": u.GuestNumSta, + "ip": u.IP, + "bytes": u.Bytes, + "fan_level": u.FanLevel, + "general_temperature": u.GeneralTemperature, + "last_seen": u.LastSeen, + "license_state": u.LicenseState, + "overheating": u.Overheating, + "rx_bytes": u.RxBytes, + "tx_bytes": u.TxBytes, + "uptime": u.Uptime, + "considered_lost_at": u.ConsideredLostAt, + "next_heartbeat_at": u.NextHeartbeatAt, + "roll_upgrade": u.Rollupgrade, + "state": u.State, + "upgradable": u.Upgradable, + "user-num_sta": u.UserNumSta, + "version": u.Version, + "loadavg_1": u.SysStats.Loadavg1, + "loadavg_5": u.SysStats.Loadavg5, + "loadavg_15": u.SysStats.Loadavg15, + "mem_buffer": u.SysStats.MemBuffer, + "mem_used": u.SysStats.MemUsed, + "mem_total": u.SysStats.MemTotal, + "cpu": u.SystemStats.CPU, + "mem": u.SystemStats.Mem, + "system_uptime": u.SystemStats.Uptime, + "stat_bytes": u.Stat.Bytes, + "stat_duration": u.Stat.Duration, + "stat_guest-rx_bytes": u.Stat.RxBytes, + "stat_guest-rx_crypts": u.Stat.RxCrypts, + "stat_guest-rx_dropped": u.Stat.RxDropped, + "stat_guest-rx_errors": u.Stat.RxErrors, + "stat_guest-rx_frags": u.Stat.RxFrags, + "stat_guest-rx_packets": u.Stat.RxPackets, + "stat_guest-tx_bytes": u.Stat.TxBytes, + "stat_guest-tx_dropped": u.Stat.TxDropped, + "stat_guest-tx_errors": u.Stat.TxErrors, + "stat_guest-tx_packets": u.Stat.TxPackets, + "stat_guest-tx_retries": u.Stat.TxRetries, + "stat_port_1-rx_broadcast": u.Stat.Port1RxBroadcast, + "stat_port_1-rx_bytes": u.Stat.Port1RxBytes, + "stat_port_1-rx_multicast": u.Stat.Port1RxMulticast, + "stat_port_1-rx_packets": u.Stat.Port1RxPackets, + "stat_port_1-tx_broadcast": u.Stat.Port1TxBroadcast, + "stat_port_1-tx_bytes": u.Stat.Port1TxBytes, + "stat_port_1-tx_multicast": u.Stat.Port1TxMulticast, + "stat_port_1-tx_packets": u.Stat.Port1TxPackets, + "stat_rx_bytes": u.Stat.RxBytes, + "stat_rx_crypts": u.Stat.RxCrypts, + "stat_rx_dropped": u.Stat.RxDropped, + "stat_rx_errors": u.Stat.RxErrors, + "stat_rx_frags": u.Stat.RxFrags, + "stat_rx_packets": u.Stat.TxPackets, + "stat_tx_bytes": u.Stat.TxBytes, + "stat_tx_dropped": u.Stat.TxDropped, + "stat_tx_errors": u.Stat.TxErrors, + "stat_tx_packets": u.Stat.TxPackets, + "stat_tx_retries": u.Stat.TxRetries, + "uplink_depth": strconv.FormatFloat(u.UplinkDepth, 'f', 6, 64), + // Add the port stats too. + } + pt, err := influx.NewPoint("usw", tags, fields, time.Now()) + if err == nil { + points = append(points, pt) + } + return points, err +} diff --git a/core/unifi/usw_type.go b/core/unifi/usw_type.go new file mode 100644 index 00000000..0b61ce17 --- /dev/null +++ b/core/unifi/usw_type.go @@ -0,0 +1,415 @@ +package unidev + +// USW is a Unifi Switch +type USW struct { + ID string `json:"_id"` + UUptime float64 `json:"_uptime"` + AdoptIP string `json:"adopt_ip"` + AdoptURL string `json:"adopt_url"` + Adopted bool `json:"adopted"` + BoardRev float64 `json:"board_rev"` + Bytes float64 `json:"bytes"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork struct { + IP string `json:"ip"` + Type string `json:"type"` + } `json:"config_network"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + ConsideredLostAt float64 `json:"considered_lost_at"` + Default bool `json:"default"` + DeviceID string `json:"device_id"` + DhcpServerTable []interface{} `json:"dhcp_server_table"` + DiscoveredVia string `json:"discovered_via"` + Dot1XPortctrlEnabled bool `json:"dot1x_portctrl_enabled"` + DownlinkTable []struct { + FullDuplex bool `json:"full_duplex"` + Mac string `json:"mac"` + PortIdx float64 `json:"port_idx"` + Speed float64 `json:"speed"` + } `json:"downlink_table"` + EthernetTable []struct { + Mac string `json:"mac"` + Name string `json:"name"` + NumPort float64 `json:"num_port,omitempty"` + } `json:"ethernet_table"` + FanLevel float64 `json:"fan_level"` + FlowctrlEnabled bool `json:"flowctrl_enabled"` + FwCaps float64 `json:"fw_caps"` + GeneralTemperature float64 `json:"general_temperature"` + GuestNumSta float64 `json:"guest-num_sta"` + HasFan bool `json:"has_fan"` + HasTemperature bool `json:"has_temperature"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + JumboframeEnabled bool `json:"jumboframe_enabled"` + KnownCfgversion string `json:"known_cfgversion"` + LastSeen float64 `json:"last_seen"` + LastUplink struct { + UplinkMac string `json:"uplink_mac"` + } `json:"last_uplink"` + LedOverride string `json:"led_override"` + LicenseState string `json:"license_state"` + Locating bool `json:"locating"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + NextHeartbeatAt float64 `json:"next_heartbeat_at"` + NumSta float64 `json:"num_sta"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + Overheating bool `json:"overheating"` + PortOverrides []struct { + Name string `json:"name,omitempty"` + PoeMode string `json:"poe_mode,omitempty"` + PortIdx float64 `json:"port_idx"` + PortconfID string `json:"portconf_id"` + } `json:"port_overrides"` + PortTable []struct { + AggregatedBy bool `json:"aggregated_by"` + Autoneg bool `json:"autoneg"` + BytesR float64 `json:"bytes-r"` + Dot1XMode string `json:"dot1x_mode"` + Dot1XStatus string `json:"dot1x_status"` + Enable bool `json:"enable"` + FlowctrlRx bool `json:"flowctrl_rx"` + FlowctrlTx bool `json:"flowctrl_tx"` + FullDuplex bool `json:"full_duplex"` + IsUplink bool `json:"is_uplink"` + Jumbo bool `json:"jumbo"` + LldpTable []interface{} `json:"lldp_table"` + Masked bool `json:"masked"` + Media string `json:"media"` + Name string `json:"name"` + OpMode string `json:"op_mode"` + PoeCaps float64 `json:"poe_caps"` + PoeClass string `json:"poe_class,omitempty"` + PoeCurrent string `json:"poe_current,omitempty"` + PoeEnable bool `json:"poe_enable,omitempty"` + PoeGood bool `json:"poe_good,omitempty"` + PoeMode string `json:"poe_mode,omitempty"` + PoePower string `json:"poe_power,omitempty"` + PoeVoltage string `json:"poe_voltage,omitempty"` + PortIdx float64 `json:"port_idx"` + PortPoe bool `json:"port_poe"` + PortconfID string `json:"portconf_id"` + RxBroadcast float64 `json:"rx_broadcast"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + StpPathcost float64 `json:"stp_pathcost"` + StpState string `json:"stp_state"` + TxBroadcast float64 `json:"tx_broadcast"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxMulticast float64 `json:"tx_multicast"` + TxPackets float64 `json:"tx_packets"` + Up bool `json:"up"` + SfpFound bool `json:"sfp_found,omitempty"` + } `json:"port_table"` + Rollupgrade bool `json:"rollupgrade"` + RxBytes float64 `json:"rx_bytes"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SSHSessionTable []interface{} `json:"ssh_session_table"` + + Stat struct { + Bytes float64 `json:"bytes"` + Datetime string `json:"datetime"` + Duration float64 `json:"duration"` + O string `json:"o"` + Oid string `json:"oid"` + Port1RxBroadcast float64 `json:"port_1-rx_broadcast"` + Port1RxBytes float64 `json:"port_1-rx_bytes"` + Port1RxDropped float64 `json:"port_1-rx_dropped"` + Port1RxMulticast float64 `json:"port_1-rx_multicast"` + Port1RxPackets float64 `json:"port_1-rx_packets"` + Port1TxBroadcast float64 `json:"port_1-tx_broadcast"` + Port1TxBytes float64 `json:"port_1-tx_bytes"` + Port1TxMulticast float64 `json:"port_1-tx_multicast"` + Port1TxPackets float64 `json:"port_1-tx_packets"` + Port2RxBroadcast float64 `json:"port_2-rx_broadcast"` + Port2RxBytes float64 `json:"port_2-rx_bytes"` + Port2RxDropped float64 `json:"port_2-rx_dropped"` + Port2RxMulticast float64 `json:"port_2-rx_multicast"` + Port2RxPackets float64 `json:"port_2-rx_packets"` + Port2TxBroadcast float64 `json:"port_2-tx_broadcast"` + Port2TxBytes float64 `json:"port_2-tx_bytes"` + Port2TxMulticast float64 `json:"port_2-tx_multicast"` + Port2TxPackets float64 `json:"port_2-tx_packets"` + Port3RxBroadcast float64 `json:"port_3-rx_broadcast"` + Port3RxBytes float64 `json:"port_3-rx_bytes"` + Port3RxDropped float64 `json:"port_3-rx_dropped"` + Port3RxMulticast float64 `json:"port_3-rx_multicast"` + Port3RxPackets float64 `json:"port_3-rx_packets"` + Port3TxBroadcast float64 `json:"port_3-tx_broadcast"` + Port3TxBytes float64 `json:"port_3-tx_bytes"` + Port3TxMulticast float64 `json:"port_3-tx_multicast"` + Port3TxPackets float64 `json:"port_3-tx_packets"` + Port4RxBroadcast float64 `json:"port_4-rx_broadcast"` + Port4RxBytes float64 `json:"port_4-rx_bytes"` + Port4RxDropped float64 `json:"port_4-rx_dropped"` + Port4RxMulticast float64 `json:"port_4-rx_multicast"` + Port4RxPackets float64 `json:"port_4-rx_packets"` + Port4TxBroadcast float64 `json:"port_4-tx_broadcast"` + Port4TxBytes float64 `json:"port_4-tx_bytes"` + Port4TxMulticast float64 `json:"port_4-tx_multicast"` + Port4TxPackets float64 `json:"port_4-tx_packets"` + Port5RxBroadcast float64 `json:"port_5-rx_broadcast"` + Port5RxBytes float64 `json:"port_5-rx_bytes"` + Port5RxDropped float64 `json:"port_5-rx_dropped"` + Port5RxMulticast float64 `json:"port_5-rx_multicast"` + Port5RxPackets float64 `json:"port_5-rx_packets"` + Port5TxBroadcast float64 `json:"port_5-tx_broadcast"` + Port5TxBytes float64 `json:"port_5-tx_bytes"` + Port5TxMulticast float64 `json:"port_5-tx_multicast"` + Port5TxPackets float64 `json:"port_5-tx_packets"` + Port6RxBroadcast float64 `json:"port_6-rx_broadcast"` + Port6RxBytes float64 `json:"port_6-rx_bytes"` + Port6RxDropped float64 `json:"port_6-rx_dropped"` + Port6RxMulticast float64 `json:"port_6-rx_multicast"` + Port6RxPackets float64 `json:"port_6-rx_packets"` + Port6TxBroadcast float64 `json:"port_6-tx_broadcast"` + Port6TxBytes float64 `json:"port_6-tx_bytes"` + Port6TxMulticast float64 `json:"port_6-tx_multicast"` + Port6TxPackets float64 `json:"port_6-tx_packets"` + Port7RxBroadcast float64 `json:"port_7-rx_broadcast"` + Port7RxBytes float64 `json:"port_7-rx_bytes"` + Port7RxDropped float64 `json:"port_7-rx_dropped"` + Port7RxMulticast float64 `json:"port_7-rx_multicast"` + Port7RxPackets float64 `json:"port_7-rx_packets"` + Port7TxBroadcast float64 `json:"port_7-tx_broadcast"` + Port7TxBytes float64 `json:"port_7-tx_bytes"` + Port7TxMulticast float64 `json:"port_7-tx_multicast"` + Port7TxPackets float64 `json:"port_7-tx_packets"` + Port8RxBroadcast float64 `json:"port_8-rx_broadcast"` + Port8RxBytes float64 `json:"port_8-rx_bytes"` + Port8RxDropped float64 `json:"port_8-rx_dropped"` + Port8RxMulticast float64 `json:"port_8-rx_multicast"` + Port8RxPackets float64 `json:"port_8-rx_packets"` + Port8TxBroadcast float64 `json:"port_8-tx_broadcast"` + Port8TxBytes float64 `json:"port_8-tx_bytes"` + Port8TxMulticast float64 `json:"port_8-tx_multicast"` + Port8TxPackets float64 `json:"port_8-tx_packets"` + Port9RxBroadcast float64 `json:"port_9-rx_broadcast"` + Port9RxBytes float64 `json:"port_9-rx_bytes"` + Port9RxDropped float64 `json:"port_9-rx_dropped"` + Port9RxMulticast float64 `json:"port_9-rx_multicast"` + Port9RxPackets float64 `json:"port_9-rx_packets"` + Port9TxBroadcast float64 `json:"port_9-tx_broadcast"` + Port9TxBytes float64 `json:"port_9-tx_bytes"` + Port9TxMulticast float64 `json:"port_9-tx_multicast"` + Port9TxPackets float64 `json:"port_9-tx_packets"` + Port10RxBroadcast float64 `json:"port_10-rx_broadcast"` + Port10RxBytes float64 `json:"port_10-rx_bytes"` + Port10RxDropped float64 `json:"port_10-rx_dropped"` + Port10RxMulticast float64 `json:"port_10-rx_multicast"` + Port10RxPackets float64 `json:"port_10-rx_packets"` + Port10TxBroadcast float64 `json:"port_10-tx_broadcast"` + Port10TxBytes float64 `json:"port_10-tx_bytes"` + Port10TxMulticast float64 `json:"port_10-tx_multicast"` + Port10TxPackets float64 `json:"port_10-tx_packets"` + Port11RxBroadcast float64 `json:"port_11-rx_broadcast"` + Port11RxBytes float64 `json:"port_11-rx_bytes"` + Port11RxDropped float64 `json:"port_11-rx_dropped"` + Port11RxMulticast float64 `json:"port_11-rx_multicast"` + Port11RxPackets float64 `json:"port_11-rx_packets"` + Port11TxBroadcast float64 `json:"port_11-tx_broadcast"` + Port11TxBytes float64 `json:"port_11-tx_bytes"` + Port11TxMulticast float64 `json:"port_11-tx_multicast"` + Port11TxPackets float64 `json:"port_11-tx_packets"` + Port12RxBroadcast float64 `json:"port_12-rx_broadcast"` + Port12RxBytes float64 `json:"port_12-rx_bytes"` + Port12RxDropped float64 `json:"port_12-rx_dropped"` + Port12RxMulticast float64 `json:"port_12-rx_multicast"` + Port12RxPackets float64 `json:"port_12-rx_packets"` + Port12TxBroadcast float64 `json:"port_12-tx_broadcast"` + Port12TxBytes float64 `json:"port_12-tx_bytes"` + Port12TxMulticast float64 `json:"port_12-tx_multicast"` + Port12TxPackets float64 `json:"port_12-tx_packets"` + Port13RxBroadcast float64 `json:"port_13-rx_broadcast"` + Port13RxBytes float64 `json:"port_13-rx_bytes"` + Port13RxDropped float64 `json:"port_13-rx_dropped"` + Port13RxMulticast float64 `json:"port_13-rx_multicast"` + Port13RxPackets float64 `json:"port_13-rx_packets"` + Port13TxBroadcast float64 `json:"port_13-tx_broadcast"` + Port13TxBytes float64 `json:"port_13-tx_bytes"` + Port13TxMulticast float64 `json:"port_13-tx_multicast"` + Port13TxPackets float64 `json:"port_13-tx_packets"` + Port14RxBroadcast float64 `json:"port_14-rx_broadcast"` + Port14RxBytes float64 `json:"port_14-rx_bytes"` + Port14RxDropped float64 `json:"port_14-rx_dropped"` + Port14RxMulticast float64 `json:"port_14-rx_multicast"` + Port14RxPackets float64 `json:"port_14-rx_packets"` + Port14TxBroadcast float64 `json:"port_14-tx_broadcast"` + Port14TxBytes float64 `json:"port_14-tx_bytes"` + Port14TxMulticast float64 `json:"port_14-tx_multicast"` + Port14TxPackets float64 `json:"port_14-tx_packets"` + Port15RxBroadcast float64 `json:"port_15-rx_broadcast"` + Port15RxBytes float64 `json:"port_15-rx_bytes"` + Port15RxDropped float64 `json:"port_15-rx_dropped"` + Port15RxMulticast float64 `json:"port_15-rx_multicast"` + Port15RxPackets float64 `json:"port_15-rx_packets"` + Port15TxBroadcast float64 `json:"port_15-tx_broadcast"` + Port15TxBytes float64 `json:"port_15-tx_bytes"` + Port15TxMulticast float64 `json:"port_15-tx_multicast"` + Port15TxPackets float64 `json:"port_15-tx_packets"` + Port16RxBroadcast float64 `json:"port_16-rx_broadcast"` + Port16RxBytes float64 `json:"port_16-rx_bytes"` + Port16RxDropped float64 `json:"port_16-rx_dropped"` + Port16RxMulticast float64 `json:"port_16-rx_multicast"` + Port16RxPackets float64 `json:"port_16-rx_packets"` + Port16TxBroadcast float64 `json:"port_16-tx_broadcast"` + Port16TxBytes float64 `json:"port_16-tx_bytes"` + Port16TxMulticast float64 `json:"port_16-tx_multicast"` + Port16TxPackets float64 `json:"port_16-tx_packets"` + Port17RxBroadcast float64 `json:"port_17-rx_broadcast"` + Port17RxBytes float64 `json:"port_17-rx_bytes"` + Port17RxDropped float64 `json:"port_17-rx_dropped"` + Port17RxMulticast float64 `json:"port_17-rx_multicast"` + Port17RxPackets float64 `json:"port_17-rx_packets"` + Port17TxBroadcast float64 `json:"port_17-tx_broadcast"` + Port17TxBytes float64 `json:"port_17-tx_bytes"` + Port17TxMulticast float64 `json:"port_17-tx_multicast"` + Port17TxPackets float64 `json:"port_17-tx_packets"` + Port18RxBroadcast float64 `json:"port_18-rx_broadcast"` + Port18RxBytes float64 `json:"port_18-rx_bytes"` + Port18RxDropped float64 `json:"port_18-rx_dropped"` + Port18RxMulticast float64 `json:"port_18-rx_multicast"` + Port18RxPackets float64 `json:"port_18-rx_packets"` + Port18TxBroadcast float64 `json:"port_18-tx_broadcast"` + Port18TxBytes float64 `json:"port_18-tx_bytes"` + Port18TxMulticast float64 `json:"port_18-tx_multicast"` + Port18TxPackets float64 `json:"port_18-tx_packets"` + Port19RxBroadcast float64 `json:"port_19-rx_broadcast"` + Port19RxBytes float64 `json:"port_19-rx_bytes"` + Port19RxDropped float64 `json:"port_19-rx_dropped"` + Port19RxMulticast float64 `json:"port_19-rx_multicast"` + Port19RxPackets float64 `json:"port_19-rx_packets"` + Port19TxBroadcast float64 `json:"port_19-tx_broadcast"` + Port19TxBytes float64 `json:"port_19-tx_bytes"` + Port19TxMulticast float64 `json:"port_19-tx_multicast"` + Port19TxPackets float64 `json:"port_19-tx_packets"` + Port20RxBroadcast float64 `json:"port_20-rx_broadcast"` + Port20RxBytes float64 `json:"port_20-rx_bytes"` + Port20RxDropped float64 `json:"port_20-rx_dropped"` + Port20RxMulticast float64 `json:"port_20-rx_multicast"` + Port20RxPackets float64 `json:"port_20-rx_packets"` + Port20TxBroadcast float64 `json:"port_20-tx_broadcast"` + Port20TxBytes float64 `json:"port_20-tx_bytes"` + Port20TxMulticast float64 `json:"port_20-tx_multicast"` + Port20TxPackets float64 `json:"port_20-tx_packets"` + Port21RxBroadcast float64 `json:"port_21-rx_broadcast"` + Port21RxBytes float64 `json:"port_21-rx_bytes"` + Port21RxDropped float64 `json:"port_21-rx_dropped"` + Port21RxMulticast float64 `json:"port_21-rx_multicast"` + Port21RxPackets float64 `json:"port_21-rx_packets"` + Port21TxBroadcast float64 `json:"port_21-tx_broadcast"` + Port21TxBytes float64 `json:"port_21-tx_bytes"` + Port21TxMulticast float64 `json:"port_21-tx_multicast"` + Port21TxPackets float64 `json:"port_21-tx_packets"` + Port22RxBroadcast float64 `json:"port_22-rx_broadcast"` + Port22RxBytes float64 `json:"port_22-rx_bytes"` + Port22RxDropped float64 `json:"port_22-rx_dropped"` + Port22RxMulticast float64 `json:"port_22-rx_multicast"` + Port22RxPackets float64 `json:"port_22-rx_packets"` + Port22TxBroadcast float64 `json:"port_22-tx_broadcast"` + Port22TxBytes float64 `json:"port_22-tx_bytes"` + Port22TxMulticast float64 `json:"port_22-tx_multicast"` + Port22TxPackets float64 `json:"port_22-tx_packets"` + Port23RxBroadcast float64 `json:"port_23-rx_broadcast"` + Port23RxBytes float64 `json:"port_23-rx_bytes"` + Port23RxDropped float64 `json:"port_23-rx_dropped"` + Port23RxMulticast float64 `json:"port_23-rx_multicast"` + Port23RxPackets float64 `json:"port_23-rx_packets"` + Port23TxBroadcast float64 `json:"port_23-tx_broadcast"` + Port23TxBytes float64 `json:"port_23-tx_bytes"` + Port23TxMulticast float64 `json:"port_23-tx_multicast"` + Port23TxPackets float64 `json:"port_23-tx_packets"` + Port24RxBroadcast float64 `json:"port_24-rx_broadcast"` + Port24RxBytes float64 `json:"port_24-rx_bytes"` + Port24RxDropped float64 `json:"port_24-rx_dropped"` + Port24RxMulticast float64 `json:"port_24-rx_multicast"` + Port24RxPackets float64 `json:"port_24-rx_packets"` + Port24TxBroadcast float64 `json:"port_24-tx_broadcast"` + Port24TxBytes float64 `json:"port_24-tx_bytes"` + Port24TxMulticast float64 `json:"port_24-tx_multicast"` + Port24TxPackets float64 `json:"port_24-tx_packets"` + // Have a 48 port switch? How 'bout a pull request. :D + RxBroadcast float64 `json:"rx_broadcast"` + RxBytes float64 `json:"rx_bytes"` + RxCrypts float64 `json:"rx_crypts"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxFrags float64 `json:"rx_frags"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + SiteID string `json:"site_id"` + Sw string `json:"sw"` + Time float64 `json:"time"` + TxBroadcast float64 `json:"tx_broadcast"` + TxBytes float64 `json:"tx_bytes"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxMulticast float64 `json:"tx_multicast"` + TxPackets float64 `json:"tx_packets"` + TxRetries float64 `json:"tx_retries"` + } `json:"stat"` + + State float64 `json:"state"` + StpPriority string `json:"stp_priority"` + StpVersion string `json:"stp_version"` + SysStats struct { + Loadavg1 float64 `json:"loadavg_1,string"` + Loadavg15 float64 `json:"loadavg_15,string"` + Loadavg5 float64 `json:"loadavg_5,string"` + MemBuffer float64 `json:"mem_buffer"` + MemTotal float64 `json:"mem_total"` + MemUsed float64 `json:"mem_used"` + } `json:"sys_stats"` + SystemStats struct { + CPU float64 `json:"cpu,string"` + Mem float64 `json:"mem,string"` + Uptime float64 `json:"uptime,string"` + } `json:"system-stats"` + TxBytes float64 `json:"tx_bytes"` + Type string `json:"type"` + Upgradable bool `json:"upgradable"` + Uplink struct { + FullDuplex bool `json:"full_duplex"` + IP string `json:"ip"` + Mac string `json:"mac"` + MaxSpeed float64 `json:"max_speed"` + Media string `json:"media"` + Name string `json:"name"` + Netmask string `json:"netmask"` + NumPort float64 `json:"num_port"` + PortIdx float64 `json:"port_idx"` + RxBytes float64 `json:"rx_bytes"` + RxBytesR float64 `json:"rx_bytes-r"` + RxDropped float64 `json:"rx_dropped"` + RxErrors float64 `json:"rx_errors"` + RxMulticast float64 `json:"rx_multicast"` + RxPackets float64 `json:"rx_packets"` + Speed float64 `json:"speed"` + TxBytes float64 `json:"tx_bytes"` + TxBytesR float64 `json:"tx_bytes-r"` + TxDropped float64 `json:"tx_dropped"` + TxErrors float64 `json:"tx_errors"` + TxPackets float64 `json:"tx_packets"` + Type string `json:"type"` + Up bool `json:"up"` + UplinkMac string `json:"uplink_mac"` + } `json:"uplink"` + UplinkDepth float64 `json:"uplink_depth"` + Uptime float64 `json:"uptime"` + UserNumSta float64 `json:"user-num_sta"` + Version string `json:"version"` + VersionIncompatible bool `json:"version_incompatible"` +}