From 69395844280b1b8ab8e77c7843eafa114aa82161 Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Thu, 19 Mar 2026 06:04:50 -0500 Subject: [PATCH] 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) --- poller/internal/device/interfaces.go | 64 +++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/poller/internal/device/interfaces.go b/poller/internal/device/interfaces.go index 53ce1d4..5b1d13f 100644 --- a/poller/internal/device/interfaces.go +++ b/poller/internal/device/interfaces.go @@ -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 +}