Add UXG, UDM storage, minor lint fixups.

This commit is contained in:
David Newhall II 2021-03-07 17:23:31 -08:00
parent bfe80a59e1
commit e1be3246f1
12 changed files with 362 additions and 275 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -21,6 +21,17 @@ type DPIData struct {
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.

View File

@ -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 {

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().
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

View File

@ -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

View File

@ -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

View File

@ -28,10 +28,7 @@ type UDM struct {
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"`
ConfigNetwork *ConfigNetwork `json:"config_network"`
VwireTable []interface{} `json:"vwire_table"`
Dot1XPortctrlEnabled FlexBool `json:"dot1x_portctrl_enabled"`
JumboframeEnabled FlexBool `json:"jumboframe_enabled"`
@ -49,16 +46,9 @@ type UDM struct {
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"`
EthernetTable []*EthernetTable `json:"ethernet_table"`
PortTable []Port `json:"port_table"`
EthernetOverrides []struct {
Ifname string `json:"ifname"`
Networkgroup string `json:"networkgroup"`
} `json:"ethernet_overrides"`
EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
UsgCaps FlexInt `json:"usg_caps"`
HasSpeaker FlexBool `json:"has_speaker"`
HasEth1 FlexBool `json:"has_eth1"`
@ -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"`
@ -110,6 +100,7 @@ type UDM struct {
PortconfID string `json:"portconf_id"`
} `json:"port_overrides"`
Stat UDMStat `json:"stat"`
Storage []*Storage `json:"storage"`
TxBytes FlexInt `json:"tx_bytes"`
RxBytes FlexInt `json:"rx_bytes"`
Bytes FlexInt `json:"bytes"`
@ -131,7 +122,19 @@ type UDM struct {
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"`
@ -150,6 +153,7 @@ type NetworkTable []struct {
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"`
@ -167,6 +171,9 @@ type NetworkTable []struct {
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"`
@ -174,6 +181,15 @@ type NetworkTable []struct {
TxPackets FlexInt `json:"tx_packets"`
}
// Storage is hard drive into for a device with storage.
type Storage struct {
MountPoint string `json:"mount_point"`
Name string `json:"name"`
Size FlexInt `json:"size"`
Type string `json:"type"`
Used FlexInt `json:"used"`
}
type Temperature struct {
Name string `json:"name"`
Type string `json:"type"`

View File

@ -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,8 +47,10 @@ func NewUnifi(config *Config) (*Unifi, error) {
config.DebugLog = discardLogs
}
u := &Unifi{Config: config,
u := &Unifi{
Config: config,
Client: &http.Client{
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

View File

@ -11,15 +11,8 @@ type USG struct {
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"`
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"`
@ -37,10 +30,7 @@ type USG struct {
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"`
EthernetOverrides []*EthernetOverrides `json:"ethernet_overrides"`
HwCaps FlexInt `json:"hw_caps"`
BoardRev FlexInt `json:"board_rev"`
Unsupported FlexBool `json:"unsupported"`
@ -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,6 +168,7 @@ 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"`
}
@ -182,6 +179,7 @@ type SpeedtestStatus struct {
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"`
@ -190,6 +188,22 @@ type SpeedtestStatus struct {
XputUpload FlexInt `json:"xput_upload"`
}
type SpeedtestServer struct {
Cc string `json:"cc"`
City string `json:"city"`
Country string `json:"country"`
Lat FlexInt `json:"lat"`
Lon FlexInt `json:"lon"`
Provider string `json:"provider"`
ProviderURL string `json:"provider_url"`
}
// ConfigNetwork comes from gateways.
type ConfigNetwork struct {
Type string `json:"type"`
IP string `json:"ip"`
}
// SystemStats is system info for a UDM, USG, USW.
type SystemStats struct {
CPU FlexInt `json:"cpu"`
@ -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.

View File

@ -51,11 +51,7 @@ type USW struct {
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"`
SwitchCaps *SwitchCaps `json:"switch_caps"`
HwCaps FlexInt `json:"hw_caps"`
Unsupported FlexBool `json:"unsupported"`
UnsupportedReason FlexInt `json:"unsupported_reason"`
@ -97,6 +93,22 @@ 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"`
@ -113,11 +125,13 @@ type Port struct {
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"`
@ -130,6 +144,7 @@ type Port struct {
PoeVoltage FlexInt `json:"poe_voltage,omitempty"`
PortDelta struct {
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"`
}