Exclude infrastructure MACs from personal device detection via NET_ALERTER_INFRA_MACS
Travel routers and other LAA-bit devices get misclassified as personal phones because the LAA heuristic fires on any MAC with bit 1 set (b6:cc is 0xb6). Add infrastructure_macs set checked before the LAA heuristic in is_personal_device(). NET_ALERTER_INFRA_MACS env var (comma-separated MACs) populates the set at startup.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user