From e1be3246f1afa5f31a6c0fade3c7196004703f0e Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Sun, 7 Mar 2021 17:23:31 -0800 Subject: [PATCH] Add UXG, UDM storage, minor lint fixups. --- core/unifi/alarms.go | 2 +- core/unifi/anomalies.go | 4 +- core/unifi/devices.go | 6 + core/unifi/dpi.go | 23 +++- core/unifi/events.go | 6 +- core/unifi/ids.go | 9 +- core/unifi/site.go | 6 +- core/unifi/types.go | 3 + core/unifi/udm.go | 262 +++++++++++++++++++++------------------- core/unifi/unifi.go | 64 ++++++---- core/unifi/usg.go | 149 +++++++++++++---------- core/unifi/usw.go | 103 +++++++++------- 12 files changed, 362 insertions(+), 275 deletions(-) diff --git a/core/unifi/alarms.go b/core/unifi/alarms.go index 63c558e9..863e27ca 100644 --- a/core/unifi/alarms.go +++ b/core/unifi/alarms.go @@ -73,7 +73,7 @@ func (u *Unifi) GetAlarms(sites []*Site) ([]*Alarm, error) { // GetAlarmsSite retreives the Alarms for a single Site. func (u *Unifi) GetAlarmsSite(site *Site) ([]*Alarm, error) { if site == nil || site.Name == "" { - return nil, errNoSiteProvided + return nil, ErrNoSiteProvided } u.DebugLog("Polling Controller for Alarms, site %s (%s)", site.Name, site.Desc) diff --git a/core/unifi/anomalies.go b/core/unifi/anomalies.go index 9f7de0e2..2dfb5238 100644 --- a/core/unifi/anomalies.go +++ b/core/unifi/anomalies.go @@ -44,7 +44,7 @@ func (u *Unifi) GetAnomalies(sites []*Site, timeRange ...time.Time) ([]*Anomaly, // GetAnomaliesSite retreives the Anomalies for a single Site. func (u *Unifi) GetAnomaliesSite(site *Site, timeRange ...time.Time) ([]*Anomaly, error) { if site == nil || site.Name == "" { - return nil, errNoSiteProvided + return nil, ErrNoSiteProvided } 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) out = append(out, "end="+strconv.FormatInt(end, 10), "start="+strconv.FormatInt(start, 10)) default: - return "", errInvalidTimeRange + return "", ErrInvalidTimeRange } if len(out) == 0 { diff --git a/core/unifi/devices.go b/core/unifi/devices.go index 045e43d7..505d2743 100644 --- a/core/unifi/devices.go +++ b/core/unifi/devices.go @@ -72,6 +72,12 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices { dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) devices.UDMs = append(devices.UDMs, dev) } + case "uxg": + dev := &UXG{SiteName: siteName, SourceName: u.URL} + if u.unmarshalDevice(assetType, r, dev) == nil { + dev.Name = strings.TrimSpace(pick(dev.Name, dev.Mac)) + devices.UXGs = append(devices.UXGs, dev) + } default: u.ErrorLog("unknown asset type - %v - skipping", assetType) } diff --git a/core/unifi/dpi.go b/core/unifi/dpi.go index 6dc3d843..f487c877 100644 --- a/core/unifi/dpi.go +++ b/core/unifi/dpi.go @@ -15,12 +15,23 @@ type DPITable struct { // DPIData is the DPI data in the DPI table. type DPIData struct { - Cat int `json:"cat"` - App int `json:"app"` - RxBytes int64 `json:"rx_bytes"` - TxBytes int64 `json:"tx_bytes"` - RxPackets int64 `json:"rx_packets"` - TxPackets int64 `json:"tx_packets"` + Cat int `json:"cat"` + App int `json:"app"` + RxBytes int64 `json:"rx_bytes"` + TxBytes int64 `json:"tx_bytes"` + RxPackets int64 `json:"rx_packets"` + TxPackets int64 `json:"tx_packets"` + Clients []*DPIClient `json:"clients,omitempty"` + KnownClients FlexInt `json:"known_clients,omitempty"` +} + +// DPIClient data is sometimes included in ByApp output. +type DPIClient struct { + Mac string `json:"mac"` + RxBytes FlexInt `json:"rx_bytes"` + TxBytes FlexInt `json:"tx_bytes"` + RxPackets FlexInt `json:"rx_packets"` + TxPackets FlexInt `json:"tx_packets"` } // DPIMap allows binding methods to the DPICat and DPIApps variables. diff --git a/core/unifi/events.go b/core/unifi/events.go index 1f5f8458..d716daee 100644 --- a/core/unifi/events.go +++ b/core/unifi/events.go @@ -8,8 +8,8 @@ import ( ) var ( - errNoSiteProvided = fmt.Errorf("site must not be nil or empty") - errInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange") + ErrNoSiteProvided = fmt.Errorf("site must not be nil or empty") + ErrInvalidTimeRange = fmt.Errorf("only 0, 1 or 2 times may be provided to timeRange") ) const ( @@ -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. func (u *Unifi) GetSiteEvents(site *Site, hours time.Duration) ([]*Event, error) { if site == nil || site.Name == "" { - return nil, errNoSiteProvided + return nil, ErrNoSiteProvided } if hours < time.Hour { diff --git a/core/unifi/ids.go b/core/unifi/ids.go index 0140e157..c290cbe2 100644 --- a/core/unifi/ids.go +++ b/core/unifi/ids.go @@ -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(). func (u *Unifi) GetIDSSite(site *Site, timeRange ...time.Time) ([]*IDS, error) { 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) @@ -126,12 +126,15 @@ func makeEventParams(timeRange ...time.Time) (string, error) { rp.Start = timeRange[0].Unix() * int64(time.Microsecond) rp.End = timeRange[1].Unix() * int64(time.Microsecond) default: - return "", errInvalidTimeRange + return "", ErrInvalidTimeRange } 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 diff --git a/core/unifi/site.go b/core/unifi/site.go index 7939cb06..0d3826a1 100644 --- a/core/unifi/site.go +++ b/core/unifi/site.go @@ -5,9 +5,7 @@ import ( "strings" ) -var ( - errDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report") -) +var ErrDPIDataBug = fmt.Errorf("dpi data table contains more than 1 item; please open a bug report") // GetSites returns a list of configured sites on the UniFi controller. func (u *Unifi) GetSites() ([]*Site, error) { @@ -53,7 +51,7 @@ func (u *Unifi) GetSiteDPI(sites []*Site) ([]*DPITable, error) { } if l := len(response.Data); l > 1 { - return nil, errDPIDataBug + return nil, ErrDPIDataBug } else if l == 0 { u.DebugLog("Site DPI data missing! Is DPI enabled in UniFi controller? Site %s (%s) ", site.Name, site.Desc) continue diff --git a/core/unifi/types.go b/core/unifi/types.go index 626ce333..b3ce54a8 100644 --- a/core/unifi/types.go +++ b/core/unifi/types.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/pkg/errors" ) @@ -79,6 +80,7 @@ type Devices struct { USGs []*USG USWs []*USW UDMs []*UDM + UXGs []*UXG } // Config is the data passed into our library. This configures things and allows @@ -91,6 +93,7 @@ type Config struct { New bool ErrorLog 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 diff --git a/core/unifi/udm.go b/core/unifi/udm.go index 041eb2b5..49053539 100644 --- a/core/unifi/udm.go +++ b/core/unifi/udm.go @@ -3,69 +3,59 @@ package unifi // UDM represents all the data from the Ubiquiti Controller for a Unifi Dream Machine. // The UDM shares several structs/type-data with USW and USG. type UDM struct { - SourceName string `json:"-"` - SiteID string `json:"site_id"` - SiteName string `json:"-"` - Mac string `json:"mac"` - Adopted FlexBool `json:"adopted"` - Serial string `json:"serial"` - IP string `json:"ip"` - Uptime FlexInt `json:"uptime"` - Model string `json:"model"` - Version string `json:"version"` - Name string `json:"name"` - Default FlexBool `json:"default"` - Locating FlexBool `json:"locating"` - Type string `json:"type"` - Unsupported FlexBool `json:"unsupported"` - UnsupportedReason FlexInt `json:"unsupported_reason"` - DiscoveredVia string `json:"discovered_via"` - AdoptIP string `json:"adopt_ip"` - AdoptURL string `json:"adopt_url"` - State FlexInt `json:"state"` - AdoptStatus FlexInt `json:"adopt_status"` - UpgradeState FlexInt `json:"upgrade_state"` - LastSeen FlexInt `json:"last_seen"` - AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` - Cfgversion string `json:"cfgversion"` - ConfigNetwork struct { - Type string `json:"type"` - IP string `json:"ip"` - } `json:"config_network"` - VwireTable []interface{} `json:"vwire_table"` - Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` - JumboframeEnabled FlexBool `json:"jumboframe_enabled"` - FlowctrlEnabled FlexBool `json:"flowctrl_enabled"` - StpVersion string `json:"stp_version"` - StpPriority FlexInt `json:"stp_priority"` - PowerSourceCtrlEnabled FlexBool `json:"power_source_ctrl_enabled"` - LicenseState string `json:"license_state"` - ID string `json:"_id"` - DeviceID string `json:"device_id"` - AdoptState FlexInt `json:"adopt_state"` - AdoptTries FlexInt `json:"adopt_tries"` - AdoptManual FlexBool `json:"adopt_manual"` - InformURL string `json:"inform_url"` - InformIP string `json:"inform_ip"` - RequiredVersion string `json:"required_version"` - BoardRev FlexInt `json:"board_rev"` - EthernetTable []struct { - Mac string `json:"mac"` - NumPort FlexInt `json:"num_port"` - Name string `json:"name"` - } `json:"ethernet_table"` - PortTable []Port `json:"port_table"` - EthernetOverrides []struct { - Ifname string `json:"ifname"` - Networkgroup string `json:"networkgroup"` - } `json:"ethernet_overrides"` - UsgCaps FlexInt `json:"usg_caps"` - HasSpeaker FlexBool `json:"has_speaker"` - HasEth1 FlexBool `json:"has_eth1"` - FwCaps FlexInt `json:"fw_caps"` - HwCaps FlexInt `json:"hw_caps"` - WifiCaps FlexInt `json:"wifi_caps"` - SwitchCaps struct { + SourceName string `json:"-"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + Mac string `json:"mac"` + Adopted FlexBool `json:"adopted"` + Serial string `json:"serial"` + IP string `json:"ip"` + Uptime FlexInt `json:"uptime"` + Model string `json:"model"` + Version string `json:"version"` + Name string `json:"name"` + Default FlexBool `json:"default"` + Locating FlexBool `json:"locating"` + Type string `json:"type"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + DiscoveredVia string `json:"discovered_via"` + AdoptIP string `json:"adopt_ip"` + AdoptURL string `json:"adopt_url"` + State FlexInt `json:"state"` + AdoptStatus FlexInt `json:"adopt_status"` + UpgradeState FlexInt `json:"upgrade_state"` + LastSeen FlexInt `json:"last_seen"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + VwireTable []interface{} `json:"vwire_table"` + Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"` + JumboframeEnabled FlexBool `json:"jumboframe_enabled"` + FlowctrlEnabled FlexBool `json:"flowctrl_enabled"` + StpVersion string `json:"stp_version"` + StpPriority FlexInt `json:"stp_priority"` + PowerSourceCtrlEnabled FlexBool `json:"power_source_ctrl_enabled"` + LicenseState string `json:"license_state"` + ID string `json:"_id"` + DeviceID string `json:"device_id"` + AdoptState FlexInt `json:"adopt_state"` + AdoptTries FlexInt `json:"adopt_tries"` + AdoptManual FlexBool `json:"adopt_manual"` + InformURL string `json:"inform_url"` + InformIP string `json:"inform_ip"` + RequiredVersion string `json:"required_version"` + BoardRev FlexInt `json:"board_rev"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + PortTable []Port `json:"port_table"` + EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"` + UsgCaps FlexInt `json:"usg_caps"` + HasSpeaker FlexBool `json:"has_speaker"` + HasEth1 FlexBool `json:"has_eth1"` + FwCaps FlexInt `json:"fw_caps"` + HwCaps FlexInt `json:"hw_caps"` + WifiCaps FlexInt `json:"wifi_caps"` + SwitchCaps struct { MaxMirrorSessions FlexInt `json:"max_mirror_sessions"` MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` } `json:"switch_caps"` @@ -96,7 +86,7 @@ type UDM struct { Uplink Uplink `json:"uplink"` ConnectRequestIP string `json:"connect_request_ip"` ConnectRequestPort string `json:"connect_request_port"` - DownlinkTable []interface{} `json:"downlink_table"` + DownlinkTable []*DownlinkTable `json:"downlink_table"` WlangroupIDNa string `json:"wlangroup_id_na"` WlangroupIDNg string `json:"wlangroup_id_ng"` BandsteeringMode string `json:"bandsteering_mode"` @@ -109,69 +99,95 @@ type UDM struct { PortIdx FlexInt `json:"port_idx"` PortconfID string `json:"portconf_id"` } `json:"port_overrides"` - Stat UDMStat `json:"stat"` - TxBytes FlexInt `json:"tx_bytes"` - RxBytes FlexInt `json:"rx_bytes"` - Bytes FlexInt `json:"bytes"` - BytesD FlexInt `json:"bytes-d"` - TxBytesD FlexInt `json:"tx_bytes-d"` - RxBytesD FlexInt `json:"rx_bytes-d"` - BytesR FlexInt `json:"bytes-r"` - NumSta FlexInt `json:"num_sta"` // USG - WlanNumSta FlexInt `json:"wlan-num_sta"` // UAP - LanNumSta FlexInt `json:"lan-num_sta"` // USW - UserWlanNumSta FlexInt `json:"user-wlan-num_sta"` // UAP - UserLanNumSta FlexInt `json:"user-lan-num_sta"` // USW - UserNumSta FlexInt `json:"user-num_sta"` // USG - GuestWlanNumSta FlexInt `json:"guest-wlan-num_sta"` // UAP - GuestLanNumSta FlexInt `json:"guest-lan-num_sta"` // USW - GuestNumSta FlexInt `json:"guest-num_sta"` // USG - NumDesktop FlexInt `json:"num_desktop"` // USG - NumMobile FlexInt `json:"num_mobile"` // USG - NumHandheld FlexInt `json:"num_handheld"` // USG + Stat UDMStat `json:"stat"` + Storage []*Storage `json:"storage"` + TxBytes FlexInt `json:"tx_bytes"` + RxBytes FlexInt `json:"rx_bytes"` + Bytes FlexInt `json:"bytes"` + BytesD FlexInt `json:"bytes-d"` + TxBytesD FlexInt `json:"tx_bytes-d"` + RxBytesD FlexInt `json:"rx_bytes-d"` + BytesR FlexInt `json:"bytes-r"` + NumSta FlexInt `json:"num_sta"` // USG + WlanNumSta FlexInt `json:"wlan-num_sta"` // UAP + LanNumSta FlexInt `json:"lan-num_sta"` // USW + UserWlanNumSta FlexInt `json:"user-wlan-num_sta"` // UAP + UserLanNumSta FlexInt `json:"user-lan-num_sta"` // USW + UserNumSta FlexInt `json:"user-num_sta"` // USG + GuestWlanNumSta FlexInt `json:"guest-wlan-num_sta"` // UAP + GuestLanNumSta FlexInt `json:"guest-lan-num_sta"` // USW + GuestNumSta FlexInt `json:"guest-num_sta"` // USG + NumDesktop FlexInt `json:"num_desktop"` // USG + NumMobile FlexInt `json:"num_mobile"` // USG + NumHandheld FlexInt `json:"num_handheld"` // USG +} + +type EthernetOverrides struct { + Ifname string `json:"ifname"` + Networkgroup string `json:"networkgroup"` +} + +type EthernetTable struct { + Mac string `json:"mac"` + NumPort FlexInt `json:"num_port"` + Name string `json:"name"` } // NetworkTable is the list of networks on a gateway. +// Not all gateways have all features. type NetworkTable []struct { - ID string `json:"_id"` - AttrNoDelete FlexBool `json:"attr_no_delete"` - AttrHiddenID string `json:"attr_hidden_id"` - Name string `json:"name"` - SiteID string `json:"site_id"` - VlanEnabled FlexBool `json:"vlan_enabled"` - Purpose string `json:"purpose"` - IPSubnet string `json:"ip_subnet"` - Ipv6InterfaceType string `json:"ipv6_interface_type"` - DomainName string `json:"domain_name"` - IsNat FlexBool `json:"is_nat"` - DhcpdEnabled FlexBool `json:"dhcpd_enabled"` - DhcpdStart string `json:"dhcpd_start"` - DhcpdStop string `json:"dhcpd_stop"` - Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"` - Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"` - LteLanEnabled FlexBool `json:"lte_lan_enabled"` - Networkgroup string `json:"networkgroup"` - DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"` - DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` - DhcpdGatewayEnabled FlexBool `json:"dhcpd_gateway_enabled"` - DhcpdTimeOffsetEnabled FlexBool `json:"dhcpd_time_offset_enabled"` - Ipv6PdStart string `json:"ipv6_pd_start"` - Ipv6PdStop string `json:"ipv6_pd_stop"` - DhcpdDNS1 string `json:"dhcpd_dns_1"` - DhcpdDNS2 string `json:"dhcpd_dns_2"` - DhcpdDNS3 string `json:"dhcpd_dns_3"` - DhcpdDNS4 string `json:"dhcpd_dns_4"` - Enabled FlexBool `json:"enabled"` - DhcpRelayEnabled FlexBool `json:"dhcp_relay_enabled"` - Mac string `json:"mac"` - IsGuest FlexBool `json:"is_guest"` - IP string `json:"ip"` - Up FlexBool `json:"up"` - NumSta FlexInt `json:"num_sta"` - RxBytes FlexInt `json:"rx_bytes"` - RxPackets FlexInt `json:"rx_packets"` - TxBytes FlexInt `json:"tx_bytes"` - TxPackets FlexInt `json:"tx_packets"` + ID string `json:"_id"` + AttrNoDelete FlexBool `json:"attr_no_delete"` + AttrHiddenID string `json:"attr_hidden_id"` + Name string `json:"name"` + SiteID string `json:"site_id"` + VlanEnabled FlexBool `json:"vlan_enabled"` + Purpose string `json:"purpose"` + IPSubnet string `json:"ip_subnet"` + Ipv6InterfaceType string `json:"ipv6_interface_type"` + DomainName string `json:"domain_name"` + IsNat FlexBool `json:"is_nat"` + DhcpdEnabled FlexBool `json:"dhcpd_enabled"` + DhcpdStart string `json:"dhcpd_start"` + DhcpdStop string `json:"dhcpd_stop"` + Dhcpdv6Enabled FlexBool `json:"dhcpdv6_enabled"` + Ipv6RaEnabled FlexBool `json:"ipv6_ra_enabled"` + LteLanEnabled FlexBool `json:"lte_lan_enabled"` + AutoScaleEnabled FlexBool `json:"auto_scale_enabled"` + Networkgroup string `json:"networkgroup"` + DhcpdLeasetime FlexInt `json:"dhcpd_leasetime"` + DhcpdDNSEnabled FlexBool `json:"dhcpd_dns_enabled"` + DhcpdGatewayEnabled FlexBool `json:"dhcpd_gateway_enabled"` + DhcpdTimeOffsetEnabled FlexBool `json:"dhcpd_time_offset_enabled"` + Ipv6PdStart string `json:"ipv6_pd_start"` + Ipv6PdStop string `json:"ipv6_pd_stop"` + DhcpdDNS1 string `json:"dhcpd_dns_1"` + DhcpdDNS2 string `json:"dhcpd_dns_2"` + DhcpdDNS3 string `json:"dhcpd_dns_3"` + DhcpdDNS4 string `json:"dhcpd_dns_4"` + Enabled FlexBool `json:"enabled"` + DhcpRelayEnabled FlexBool `json:"dhcp_relay_enabled"` + Mac string `json:"mac"` + IsGuest FlexBool `json:"is_guest"` + IP string `json:"ip"` + Up FlexBool `json:"up"` + ActiveDhcpLeaseCount int `json:"active_dhcp_lease_count"` + GatewayInterfaceName string `json:"gateway_interface_name"` + DPIStatsTable *DPITable `json:"dpistats_table"` + NumSta FlexInt `json:"num_sta"` + RxBytes FlexInt `json:"rx_bytes"` + RxPackets FlexInt `json:"rx_packets"` + TxBytes FlexInt `json:"tx_bytes"` + TxPackets FlexInt `json:"tx_packets"` +} + +// Storage is hard drive into for a device with storage. +type Storage struct { + MountPoint string `json:"mount_point"` + Name string `json:"name"` + Size FlexInt `json:"size"` + Type string `json:"type"` + Used FlexInt `json:"used"` } type Temperature struct { diff --git a/core/unifi/unifi.go b/core/unifi/unifi.go index 79e0c6a4..b5e8aa21 100644 --- a/core/unifi/unifi.go +++ b/core/unifi/unifi.go @@ -7,6 +7,7 @@ package unifi import ( "bytes" + "context" "crypto/tls" "encoding/json" "fmt" @@ -18,13 +19,13 @@ import ( "strings" "time" - "github.com/pkg/errors" "golang.org/x/net/publicsuffix" ) var ( - errAuthenticationFailed = fmt.Errorf("authentication failed") - errInvalidStatusCode = fmt.Errorf("invalid status code from server") + ErrAuthenticationFailed = fmt.Errorf("authentication failed") + ErrInvalidStatusCode = fmt.Errorf("invalid status code from server") + ErrNoParams = fmt.Errorf("requedted PUT with no parameters") ) // NewUnifi creates a http.Client with authenticated cookies. @@ -46,9 +47,11 @@ func NewUnifi(config *Config) (*Unifi, error) { config.DebugLog = discardLogs } - u := &Unifi{Config: config, + u := &Unifi{ + Config: config, Client: &http.Client{ - Jar: jar, + Timeout: config.Timeout, + Jar: jar, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: !config.VerifySSL}, // nolint: gosec }, @@ -64,7 +67,7 @@ func NewUnifi(config *Config) (*Unifi, error) { } 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 @@ -91,8 +94,8 @@ func (u *Unifi) Login() error { req.URL, time.Since(start).Round(time.Millisecond), resp.ContentLength) if resp.StatusCode != http.StatusOK { - return errors.Wrapf(errAuthenticationFailed, "(user: %s): %s (status: %s)", - u.User, req.URL, resp.Status) + return fmt.Errorf("(user: %s): %s (status: %s): %w", + u.User, req.URL, resp.Status, ErrAuthenticationFailed) } return nil @@ -185,39 +188,44 @@ func (u *Unifi) PutData(apiPath string, v interface{}, params ...string) error { // Use this if you're unmarshalling UniFi data into custom types. // And if you're doing that... sumbut a pull request with your new struct. :) // This is a helper method that is exposed for convenience. -func (u *Unifi) UniReq(apiPath string, params string) (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 { case "": - req, err = http.NewRequest("GET", u.URL+apiPath, nil) + req, err = http.NewRequest(http.MethodGet, u.URL+apiPath, nil) 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 { - return + return nil, fmt.Errorf("creating request: %w", err) } u.setHeaders(req, params) - return + return req, nil } // UniReqPut is the Put call equivalent to UniReq -func (u *Unifi) UniReqPut(apiPath string, params string) (req *http.Request, err error) { - switch apiPath = u.path(apiPath); params { - case "": - err = fmt.Errorf("Put with no parameters. Use UniReq()") - default: - req, err = http.NewRequest("PUT", u.URL+apiPath, bytes.NewBufferString(params)) +func (u *Unifi) UniReqPut(apiPath string, params string) (*http.Request, error) { + if params == "" { + return nil, ErrNoParams } + apiPath = u.path(apiPath) + + req, err := http.NewRequest(http.MethodPut, u.URL+apiPath, bytes.NewBufferString(params)) //nolint:noctx if err != nil { - return + return nil, fmt.Errorf("creating request: %w", err) } u.setHeaders(req, params) - return + return req, nil } // GetJSON returns the raw JSON from a path. This is useful for debugging. @@ -242,7 +250,17 @@ func (u *Unifi) PutJSON(apiPath string, params ...string) ([]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 { return []byte{}, err } @@ -260,7 +278,7 @@ func (u *Unifi) do(req *http.Request) ([]byte, error) { } 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 diff --git a/core/unifi/usg.go b/core/unifi/usg.go index d7aff5a1..60815209 100644 --- a/core/unifi/usg.go +++ b/core/unifi/usg.go @@ -7,62 +7,52 @@ import ( // USG represents all the data from the Ubiquiti Controller for a Unifi Security Gateway. type USG struct { - SourceName string `json:"-"` - ID string `json:"_id"` - Adopted FlexBool `json:"adopted"` - Cfgversion string `json:"cfgversion"` - ConfigNetwork struct { - Type string `json:"type"` - 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"` - InformIP string `json:"inform_ip"` - InformURL string `json:"inform_url"` - IP string `json:"ip"` - LedOverride string `json:"led_override"` - LicenseState string `json:"license_state"` - Mac string `json:"mac"` - Model string `json:"model"` - Name string `json:"name"` - OutdoorModeOverride string `json:"outdoor_mode_override"` - Serial string `json:"serial"` - SiteID string `json:"site_id"` - SiteName string `json:"-"` - Type string `json:"type"` - UsgCaps FlexInt `json:"usg_caps"` - Version string `json:"version"` - RequiredVersion string `json:"required_version"` - EthernetOverrides []struct { - Ifname string `json:"ifname"` - Networkgroup string `json:"networkgroup"` - } `json:"ethernet_overrides"` - HwCaps FlexInt `json:"hw_caps"` - BoardRev FlexInt `json:"board_rev"` - Unsupported FlexBool `json:"unsupported"` - UnsupportedReason FlexInt `json:"unsupported_reason"` - DeviceID string `json:"device_id"` - State FlexInt `json:"state"` - LastSeen FlexInt `json:"last_seen"` - Upgradable FlexBool `json:"upgradable"` - AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` - Rollupgrade FlexBool `json:"rollupgrade"` - KnownCfgversion string `json:"known_cfgversion"` - Uptime FlexInt `json:"uptime"` - Locating FlexBool `json:"locating"` - ConnectRequestIP string `json:"connect_request_ip"` - ConnectRequestPort string `json:"connect_request_port"` - SysStats SysStats `json:"sys_stats"` - SystemStats SystemStats `json:"system-stats"` - GuestToken string `json:"guest_token"` - SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` - SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` - Wan1 Wan `json:"wan1"` - Wan2 Wan `json:"wan2"` + SourceName string `json:"-"` + ID string `json:"_id"` + Adopted FlexBool `json:"adopted"` + Cfgversion string `json:"cfgversion"` + ConfigNetwork *ConfigNetwork `json:"config_network"` + EthernetTable []*EthernetTable `json:"ethernet_table"` + FwCaps FlexInt `json:"fw_caps"` + InformIP string `json:"inform_ip"` + InformURL string `json:"inform_url"` + IP string `json:"ip"` + LedOverride string `json:"led_override"` + LicenseState string `json:"license_state"` + Mac string `json:"mac"` + Model string `json:"model"` + Name string `json:"name"` + OutdoorModeOverride string `json:"outdoor_mode_override"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + SiteName string `json:"-"` + Type string `json:"type"` + UsgCaps FlexInt `json:"usg_caps"` + Version string `json:"version"` + RequiredVersion string `json:"required_version"` + EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"` + HwCaps FlexInt `json:"hw_caps"` + BoardRev FlexInt `json:"board_rev"` + Unsupported FlexBool `json:"unsupported"` + UnsupportedReason FlexInt `json:"unsupported_reason"` + DeviceID string `json:"device_id"` + State FlexInt `json:"state"` + LastSeen FlexInt `json:"last_seen"` + Upgradable FlexBool `json:"upgradable"` + AdoptableWhenUpgraded FlexBool `json:"adoptable_when_upgraded"` + Rollupgrade FlexBool `json:"rollupgrade"` + KnownCfgversion string `json:"known_cfgversion"` + Uptime FlexInt `json:"uptime"` + Locating FlexBool `json:"locating"` + ConnectRequestIP string `json:"connect_request_ip"` + ConnectRequestPort string `json:"connect_request_port"` + SysStats SysStats `json:"sys_stats"` + SystemStats SystemStats `json:"system-stats"` + GuestToken string `json:"guest_token"` + SpeedtestStatus SpeedtestStatus `json:"speedtest-status"` + SpeedtestStatusSaved FlexBool `json:"speedtest-status-saved"` + Wan1 Wan `json:"wan1"` + Wan2 Wan `json:"wan2"` PortTable []struct { Name string `json:"name"` Ifname string `json:"ifname"` @@ -114,12 +104,15 @@ type Uplink struct { Nameservers []string `json:"nameservers"` Netmask string `json:"netmask"` NumPort FlexInt `json:"num_port"` + Media string `json:"media"` + PortIdx FlexInt `json:"port_idx"` RxBytes FlexInt `json:"rx_bytes"` RxBytesR FlexInt `json:"rx_bytes-r"` RxDropped FlexInt `json:"rx_dropped"` RxErrors FlexInt `json:"rx_errors"` RxMulticast FlexInt `json:"rx_multicast"` RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate"` Speed FlexInt `json:"speed"` SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"` SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"` @@ -129,6 +122,7 @@ type Uplink struct { TxDropped FlexInt `json:"tx_dropped"` TxErrors FlexInt `json:"tx_errors"` TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate"` Type string `json:"type"` Up FlexBool `json:"up"` Uptime FlexInt `json:"uptime"` @@ -140,7 +134,7 @@ type Uplink struct { type Wan struct { Autoneg FlexBool `json:"autoneg"` BytesR FlexInt `json:"bytes-r"` - DNS []string `json:"dns"` + DNS []string `json:"dns"` // may be deprecated Enable FlexBool `json:"enable"` FlowctrlRx FlexBool `json:"flowctrl_rx"` FlowctrlTx FlexBool `json:"flowctrl_tx"` @@ -164,7 +158,9 @@ type Wan struct { RxErrors FlexInt `json:"rx_errors"` RxMulticast FlexInt `json:"rx_multicast"` RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate"` Speed FlexInt `json:"speed"` + SpeedCaps FlexInt `json:"speed_caps"` TxBroadcast FlexInt `json:"tx_broadcast"` TxBytes FlexInt `json:"tx_bytes"` TxBytesR FlexInt `json:"tx_bytes-r"` @@ -172,22 +168,40 @@ type Wan struct { TxErrors FlexInt `json:"tx_errors"` TxMulticast FlexInt `json:"tx_multicast"` TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate"` Type string `json:"type"` Up FlexBool `json:"up"` } // SpeedtestStatus is the speed test info on a USG or UDM. type SpeedtestStatus struct { - Latency FlexInt `json:"latency"` - Rundate FlexInt `json:"rundate"` - Runtime FlexInt `json:"runtime"` - ServerDesc string `json:"server_desc,omitempty"` - StatusDownload FlexInt `json:"status_download"` - StatusPing FlexInt `json:"status_ping"` - StatusSummary FlexInt `json:"status_summary"` - StatusUpload FlexInt `json:"status_upload"` - XputDownload FlexInt `json:"xput_download"` - XputUpload FlexInt `json:"xput_upload"` + Latency FlexInt `json:"latency"` + Rundate FlexInt `json:"rundate"` + Runtime FlexInt `json:"runtime"` + ServerDesc string `json:"server_desc,omitempty"` + Server *SpeedtestServer `json:"server"` + StatusDownload FlexInt `json:"status_download"` + StatusPing FlexInt `json:"status_ping"` + StatusSummary FlexInt `json:"status_summary"` + StatusUpload FlexInt `json:"status_upload"` + XputDownload FlexInt `json:"xput_download"` + XputUpload FlexInt `json:"xput_upload"` +} + +type SpeedtestServer struct { + Cc string `json:"cc"` + City string `json:"city"` + Country string `json:"country"` + Lat FlexInt `json:"lat"` + Lon FlexInt `json:"lon"` + Provider string `json:"provider"` + ProviderURL string `json:"provider_url"` +} + +// ConfigNetwork comes from gateways. +type ConfigNetwork struct { + Type string `json:"type"` + IP string `json:"ip"` } // SystemStats is system info for a UDM, USG, USW. @@ -233,6 +247,7 @@ type Gw struct { LanTxBytes FlexInt `json:"lan-tx_bytes"` LanRxDropped FlexInt `json:"lan-rx_dropped"` WanRxErrors FlexInt `json:"wan-rx_errors,omitempty"` + LanRxErrors FlexInt `json:"lan-rx_errors,omitempty"` } // UnmarshalJSON unmarshalls 5.10 or 5.11 formatted Gateway Stat data. diff --git a/core/unifi/usw.go b/core/unifi/usw.go index 242b2053..fcd75c9c 100644 --- a/core/unifi/usw.go +++ b/core/unifi/usw.go @@ -43,19 +43,15 @@ type USW struct { PortIdx FlexInt `json:"port_idx"` PortconfID string `json:"portconf_id"` } `json:"port_overrides"` - PortTable []Port `json:"port_table"` - Serial string `json:"serial"` - SiteID string `json:"site_id"` - StpPriority FlexInt `json:"stp_priority"` - StpVersion string `json:"stp_version"` - Type string `json:"type"` - Version string `json:"version"` - RequiredVersion string `json:"required_version"` - SwitchCaps struct { - FeatureCaps FlexInt `json:"feature_caps"` - MaxMirrorSessions FlexInt `json:"max_mirror_sessions"` - MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` - } `json:"switch_caps"` + PortTable []Port `json:"port_table"` + Serial string `json:"serial"` + SiteID string `json:"site_id"` + StpPriority FlexInt `json:"stp_priority"` + StpVersion string `json:"stp_version"` + Type string `json:"type"` + Version string `json:"version"` + RequiredVersion string `json:"required_version"` + SwitchCaps *SwitchCaps `json:"switch_caps"` HwCaps FlexInt `json:"hw_caps"` Unsupported FlexBool `json:"unsupported"` UnsupportedReason FlexInt `json:"unsupported_reason"` @@ -97,39 +93,58 @@ type USW struct { GuestNumSta FlexInt `json:"guest-num_sta"` } +type SwitchCaps struct { + FeatureCaps FlexInt `json:"feature_caps"` + MaxMirrorSessions FlexInt `json:"max_mirror_sessions"` + MaxAggregateSessions FlexInt `json:"max_aggregate_sessions"` +} + +// MacTable is a newer feature on some switched ports. +type MacTable struct { + Age int64 `json:"age"` + Authorized FlexBool `json:"authorized"` + Hostname string `json:"hostname"` + IP string `json:"ip"` + LastReachable int64 `json:"lastReachable"` + Mac string `json:"mac"` +} + // Port is a physical connection on a USW or UDM. type Port struct { - AggregatedBy FlexBool `json:"aggregated_by"` - Autoneg FlexBool `json:"autoneg,omitempty"` - BytesR FlexInt `json:"bytes-r"` - DNS []string `json:"dns,omitempty"` - Dot1XMode string `json:"dot1x_mode"` - Dot1XStatus string `json:"dot1x_status"` - Enable FlexBool `json:"enable"` - FlowctrlRx FlexBool `json:"flowctrl_rx"` - FlowctrlTx FlexBool `json:"flowctrl_tx"` - FullDuplex FlexBool `json:"full_duplex"` - IP string `json:"ip,omitempty"` - Ifname string `json:"ifname,omitempty"` - IsUplink FlexBool `json:"is_uplink"` - Mac string `json:"mac,omitempty"` - Jumbo FlexBool `json:"jumbo,omitempty"` - Masked FlexBool `json:"masked"` - Media string `json:"media"` - Name string `json:"name"` - NetworkName string `json:"network_name,omitempty"` - NumPort int `json:"num_port,omitempty"` - OpMode string `json:"op_mode"` - PoeCaps FlexInt `json:"poe_caps"` - PoeClass string `json:"poe_class,omitempty"` - PoeCurrent FlexInt `json:"poe_current,omitempty"` - PoeEnable FlexBool `json:"poe_enable,omitempty"` - PoeGood FlexBool `json:"poe_good,omitempty"` - PoeMode string `json:"poe_mode,omitempty"` - PoePower FlexInt `json:"poe_power,omitempty"` - PoeVoltage FlexInt `json:"poe_voltage,omitempty"` + AggregatedBy FlexBool `json:"aggregated_by"` + Autoneg FlexBool `json:"autoneg,omitempty"` + BytesR FlexInt `json:"bytes-r"` + DNS []string `json:"dns,omitempty"` + Dot1XMode string `json:"dot1x_mode"` + Dot1XStatus string `json:"dot1x_status"` + Enable FlexBool `json:"enable"` + FlowctrlRx FlexBool `json:"flowctrl_rx"` + FlowctrlTx FlexBool `json:"flowctrl_tx"` + FullDuplex FlexBool `json:"full_duplex"` + IP string `json:"ip,omitempty"` + Ifname string `json:"ifname,omitempty"` + IsUplink FlexBool `json:"is_uplink"` + Mac string `json:"mac,omitempty"` + MacTable []MacTable `json:"mac_table,omitempty"` + Jumbo FlexBool `json:"jumbo,omitempty"` + Masked FlexBool `json:"masked"` + Media string `json:"media"` + Name string `json:"name"` + NetworkName string `json:"network_name,omitempty"` + Netmask string `json:"netmask,omitempty"` + NumPort int `json:"num_port,omitempty"` + OpMode string `json:"op_mode"` + PoeCaps FlexInt `json:"poe_caps"` + PoeClass string `json:"poe_class,omitempty"` + PoeCurrent FlexInt `json:"poe_current,omitempty"` + PoeEnable FlexBool `json:"poe_enable,omitempty"` + PoeGood FlexBool `json:"poe_good,omitempty"` + PoeMode string `json:"poe_mode,omitempty"` + PoePower FlexInt `json:"poe_power,omitempty"` + PoeVoltage FlexInt `json:"poe_voltage,omitempty"` PortDelta struct { - TimeDelta int64 `json:"time_delta"` + TimeDelta int64 `json:"time_delta"` + TimeDeltaActivity int64 `json:"time_delta_activity"` } `json:"port_delta,omitempty"` PortIdx FlexInt `json:"port_idx"` PortPoe FlexBool `json:"port_poe"` @@ -141,6 +156,7 @@ type Port struct { RxErrors FlexInt `json:"rx_errors"` RxMulticast FlexInt `json:"rx_multicast"` RxPackets FlexInt `json:"rx_packets"` + RxRate FlexInt `json:"rx_rate,omitempty"` Satisfaction FlexInt `json:"satisfaction,omitempty"` SfpFound FlexBool `json:"sfp_found,omitempty"` Speed FlexInt `json:"speed"` @@ -154,6 +170,7 @@ type Port struct { TxErrors FlexInt `json:"tx_errors"` TxMulticast FlexInt `json:"tx_multicast"` TxPackets FlexInt `json:"tx_packets"` + TxRate FlexInt `json:"tx_rate,omitempty"` Type string `json:"type,omitempty"` Up FlexBool `json:"up"` }