diff --git a/main.go b/main.go index 6425355..0a11a8b 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,7 @@ package main import ( - "bytes" - "encoding/csv" "encoding/json" - "errors" "flag" "fmt" "io/ioutil" @@ -12,8 +9,6 @@ import ( "net/http" "net/http/cookiejar" "os" - "strconv" - "strings" "time" _ "git.nils.zone/nils/prettify" @@ -65,205 +60,25 @@ func itob(i int) bool { return false } -//Unifi Controller API processing -func processUcAPIs() ([]node, []link) { - //get list of Unifi devices to display - var nodes []node - var links []link - d := getDevices(conf.Unifi.UCDevicesURL) - - //call Unifi Controller - ucAPI := newAPI(conf.Unifi.User, conf.Unifi.Password, conf.Unifi.APIURL) - //login - ucAPI.ucLogin() - //get all Sites from Controller - sites, err := ucAPI.ucGetSites() - if err != nil { - log.Fatalln(err) - } - //get all devices in all sites - devices, err := ucAPI.ucGetDevices(sites) - if err != nil { - log.Fatalln(err) - } - - //build nodes struct - //mvJson, err := getMeshviewerJSON() - for _, jsonDevice := range d.Devices { - var currentDevice ucDevice - var currentJSONDevice device - for _, device := range devices { - if strings.ToUpper(device.Mac) == strings.ToUpper(jsonDevice.MAC) { - currentDevice = device - currentJSONDevice = jsonDevice - } - } - if isRemoteMACpublished(jsonDevice.MAC, d.Devices) == true { - //hier muss gecheckt werden ob der link valide ist - if checkMeshviewerLink(jsonDevice.LinkedTo) { - links = ucAddLink(jsonDevice, links) - } - } - load, err := strconv.ParseFloat(currentDevice.Sysstats.CPU, 64) - if err != nil { - fmt.Println("Error: ", currentDevice.Name) - //log.Fatalln(err) - load = 0 - } - mem, err := strconv.ParseFloat(currentDevice.Sysstats.Memory, 64) - if err != nil { - //log.Fatalln(err) - load = 0 - } - var model = lookupModels(currentDevice.Model) - var clients = currentDevice.Users - if conf.Unifi.DisplayUsers == false { - clients = 0 - } - nodes = append(nodes, node{ - Firstseen: "0", - Lastseen: time.Unix(int64(currentDevice.LastSeen), 0).Format(iso8601), - IsOnline: itob(currentDevice.State), - IsGateway: false, - Clients: clients, - ClientsWifi24: 0, - ClientsWifi5: 0, - ClientsOther: clients, - RootFSUsage: 0, - LoadAVG: load / 100, - MemoryUsage: mem / 100, - Uptime: time.Now().Add(-1 * time.Second * time.Duration(currentDevice.Uptime)).Format(iso8601), - GatewayNexthop: currentJSONDevice.GatewayNexthop, - Gateway: currentJSONDevice.Gateway, - Location: ¤tJSONDevice.Location, - NodeID: strings.ReplaceAll(currentDevice.Mac, ":", ""), - MAC: currentDevice.Mac, - Adresses: []string{currentDevice.IP}, - Domain: currentJSONDevice.Domain, - Hostname: "[Unifi] " + currentDevice.Name, - Owner: "Freifunk Rhein-Sieg", - Firmware: firmware{ - Base: "Ubiquiti - Stock", - Release: currentDevice.Version, - }, - Autoupdater: autoupdater{ - Enabled: false, - Branch: "stable", - }, - NProc: 1, - Model: model, - }) - } - return nodes, links -} - -//UNMS API processing (Richtfunk) -func processUNMSAPI() ([]node, []link) { - // Variables for runtime - var links []link - var nodes []node - - d := getDevices(conf.Unms.DevicesURL) - - // API CALL 1 - log.Println("calling API 1") - var u []unifiAPIResponse - err := callUnifiAPI("/devices", &u) - if err != nil { - log.Fatalln(err) - } - - for i := range d.Devices { - var dev unifiAPIResponse - var currentDevice device - for j := range u { - if strings.ToUpper(u[j].Identification.MAC) == strings.ToUpper(d.Devices[i].MAC) { - dev = u[j] - currentDevice = d.Devices[i] - } - } - - var isOnline bool = false - if dev.Overview.Status == "active" { - isOnline = true - } - // END OF API CALL 1 - - // API CALL 2 - log.Println("calling API 2 for device", d.Devices[i].Name) - var details unifiAPIDetails - callUnifiAPI("/devices/erouters/"+dev.Identification.ID, &details) - // END OF API CALL 2 - - // API CALL 3 - log.Println("calling API 3 for device", d.Devices[i].Name) - var airmaxes []unifiAPIAirmax - callUnifiAPI("/devices/airmaxes/"+dev.Identification.ID+"/stations", &airmaxes) - // check if remote mac address is part of our published network - for i := range airmaxes { - if isRemoteMACpublished(airmaxes[i].DeviceIdentification.MAC, d.Devices) == true { - links = addLink(dev, airmaxes[i], links) - } - } - // END OF API CALL 3 - - // Get info from json file (static) - nodes = append(nodes, node{ - Firstseen: dev.Overview.CreatedAt.Format(iso8601), - Lastseen: dev.Overview.LastSeen.Format(iso8601), - IsOnline: isOnline, - IsGateway: false, - Clients: 0, - ClientsWifi24: 0, - ClientsWifi5: 0, - ClientsOther: 0, - RootFSUsage: 0, - LoadAVG: details.Overview.CPU / 100, - MemoryUsage: details.Overview.RAM / 100, - Uptime: dev.Identification.Started.Format(iso8601), - GatewayNexthop: currentDevice.GatewayNexthop, - Gateway: currentDevice.Gateway, - Location: ¤tDevice.Location, - NodeID: strings.ReplaceAll(dev.Identification.MAC, ":", ""), - MAC: dev.Identification.MAC, - Adresses: getAddresses(details.IPAddress), - Domain: currentDevice.Domain, - Hostname: "[RiFu] " + details.Identification.Name, - Owner: "Freifunk Rhein-Sieg", - Firmware: firmware{ - Base: "Ubiquiti - Stock", - Release: details.Firmware.Current, - }, - Autoupdater: autoupdater{ - Enabled: false, - Branch: "stable", - }, - NProc: 1, - Model: details.Identification.Model, - }) - } - return nodes, links -} - func processAPIs() { tick := time.Tick(delay) for range tick { var nodes []node var links []link - if conf.Unms.Enabled == true { + if conf.Unms.Enabled { log.Println("Processing UNMS") unmsNodes, unmsLinks := processUNMSAPI() nodes = append(nodes, unmsNodes...) links = append(links, unmsLinks...) } - if conf.Unifi.Enabled == true { + if conf.Unifi.Enabled { log.Println("Processing Unifi") ucNodes, ucLinks := processUcAPIs() nodes = append(nodes, ucNodes...) links = append(links, ucLinks...) } - if conf.Meshviewer.Enabled == true { + if conf.Meshviewer.Enabled { log.Println("Processing Meshviewer") mvNodes, mvLinks := getMeshviewer() nodes = append(nodes, mvNodes...) @@ -292,6 +107,7 @@ func processAPIs() { log.Println("...done") } } + func getFile(url string) []byte { resp, err := http.Get(url) if err != nil { @@ -302,10 +118,10 @@ func getFile(url string) []byte { return byteValue } +//get devices from devices file on webserver (config) func getDevices(url string) devices { // get devices from JSON file jsonFile := getFile(url) - // read file to bytes // variable for d var d devices @@ -318,30 +134,7 @@ func getDevices(url string) devices { return d } -func callUnifiAPI(url string, i interface{}) error { - request, err := http.NewRequest(http.MethodGet, conf.Unms.UnmsAPIURL+url, nil) - if err != nil { - return errors.New(fmt.Sprint("can't set request", conf.Unms.UnmsAPIURL+url)) - } - request.Header.Set("x-auth-token", conf.Unms.APItoken) - client := &http.Client{} - response, err := client.Do(request) - if err != nil { - return fmt.Errorf("can't get request %s with x-auth-token %s", conf.Unms.UnmsAPIURL+url, conf.Unms.APItoken) - } - if response.StatusCode != 200 { - log.Fatalln("Can´t call UNMS API, check token and URL. HTTP Status: ", response.StatusCode) - } - data, err := ioutil.ReadAll(response.Body) - defer response.Body.Close() - if err != nil { - return fmt.Errorf("can't read response body: %+v", response.Body) - } - // no error occurred, unmarshal to struct - json.Unmarshal(data, &i) - return nil -} - +//check for MAC Adress in current Devices func isRemoteMACpublished(mac string, devices []device) bool { for i := range devices { if devices[i].MAC == mac { @@ -351,31 +144,6 @@ func isRemoteMACpublished(mac string, devices []device) bool { return false } -func addLink(dev unifiAPIResponse, airmaxes unifiAPIAirmax, links []link) []link { - for i := range links { - if links[i].SourceAddr == airmaxes.DeviceIdentification.MAC { - // link already exists - return links - } - } - links = append(links, link{ - Type: "wifi", - Source: strings.ReplaceAll(dev.Identification.MAC, ":", ""), - Target: strings.ReplaceAll(airmaxes.DeviceIdentification.MAC, ":", ""), - SourceTQ: airmaxes.Statistics.LinkScore, - TargetTQ: airmaxes.Statistics.LinkScore, - SourceAddr: dev.Identification.MAC, - TargetAddr: airmaxes.DeviceIdentification.MAC, - }) - return links -} - -func getAddresses(ip string) []string { - var adresses []string - adresses = append(adresses, strings.Split(ip, "/")[0]) - return adresses -} - func serveJSON() { fs := http.FileServer(http.Dir("./output")) http.Handle("/", fs) @@ -395,280 +163,3 @@ func httpClient() *http.Client { client := &http.Client{Jar: jar} return client } - -func newAPI(user string, pass string, baseURL string) ucAPIData { - return ucAPIData{ - user: user, - pass: pass, - baseURL: baseURL, - client: httpClient(), - } -} - -func (u *ucAPIData) ucCallAPI(url string, method string, body *bytes.Buffer, output interface{}) error { - req, err := http.NewRequest(method, u.baseURL+url, body) - if err != nil { - return fmt.Errorf("can't set request %s", u.baseURL+url) - } - - req.Header.Set("Content-Type", "application/json") - response, err := u.client.Do(req) - if err != nil { - return fmt.Errorf("can't login %s", u.baseURL+url) - } - defer response.Body.Close() - if response.StatusCode != 200 { - return fmt.Errorf("Login failed %s", u.baseURL+url) - } - - data, err := ioutil.ReadAll(response.Body) - if err != nil { - return err - } - - err = json.Unmarshal(data, &output) - if err != nil { - return err - } - - return nil -} - -func (u *ucAPIData) ucLogin() error { - var loginData = []byte(`{"username":"` + u.user + `","password":"` + u.pass + `"}`) - - url := "/api/login" - err := u.ucCallAPI(url, http.MethodPost, bytes.NewBuffer(loginData), nil) - if err != nil { - return err - } - return nil -} - -func (u *ucAPIData) ucGetSites() ([]ucSite, error) { - var d struct { - Data []ucSite `json:"data"` - } - - url := "/api/self/sites" - err := u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) - if err != nil { - return []ucSite{}, err - } - return d.Data, nil -} - -func (u *ucAPIData) ucGetDevices(sites []ucSite) ([]ucDevice, error) { - var d struct { - Data []ucDevice `json:"data"` - } - var s []ucDevice - - for _, site := range sites { - url := "/api/s/" + site.ID + "/stat/device" - u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) - s = append(s, d.Data...) - } - return s, nil -} - -func ucAddLink(dev device, links []link) []link { - for i := range links { - if links[i].SourceAddr == dev.MAC { - // link already exists - return links - } - } - if dev.LinkedTo == "" { - //no LinkedTo in ucDevices.json - return links - } - links = append(links, link{ - Type: "cable", - Source: strings.ReplaceAll(dev.MAC, ":", ""), - Target: strings.ReplaceAll(dev.GatewayNexthop, ":", ""), - SourceTQ: 1, - TargetTQ: 1, - SourceAddr: dev.MAC, - TargetAddr: dev.LinkedTo, - }) - return links -} - -func getMeshviewerJSON(url string) (mvDevices, error) { - // get devices from JSON file - jsonFile := getFile(url) - - // read file to bytes - // variable for d - var n mvDevices - //var l []link - // unmarshal to struct - err := json.Unmarshal(jsonFile, &n) - if err != nil { - fmt.Println("can´t get Meshviewer Json file from " + url) - log.Println(err) - } - return n, nil -} - -func checkMeshviewerLink(s string) bool { - mvNodes, _ := getMeshviewer() - for i := range mvNodes { - if mvNodes[i].MAC == s { - return true - } - } - return false -} - -func findNodeID(NodeID string) bool { - for i := range ucDev.Devices { - if ucDev.Devices[i].GatewayNexthop == NodeID { - return true - } - } - return false -} -func addmvDevices(d mvDevices) ([]node, []link) { - var nodes []node - var links []link - - for i := range d.Nodes { - mvNode := d.Nodes[i] - if findNodeID(mvNode.NodeID) { - mvNode.Clients = 0 - mvNode.ClientsWifi24 = 0 - mvNode.ClientsWifi5 = 0 - mvNode.ClientsOther = 0 - } - if mvNode.Location.Latitude == 0 { - nodes = append(nodes, node{ - Firstseen: mvNode.Firstseen, - Lastseen: mvNode.Lastseen, - IsOnline: mvNode.IsOnline, - IsGateway: mvNode.IsGateway, - Clients: mvNode.Clients, - ClientsWifi24: mvNode.ClientsWifi24, - ClientsWifi5: mvNode.ClientsWifi5, - ClientsOther: mvNode.ClientsOther, - RootFSUsage: int(mvNode.RootfsUsage), - LoadAVG: mvNode.Loadavg, - MemoryUsage: mvNode.MemoryUsage, - Uptime: mvNode.Uptime, - GatewayNexthop: mvNode.GatewayNexthop, - Gateway: mvNode.Gateway, - NodeID: mvNode.NodeID, - MAC: mvNode.Mac, - Adresses: mvNode.Addresses, - Domain: mvNode.Domain, - Hostname: mvNode.Hostname, - Owner: mvNode.Owner, - Firmware: firmware{ - Base: mvNode.Firmware.Base, - Release: mvNode.Firmware.Release, - }, - Autoupdater: autoupdater{ - Enabled: mvNode.Autoupdater.Enabled, - Branch: mvNode.Autoupdater.Branch, - }, - NProc: mvNode.Nproc, - Model: mvNode.Model, - }) - } else { - nodes = append(nodes, node{ - Firstseen: mvNode.Firstseen, - Lastseen: mvNode.Lastseen, - IsOnline: mvNode.IsOnline, - IsGateway: mvNode.IsGateway, - Clients: mvNode.Clients, - ClientsWifi24: mvNode.ClientsWifi24, - ClientsWifi5: mvNode.ClientsWifi5, - ClientsOther: mvNode.ClientsOther, - RootFSUsage: int(mvNode.RootfsUsage), - LoadAVG: mvNode.Loadavg, - MemoryUsage: mvNode.MemoryUsage, - Uptime: mvNode.Uptime, - GatewayNexthop: mvNode.GatewayNexthop, - Gateway: mvNode.Gateway, - Location: &mvNode.Location, - NodeID: mvNode.NodeID, - MAC: mvNode.Mac, - Adresses: mvNode.Addresses, - Domain: mvNode.Domain, - Hostname: mvNode.Hostname, - Owner: mvNode.Owner, - Firmware: firmware{ - Base: mvNode.Firmware.Base, - Release: mvNode.Firmware.Release, - }, - Autoupdater: autoupdater{ - Enabled: mvNode.Autoupdater.Enabled, - Branch: mvNode.Autoupdater.Branch, - }, - NProc: mvNode.Nproc, - Model: mvNode.Model, - }) - } - } - for i := range d.Links { - mvNode := d.Links[i] - links = append(links, link{ - Type: mvNode.Type, - Source: mvNode.Source, - Target: mvNode.Target, - SourceTQ: mvNode.SourceTq, - TargetTQ: mvNode.TargetTq, - SourceAddr: mvNode.SourceAddr, - TargetAddr: mvNode.TargetAddr, - }) - } - return nodes, links -} - -func getMeshviewer() ([]node, []link) { - var nodes []node - var links []link - - for i := range conf.Meshviewer.Files { - log.Println("Hole Meshviewer JSON von: ", conf.Meshviewer.Files[i].URL) - m, err := getMeshviewerJSON(conf.Meshviewer.Files[i].URL) - if err != nil { - return nodes, links - } - mvNodes, mvLinks := addmvDevices(m) - nodes = append(nodes, mvNodes...) - links = append(links, mvLinks...) - } - return nodes, links -} - -func getUNMSLogs() UNMSLogResponse { - - var l UNMSLogResponse - log.Println("Get Outages from UNMS") - err := callUnifiAPI("/outages?count=100&page=1&type=outage", &l) - if err != nil { - log.Fatalln("Error calling Outages API") - } - return l -} - -func writeOutagesToCSV(l UNMSLogResponse) error { - csvFile, err := os.Create("output/outages.csv") - if err != nil { - return err - } - writer := csv.NewWriter(csvFile) - - for _, o := range l.Items { - var row []string - row = append(row, o.StartTime.Format("02.01.2006 15:04:05")) - row = append(row, o.EndTime.Format("02.01.2006 15:04:05")) - row = append(row, o.Site.Name) - row = append(row, o.Device.DisplayName) - writer.Write(row) - } - writer.Flush() - return nil -} diff --git a/meshviewer.go b/meshviewer.go new file mode 100644 index 0000000..e3538bb --- /dev/null +++ b/meshviewer.go @@ -0,0 +1,147 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" +) + +func getMeshviewerJSON(url string) (mvDevices, error) { + // get devices from JSON file + jsonFile := getFile(url) + + // read file to bytes + // variable for d + var n mvDevices + //var l []link + // unmarshal to struct + err := json.Unmarshal(jsonFile, &n) + if err != nil { + fmt.Println("can´t get Meshviewer Json file from " + url) + log.Println(err) + } + return n, nil +} + +func checkMeshviewerLink(s string) bool { + mvNodes, _ := getMeshviewer() + for i := range mvNodes { + if mvNodes[i].MAC == s { + return true + } + } + return false +} + +func addmvDevices(d mvDevices) ([]node, []link) { + var nodes []node + var links []link + + for i := range d.Nodes { + mvNode := d.Nodes[i] + if findNodeID(mvNode.NodeID) { + mvNode.Clients = 0 + mvNode.ClientsWifi24 = 0 + mvNode.ClientsWifi5 = 0 + mvNode.ClientsOther = 0 + } + if mvNode.Location.Latitude == 0 { + nodes = append(nodes, node{ + Firstseen: mvNode.Firstseen, + Lastseen: mvNode.Lastseen, + IsOnline: mvNode.IsOnline, + IsGateway: mvNode.IsGateway, + Clients: mvNode.Clients, + ClientsWifi24: mvNode.ClientsWifi24, + ClientsWifi5: mvNode.ClientsWifi5, + ClientsOther: mvNode.ClientsOther, + RootFSUsage: int(mvNode.RootfsUsage), + LoadAVG: mvNode.Loadavg, + MemoryUsage: mvNode.MemoryUsage, + Uptime: mvNode.Uptime, + GatewayNexthop: mvNode.GatewayNexthop, + Gateway: mvNode.Gateway, + NodeID: mvNode.NodeID, + MAC: mvNode.Mac, + Adresses: mvNode.Addresses, + Domain: mvNode.Domain, + Hostname: mvNode.Hostname, + Owner: mvNode.Owner, + Firmware: firmware{ + Base: mvNode.Firmware.Base, + Release: mvNode.Firmware.Release, + }, + Autoupdater: autoupdater{ + Enabled: mvNode.Autoupdater.Enabled, + Branch: mvNode.Autoupdater.Branch, + }, + NProc: mvNode.Nproc, + Model: mvNode.Model, + }) + } else { + nodes = append(nodes, node{ + Firstseen: mvNode.Firstseen, + Lastseen: mvNode.Lastseen, + IsOnline: mvNode.IsOnline, + IsGateway: mvNode.IsGateway, + Clients: mvNode.Clients, + ClientsWifi24: mvNode.ClientsWifi24, + ClientsWifi5: mvNode.ClientsWifi5, + ClientsOther: mvNode.ClientsOther, + RootFSUsage: int(mvNode.RootfsUsage), + LoadAVG: mvNode.Loadavg, + MemoryUsage: mvNode.MemoryUsage, + Uptime: mvNode.Uptime, + GatewayNexthop: mvNode.GatewayNexthop, + Gateway: mvNode.Gateway, + Location: &mvNode.Location, + NodeID: mvNode.NodeID, + MAC: mvNode.Mac, + Adresses: mvNode.Addresses, + Domain: mvNode.Domain, + Hostname: mvNode.Hostname, + Owner: mvNode.Owner, + Firmware: firmware{ + Base: mvNode.Firmware.Base, + Release: mvNode.Firmware.Release, + }, + Autoupdater: autoupdater{ + Enabled: mvNode.Autoupdater.Enabled, + Branch: mvNode.Autoupdater.Branch, + }, + NProc: mvNode.Nproc, + Model: mvNode.Model, + }) + } + } + for i := range d.Links { + mvNode := d.Links[i] + links = append(links, link{ + Type: mvNode.Type, + Source: mvNode.Source, + Target: mvNode.Target, + SourceTQ: mvNode.SourceTq, + TargetTQ: mvNode.TargetTq, + SourceAddr: mvNode.SourceAddr, + TargetAddr: mvNode.TargetAddr, + }) + } + return nodes, links +} + +func getMeshviewer() ([]node, []link) { + var nodes []node + var links []link + + for i := range conf.Meshviewer.Files { + log.Println("Hole Meshviewer JSON von: ", conf.Meshviewer.Files[i].URL) + m, err := getMeshviewerJSON(conf.Meshviewer.Files[i].URL) + if err != nil { + return nodes, links + } + mvNodes, mvLinks := addmvDevices(m) + nodes = append(nodes, mvNodes...) + links = append(links, mvLinks...) + } + return nodes, links +} diff --git a/outages.go b/outages.go new file mode 100644 index 0000000..c4a1143 --- /dev/null +++ b/outages.go @@ -0,0 +1,37 @@ +package main + +import ( + "encoding/csv" + "log" + "os" +) + +func getUNMSLogs() UNMSLogResponse { + + var l UNMSLogResponse + log.Println("Get Outages from UNMS") + err := UnmsCallAPI("/outages?count=100&page=1&type=outage", &l) + if err != nil { + log.Fatalln("Error calling Outages API") + } + return l +} + +func writeOutagesToCSV(l UNMSLogResponse) error { + csvFile, err := os.Create("output/outages.csv") + if err != nil { + return err + } + writer := csv.NewWriter(csvFile) + + for _, o := range l.Items { + var row []string + row = append(row, o.StartTime.Format("02.01.2006 15:04:05")) + row = append(row, o.EndTime.Format("02.01.2006 15:04:05")) + row = append(row, o.Site.Name) + row = append(row, o.Device.DisplayName) + writer.Write(row) + } + writer.Flush() + return nil +} diff --git a/types.go b/types.go index ac87886..bc6541c 100644 --- a/types.go +++ b/types.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "net/http" "os" - "sync" "time" ) @@ -164,17 +163,6 @@ func (o *output) writeToFile() error { return nil } -type apiResponse struct { - StatusCode int `json:"statusCode"` - Error string `json:"error"` - Message string `json:"message"` -} - -type jar struct { - lk sync.Mutex - cookies map[string][]*http.Cookie -} - type ucSite struct { Name string `json:"desc"` ID string `json:"name"` @@ -197,7 +185,7 @@ type ucDevice struct { } `json:"system-stats"` } -type ucAPIData struct { +type UnifiAPIData struct { user string pass string baseURL string diff --git a/unifi.go b/unifi.go new file mode 100644 index 0000000..bd3f090 --- /dev/null +++ b/unifi.go @@ -0,0 +1,213 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +//Unifi Controller API processing +func processUcAPIs() ([]node, []link) { + //get list of Unifi devices to display + var nodes []node + var links []link + d := getDevices(conf.Unifi.UCDevicesURL) + + //call Unifi Controller + ucAPI := UnifiNewAPI(conf.Unifi.User, conf.Unifi.Password, conf.Unifi.APIURL) + //login + ucAPI.ucLogin() + //get all Sites from Controller + sites, err := ucAPI.ucGetSites() + if err != nil { + log.Fatalln(err) + } + //get all devices in all sites + devices, err := ucAPI.ucGetDevices(sites) + if err != nil { + log.Fatalln(err) + } + + //build nodes struct + //mvJson, err := getMeshviewerJSON() + for _, jsonDevice := range d.Devices { + var currentDevice ucDevice + var currentJSONDevice device + for _, device := range devices { + if strings.ToUpper(device.Mac) == strings.ToUpper(jsonDevice.MAC) { + currentDevice = device + currentJSONDevice = jsonDevice + } + } + if isRemoteMACpublished(jsonDevice.MAC, d.Devices) { + //hier muss gecheckt werden ob der link valide ist + if checkMeshviewerLink(jsonDevice.LinkedTo) { + links = UnifiAddLink(jsonDevice, links) + } + } + load, err := strconv.ParseFloat(currentDevice.Sysstats.CPU, 64) + if err != nil { + fmt.Println("Error: ", currentDevice.Name) + //log.Fatalln(err) + load = 0 + } + mem, err := strconv.ParseFloat(currentDevice.Sysstats.Memory, 64) + if err != nil { + //log.Fatalln(err) + load = 0 + } + var model = lookupModels(currentDevice.Model) + var clients = currentDevice.Users + if conf.Unifi.DisplayUsers == false { + clients = 0 + } + nodes = append(nodes, node{ + Firstseen: "0", + Lastseen: time.Unix(int64(currentDevice.LastSeen), 0).Format(iso8601), + IsOnline: itob(currentDevice.State), + IsGateway: false, + Clients: clients, + ClientsWifi24: 0, + ClientsWifi5: 0, + ClientsOther: clients, + RootFSUsage: 0, + LoadAVG: load / 100, + MemoryUsage: mem / 100, + Uptime: time.Now().Add(-1 * time.Second * time.Duration(currentDevice.Uptime)).Format(iso8601), + GatewayNexthop: currentJSONDevice.GatewayNexthop, + Gateway: currentJSONDevice.Gateway, + Location: ¤tJSONDevice.Location, + NodeID: strings.ReplaceAll(currentDevice.Mac, ":", ""), + MAC: currentDevice.Mac, + Adresses: []string{currentDevice.IP}, + Domain: currentJSONDevice.Domain, + Hostname: "[Unifi] " + currentDevice.Name, + Owner: "Freifunk Rhein-Sieg", + Firmware: firmware{ + Base: "Ubiquiti - Stock", + Release: currentDevice.Version, + }, + Autoupdater: autoupdater{ + Enabled: false, + Branch: "stable", + }, + NProc: 1, + Model: model, + }) + } + return nodes, links +} + +func UnifiNewAPI(user string, pass string, baseURL string) UnifiAPIData { + return UnifiAPIData{ + user: user, + pass: pass, + baseURL: baseURL, + client: httpClient(), + } +} + +func (u *UnifiAPIData) ucCallAPI(url string, method string, body *bytes.Buffer, output interface{}) error { + req, err := http.NewRequest(method, u.baseURL+url, body) + if err != nil { + return fmt.Errorf("can't set request %s", u.baseURL+url) + } + + req.Header.Set("Content-Type", "application/json") + response, err := u.client.Do(req) + if err != nil { + return fmt.Errorf("can't login %s", u.baseURL+url) + } + defer response.Body.Close() + if response.StatusCode != 200 { + return fmt.Errorf("Login failed %s", u.baseURL+url) + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + err = json.Unmarshal(data, &output) + if err != nil { + return err + } + + return nil +} + +func (u *UnifiAPIData) ucLogin() error { + var loginData = []byte(`{"username":"` + u.user + `","password":"` + u.pass + `"}`) + + url := "/api/login" + err := u.ucCallAPI(url, http.MethodPost, bytes.NewBuffer(loginData), nil) + if err != nil { + return err + } + return nil +} + +func (u *UnifiAPIData) ucGetSites() ([]ucSite, error) { + var d struct { + Data []ucSite `json:"data"` + } + + url := "/api/self/sites" + err := u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) + if err != nil { + return []ucSite{}, err + } + return d.Data, nil +} + +func (u *UnifiAPIData) ucGetDevices(sites []ucSite) ([]ucDevice, error) { + var d struct { + Data []ucDevice `json:"data"` + } + var s []ucDevice + + for _, site := range sites { + url := "/api/s/" + site.ID + "/stat/device" + u.ucCallAPI(url, http.MethodGet, bytes.NewBuffer([]byte{}), &d) + s = append(s, d.Data...) + } + return s, nil +} + +func UnifiAddLink(dev device, links []link) []link { + for i := range links { + if links[i].SourceAddr == dev.MAC { + // link already exists + return links + } + } + if dev.LinkedTo == "" { + //no LinkedTo in ucDevices.json + return links + } + links = append(links, link{ + Type: "cable", + Source: strings.ReplaceAll(dev.MAC, ":", ""), + Target: strings.ReplaceAll(dev.GatewayNexthop, ":", ""), + SourceTQ: 1, + TargetTQ: 1, + SourceAddr: dev.MAC, + TargetAddr: dev.LinkedTo, + }) + return links +} + +func findNodeID(NodeID string) bool { + for i := range ucDev.Devices { + if ucDev.Devices[i].GatewayNexthop == NodeID { + return true + } + } + return false +} diff --git a/unms.go b/unms.go new file mode 100644 index 0000000..61dfc8b --- /dev/null +++ b/unms.go @@ -0,0 +1,148 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "strings" +) + +//UNMS API processing (Richtfunk) +func processUNMSAPI() ([]node, []link) { + // Variables for runtime + var links []link + var nodes []node + + d := getDevices(conf.Unms.DevicesURL) + + // API CALL 1 + log.Println("calling API 1") + var u []unifiAPIResponse + err := UnmsCallAPI("/devices", &u) + if err != nil { + log.Fatalln(err) + } + + for i := range d.Devices { + var dev unifiAPIResponse + var currentDevice device + for j := range u { + if strings.ToUpper(u[j].Identification.MAC) == strings.ToUpper(d.Devices[i].MAC) { + dev = u[j] + currentDevice = d.Devices[i] + } + } + + var isOnline bool = false + if dev.Overview.Status == "active" { + isOnline = true + } + // END OF API CALL 1 + + // API CALL 2 + log.Println("calling API 2 for device", d.Devices[i].Name) + var details unifiAPIDetails + UnmsCallAPI("/devices/erouters/"+dev.Identification.ID, &details) + // END OF API CALL 2 + + // API CALL 3 + log.Println("calling API 3 for device", d.Devices[i].Name) + var airmaxes []unifiAPIAirmax + UnmsCallAPI("/devices/airmaxes/"+dev.Identification.ID+"/stations", &airmaxes) + // check if remote mac address is part of our published network + for i := range airmaxes { + if isRemoteMACpublished(airmaxes[i].DeviceIdentification.MAC, d.Devices) { + links = UnmsAddLink(dev, airmaxes[i], links) + } + } + // END OF API CALL 3 + + // Get info from json file (static) + nodes = append(nodes, node{ + Firstseen: dev.Overview.CreatedAt.Format(iso8601), + Lastseen: dev.Overview.LastSeen.Format(iso8601), + IsOnline: isOnline, + IsGateway: false, + Clients: 0, + ClientsWifi24: 0, + ClientsWifi5: 0, + ClientsOther: 0, + RootFSUsage: 0, + LoadAVG: details.Overview.CPU / 100, + MemoryUsage: details.Overview.RAM / 100, + Uptime: dev.Identification.Started.Format(iso8601), + GatewayNexthop: currentDevice.GatewayNexthop, + Gateway: currentDevice.Gateway, + Location: ¤tDevice.Location, + NodeID: strings.ReplaceAll(dev.Identification.MAC, ":", ""), + MAC: dev.Identification.MAC, + Adresses: UnmsGetAddresses(details.IPAddress), + Domain: currentDevice.Domain, + Hostname: "[RiFu] " + details.Identification.Name, + Owner: "Freifunk Rhein-Sieg", + Firmware: firmware{ + Base: "Ubiquiti - Stock", + Release: details.Firmware.Current, + }, + Autoupdater: autoupdater{ + Enabled: false, + Branch: "stable", + }, + NProc: 1, + Model: details.Identification.Model, + }) + } + return nodes, links +} + +func UnmsCallAPI(url string, i interface{}) error { + request, err := http.NewRequest(http.MethodGet, conf.Unms.UnmsAPIURL+url, nil) + if err != nil { + return errors.New(fmt.Sprint("can't set request", conf.Unms.UnmsAPIURL+url)) + } + request.Header.Set("x-auth-token", conf.Unms.APItoken) + client := &http.Client{} + response, err := client.Do(request) + if err != nil { + return fmt.Errorf("can't get request %s with x-auth-token %s", conf.Unms.UnmsAPIURL+url, conf.Unms.APItoken) + } + if response.StatusCode != 200 { + log.Fatalln("Can´t call UNMS API, check token and URL. HTTP Status: ", response.StatusCode) + } + data, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() + if err != nil { + return fmt.Errorf("can't read response body: %+v", response.Body) + } + // no error occurred, unmarshal to struct + json.Unmarshal(data, &i) + return nil +} + +func UnmsAddLink(dev unifiAPIResponse, airmaxes unifiAPIAirmax, links []link) []link { + for i := range links { + if links[i].SourceAddr == airmaxes.DeviceIdentification.MAC { + // link already exists + return links + } + } + links = append(links, link{ + Type: "wifi", + Source: strings.ReplaceAll(dev.Identification.MAC, ":", ""), + Target: strings.ReplaceAll(airmaxes.DeviceIdentification.MAC, ":", ""), + SourceTQ: airmaxes.Statistics.LinkScore, + TargetTQ: airmaxes.Statistics.LinkScore, + SourceAddr: dev.Identification.MAC, + TargetAddr: airmaxes.DeviceIdentification.MAC, + }) + return links +} + +func UnmsGetAddresses(ip string) []string { + var adresses []string + adresses = append(adresses, strings.Split(ip, "/")[0]) + return adresses +}