Housekeeping
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Stefan Hoffmann 2021-05-18 21:17:09 +02:00
parent 88fdc227a3
commit e7aa06f647
6 changed files with 552 additions and 528 deletions

521
main.go
View File

@ -1,10 +1,7 @@
package main package main
import ( import (
"bytes"
"encoding/csv"
"encoding/json" "encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -12,8 +9,6 @@ import (
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"os" "os"
"strconv"
"strings"
"time" "time"
_ "git.nils.zone/nils/prettify" _ "git.nils.zone/nils/prettify"
@ -65,205 +60,25 @@ func itob(i int) bool {
return false 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: &currentJSONDevice.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: &currentDevice.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() { func processAPIs() {
tick := time.Tick(delay) tick := time.Tick(delay)
for range tick { for range tick {
var nodes []node var nodes []node
var links []link var links []link
if conf.Unms.Enabled == true { if conf.Unms.Enabled {
log.Println("Processing UNMS") log.Println("Processing UNMS")
unmsNodes, unmsLinks := processUNMSAPI() unmsNodes, unmsLinks := processUNMSAPI()
nodes = append(nodes, unmsNodes...) nodes = append(nodes, unmsNodes...)
links = append(links, unmsLinks...) links = append(links, unmsLinks...)
} }
if conf.Unifi.Enabled == true { if conf.Unifi.Enabled {
log.Println("Processing Unifi") log.Println("Processing Unifi")
ucNodes, ucLinks := processUcAPIs() ucNodes, ucLinks := processUcAPIs()
nodes = append(nodes, ucNodes...) nodes = append(nodes, ucNodes...)
links = append(links, ucLinks...) links = append(links, ucLinks...)
} }
if conf.Meshviewer.Enabled == true { if conf.Meshviewer.Enabled {
log.Println("Processing Meshviewer") log.Println("Processing Meshviewer")
mvNodes, mvLinks := getMeshviewer() mvNodes, mvLinks := getMeshviewer()
nodes = append(nodes, mvNodes...) nodes = append(nodes, mvNodes...)
@ -292,6 +107,7 @@ func processAPIs() {
log.Println("...done") log.Println("...done")
} }
} }
func getFile(url string) []byte { func getFile(url string) []byte {
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
@ -302,10 +118,10 @@ func getFile(url string) []byte {
return byteValue return byteValue
} }
//get devices from devices file on webserver (config)
func getDevices(url string) devices { func getDevices(url string) devices {
// get devices from JSON file // get devices from JSON file
jsonFile := getFile(url) jsonFile := getFile(url)
// read file to bytes // read file to bytes
// variable for d // variable for d
var d devices var d devices
@ -318,30 +134,7 @@ func getDevices(url string) devices {
return d return d
} }
func callUnifiAPI(url string, i interface{}) error { //check for MAC Adress in current Devices
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 isRemoteMACpublished(mac string, devices []device) bool { func isRemoteMACpublished(mac string, devices []device) bool {
for i := range devices { for i := range devices {
if devices[i].MAC == mac { if devices[i].MAC == mac {
@ -351,31 +144,6 @@ func isRemoteMACpublished(mac string, devices []device) bool {
return false 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() { func serveJSON() {
fs := http.FileServer(http.Dir("./output")) fs := http.FileServer(http.Dir("./output"))
http.Handle("/", fs) http.Handle("/", fs)
@ -395,280 +163,3 @@ func httpClient() *http.Client {
client := &http.Client{Jar: jar} client := &http.Client{Jar: jar}
return client 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
}

147
meshviewer.go Normal file
View File

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

37
outages.go Normal file
View File

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

View File

@ -6,7 +6,6 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"sync"
"time" "time"
) )
@ -164,17 +163,6 @@ func (o *output) writeToFile() error {
return nil 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 { type ucSite struct {
Name string `json:"desc"` Name string `json:"desc"`
ID string `json:"name"` ID string `json:"name"`
@ -197,7 +185,7 @@ type ucDevice struct {
} `json:"system-stats"` } `json:"system-stats"`
} }
type ucAPIData struct { type UnifiAPIData struct {
user string user string
pass string pass string
baseURL string baseURL string

213
unifi.go Normal file
View File

@ -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: &currentJSONDevice.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
}

148
unms.go Normal file
View File

@ -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: &currentDevice.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
}