Merge pull request #44 from unifi-poller/dn2_uxg_plus

Add UXG data struct, storage for UDM, and other minor improvements.
This commit is contained in:
David Newhall 2021-03-07 22:15:43 -08:00 committed by GitHub
commit ca6239efa9
18 changed files with 686 additions and 426 deletions

View File

@ -1,9 +1,9 @@
language: go language: go
go: go:
- 1.14.x - 1.15.x
before_install: before_install:
# download super-linter: golangci-lint # download super-linter: golangci-lint
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest - curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
script: script:
- golangci-lint run --enable-all - golangci-lint run --enable-all -D exhaustivestruct,nlreturn
- go test ./... - go test ./...

View File

@ -73,7 +73,7 @@ func (u *Unifi) GetAlarms(sites []*Site) ([]*Alarm, error) {
// GetAlarmsSite retreives the Alarms for a single Site. // GetAlarmsSite retreives the Alarms for a single Site.
func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) { func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) {
if site == nil || site.Name == "" { if site == nil || site.Name == "" {
return nil, errNoSiteProvided return nil, ErrNoSiteProvided
} }
u.DebugLog("Polling Controller for Alarms, site %s (%s)", site.Name, site.Desc) u.DebugLog("Polling Controller for Alarms, site %s (%s)", site.Name, site.Desc)

View File

@ -44,7 +44,7 @@ func (u *Unifi) GetAnomalies(sites []*Site, timeRange ...time.Time) ([]*Anomaly,
// GetAnomaliesSite retreives the Anomalies for a single Site. // GetAnomaliesSite retreives the Anomalies for a single Site.
func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) { func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) {
if site == nil || site.Name == "" { if site == nil || site.Name == "" {
return nil, errNoSiteProvided return nil, ErrNoSiteProvided
} }
u.DebugLog("Polling Controller for Anomalies, site %s (%s)", site.Name, site.Desc) u.DebugLog("Polling Controller for Anomalies, site %s (%s)", site.Name, site.Desc)
@ -119,7 +119,7 @@ func makeAnomalyParams(scale string, timeRange ...time.Time) (string, error) {
end := timeRange[1].Unix() * int64(time.Microsecond) end := timeRange[1].Unix() * int64(time.Microsecond)
out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10))
default: default:
return "", errInvalidTimeRange return "", ErrInvalidTimeRange
} }
if len(out) == 0 { if len(out) == 0 {

View File

@ -36,42 +36,27 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
for _, r := range data { for _, r := range data {
// Loop each item in the raw JSON message, detect its type and unmarshal it. // Loop each item in the raw JSON message, detect its type and unmarshal it.
assetType := "<type key missing>" o := make(map[string]interface{})
if u.unmarshalDevice("map", r, &o) != nil {
if o := make(map[string]interface{}); u.unmarshalDevice("map", r, &o) != nil { u.ErrorLog("unknown asset type - cannot find asset type in payload - skipping")
continue continue
} else if t, ok := o["type"].(string); ok {
assetType = t
} }
assetType, _ := o["type"].(string)
u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, siteName) u.DebugLog("Unmarshalling Device Type: %v, site %s ", assetType, siteName)
// Choose which type to unmarshal into based on the "type" json key. // Choose which type to unmarshal into based on the "type" json key.
switch assetType { // Unmarshal again into the correct type.. switch assetType { // Unmarshal again into the correct type..
case "uap": case "uap":
dev := &UAP{SiteName: siteName, SourceName: u.URL} u.unmarshallUAP(siteName, r, devices)
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UAPs = append(devices.UAPs, dev)
}
case "ugw", "usg": // in case they ever fix the name in the api. case "ugw", "usg": // in case they ever fix the name in the api.
dev := &USG{SiteName: siteName, SourceName: u.URL} u.unmarshallUSG(siteName, r, devices)
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USGs = append(devices.USGs, dev)
}
case "usw": case "usw":
dev := &USW{SiteName: siteName, SourceName: u.URL} u.unmarshallUSW(siteName, r, devices)
if u.unmarshalDevice(assetType, r, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USWs = append(devices.USWs, dev)
}
case "udm": case "udm":
dev := &UDM{SiteName: siteName, SourceName: u.URL} u.unmarshallUDM(siteName, r, devices)
if u.unmarshalDevice(assetType, r, dev) == nil { case "uxg":
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) u.unmarshallUXG(siteName, r, devices)
devices.UDMs = append(devices.UDMs, dev)
}
default: default:
u.ErrorLog("unknown asset type - %v - skipping", assetType) u.ErrorLog("unknown asset type - %v - skipping", assetType)
} }
@ -80,6 +65,46 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
return devices return devices
} }
func (u *Unifi) unmarshallUAP(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UAP{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("uap", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UAPs = append(devices.UAPs, dev)
}
}
func (u *Unifi) unmarshallUSG(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USG{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("ugw", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USGs = append(devices.USGs, dev)
}
}
func (u *Unifi) unmarshallUSW(siteName string, payload json.RawMessage, devices *Devices) {
dev := &USW{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("usw", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.USWs = append(devices.USWs, dev)
}
}
func (u *Unifi) unmarshallUXG(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UXG{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("uxg", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UXGs = append(devices.UXGs, dev)
}
}
func (u *Unifi) unmarshallUDM(siteName string, payload json.RawMessage, devices *Devices) {
dev := &UDM{SiteName: siteName, SourceName: u.URL}
if u.unmarshalDevice("udm", payload, dev) == nil {
dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac))
devices.UDMs = append(devices.UDMs, dev)
}
}
// unmarshalDevice handles logging for the unmarshal operations in parseDevices(). // unmarshalDevice handles logging for the unmarshal operations in parseDevices().
func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) { func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) {
if err = json.Unmarshal(data, v); err != nil { if err = json.Unmarshal(data, v); err != nil {
@ -92,7 +117,11 @@ func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{})
u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==") u.DebugLog("==- https://github.com/unifi-poller/unifi/issues/new -==")
} }
return err if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
} }
// pick returns the first non empty string in a list. // pick returns the first non empty string in a list.

