Compare commits
	
		
			2 Commits
		
	
	
		
			6ee0cb4cf1
			...
			7d27e69400
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7d27e69400 | ||
|  | aebb7729ba | 
							
								
								
									
										394
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										394
									
								
								main.go
									
									
									
									
									
								
							| @ -1,6 +1,7 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"flag" | ||||
| @ -8,7 +9,9 @@ import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| @ -16,12 +19,15 @@ import ( | ||||
| // types
 | ||||
| 
 | ||||
| const ( | ||||
| 	baseURL = "https://unifi.freifunk-troisdorf.de/v2.1" | ||||
| 	iso8601 = "2006-01-02T15:04:05-0700" | ||||
| 	baseURL   = "https://unifi.freifunk-troisdorf.de/v2.1" | ||||
| 	ucBaseURL = "https://unifi.freifunk-troisdorf.de:8443" | ||||
| 	iso8601   = "2006-01-02T15:04:05-0700" | ||||
| ) | ||||
| 
 | ||||
| // flags
 | ||||
| var token = flag.String("token", "", "Defines the x-auth-token") | ||||
| var ucUser = flag.String("ucUser", "", "Defines the Unifi API User") | ||||
| var ucPass = flag.String("ucPass", "", "Defines the Unifi API Password") | ||||
| var version = "development" | ||||
| var delay time.Duration = 60 * time.Second | ||||
| 
 | ||||
| @ -33,7 +39,12 @@ func main() { | ||||
| 	if *token == "" { | ||||
| 		log.Fatalln("Please specify an API token via the flag '-token'") | ||||
| 	} | ||||
| 
 | ||||
| 	if *ucPass == "" { | ||||
| 		log.Fatalln("Please specify an API Password via the flag '-ucPass'") | ||||
| 	} | ||||
| 	if *ucUser == "" { | ||||
| 		log.Fatalln("Please specify an API User via the flag '-ucUser'") | ||||
| 	} | ||||
| 	// start API processing (runs in a loop)
 | ||||
| 	go processAPIs() | ||||
| 
 | ||||
| @ -41,95 +52,215 @@ func main() { | ||||
| 	serveJSON() | ||||
| } | ||||
| 
 | ||||
| func lookupModels(model string) string { | ||||
| 	switch model { | ||||
| 	case "BZ2", "U2S48", "U2Sv2": | ||||
| 		return "Unifi AP" | ||||
| 	case "BZ2LR", "U2L48", "U2Lv2": | ||||
| 		return "UniFi AP-LR" | ||||
| 	case "U7E", "U7Ev2": | ||||
| 		return "UniFi AP-AC" | ||||
| 	case "U7HD", "U7SHD": | ||||
| 		return "UniFi AP-HD" | ||||
| 	case "UXSDM": | ||||
| 		return "UniFi AP-BaseStationXG" | ||||
| 	case "UCMSH": | ||||
| 		return "AP-MeshXG" | ||||
| 	case "U7MP": | ||||
| 		return "AP-AC-Mesh-Pro" | ||||
| 	case "U7LR": | ||||
| 		return "UniFi AP-AC-LR" | ||||
| 	case "U7LT": | ||||
| 		return "UniFi AP-AC-Lite" | ||||
| 	case "U7P": | ||||
| 		return "UniFi AP-Pro" | ||||
| 	case "U7MSH": | ||||
| 		return "UniFi AP-AC-Mesh" | ||||
| 	case "U7PG2": | ||||
| 		return "UniFi AP-AC-Pro" | ||||
| 	default: | ||||
| 		return "Unifi Gerät" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func itob(i int) bool { | ||||
| 	if i == 1 { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func processUcAPIs() []node { | ||||
| 	var nodes []node | ||||
| 	d, err := getDevices("ucDevices.json") | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
| 	ucAPI := newAPI(*ucUser, *ucPass, ucBaseURL) | ||||
| 	ucAPI.ucLogin() | ||||
| 	sites, err := ucAPI.ucGetSites() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	devices, err := ucAPI.ucGetDevices(sites) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	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 | ||||
| 			} | ||||
| 		} | ||||
| 		load, err := strconv.ParseFloat(currentDevice.Sysstats.CPU, 64) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 		mem, err := strconv.ParseFloat(currentDevice.Sysstats.Memory, 64) | ||||
| 		if err != nil { | ||||
| 			panic(err) | ||||
| 		} | ||||
| 
 | ||||
| 		nodes = append(nodes, node{ | ||||
| 			Firstseen:      "0", | ||||
| 			Lastseen:       time.Unix(int64(currentDevice.LastSeen), 0).Format(iso8601), | ||||
| 			IsOnline:       itob(currentDevice.State), | ||||
| 			IsGateway:      false, | ||||
| 			Clients:        0, | ||||
| 			ClientsWifi24:  0, | ||||
| 			ClientsWifi5:   0, | ||||
| 			ClientsOther:   0, | ||||
| 			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: lookupModels(currentDevice.Model), | ||||
| 		}) | ||||
| 	} | ||||
| 	return nodes | ||||
| } | ||||
| 
 | ||||
| func processUNMSAPI() ([]node, []link) { | ||||
| 	// Variables for runtime
 | ||||
| 	var links []link | ||||
| 	var nodes []node | ||||
| 
 | ||||
| 	d, err := getDevices("devices.json") | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// 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() { | ||||
| 	tick := time.Tick(delay) | ||||
| 	for range tick { | ||||
| 		// Variables for runtime
 | ||||
| 		var links []link | ||||
| 		var nodes []node | ||||
| 		var links []link | ||||
| 
 | ||||
| 		d, err := getDevices() | ||||
| 		if err != nil { | ||||
| 			log.Fatalln(err) | ||||
| 		} | ||||
| 
 | ||||
| 		// 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, | ||||
| 			}) | ||||
| 		} | ||||
| 		unmsNodes, links := processUNMSAPI() | ||||
| 		ucNodes := processUcAPIs() | ||||
| 		nodes = append(nodes, unmsNodes...) | ||||
| 		nodes = append(nodes, ucNodes...) | ||||
| 
 | ||||
| 		// assemble final struct
 | ||||
| 		o := output{ | ||||
| @ -150,9 +281,9 @@ func processAPIs() { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getDevices() (devices, error) { | ||||
| func getDevices(file string) (devices, error) { | ||||
| 	// get devices from JSON file
 | ||||
| 	jsonFile, err := os.Open("devices.json") | ||||
| 	jsonFile, err := os.Open(file) | ||||
| 	if err != nil { | ||||
| 		return devices{}, errors.New("can't open devices.json") | ||||
| 	} | ||||
| @ -242,3 +373,88 @@ func serveJSON() { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func httpClient() *http.Client { | ||||
| 	jar, err := cookiejar.New(nil) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										35
									
								
								types.go
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								types.go
									
									
									
									
									
								
							| @ -4,7 +4,9 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| @ -142,3 +144,36 @@ type apiResponse struct { | ||||
| 	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"` | ||||
| } | ||||
| 
 | ||||
| type ucDevice struct { | ||||
| 	IP       string `json:"ip"` | ||||
| 	Mac      string `json:"mac"` | ||||
| 	Model    string `json:"model"` | ||||
| 	Version  string `json:"version"` | ||||
| 	Gateway  string `json:"gateway_mac"` | ||||
| 	Name     string `json:"name"` | ||||
| 	State    int    `json:"state"` | ||||
| 	LastSeen int    `json:"last_seen"` | ||||
| 	Uptime   int    `json:"uptime"` | ||||
| 	Sysstats struct { | ||||
| 		CPU    string `json:"cpu"` | ||||
| 		Memory string `json:"mem"` | ||||
| 	} `json:"system-stats"` | ||||
| } | ||||
| 
 | ||||
| type ucAPIData struct { | ||||
| 	user    string | ||||
| 	pass    string | ||||
| 	baseURL string | ||||
| 	client  *http.Client | ||||
| } | ||||
|  | ||||
							
								
								
									
										26
									
								
								ucDevices.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								ucDevices.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| { | ||||
|     "devices":[ | ||||
|       { | ||||
|         "name": "UAP-AC-M_S03", | ||||
|         "mac": "18:e8:29:56:6d:9e", | ||||
|         "gateway_nexthop": "18e8292f7de6", | ||||
|         "gateway": "a28cae6ff604", | ||||
|         "domain": "unifi", | ||||
|         "location": { | ||||
|           "longitude":7.148406208, | ||||
|           "latitude":50.817093402 | ||||
|         } | ||||
|       }, | ||||
|       { | ||||
|         "name": "Am-Krausacker-2", | ||||
|         "mac": "18:e8:29:a0:6f:23", | ||||
|         "gateway_nexthop": "18e8292f7de6", | ||||
|         "gateway": "a28cae6ff604", | ||||
|         "domain": "unifi", | ||||
|         "location": { | ||||
|           "longitude":7.148406208, | ||||
|           "latitude":50.817093402 | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user