diff --git a/devices.json b/devices.json new file mode 100644 index 0000000..c037824 --- /dev/null +++ b/devices.json @@ -0,0 +1,26 @@ +{ + "devices":[ + { + "name": "Rathaus2Bahnhof", + "mac": "18:E8:29:8E:C6:4D", + "gateway_nexthop": "18e8292f7de6", + "gateway": "a28cae6ff604", + "domain": "rifu", + "location": { + "longitude":7.148406208, + "latitude":50.817093402 + } + }, + { + "name": "Bahnhof2Rathaus", + "mac": "18:e8:29:dc:c3:7e", + "gateway_nexthop": "18e8292f7de6", + "gateway": "a28cae6ff604", + "domain": "rifu", + "location": { + "longitude":7.150436640, + "latitude":50.814456507 + } + } + ] +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..13f5908 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.freifunk-rhein-sieg.net/Freifunk-Troisdorf/ubnt-freifunk-map-api + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d1a62e2 --- /dev/null +++ b/main.go @@ -0,0 +1,212 @@ +package main + +import ( + "encoding/json" + "flag" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" +) + +// types + +const ( + baseURL = "https://unifi.freifunk-troisdorf.de/v2.1" + iso8601 = "2006-01-02T15:04:05-0700" +) + +// flags +var token = flag.String("token", "", "Defines the x-auth-token") + +func main() { + // parse all flags + flag.Parse() + + // check if token is set + if *token == "" { + log.Println("Please specify an API token via the flag '-token'") + os.Exit(1) + } + + // Variables for runtime + var links []link + var nodes []node + + d := getDevices() + + // API CALL 1 + var u []unifiAPIResponse + callUnifiAPI("/devices", &u) + + 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] + } + } + + // fmt.Println(time.Now().Format(iso8601)) + var isOnline bool = false + if dev.Overview.Status == "active" { + isOnline = true + } + + // uptime := dev.Identification.Started.Format(iso8601) + // nodeID := strings.ReplaceAll(dev.Identification.MAC, ":", "") + // END OF API CALL 1 + + // API CALL 2 + var details unifiAPIDetails + callUnifiAPI("/devices/erouters/"+dev.Identification.ID, &details) + // END OF API CALL 2 + + // API CALL 3 + var airmaxes []unifiAPIAirmax + callUnifiAPI("/devices/airmaxes/"+dev.Identification.ID+"/stations", &airmaxes) + // check if remote mac address is part of our published network + // if isRemoteMACpublished() + 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, + }) + } + + // assemble final struct + o := output{ + Timestamp: time.Now().Format(iso8601), + Nodes: nodes, + Links: links, + } + + // create file output + writeJSONtoFile(o) +} + +func getDevices() devices { + // get devices from JSON file + jsonFile, err := os.Open("devices.json") + if err != nil { + panic(err) + } + defer jsonFile.Close() + + // read file to bytes + byteValue, _ := ioutil.ReadAll(jsonFile) + + // variable for d + var d devices + // unmarshal to struct + json.Unmarshal(byteValue, &d) + return d +} + +func callUnifiAPI(url string, i interface{}) { + request, err := http.NewRequest(http.MethodGet, baseURL+url, nil) + if err != nil { + panic(err) + } + request.Header.Set("x-auth-token", *token) + client := &http.Client{} + response, err := client.Do(request) + if err != nil { + panic(err) + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + panic(err) + } + // fmt.Println(string(data)) + + json.Unmarshal(data, &i) +} + +func isRemoteMACpublished(mac string, devices []device) bool { + for i := range devices { + if devices[i].MAC == mac { + return true + } + } + 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 writeJSONtoFile(o output) { + file, err := json.MarshalIndent(o, "", " ") + if err != nil { + panic(err) + } + + // write to file + err = ioutil.WriteFile("example.json", file, 0644) + if err != nil { + panic(err) + } +} + +func getAddresses(ip string) []string { + var adresses []string + adresses = append(adresses, strings.Split(ip, "/")[0]) + return adresses +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..060badd --- /dev/null +++ b/types.go @@ -0,0 +1,112 @@ +package main + +import "time" + +type device struct { + Name string `json:"name"` + MAC string `json:"mac"` + GatewayNexthop string `json:"gateway_nexthop"` + Gateway string `json:"gateway"` + Domain string `json:"domain"` + Location struct { + Longitude float64 `json:"longitude"` + Latitude float64 `json:"latitude"` + } `json:"location"` +} +type devices struct { + Devices []device `json:"devices"` +} + +type unifiAPIResponse struct { + Identification struct { + ID string `json:"id"` + MAC string `json:"mac"` + Started time.Time `json:"started"` + } `json:"identification"` + Overview struct { + LastSeen time.Time `json:"lastSeen"` + CreatedAt time.Time `json:"createdAt"` + Status string `json:"status"` + } `json:"overview"` +} + +type unifiAPIDetails struct { + Identification struct { + Name string `json:"name"` + Model string `json:"model"` + } `json:"identification"` + Firmware struct { + Current string `json:"current"` + } `json:"firmware"` + Overview struct { + CPU float64 `json:"cpu"` + RAM float64 `json:"ram"` + } `json:"overview"` + IPAddress string `json:"ipAddress"` +} + +type unifiAPIAirmax struct { + DeviceIdentification struct { + MAC string `json:"mac"` + } `json:"deviceidentification"` + Statistics struct { + LinkScore float64 `json:"linkScore"` + } `json:"statistics"` +} + +type link struct { + Type string `json:"type"` + Source string `json:"source"` + Target string `json:"target"` + SourceTQ float64 `json:"source_tq"` + TargetTQ float64 `json:"target_tq"` + SourceAddr string `json:"source_addr"` + TargetAddr string `json:"target_addr"` +} + +type node struct { + Firstseen string `json:"firstseen"` + Lastseen string `json:"lastseen"` + IsOnline bool `json:"is_online"` + IsGateway bool `json:"is_gateway"` + Clients int `json:"clients"` + ClientsWifi24 int `json:"clients_wifi24"` + ClientsWifi5 int `json:"clients_wifi5"` + ClientsOther int `json:"clients_other"` + RootFSUsage int `json:"rootfs_usage"` + LoadAVG float64 `json:"loadavg"` + MemoryUsage float64 `json:"memory_usage"` + Uptime string `json:"uptime"` + GatewayNexthop string `json:"gateway_nexthop"` + Gateway string `json:"gateway"` + Location struct { + Longitude float64 `json:"longitude"` + Latitude float64 `json:"latitude"` + } `json:"location"` + NodeID string `json:"node_id"` + MAC string `json:"mac"` + Adresses []string `json:"addresses"` + Domain string `json:"domain"` + Hostname string `json:"hostname"` + Owner string `json:"owner"` + Firmware firmware `json:"firmware"` + Autoupdater autoupdater `json:"autoupdater"` + NProc int `json:"nproc"` + Model string `json:"model"` +} + +type firmware struct { + Base string `json:"base"` + Release string `json:"release"` +} + +type autoupdater struct { + Enabled bool `json:"enabled"` + Branch string `json:"branch"` +} + +type output struct { + Timestamp string `json:"timestamp"` + Nodes []node `json:"nodes"` + Links []link `json:"links"` +} diff --git a/unifi.ps1 b/unifi.ps1 deleted file mode 100644 index 28fdf22..0000000 --- a/unifi.ps1 +++ /dev/null @@ -1,137 +0,0 @@ - -$URI = "https://unifi.freifunk-troisdorf.de/v2.1/devices" -$headers = @{ - "x-auth-token" = "" -} - -$devices = (Get-Content -Path .\devices.json | ConvertFrom-Json).devices - -$request = Invoke-Restmethod -URI $URI -Method GET -Headers $headers - -# Get timestamp -$timestamp = Get-Date -UFormat "%Y-%m-%dT%R:00%Z00" -$timeDiffFromUTC = Get-Date -UFormat "%Z00" - - - -# create empty nodes array -$nodes = @() -$links = @() - -# loop through each device -foreach ($device in $devices) { - $dev = $request | Where-Object { $_.identification.mac -eq $device.mac} - # implement function that reports not found mac addresses in API - - ## Get info from API - $firstseen = Get-Date -Date $dev.overview.createdAt -UFormat "%Y-%m-%dT%R:00$timeDiffFromUTC" - $lastseen = Get-Date -Date $dev.overview.lastSeen -UFormat "%Y-%m-%dT%R:00$timeDiffFromUTC" - if ($dev.overview.status -eq "active") { - $is_online = $true - } else { - $is_online = $false - } - - $uptime = Get-Date -Date $dev.identification.started -UFormat "%Y-%m-%dT%R:00$timeDiffFromUTC" - $mac = $dev.identification.mac - $node_id = $mac.replace(":","") - - - ## End of API call 1 - - - # get more info from each device - ## Get more info from second API call - $id = $dev.identification.id - $URI = "https://unifi.freifunk-troisdorf.de/v2.1/devices/erouters/$id" - $details = Invoke-Restmethod -URI $URI -Method GET -Headers $headers - - $loadavg = $details.overview.cpu / 100 - $memory_usage = $details.overview.ram / 100 - - $addresses = @() - # cut away network mask - $addresses += $details.ipAddress.split("/")[0] - - $hostname = "[RiFu] " + $details.identification.name - - $firmware = @{ - base = "Ubiquiti - Stock" - release = $details.firmware.current - } - $model = $details.identification.model - - ## End of API call 2 - - ## API Call 3 (Links) - $URI = "https://unifi.freifunk-troisdorf.de/v2.1/devices/airmaxes/$id/stations" - $airmaxes = Invoke-Restmethod -URI $URI -Method GET -Headers $headers - $remoteMAC = $airmaxes.deviceIdentification.mac - - - # check if remote mac address is part of our published network - if ($devices | Where-Object -Property mac -EQ $remoteMAC) { - # this is a public link, let's advertise it - if (!($links | Where-Object -Property source_addr -EQ $remoteMAC)) { - # this link does not exist already, let's add it - $links += @{ - type = "wifi" - source = $mac.replace(":","") - target = $remoteMAC.replace(":","") - source_tq = $airmaxes.statistics.linkScore - target_tq = $airmaxes.statistics.linkScore - source_addr = $mac - target_addr = $remoteMAC - } - } - } - - ## Get info from json file (static) - $currentDev = $devices | Where-Object -Property mac -EQ $dev.identification.mac - $gateway_nexthop = $currentDev.gateway_nexthop - $gateway = $currentDev.gateway - $domain = $currentDev.domain - $location = $currentDev.location - - # assemble all into nodes an links - $nodes += @{ - firstseen = $firstseen - lastseen = $lastseen - is_online = $is_online - is_gateway = $false - clients = 0 - clients_wifi24 = 0 - clients_wifi5 = 0 - clients_other = 0 - rootfs_usage = 0 - loadavg = $loadavg - memory_usage = $memory_usage - uptime = $uptime - gateway_nexthop = $gateway_nexthop - gateway = $gateway - location = $location - node_id = $node_id - mac = $mac - addresses = $addresses - domain = $domain - hostname = $hostname - owner = "Freifunk Rhein-Sieg" - firmware = $firmware - autoupdater = @{ - enabled = $false - branch = "stable" - } - nproc = 1 - model = $model - } -} - - -# create file output -$output = @{ - timestamp = $timestamp - nodes = $nodes - links = $links -} - -$output | ConvertTo-Json -Depth 4 | Out-File example.json \ No newline at end of file