View File

@ -21,6 +21,17 @@ type DPIData struct {
TxBytes int64 `json:"tx_bytes"` TxBytes int64 `json:"tx_bytes"`
RxPackets int64 `json:"rx_packets"` RxPackets int64 `json:"rx_packets"`
TxPackets int64 `json:"tx_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. // DPIMap allows binding methods to the DPICat and DPIApps variables.

View File

@ -8,8 +8,8 @@ import (
) )
var ( var (
errNoSiteProvided = fmt.Errorf("site must not be nil or empty") ErrNoSiteProvided = fmt.Errorf("site must not be nil or empty")
errInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange") ErrInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange")
) )
const ( const (
@ -35,7 +35,7 @@ func (u *Unifi) GetEvents(sites []*Site, hours time.Duration) ([]*Event, error)
// GetSiteEvents retrieves the last 1 hour's worth of events from a single site. // 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) { func (u *Unifi) GetSiteEvents(site *Site, hours time.Duration) ([]*Event, error) {
if site == nil || site.Name == "" { if site == nil || site.Name == "" {
return nil, errNoSiteProvided return nil, ErrNoSiteProvided
} }
if hours < time.Hour { if hours < time.Hour {
@ -196,5 +196,9 @@ func (v *IPGeo) UnmarshalJSON(data []byte) error {
v.CountryName = g.CountryName v.CountryName = g.CountryName
v.Organization = g.Organization v.Organization = g.Organization
return err if err != nil {
return fmt.Errorf("json unmarshal: %w", err)
}
return nil
} }

View File

@ -1,10 +1,10 @@
module github.com/unifi-poller/unifi module github.com/unifi-poller/unifi
go 1.14 go 1.15
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
) )

View File

@ -13,10 +13,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

View File

@ -76,7 +76,7 @@ func (u *Unifi) GetIDS(sites []*Site, timeRange ...time.Time) ([]*IDS, error) {
// Events between start and end are returned. End defaults to time.Now(). // Events between start and end are returned. End defaults to time.Now().
func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) { func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) {
if site == nil || site.Name == "" { if site == nil || site.Name == "" {
return nil, errNoSiteProvided return nil, ErrNoSiteProvided
} }
u.DebugLog("Polling Controller for IDS Events, site %s (%s)", site.Name, site.Desc) u.DebugLog("Polling Controller for IDS Events, site %s (%s)", site.Name, site.Desc)
@ -126,12 +126,15 @@ func makeEventParams(timeRange ...time.Time) (string, error) {
rp.Start = timeRange[0].Unix() * int64(time.Microsecond) rp.Start = timeRange[0].Unix() * int64(time.Microsecond)
rp.End = timeRange[1].Unix() * int64(time.Microsecond) rp.End = timeRange[1].Unix() * int64(time.Microsecond)
default: default:
return "", errInvalidTimeRange return "", ErrInvalidTimeRange
} }
params, err := json.Marshal(&rp) params, err := json.Marshal(&rp)
if err != nil {
return "", fmt.Errorf("json marshal: %w", err)
}
return string(params), err return string(params), nil
} }
type idsList []*IDS type idsList []*IDS

View File

@ -38,7 +38,7 @@ func (u *Unifi) parseNetwork(data json.RawMessage, siteName string) (*Network, e
return network, u.unmarshalDevice(siteName, data, network) return network, u.unmarshalDevice(siteName, data, network)
} }
// Network is metadata about a network managed by a UniFi controller // Network is metadata about a network managed by a UniFi controller.
type Network struct { type Network struct {
DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"`
DhcpdEnabled FlexBool `json:"dhcpd_enabled"` DhcpdEnabled FlexBool `json:"dhcpd_enabled"`

View File

@ -5,9 +5,7 @@ import (
"strings" "strings"
) )
var ( var ErrDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report")
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. // GetSites returns a list of configured sites on the UniFi controller.
func (u *Unifi) GetSites() ([]*Site, error) { func (u *Unifi) GetSites() ([]*Site, error) {
@ -53,7 +51,7 @@ func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) {
} }
if l := len(response.Data); l > 1 { if l := len(response.Data); l > 1 {
return nil, errDPIDataBug return nil, ErrDPIDataBug
} else if l == 0 { } else if l == 0 {
u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s (%s) ", site.Name, site.Desc) u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s (%s) ", site.Name, site.Desc)
continue continue

View File

@ -6,13 +6,10 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/pkg/errors"
) )
var ( var ErrCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt")
errCannotUnmarshalFlexInt = fmt.Errorf("cannot unmarshal to FlexInt")
)
// This is a list of unifi API paths. // This is a list of unifi API paths.
// The %s in each string must be replaced with a Site.Name. // The %s in each string must be replaced with a Site.Name.
@ -27,17 +24,17 @@ const (
APISiteDPI string = "/api/s/%s/stat/sitedpi" APISiteDPI string = "/api/s/%s/stat/sitedpi"
// APISiteDPI is site DPI data. // APISiteDPI is site DPI data.
APIClientDPI string = "/api/s/%s/stat/stadpi" APIClientDPI string = "/api/s/%s/stat/stadpi"
// APIClientPath is Unifi Clients API Path // APIClientPath is Unifi Clients API Path.
APIClientPath string = "/api/s/%s/stat/sta" APIClientPath string = "/api/s/%s/stat/sta"
// APINetworkPath is where we get data about Unifi networks. // APINetworkPath is where we get data about Unifi networks.
APINetworkPath string = "/api/s/%s/rest/networkconf" APINetworkPath string = "/api/s/%s/rest/networkconf"
// APIDevicePath is where we get data about Unifi devices. // APIDevicePath is where we get data about Unifi devices.
APIDevicePath string = "/api/s/%s/stat/device" APIDevicePath string = "/api/s/%s/stat/device"
// APILoginPath is Unifi Controller Login API Path // APILoginPath is Unifi Controller Login API Path.
APILoginPath string = "/api/login" APILoginPath string = "/api/login"
// APILoginPathNew is how we log into UDM 5.12.55+ // APILoginPathNew is how we log into UDM 5.12.55+.
APILoginPathNew string = "/api/auth/login" APILoginPathNew string = "/api/auth/login"
// APIEventPathIDS returns Intrusion Detection/Prevention Systems Events // APIEventPathIDS returns Intrusion Detection/Prevention Systems Events.
APIEventPathIDS string = "/api/s/%s/stat/ips/event" APIEventPathIDS string = "/api/s/%s/stat/ips/event"
// APIEventPathAlarms contains the site alarms. // APIEventPathAlarms contains the site alarms.
APIEventPathAlarms string = "/api/s/%s/list/alarm" APIEventPathAlarms string = "/api/s/%s/list/alarm"
@ -79,6 +76,7 @@ type Devices struct {
USGs []*USG USGs []*USG
USWs []*USW USWs []*USW
UDMs []*UDM UDMs []*UDM
UXGs []*UXG
} }
// Config is the data passed into our library. This configures things and allows // Config is the data passed into our library. This configures things and allows
@ -91,6 +89,7 @@ type Config struct {
New bool New bool
ErrorLog Logger ErrorLog Logger
DebugLog Logger DebugLog Logger
Timeout time.Duration // how long to wait for replies, default: forever.
} }
// Unifi is what you get in return for providing a password! Unifi represents // Unifi is what you get in return for providing a password! Unifi represents
@ -124,7 +123,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error {
var unk interface{} var unk interface{}
if err := json.Unmarshal(b, &unk); err != nil { if err := json.Unmarshal(b, &unk); err != nil {
return err return fmt.Errorf("json unmarshal: %w", err)
} }
switch i := unk.(type) { switch i := unk.(type) {
@ -138,7 +137,7 @@ func (f *FlexInt) UnmarshalJSON(b []byte) error {
f.Txt = "0" f.Txt = "0"
f.Val = 0 f.Val = 0
default: default:
return errors.Wrapf(errCannotUnmarshalFlexInt, "%v", b) return fmt.Errorf("%v: %w", b, ErrCannotUnmarshalFlexInt)
} }
return nil return nil

View File

@ -28,10 +28,7 @@ type UDM struct {
LastSeen FlexInt `json:"last_seen"` LastSeen FlexInt `json:"last_seen"`
AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"`
Cfgversion string `json:"cfgversion"` Cfgversion string `json:"cfgversion"`
ConfigNetwork struct { ConfigNetwork *ConfigNetwork `json:"config_network"`
Type string `json:"type"`
IP string `json:"ip"`
} `json:"config_network"`
VwireTable []interface{} `json:"vwire_table"` VwireTable []interface{} `json:"vwire_table"`
Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"`
JumboframeEnabled FlexBool `json:"jumboframe_enabled"` JumboframeEnabled FlexBool `json:"jumboframe_enabled"`
@ -49,16 +46,9 @@ type UDM struct {
InformIP string `json:"inform_ip"` InformIP string `json:"inform_ip"`
RequiredVersion string `json:"required_version"` RequiredVersion string `json:"required_version"`
BoardRev FlexInt `json:"board_rev"` BoardRev FlexInt `json:"board_rev"`
EthernetTable []struct { EthernetTable []*EthernetTable `json:"ethernet_table"`
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port"`
Name string `json:"name"`
} `json:"ethernet_table"`
PortTable []Port `json:"port_table"` PortTable []Port `json:"port_table"`
EthernetOverrides []struct { EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
} `json:"ethernet_overrides"`
UsgCaps FlexInt `json:"usg_caps"` UsgCaps FlexInt `json:"usg_caps"`
HasSpeaker FlexBool `json:"has_speaker"` HasSpeaker FlexBool `json:"has_speaker"`
HasEth1 FlexBool `json:"has_eth1"` HasEth1 FlexBool `json:"has_eth1"`
@ -96,7 +86,7 @@ type UDM struct {
Uplink Uplink `json:"uplink"` Uplink Uplink `json:"uplink"`
ConnectRequestIP string `json:"connect_request_ip"` ConnectRequestIP string `json:"connect_request_ip"`
ConnectRequestPort string `json:"connect_request_port"` ConnectRequestPort string `json:"connect_request_port"`
DownlinkTable []interface{} `json:"downlink_table"` DownlinkTable []*DownlinkTable `json:"downlink_table"`
WlangroupIDNa string `json:"wlangroup_id_na"` WlangroupIDNa string `json:"wlangroup_id_na"`
WlangroupIDNg string `json:"wlangroup_id_ng"` WlangroupIDNg string `json:"wlangroup_id_ng"`
BandsteeringMode string `json:"bandsteering_mode"` BandsteeringMode string `json:"bandsteering_mode"`
@ -110,6 +100,7 @@ type UDM struct {
PortconfID string `json:"portconf_id"` PortconfID string `json:"portconf_id"`
} `json:"port_overrides"` } `json:"port_overrides"`
Stat UDMStat `json:"stat"` Stat UDMStat `json:"stat"`
Storage []*Storage `json:"storage"`
TxBytes FlexInt `json:"tx_bytes"` TxBytes FlexInt `json:"tx_bytes"`
RxBytes FlexInt `json:"rx_bytes"` RxBytes FlexInt `json:"rx_bytes"`
Bytes FlexInt `json:"bytes"` Bytes FlexInt `json:"bytes"`
@ -131,7 +122,19 @@ type UDM struct {
NumHandheld FlexInt `json:"num_handheld"` // 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. // NetworkTable is the list of networks on a gateway.
// Not all gateways have all features.
type NetworkTable []struct { type NetworkTable []struct {
ID string `json:"_id"` ID string `json:"_id"`
AttrNoDelete FlexBool `json:"attr_no_delete"` AttrNoDelete FlexBool `json:"attr_no_delete"`
@ -150,6 +153,7 @@ type NetworkTable []struct {
Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"` Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"`
Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"` Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"`
LteLanEnabled FlexBool `json:"lte_lan_enabled"` LteLanEnabled FlexBool `json:"lte_lan_enabled"`
AutoScaleEnabled FlexBool `json:"auto_scale_enabled"`
Networkgroup string `json:"networkgroup"` Networkgroup string `json:"networkgroup"`
DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"` DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"`
DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"`
@ -167,6 +171,9 @@ type NetworkTable []struct {
IsGuest FlexBool `json:"is_guest"` IsGuest FlexBool `json:"is_guest"`
IP string `json:"ip"` IP string `json:"ip"`
Up FlexBool `json:"up"` 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"` NumSta FlexInt `json:"num_sta"`
RxBytes FlexInt `json:"rx_bytes"` RxBytes FlexInt `json:"rx_bytes"`
RxPackets FlexInt `json:"rx_packets"` RxPackets FlexInt `json:"rx_packets"`
@ -174,6 +181,15 @@ type NetworkTable []struct {
TxPackets FlexInt `json:"tx_packets"` 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 { type Temperature struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`

View File

@ -7,6 +7,7 @@ package unifi
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -18,13 +19,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/pkg/errors"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
var ( var (
errAuthenticationFailed = fmt.Errorf("authentication failed") ErrAuthenticationFailed = fmt.Errorf("authentication failed")
errInvalidStatusCode = fmt.Errorf("invalid status code from server") ErrInvalidStatusCode = fmt.Errorf("invalid status code from server")
ErrNoParams = fmt.Errorf("requedted PUT with no parameters")
) )
// NewUnifi creates a http.Client with authenticated cookies. // NewUnifi creates a http.Client with authenticated cookies.
@ -33,7 +34,7 @@ var (
func NewUnifi(config *Config) (*Unifi, error) { func NewUnifi(config *Config) (*Unifi, error) {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("creating cookiejar: %w", err)
} }
config.URL = strings.TrimRight(config.URL, "/") config.URL = strings.TrimRight(config.URL, "/")
@ -46,8 +47,10 @@ func NewUnifi(config *Config) (*Unifi, error) {
config.DebugLog = discardLogs config.DebugLog = discardLogs
} }
u := &Unifi{Config: config, u := &Unifi{
Config: config,
Client: &http.Client{ Client: &http.Client{
Timeout: config.Timeout,
Jar: jar, Jar: jar,
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, // nolint: gosec TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, // nolint: gosec
@ -64,7 +67,7 @@ func NewUnifi(config *Config) (*Unifi, error) {
} }
if err := u.GetServerData(); err != nil { if err := u.GetServerData(); err != nil {
return u, errors.Wrap(err, "unable to get server version") return u, fmt.Errorf("unable to get server version: %w", err)
} }
return u, nil return u, nil
@ -82,7 +85,7 @@ func (u *Unifi) Login() error {
resp, err := u.Do(req) resp, err := u.Do(req)
if err != nil { if err != nil {
return err return fmt.Errorf("making request: %w", err)
} }
defer resp.Body.Close() // we need no data here. defer resp.Body.Close() // we need no data here.
@ -91,8 +94,8 @@ func (u *Unifi) Login() error {
req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength) req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return errors.Wrapf(errAuthenticationFailed, "(user: %s): %s (status: %s)", return fmt.Errorf("(user: %s): %s (status: %s): %w",
u.User, req.URL, resp.Status) u.User, req.URL, resp.Status, ErrAuthenticationFailed)
} }
return nil return nil
@ -103,11 +106,21 @@ func (u *Unifi) Login() error {
// check if this is a newer controller or not. If it is, we set new to true. // 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. // Setting new to true makes the path() method return different (new) paths.
func (u *Unifi) checkNewStyleAPI() error { 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) u.DebugLog("Requesting %s/ to determine API paths", u.URL)
req, err := http.NewRequest("GET", u.URL+"/", nil) req, err := http.NewRequestWithContext(ctx, "GET", u.URL+"/", nil)
if err != nil { if err != nil {
return err return fmt.Errorf("creating request: %w", err)
} }
// We can't share these cookies with other requests, so make a new client. // We can't share these cookies with other requests, so make a new client.
@ -123,7 +136,7 @@ func (u *Unifi) checkNewStyleAPI() error {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return fmt.Errorf("making request: %w", err)
} }
defer resp.Body.Close() // we need no data here. defer resp.Body.Close() // we need no data here.
@ -185,39 +198,44 @@ func (u *Unifi) PutData(apiPath string, v interface{}, params ...string) error {
// Use this if you're unmarshalling UniFi data into custom types. // 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. :) // And if you're doing that... sumbut a pull request with your new struct. :)
// This is a helper method that is exposed for convenience. // This is a helper method that is exposed for convenience.
func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) { func (u *Unifi) UniReq(apiPath string, params string) (*http.Request, error) {
var (
req *http.Request
err error
)
switch apiPath = u.path(apiPath); params { switch apiPath = u.path(apiPath); params {
case "": case "":
req, err = http.NewRequest("GET", u.URL+apiPath, nil) req, err = http.NewRequest(http.MethodGet, u.URL+apiPath, nil)
default: default:
req, err = http.NewRequest("POST", u.URL+apiPath, bytes.NewBufferString(params)) req, err = http.NewRequest(http.MethodPost, u.URL+apiPath, bytes.NewBufferString(params))
} }
if err != nil { if err != nil {
return return nil, fmt.Errorf("creating request: %w", err)
} }
u.setHeaders(req, params) u.setHeaders(req, params)
return return req, nil
} }
// UniReqPut is the Put call equivalent to UniReq // UniReqPut is the Put call equivalent to UniReq.
func (u *Unifi) UniReqPut(apiPath string, params string) (req *http.Request, err error) { func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) {
switch apiPath = u.path(apiPath); params { if params == "" {
case "": return nil, ErrNoParams
err = fmt.Errorf("Put with no parameters. Use UniReq()")
default:
req, err = http.NewRequest("PUT", u.URL+apiPath, bytes.NewBufferString(params))
} }
apiPath = u.path(apiPath)
req, err := http.NewRequest(http.MethodPut, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx
if err != nil { if err != nil {
return return nil, fmt.Errorf("creating request: %w", err)
} }
u.setHeaders(req, params) u.setHeaders(req, params)
return return req, nil
} }
// GetJSON returns the raw JSON from a path. This is useful for debugging. // GetJSON returns the raw JSON from a path. This is useful for debugging.
@ -231,7 +249,7 @@ func (u *Unifi) GetJSON(apiPath string, params ...string) ([]byte, error) {
} }
// PutJSON uses a PUT call and returns the raw JSON in the same way as GetData // 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 // Use this if you want to change data via the REST API.
func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) { func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) {
req, err := u.UniReqPut(apiPath, strings.Join(params, " ")) req, err := u.UniReqPut(apiPath, strings.Join(params, " "))
if err != nil { if err != nil {
@ -242,16 +260,26 @@ func (u *Unifi) PutJSON(apiPath string, params ...string) ([]byte, error) {
} }
func (u *Unifi) do(req *http.Request) ([]byte, error) { func (u *Unifi) do(req *http.Request) ([]byte, error) {
resp, err := u.Do(req) 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 { if err != nil {
return []byte{}, err return []byte{}, fmt.Errorf("making request: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return body, err return body, fmt.Errorf("reading response: %w", err)
} }
// Save the returned CSRF header. // Save the returned CSRF header.
@ -260,7 +288,7 @@ func (u *Unifi) do(req *http.Request) ([]byte, error) {
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
err = errors.Wrapf(errInvalidStatusCode, "%s: %s", req.URL, resp.Status) err = fmt.Errorf("%s: %s: %w", req.URL, resp.Status, ErrInvalidStatusCode)
} }
return body, err return body, err

View File

@ -30,9 +30,11 @@ func TestUniReq(t *testing.T) {
a := assert.New(t) a := assert.New(t)
p := "/test/path" p := "/test/path"
u := "http://some.url:8443" u := "http://some.url:8443"
// Test empty parameters. // Test empty parameters.
authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}}
r, err := authReq.UniReq(p, "") r, err := authReq.UniReq(p, "")
a.Nil(err, "newrequest must not produce an error") a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path, a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request") "the provided apiPath was not added to http request")
@ -45,11 +47,13 @@ func TestUniReq(t *testing.T) {
authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}}
r, err = authReq.UniReq(p, k) r, err = authReq.UniReq(p, k)
a.Nil(err, "newrequest must not produce an error") a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path, a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request") "the provided apiPath was not added to http request")
a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") 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("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") a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json")
// Check the parameters. // Check the parameters.
d, err := ioutil.ReadAll(r.Body) d, err := ioutil.ReadAll(r.Body)
a.Nil(err, "problem reading request body, POST parameters may be malformed") a.Nil(err, "problem reading request body, POST parameters may be malformed")
@ -61,28 +65,31 @@ func TestUniReqPut(t *testing.T) {
a := assert.New(t) a := assert.New(t)
p := "/test/path" p := "/test/path"
u := "http://some.url:8443" u := "http://some.url:8443"
// Test empty parameters. // Test empty parameters.
authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}} authReq := &Unifi{Client: &http.Client{}, Config: &Config{URL: u, DebugLog: discardLogs}}
r, err := authReq.UniReqPut(p, "") _, err := authReq.UniReqPut(p, "")
a.NotNil(err, "empty params must produce an error") a.NotNil(err, "empty params must produce an error")
// Test with parameters // Test with parameters
k := "key1=value9&key2=value7" k := "key1=value9&key2=value7"
authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}} authReq = &Unifi{Client: &http.Client{}, Config: &Config{URL: "http://some.url:8443", DebugLog: discardLogs}}
r, err = authReq.UniReqPut(p, k) r, err := authReq.UniReqPut(p, k)
a.Nil(err, "newrequest must not produce an error") a.Nil(err, "newrequest must not produce an error")
a.EqualValues(p, r.URL.Path, a.EqualValues(p, r.URL.Path,
"the provided apiPath was not added to http request") "the provided apiPath was not added to http request")
a.EqualValues(u, r.URL.Scheme+"://"+r.URL.Host, "URL improperly encoded") 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("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") a.EqualValues("application/json", r.Header.Get("Accept"), "Accept header must be set to application/json")
// Check the parameters. // Check the parameters.
d, err := ioutil.ReadAll(r.Body) d, err := ioutil.ReadAll(r.Body)
a.Nil(err, "problem reading request body, PUT parameters may be malformed") a.Nil(err, "problem reading request body, PUT parameters may be malformed")
a.EqualValues(k, string(d), "PUT parameters improperly encoded") a.EqualValues(k, string(d), "PUT parameters improperly encoded")
} }
/* NOT DONE: OPEN web server, check parameters posted, more. This test is incomplete. /* NOT DONE: OPEN web server, check parameters posted, more. These tests are incomplete.
a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params),
"user/pass json parameters improperly encoded") "user/pass json parameters improperly encoded")
*/ */

View File

@ -11,15 +11,8 @@ type USG struct {
ID string `json:"_id"` ID string `json:"_id"`
Adopted FlexBool `json:"adopted"` Adopted FlexBool `json:"adopted"`
Cfgversion string `json:"cfgversion"` Cfgversion string `json:"cfgversion"`
ConfigNetwork struct { ConfigNetwork *ConfigNetwork `json:"config_network"`
Type string `json:"type"` EthernetTable []*EthernetTable `json:"ethernet_table"`
IP string `json:"ip"`
} `json:"config_network"`
EthernetTable []struct {
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port"`
Name string `json:"name"`
} `json:"ethernet_table"`
FwCaps FlexInt `json:"fw_caps"` FwCaps FlexInt `json:"fw_caps"`
InformIP string `json:"inform_ip"` InformIP string `json:"inform_ip"`
InformURL string `json:"inform_url"` InformURL string `json:"inform_url"`
@ -37,10 +30,7 @@ type USG struct {
UsgCaps FlexInt `json:"usg_caps"` UsgCaps FlexInt `json:"usg_caps"`
Version string `json:"version"` Version string `json:"version"`
RequiredVersion string `json:"required_version"` RequiredVersion string `json:"required_version"`
EthernetOverrides []struct { EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
} `json:"ethernet_overrides"`
HwCaps FlexInt `json:"hw_caps"` HwCaps FlexInt `json:"hw_caps"`
BoardRev FlexInt `json:"board_rev"` BoardRev FlexInt `json:"board_rev"`
Unsupported FlexBool `json:"unsupported"` Unsupported FlexBool `json:"unsupported"`
@ -57,34 +47,14 @@ type USG struct {
ConnectRequestIP string `json:"connect_request_ip"` ConnectRequestIP string `json:"connect_request_ip"`
ConnectRequestPort string `json:"connect_request_port"` ConnectRequestPort string `json:"connect_request_port"`
SysStats SysStats `json:"sys_stats"` SysStats SysStats `json:"sys_stats"`
Temperatures []Temperature `json:"temperatures,omitempty"`
SystemStats SystemStats `json:"system-stats"` SystemStats SystemStats `json:"system-stats"`
GuestToken string `json:"guest_token"` GuestToken string `json:"guest_token"`
SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` SpeedtestStatus SpeedtestStatus `json:"speedtest-status"`
SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"`
Wan1 Wan `json:"wan1"` Wan1 Wan `json:"wan1"`
Wan2 Wan `json:"wan2"` Wan2 Wan `json:"wan2"`
PortTable []struct { PortTable []*Port `json:"port_table"`
Name string `json:"name"`
Ifname string `json:"ifname"`
IP string `json:"ip"`
Netmask string `json:"netmask"`
Mac string `json:"mac"`
Up FlexBool `json:"up"`
Speed FlexInt `json:"speed"`
FullDuplex FlexBool `json:"full_duplex"`
RxBytes FlexInt `json:"rx_bytes"`
RxDropped FlexInt `json:"rx_dropped"`
RxErrors FlexInt `json:"rx_errors"`
RxPackets FlexInt `json:"rx_packets"`
TxBytes FlexInt `json:"tx_bytes"`
TxDropped FlexInt `json:"tx_dropped"`
TxErrors FlexInt `json:"tx_errors"`
TxPackets FlexInt `json:"tx_packets"`
RxMulticast FlexInt `json:"rx_multicast"`
Enable FlexBool `json:"enable"`
DNS []string `json:"dns,omitempty"`
Gateway string `json:"gateway,omitempty"`
} `json:"port_table"`
NetworkTable NetworkTable `json:"network_table"` NetworkTable NetworkTable `json:"network_table"`
Uplink Uplink `json:"uplink"` Uplink Uplink `json:"uplink"`
Stat USGStat `json:"stat"` Stat USGStat `json:"stat"`
@ -114,12 +84,15 @@ type Uplink struct {
Nameservers []string `json:"nameservers"` Nameservers []string `json:"nameservers"`
Netmask string `json:"netmask"` Netmask string `json:"netmask"`
NumPort FlexInt `json:"num_port"` NumPort FlexInt `json:"num_port"`
Media string `json:"media"`
PortIdx FlexInt `json:"port_idx"`
RxBytes FlexInt `json:"rx_bytes"` RxBytes FlexInt `json:"rx_bytes"`
RxBytesR FlexInt `json:"rx_bytes-r"` RxBytesR FlexInt `json:"rx_bytes-r"`
RxDropped FlexInt `json:"rx_dropped"` RxDropped FlexInt `json:"rx_dropped"`
RxErrors FlexInt `json:"rx_errors"` RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"` RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"` RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate"`
Speed FlexInt `json:"speed"` Speed FlexInt `json:"speed"`
SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"` SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"`
SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"` SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"`
@ -129,6 +102,7 @@ type Uplink struct {
TxDropped FlexInt `json:"tx_dropped"` TxDropped FlexInt `json:"tx_dropped"`
TxErrors FlexInt `json:"tx_errors"` TxErrors FlexInt `json:"tx_errors"`
TxPackets FlexInt `json:"tx_packets"` TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate"`
Type string `json:"type"` Type string `json:"type"`
Up FlexBool `json:"up"` Up FlexBool `json:"up"`
Uptime FlexInt `json:"uptime"` Uptime FlexInt `json:"uptime"`
@ -140,7 +114,7 @@ type Uplink struct {
type Wan struct { type Wan struct {
Autoneg FlexBool `json:"autoneg"` Autoneg FlexBool `json:"autoneg"`
BytesR FlexInt `json:"bytes-r"` BytesR FlexInt `json:"bytes-r"`
DNS []string `json:"dns"` DNS []string `json:"dns"` // may be deprecated
Enable FlexBool `json:"enable"` Enable FlexBool `json:"enable"`
FlowctrlRx FlexBool `json:"flowctrl_rx"` FlowctrlRx FlexBool `json:"flowctrl_rx"`
FlowctrlTx FlexBool `json:"flowctrl_tx"` FlowctrlTx FlexBool `json:"flowctrl_tx"`
@ -164,7 +138,9 @@ type Wan struct {
RxErrors FlexInt `json:"rx_errors"` RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"` RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"` RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate"`
Speed FlexInt `json:"speed"` Speed FlexInt `json:"speed"`
SpeedCaps FlexInt `json:"speed_caps"`
TxBroadcast FlexInt `json:"tx_broadcast"` TxBroadcast FlexInt `json:"tx_broadcast"`
TxBytes FlexInt `json:"tx_bytes"` TxBytes FlexInt `json:"tx_bytes"`
TxBytesR FlexInt `json:"tx_bytes-r"` TxBytesR FlexInt `json:"tx_bytes-r"`
@ -172,6 +148,7 @@ type Wan struct {
TxErrors FlexInt `json:"tx_errors"` TxErrors FlexInt `json:"tx_errors"`
TxMulticast FlexInt `json:"tx_multicast"` TxMulticast FlexInt `json:"tx_multicast"`
TxPackets FlexInt `json:"tx_packets"` TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate"`
Type string `json:"type"` Type string `json:"type"`
Up FlexBool `json:"up"` Up FlexBool `json:"up"`
} }
@ -182,6 +159,8 @@ type SpeedtestStatus struct {
Rundate FlexInt `json:"rundate"` Rundate FlexInt `json:"rundate"`
Runtime FlexInt `json:"runtime"` Runtime FlexInt `json:"runtime"`
ServerDesc string `json:"server_desc,omitempty"` ServerDesc string `json:"server_desc,omitempty"`
Server *SpeedtestServer `json:"server"`
SourceInterface string `json:"source_interface"`
StatusDownload FlexInt `json:"status_download"` StatusDownload FlexInt `json:"status_download"`
StatusPing FlexInt `json:"status_ping"` StatusPing FlexInt `json:"status_ping"`
StatusSummary FlexInt `json:"status_summary"` StatusSummary FlexInt `json:"status_summary"`
@ -190,6 +169,22 @@ type SpeedtestStatus struct {
XputUpload FlexInt `json:"xput_upload"` 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. // SystemStats is system info for a UDM, USG, USW.
type SystemStats struct { type SystemStats struct {
CPU FlexInt `json:"cpu"` CPU FlexInt `json:"cpu"`
@ -233,6 +228,7 @@ type Gw struct {
LanTxBytes FlexInt `json:"lan-tx_bytes"` LanTxBytes FlexInt `json:"lan-tx_bytes"`
LanRxDropped FlexInt `json:"lan-rx_dropped"` LanRxDropped FlexInt `json:"lan-rx_dropped"`
WanRxErrors FlexInt `json:"wan-rx_errors,omitempty"` 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. // UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Gateway Stat data.

View File

@ -13,16 +13,9 @@ type USW struct {
Adopted FlexBool `json:"adopted"` Adopted FlexBool `json:"adopted"`
BoardRev FlexInt `json:"board_rev"` BoardRev FlexInt `json:"board_rev"`
Cfgversion string `json:"cfgversion"` Cfgversion string `json:"cfgversion"`
ConfigNetwork struct { ConfigNetwork *ConfigNetwork `json:"config_network"`
Type string `json:"type"`
IP string `json:"ip"`
} `json:"config_network"`
Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"`
EthernetTable []struct { EthernetTable []*EthernetTable `json:"ethernet_table"`
Mac string `json:"mac"`
NumPort FlexInt `json:"num_port,omitempty"`
Name string `json:"name"`
} `json:"ethernet_table"`
FlowctrlEnabled FlexBool `json:"flowctrl_enabled"` FlowctrlEnabled FlexBool `json:"flowctrl_enabled"`
FwCaps FlexInt `json:"fw_caps"` FwCaps FlexInt `json:"fw_caps"`
HasFan FlexBool `json:"has_fan"` HasFan FlexBool `json:"has_fan"`
@ -51,11 +44,7 @@ type USW struct {
Type string `json:"type"` Type string `json:"type"`
Version string `json:"version"` Version string `json:"version"`
RequiredVersion string `json:"required_version"` RequiredVersion string `json:"required_version"`
SwitchCaps struct { SwitchCaps *SwitchCaps `json:"switch_caps"`
FeatureCaps FlexInt `json:"feature_caps"`
MaxMirrorSessions FlexInt `json:"max_mirror_sessions"`
MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"`
} `json:"switch_caps"`
HwCaps FlexInt `json:"hw_caps"` HwCaps FlexInt `json:"hw_caps"`
Unsupported FlexBool `json:"unsupported"` Unsupported FlexBool `json:"unsupported"`
UnsupportedReason FlexInt `json:"unsupported_reason"` UnsupportedReason FlexInt `json:"unsupported_reason"`
@ -77,12 +66,7 @@ type USW struct {
GeneralTemperature FlexInt `json:"general_temperature"` GeneralTemperature FlexInt `json:"general_temperature"`
Overheating FlexBool `json:"overheating"` Overheating FlexBool `json:"overheating"`
TotalMaxPower FlexInt `json:"total_max_power"` TotalMaxPower FlexInt `json:"total_max_power"`
DownlinkTable []struct { DownlinkTable []*DownlinkTable `json:"downlink_table"`
PortIdx FlexInt `json:"port_idx"`
Speed FlexInt `json:"speed"`
FullDuplex FlexBool `json:"full_duplex"`
Mac string `json:"mac"`
} `json:"downlink_table"`
Uplink Uplink `json:"uplink"` Uplink Uplink `json:"uplink"`
LastUplink struct { LastUplink struct {
UplinkMac string `json:"uplink_mac"` UplinkMac string `json:"uplink_mac"`
@ -97,7 +81,24 @@ type USW struct {
GuestNumSta FlexInt `json:"guest-num_sta"` GuestNumSta FlexInt `json:"guest-num_sta"`
} }
// Port is a physical connection on a USW or UDM. 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 { type Port struct {
AggregatedBy FlexBool `json:"aggregated_by"` AggregatedBy FlexBool `json:"aggregated_by"`
Autoneg FlexBool `json:"autoneg,omitempty"` Autoneg FlexBool `json:"autoneg,omitempty"`
@ -113,11 +114,13 @@ type Port struct {
Ifname string `json:"ifname,omitempty"` Ifname string `json:"ifname,omitempty"`
IsUplink FlexBool `json:"is_uplink"` IsUplink FlexBool `json:"is_uplink"`
Mac string `json:"mac,omitempty"` Mac string `json:"mac,omitempty"`
MacTable []MacTable `json:"mac_table,omitempty"`
Jumbo FlexBool `json:"jumbo,omitempty"` Jumbo FlexBool `json:"jumbo,omitempty"`
Masked FlexBool `json:"masked"` Masked FlexBool `json:"masked"`
Media string `json:"media"` Media string `json:"media"`
Name string `json:"name"` Name string `json:"name"`
NetworkName string `json:"network_name,omitempty"` NetworkName string `json:"network_name,omitempty"`
Netmask string `json:"netmask,omitempty"`
NumPort int `json:"num_port,omitempty"` NumPort int `json:"num_port,omitempty"`
OpMode string `json:"op_mode"` OpMode string `json:"op_mode"`
PoeCaps FlexInt `json:"poe_caps"` PoeCaps FlexInt `json:"poe_caps"`
@ -130,6 +133,7 @@ type Port struct {
PoeVoltage FlexInt `json:"poe_voltage,omitempty"` PoeVoltage FlexInt `json:"poe_voltage,omitempty"`
PortDelta struct { PortDelta struct {
TimeDelta int64 `json:"time_delta"` TimeDelta int64 `json:"time_delta"`
TimeDeltaActivity int64 `json:"time_delta_activity"`
} `json:"port_delta,omitempty"` } `json:"port_delta,omitempty"`
PortIdx FlexInt `json:"port_idx"` PortIdx FlexInt `json:"port_idx"`
PortPoe FlexBool `json:"port_poe"` PortPoe FlexBool `json:"port_poe"`
@ -141,6 +145,7 @@ type Port struct {
RxErrors FlexInt `json:"rx_errors"` RxErrors FlexInt `json:"rx_errors"`
RxMulticast FlexInt `json:"rx_multicast"` RxMulticast FlexInt `json:"rx_multicast"`
RxPackets FlexInt `json:"rx_packets"` RxPackets FlexInt `json:"rx_packets"`
RxRate FlexInt `json:"rx_rate,omitempty"`
Satisfaction FlexInt `json:"satisfaction,omitempty"` Satisfaction FlexInt `json:"satisfaction,omitempty"`
SfpFound FlexBool `json:"sfp_found,omitempty"` SfpFound FlexBool `json:"sfp_found,omitempty"`
Speed FlexInt `json:"speed"` Speed FlexInt `json:"speed"`
@ -154,6 +159,7 @@ type Port struct {
TxErrors FlexInt `json:"tx_errors"` TxErrors FlexInt `json:"tx_errors"`
TxMulticast FlexInt `json:"tx_multicast"` TxMulticast FlexInt `json:"tx_multicast"`
TxPackets FlexInt `json:"tx_packets"` TxPackets FlexInt `json:"tx_packets"`
TxRate FlexInt `json:"tx_rate,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Up FlexBool `json:"up"` Up FlexBool `json:"up"`
} }

157
core/unifi/uxg.go Normal file
View File

@ -0,0 +1,157 @@
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 {
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"`
}