diff --git a/main.go b/main.go index 09bde0a..9fd2dc4 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ func main() { } log.Printf("Network interface: %s", link.Attrs().Name) - api, err := findApi(link) + api, err := findAPI(link) if err != nil { log.Fatal(err) } @@ -48,6 +48,15 @@ func main() { if err != nil { log.Fatal(err) } + + devices, err := unifi.ListDevices() + if err != nil { + log.Fatal(err) + } + + for _, dev := range devices { + log.Printf("%#v", dev) + } } type upstream struct { @@ -102,7 +111,7 @@ func getUpstream(intName string) (*upstream, error) { return ret, nil } -func findApi(link netlink.Link) (string, error) { +func findAPI(link netlink.Link) (string, error) { addrs, err := netlink.AddrList(link, 2 /* AF_INET */) if err != nil { return "", err @@ -116,7 +125,7 @@ func findApi(link netlink.Link) (string, error) { start := ip & mask end := ip | (^mask & 0xffffffff) - addr_apis, err := findApis(start, end) + addr_apis, err := findAPIs(start, end) if err != nil { return "", err } @@ -135,7 +144,7 @@ func findApi(link netlink.Link) (string, error) { return apis[0], nil } -func findApis(start_ip, end_ip uint32) ([]string, error) { +func findAPIs(start_ip, end_ip uint32) ([]string, error) { wg := sync.WaitGroup{} wg.Add(int(end_ip - start_ip + 1)) diff --git a/unifi.go b/unifi.go index 1d950d9..f5ab5ea 100644 --- a/unifi.go +++ b/unifi.go @@ -22,6 +22,32 @@ type Client struct { base_url string } +type Device struct { + // Fields directly from UniFi + Name string `json:"name"` + MAC string `json:"mac"` + + // Constructed fields + Neighbors map[string]*Device + + // Intermediate data; ignore + Ports []*Port `json:"port_table"` +} + +type Port struct { + LLDP []*LLDP `json:"lldp_table"` +} + +type LLDP struct { + MAC string `json:"lldp_chassis_id"` + Port string `json:"lldp_port_id"` + Name string `json:"lldp_system_name"` +} + +type DeviceResp struct { + Data []*Device `json:"data"` +} + func NewClient(base_url string) (*Client, error) { jar, err := cookiejar.New(nil) if err != nil { @@ -69,6 +95,9 @@ func (c *Client) Login() error { } req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/auth/login", c.base_url), bytes.NewBuffer(body)) + if err != nil { + return err + } req.Header.Set("Content-Type", "application/json") resp, err := c.http_client.Do(req) @@ -99,6 +128,61 @@ func (c *Client) Login() error { return nil } +func (c *Client) ListDevices() (map[string]*Device, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/proxy/network/api/s/default/stat/device", c.base_url), nil) + + resp, err := c.http_client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + dec := json.NewDecoder(resp.Body) + error_resp := ErrorResp{} + err = dec.Decode(&error_resp) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("%s", error_resp.Errors[0]) + } + + dec := json.NewDecoder(resp.Body) + device_resp := DeviceResp{} + err = dec.Decode(&device_resp) + if err != nil { + return nil, err + } + + ret := map[string]*Device{} + + for _, dev := range device_resp.Data { + dev.MAC = strings.ToUpper(dev.MAC) + ret[dev.MAC] = dev + } + + // Second loop after the table is populated + for _, dev := range ret { + dev.Neighbors = map[string]*Device{} + + for _, port := range dev.Ports { + for _, lldp := range port.LLDP { + lldp.MAC = strings.ToUpper(lldp.MAC) + + neighbor := ret[lldp.MAC] + if neighbor == nil { + continue + } + + dev.Neighbors[neighbor.MAC] = neighbor + } + } + dev.Ports = nil + } + + return ret, nil +} + func loadCreds() (*Creds, error) { fh, err := os.OpenFile(credPath(), os.O_RDONLY, 0) if err != nil {