4d6c2fd878
Fast (~1-3s) endpoint that gathers network info about a device before full stream discovery. Runs ping first, then parallel probes. Features: - Ping with ICMP + TCP fallback (works without root) - Reverse DNS hostname lookup - ARP table MAC address + OUI vendor identification (2403 entries, 51 camera vendors) - mDNS HomeKit detection (camera/doorbell, paired status) - Extensible Prober interface for adding new probe types - 3-second overall timeout, parallel execution Response includes "type" field: - "unreachable" - device not responding - "standard" - normal IP camera (RTSP/HTTP/ONVIF flow) - "homekit" - Apple HomeKit camera (PIN pairing flow)
77 lines
1.8 KiB
Go
77 lines
1.8 KiB
Go
package discovery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// OUIDatabase provides MAC address prefix to vendor name lookup.
|
|
// Data is loaded from a JSON file containing camera/surveillance vendor OUI prefixes.
|
|
type OUIDatabase struct {
|
|
data map[string]string // "C0:56:E3" -> "Hikvision"
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewOUIDatabase creates an empty OUI database.
|
|
func NewOUIDatabase() *OUIDatabase {
|
|
return &OUIDatabase{
|
|
data: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// LoadFromFile loads OUI data from a JSON file.
|
|
// Expected format: {"C0:56:E3": "Hikvision", "54:EF:44": "Lumi/Aqara", ...}
|
|
func (db *OUIDatabase) LoadFromFile(path string) error {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open OUI database: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
var data map[string]string
|
|
if err := json.NewDecoder(file).Decode(&data); err != nil {
|
|
return fmt.Errorf("failed to decode OUI database: %w", err)
|
|
}
|
|
|
|
// Normalize all keys to uppercase
|
|
normalized := make(map[string]string, len(data))
|
|
for k, v := range data {
|
|
normalized[strings.ToUpper(k)] = v
|
|
}
|
|
|
|
db.mu.Lock()
|
|
db.data = normalized
|
|
db.mu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// LookupVendor returns the vendor name for a given MAC address.
|
|
// MAC can be in any format: "C0:56:E3:AA:BB:CC", "c0:56:e3:aa:bb:cc", "C0-56-E3-AA-BB-CC".
|
|
// Returns empty string if not found.
|
|
func (db *OUIDatabase) LookupVendor(mac string) string {
|
|
if len(mac) < 8 {
|
|
return ""
|
|
}
|
|
|
|
// Normalize: uppercase and replace dashes with colons
|
|
prefix := strings.ToUpper(mac[:8])
|
|
prefix = strings.ReplaceAll(prefix, "-", ":")
|
|
|
|
db.mu.RLock()
|
|
vendor := db.data[prefix]
|
|
db.mu.RUnlock()
|
|
|
|
return vendor
|
|
}
|
|
|
|
// Size returns the number of entries in the database.
|
|
func (db *OUIDatabase) Size() int {
|
|
db.mu.RLock()
|
|
defer db.mu.RUnlock()
|
|
return len(db.data)
|
|
}
|