Initial commit: IT Nexus Web-App
This commit is contained in:
129
nexus-scanner/internal/modules/macvendor/macvendor.go
Normal file
129
nexus-scanner/internal/modules/macvendor/macvendor.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Package macvendor implements the mac_vendor module.
|
||||
// It maps the OUI (first 3 bytes) of every known host's MAC address to a
|
||||
// human-readable vendor name and persists the result in the database.
|
||||
// The mapping is based on a built-in table; no external files are required.
|
||||
package macvendor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cereda-systems/nexus-scanner/internal/db"
|
||||
)
|
||||
|
||||
// ouiTable maps lowercase OUI prefixes (aa:bb:cc) to vendor names.
|
||||
// Broadcast / multicast entries are intentionally kept with an empty string
|
||||
// so they are recognised but not written to the database.
|
||||
var ouiTable = map[string]string{
|
||||
"00:0c:29": "VMware",
|
||||
"00:50:56": "VMware",
|
||||
"bc:24:11": "Proxmox/Ceph",
|
||||
"90:1b:0e": "Supermicro",
|
||||
"90:09:d0": "Synology",
|
||||
"00:17:c8": "Hewlett-Packard",
|
||||
"00:be:43": "Ubiquiti",
|
||||
"e4:43:4b": "Ubiquiti",
|
||||
"24:6a:0e": "Ubiquiti",
|
||||
"40:86:cb": "Intel",
|
||||
"68:c6:ac": "Intel",
|
||||
"1c:af:4a": "Dell",
|
||||
"50:81:40": "Dell",
|
||||
"4c:5f:70": "Lenovo",
|
||||
"dc:58:bc": "Apple",
|
||||
"c8:4b:d6": "Kyocera",
|
||||
"38:d5:7a": "Samsung",
|
||||
"00:04:a5": "Barco",
|
||||
"7c:5a:1c": "LANCOM",
|
||||
"00:0a:b3": "Cisco",
|
||||
"00:e0:67": "Aten",
|
||||
"ff:ff:ff": "", // broadcast — leave vendor empty
|
||||
}
|
||||
|
||||
// LookupVendor returns the vendor name for a MAC address.
|
||||
// The MAC must be in the format aa:bb:cc:dd:ee:ff (colon-separated).
|
||||
// An empty string is returned when no match is found.
|
||||
func LookupVendor(mac string) string {
|
||||
if len(mac) < 8 {
|
||||
return ""
|
||||
}
|
||||
oui := strings.ToLower(mac[:8])
|
||||
return ouiTable[oui]
|
||||
}
|
||||
|
||||
// Module enriches hosts in the database with OUI-based vendor names.
|
||||
type Module struct {
|
||||
site string
|
||||
store *db.Store
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// New returns a new mac_vendor module.
|
||||
func New(site string, store *db.Store) *Module {
|
||||
return &Module{
|
||||
site: site,
|
||||
store: store,
|
||||
log: slog.With("module", "mac_vendor"),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) Name() string { return "mac_vendor" }
|
||||
|
||||
// Interval returns 0, which causes the scheduler to run this module once at
|
||||
// startup and never repeat it automatically.
|
||||
func (m *Module) Interval() time.Duration { return 0 }
|
||||
|
||||
// Run assigns vendor names to all hosts that have none recorded yet.
|
||||
func (m *Module) Run(ctx context.Context) error {
|
||||
scanID, err := m.store.BeginScan(m.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin scan: %w", err)
|
||||
}
|
||||
|
||||
scanErr := m.scan(ctx)
|
||||
if endErr := m.store.EndScan(scanID, scanErr); endErr != nil {
|
||||
m.log.Error("record scan end", "err", endErr)
|
||||
}
|
||||
return scanErr
|
||||
}
|
||||
|
||||
func (m *Module) scan(ctx context.Context) error {
|
||||
hosts, err := m.store.ListHosts("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("list hosts: %w", err)
|
||||
}
|
||||
|
||||
enriched := 0
|
||||
for _, h := range hosts {
|
||||
// Skip hosts that already have a vendor assigned.
|
||||
if h.Vendor != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
vendor := LookupVendor(h.MAC)
|
||||
if vendor == "" {
|
||||
// No entry in our table — leave vendor empty.
|
||||
continue
|
||||
}
|
||||
|
||||
h.Vendor = vendor
|
||||
if err := m.store.UpsertHost(h); err != nil {
|
||||
m.log.Error("upsert host", "ip", h.IP, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
m.log.Info("vendor resolved", "ip", h.IP, "mac", h.MAC, "vendor", vendor)
|
||||
enriched++
|
||||
}
|
||||
|
||||
m.log.Info("scan complete", "checked", len(hosts), "enriched", enriched)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user