Get data from ARP cache
This commit is contained in:
parent
a5084c57f0
commit
22017abc1b
9 changed files with 197 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
build/*
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
27
internal/cache/datacache.go
vendored
27
internal/cache/datacache.go
vendored
|
@ -22,6 +22,7 @@ type DataCache struct {
|
||||||
interfaces map[int]*models.Interface
|
interfaces map[int]*models.Interface
|
||||||
cables map[int]*models.Cable
|
cables map[int]*models.Cable
|
||||||
vms map[int]*models.VM
|
vms map[int]*models.VM
|
||||||
|
arpRecords []*models.ARPRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataCache) AddDevice(device models.Device) {
|
func (d *DataCache) AddDevice(device models.Device) {
|
||||||
|
@ -92,6 +93,13 @@ func (d *DataCache) GetVMs() []*models.VM {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DataCache) AddARPRecords(records []*models.ARPRecord) {
|
||||||
|
d.lock.Lock()
|
||||||
|
defer d.lock.Unlock()
|
||||||
|
|
||||||
|
d.arpRecords = records
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DataCache) ReconcileData() {
|
func (d *DataCache) ReconcileData() {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
|
@ -110,6 +118,25 @@ func (d *DataCache) ReconcileData() {
|
||||||
cable.BTerminations[0].Object.Interface = *ifaceB
|
cable.BTerminations[0].Object.Interface = *ifaceB
|
||||||
|
|
||||||
d.cables[id] = cable
|
d.cables[id] = cable
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range d.arpRecords {
|
||||||
|
for _, iif := range d.interfaces {
|
||||||
|
if r.MacAddress == iif.MacAddress {
|
||||||
|
r.Device = &iif.Device
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DataCache) GetUnmonitoredMachines() []models.UnmonitoredDevice {
|
||||||
|
var res []models.UnmonitoredDevice
|
||||||
|
|
||||||
|
for _, r := range d.arpRecords {
|
||||||
|
if r.Device == nil {
|
||||||
|
res = append(res, models.UnmonitoredDevice{Address: r.Address, MacAddress: r.MacAddress})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
36
internal/config/config.go
Normal file
36
internal/config/config.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetboxConfig struct {
|
||||||
|
BaseURL string `json:"baseURL"`
|
||||||
|
APIKey string `json:"apiKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MikrotikConfig struct {
|
||||||
|
IPAddress string `json:"ipAddress"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
Netbox NetboxConfig `json:"netbox"`
|
||||||
|
Mikrotik []MikrotikConfig `json:"mikrotik"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(configPath string) (*AppConfig, error) {
|
||||||
|
content, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var conf AppConfig
|
||||||
|
if err := json.Unmarshal(content, &conf); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid config content: %w", err)
|
||||||
|
}
|
||||||
|
return &conf, nil
|
||||||
|
}
|
77
internal/mikrotik/mikrotik.go
Normal file
77
internal/mikrotik/mikrotik.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package mikrotik
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.faercol.me/faercol/topology-map/internal/config"
|
||||||
|
"git.faercol.me/faercol/topology-map/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
const arpRoute = "/rest/ip/arp"
|
||||||
|
|
||||||
|
func buildQueryURL(targetIP, route string) string {
|
||||||
|
return "http://" + targetIP + route
|
||||||
|
}
|
||||||
|
|
||||||
|
type MikrotikClient struct {
|
||||||
|
httpClt *http.Client
|
||||||
|
targets []config.MikrotikConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMikrotikClient(targets []config.MikrotikConfig) *MikrotikClient {
|
||||||
|
return &MikrotikClient{
|
||||||
|
httpClt: http.DefaultClient,
|
||||||
|
targets: targets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MikrotikClient) doQuery(target config.MikrotikConfig, route string) ([]byte, error) {
|
||||||
|
query, err := http.NewRequest(http.MethodGet, buildQueryURL(target.IPAddress, route), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
query.SetBasicAuth(target.User, target.Password)
|
||||||
|
|
||||||
|
resp, err := c.httpClt.Do(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MikrotikClient) getARPCacheForTarget(target config.MikrotikConfig) ([]models.ARPRecord, error) {
|
||||||
|
data, err := c.doQuery(target, arpRoute)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []models.ARPRecord
|
||||||
|
if err := json.Unmarshal(data, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MikrotikClient) GetARPRecords() ([]*models.ARPRecord, error) {
|
||||||
|
arpCache := map[string]models.ARPRecord{}
|
||||||
|
|
||||||
|
for _, t := range c.targets {
|
||||||
|
records, err := c.getARPCacheForTarget(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get ARP data for device %s: %w", t.IPAddress, err)
|
||||||
|
}
|
||||||
|
for _, r := range records {
|
||||||
|
arpCache[r.MacAddress] = r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*models.ARPRecord
|
||||||
|
for _, r := range arpCache {
|
||||||
|
res = append(res, &r)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
22
internal/models/mikrotik.go
Normal file
22
internal/models/mikrotik.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ARPRecord struct {
|
||||||
|
Device *Device `json:"-"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
MacAddress string `json:"mac-address"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnmonitoredDevice struct {
|
||||||
|
Address string
|
||||||
|
MacAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d UnmonitoredDevice) Element() Element {
|
||||||
|
return Element{
|
||||||
|
Classes: []string{"unmonitored"},
|
||||||
|
Data: ElementData{
|
||||||
|
ID: "unmonitored-" + d.MacAddress,
|
||||||
|
Label: d.Address,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,10 +9,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
devicesRoute = "dcim/devices"
|
devicesRoute = "/api/dcim/devices"
|
||||||
cablesRoute = "dcim/cables"
|
cablesRoute = "/api/dcim/cables"
|
||||||
vmsRoute = "virtualization/virtual-machines"
|
vmsRoute = "/api/virtualization/virtual-machines"
|
||||||
interfaceRoute = "dcim/interfaces"
|
interfaceRoute = "/api/dcim/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
type vmsResponse struct {
|
type vmsResponse struct {
|
||||||
|
|
23
main.go
23
main.go
|
@ -7,18 +7,23 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.faercol.me/faercol/topology-map/internal/cache"
|
"git.faercol.me/faercol/topology-map/internal/cache"
|
||||||
|
"git.faercol.me/faercol/topology-map/internal/config"
|
||||||
|
"git.faercol.me/faercol/topology-map/internal/mikrotik"
|
||||||
"git.faercol.me/faercol/topology-map/internal/models"
|
"git.faercol.me/faercol/topology-map/internal/models"
|
||||||
"git.faercol.me/faercol/topology-map/internal/netbox"
|
"git.faercol.me/faercol/topology-map/internal/netbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiKey = "4e75b8927940adc29e2e1eac042bf92bcddd57fe"
|
|
||||||
const netboxBaseURL = "https://netbox.internal.faercol.me/api/"
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
config, err := config.New("build/config.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Initializing data")
|
fmt.Println("Initializing data")
|
||||||
dataCache := cache.NewDataCache()
|
dataCache := cache.NewDataCache()
|
||||||
netboxClt := netbox.NewClient(netboxBaseURL, apiKey)
|
netboxClt := netbox.NewClient(config.Netbox.BaseURL, config.Netbox.APIKey)
|
||||||
|
mikrotikClt := mikrotik.NewMikrotikClient(config.Mikrotik)
|
||||||
|
|
||||||
fmt.Println("Getting devices")
|
fmt.Println("Getting devices")
|
||||||
devices, err := netboxClt.GetDevices()
|
devices, err := netboxClt.GetDevices()
|
||||||
|
@ -56,6 +61,13 @@ func main() {
|
||||||
dataCache.AddInterface(i)
|
dataCache.AddInterface(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Getting data from ARP cache")
|
||||||
|
arpCache, err := mikrotikClt.GetARPRecords()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dataCache.AddARPRecords(arpCache)
|
||||||
|
|
||||||
fmt.Println("Reconciling data")
|
fmt.Println("Reconciling data")
|
||||||
dataCache.ReconcileData()
|
dataCache.ReconcileData()
|
||||||
fmt.Println("Done")
|
fmt.Println("Done")
|
||||||
|
@ -80,6 +92,9 @@ func main() {
|
||||||
for _, v := range dataCache.GetVMs() {
|
for _, v := range dataCache.GetVMs() {
|
||||||
resp = append(resp, v.Elements()...)
|
resp = append(resp, v.Elements()...)
|
||||||
}
|
}
|
||||||
|
for _, unm := range dataCache.GetUnmonitoredMachines() {
|
||||||
|
resp = append(resp, unm.Element())
|
||||||
|
}
|
||||||
|
|
||||||
respBody, err := json.Marshal(resp)
|
respBody, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -65,12 +65,23 @@ function setupGraph() {
|
||||||
"line-color": "#FFC107",
|
"line-color": "#FFC107",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: ".unmonitored",
|
||||||
|
style: {
|
||||||
|
"background-color": "#E57373",
|
||||||
|
"border-color": "#C62828"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
layout: {
|
layout: {
|
||||||
name: 'cose',
|
name: 'cose',
|
||||||
nodeDimensionsIncludeLabels: true,
|
nodeDimensionsIncludeLabels: true,
|
||||||
}
|
}
|
||||||
|
// layout: {
|
||||||
|
// name: "cola",
|
||||||
|
// nodeDimensionsIncludeLabels: true,
|
||||||
|
// }
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
BIN
topology-map
BIN
topology-map
Binary file not shown.
Loading…
Reference in a new issue