diff --git a/net_alerter/net_alerter.py b/net_alerter/net_alerter.py index 1351d4e..f92eb43 100644 --- a/net_alerter/net_alerter.py +++ b/net_alerter/net_alerter.py @@ -34,6 +34,7 @@ ENV_PATH = Path(os.getenv("NET_ALERTER_ENV", "/opt/net_alerter/.env")) known_devices = {} # {mac: {ip, hostname, vendor, first_seen, last_seen, infrastructure (opt)}} known_lock = threading.Lock() infrastructure_ips = set() # IPs that should never trigger alerts (gateway, broadcast, self) +infrastructure_macs = set() # MACs that should never be treated as personal devices departure_timers = {} # {mac: threading.Timer} for 15-min departure debounce departure_lock = threading.Lock() # Protects departure_timers and last_departed_time last_departed_time = {} # {mac: timestamp} when device was actually alerted as departed @@ -683,6 +684,19 @@ def load_personal_macs_from_env() -> None: logging.info(f"Loaded {len(personal_devices)} personal device MAC(s) and {len(personal_names)} personal name(s)") +def load_infrastructure_macs_from_env() -> None: + """Parse NET_ALERTER_INFRA_MACS env var into infrastructure_macs set.""" + global infrastructure_macs + macs_str = _read_env_value("NET_ALERTER_INFRA_MACS") or os.getenv("NET_ALERTER_INFRA_MACS", "") + for mac in (m.strip() for m in macs_str.split(",") if m.strip()): + normalized = _normalize_mac(mac) + if normalized: + infrastructure_macs.add(normalized) + logging.info(f"Seeded infrastructure MAC {mac} — excluded from personal device detection") + else: + logging.warning(f"Skipping invalid MAC from NET_ALERTER_INFRA_MACS: {mac}") + + def _read_env_value(key: str) -> str: """Read a single environment variable from .env file or system env.""" if ENV_PATH.exists(): @@ -750,6 +764,10 @@ def is_personal_device(mac: str) -> bool: if oui in MOBILE_DEVICE_OUIS: return True + # Infrastructure MACs are never personal, regardless of LAA bit + if mac_normalized in infrastructure_macs: + return False + # Locally-administered bit: modern phones randomize MACs; TVs/infra don't try: return bool(int(mac_normalized[:2], 16) & 0x02) @@ -1825,6 +1843,7 @@ def main() -> None: logging.info(f"Net alerter starting on interface {iface}") seed_infrastructure_ips(iface) + load_infrastructure_macs_from_env() seed_from_arp_cache() # Start monitor threads