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
|
||||
*.so
|
||||
*.dylib
|
||||
build/*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.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
|
||||
cables map[int]*models.Cable
|
||||
vms map[int]*models.VM
|
||||
arpRecords []*models.ARPRecord
|
||||
}
|
||||
|
||||
func (d *DataCache) AddDevice(device models.Device) {
|
||||
|
@ -92,6 +93,13 @@ func (d *DataCache) GetVMs() []*models.VM {
|
|||
return res
|
||||
}
|
||||
|
||||
func (d *DataCache) AddARPRecords(records []*models.ARPRecord) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.arpRecords = records
|
||||
}
|
||||
|
||||
func (d *DataCache) ReconcileData() {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
@ -110,6 +118,25 @@ func (d *DataCache) ReconcileData() {
|
|||
cable.BTerminations[0].Object.Interface = *ifaceB
|
||||
|
||||
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 (
|
||||
devicesRoute = "dcim/devices"
|
||||
cablesRoute = "dcim/cables"
|
||||
vmsRoute = "virtualization/virtual-machines"
|
||||
interfaceRoute = "dcim/interfaces"
|
||||
devicesRoute = "/api/dcim/devices"
|
||||
cablesRoute = "/api/dcim/cables"
|
||||
vmsRoute = "/api/virtualization/virtual-machines"
|
||||
interfaceRoute = "/api/dcim/interfaces"
|
||||
)
|
||||
|
||||
type vmsResponse struct {
|
||||
|
|
23
main.go
23
main.go
|
@ -7,18 +7,23 @@ import (
|
|||
"net/http"
|
||||
|
||||
"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/netbox"
|
||||
)
|
||||
|
||||
const apiKey = "4e75b8927940adc29e2e1eac042bf92bcddd57fe"
|
||||
const netboxBaseURL = "https://netbox.internal.faercol.me/api/"
|
||||
|
||||
func main() {
|
||||
|
||||
config, err := config.New("build/config.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Initializing data")
|
||||
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")
|
||||
devices, err := netboxClt.GetDevices()
|
||||
|
@ -56,6 +61,13 @@ func main() {
|
|||
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")
|
||||
dataCache.ReconcileData()
|
||||
fmt.Println("Done")
|
||||
|
@ -80,6 +92,9 @@ func main() {
|
|||
for _, v := range dataCache.GetVMs() {
|
||||
resp = append(resp, v.Elements()...)
|
||||
}
|
||||
for _, unm := range dataCache.GetUnmonitoredMachines() {
|
||||
resp = append(resp, unm.Element())
|
||||
}
|
||||
|
||||
respBody, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
|
|
|
@ -65,12 +65,23 @@ function setupGraph() {
|
|||
"line-color": "#FFC107",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: ".unmonitored",
|
||||
style: {
|
||||
"background-color": "#E57373",
|
||||
"border-color": "#C62828"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
layout: {
|
||||
name: 'cose',
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
}
|
||||
// layout: {
|
||||
// name: "cola",
|
||||
// nodeDimensionsIncludeLabels: true,
|
||||
// }
|
||||
|
||||
});
|
||||
});
|
||||
|
|
BIN
topology-map
BIN
topology-map
Binary file not shown.
Loading…
Reference in a new issue