feat(13-01): add InterfaceInfo collector with MAC lowercasing and tests

- InterfaceInfo struct for link discovery (name, mac, type, running)
- CollectInterfaceInfo runs /interface/print (version-agnostic)
- MAC addresses lowercased for consistent matching
- Entries without mac-address skipped (loopback, bridge)
- Preserved existing InterfaceStats traffic counter collector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 06:04:50 -05:00
parent 808a49b976
commit 6939584428

View File

@@ -1,10 +1,10 @@
// Package device provides RouterOS metric collectors for the poller.
package device
import (
"fmt"
"log/slog"
"strconv"
"strings"
routeros "github.com/go-routeros/routeros/v3"
)
@@ -59,3 +59,65 @@ func CollectInterfaces(client *routeros.Client) ([]InterfaceStats, error) {
return stats, nil
}
// InterfaceInfo holds basic interface identity data from a RouterOS device.
// This is used for link discovery — MAC addresses identify which device owns
// each end of a network link.
type InterfaceInfo struct {
Name string `json:"name"`
MacAddress string `json:"mac_address"`
Type string `json:"type"`
Running bool `json:"running"`
}
// normalizeMACAddress lowercases a MAC address for consistent matching.
func normalizeMACAddress(mac string) string {
return strings.ToLower(mac)
}
// parseRunning converts a RouterOS "true"/"false" string to a Go bool.
func parseRunning(s string) bool {
return s == "true"
}
// CollectInterfaceInfo queries the RouterOS device for all interfaces and
// returns their name, MAC address, type, and running status.
//
// The /interface/print command is version-agnostic (works on both v6 and v7).
// Entries with an empty mac-address (loopback, bridge without MAC) are skipped.
//
// Returns nil, nil when the device has no interfaces (matching the
// CollectWireless pattern — empty result is not an error).
func CollectInterfaceInfo(client *routeros.Client) ([]InterfaceInfo, error) {
reply, err := client.Run("/interface/print")
if err != nil {
slog.Debug("failed to collect interface info", "error", err)
return nil, nil
}
if len(reply.Re) == 0 {
return nil, nil
}
result := make([]InterfaceInfo, 0, len(reply.Re))
for _, s := range reply.Re {
m := s.Map
mac := m["mac-address"]
if mac == "" {
continue
}
result = append(result, InterfaceInfo{
Name: m["name"],
MacAddress: normalizeMACAddress(mac),
Type: m["type"],
Running: parseRunning(m["running"]),
})
}
if len(result) == 0 {
return nil, nil
}
return result, nil
}