diff --git a/net_alerter/net_alerter.py b/net_alerter/net_alerter.py index 2ac09fc..1351d4e 100644 --- a/net_alerter/net_alerter.py +++ b/net_alerter/net_alerter.py @@ -42,11 +42,6 @@ last_departed_time = {} # {mac: timestamp} when device was actually alerted as last_seen = {} # {mac: float} — updated by passive ARP/mDNS capture last_seen_lock = threading.Lock() -# Tailscale peer MACs — excluded from presence monitoring -# OPi's WireGuard session generates bidirectional L2 traffic that would -# otherwise keep peer MACs perpetually REACHABLE in the kernel ARP cache. -tailscale_peer_macs: set = set() - # Interface flap detection — suppresses departure storms _flap_window = [] # [(timestamp, mac)] _flap_lock = threading.Lock() @@ -836,50 +831,6 @@ def seed_from_arp_cache(): logging.error(f"Failed to seed from ARP cache: {e}") -def seed_tailscale_peers() -> None: - """Populate tailscale_peer_macs with MACs of active Tailscale peer endpoints. - - OPi has a WireGuard session to each Tailscale peer. The peer's LAN IP is the - direct endpoint, so bidirectional L2 traffic keeps it REACHABLE in the kernel - ARP cache — generating ARP opcode 1 frames that would refresh last_seen and - falsely prevent departure alerts. Exclude them entirely. - """ - global tailscale_peer_macs - try: - result = subprocess.run( - ['tailscale', 'status', '--json'], - capture_output=True, text=True, timeout=5 - ) - if result.returncode != 0: - logging.warning("tailscale status failed — no Tailscale peer exclusions") - return - status = json.loads(result.stdout) - peer_ips = set() - for peer in status.get('Peer', {}).values(): - # CurAddr is the active direct endpoint (e.g. "10.0.0.0:41641") - cur = peer.get('CurAddr', '') - if cur: - ip = cur.rsplit(':', 1)[0] - if ip: - peer_ips.add(ip) - if not peer_ips: - return - # Resolve peer IPs → MACs via kernel neighbor cache - neigh = Path('/proc/net/arp').read_text() - found = set() - for line in neigh.splitlines(): - fields = line.split() - if len(fields) < 4: - continue - ip, _, flags, mac = fields[0], fields[1], fields[2], fields[3] - if ip in peer_ips and mac != '00:00:00:00:00:00' and flags != '0x0': - found.add(mac.lower()) - tailscale_peer_macs = found - logging.info(f"Tailscale peer MACs excluded from presence monitoring: {found}") - except Exception as e: - logging.warning(f"seed_tailscale_peers failed: {e}") - - def send_alert(msg: str) -> None: """Send alert to Matrix room. @@ -1606,19 +1557,15 @@ def parse_frame(data: bytes) -> None: # ARP replies (opcode 2) may be AP ARP proxy forgeries: the AP answers # on behalf of disconnected clients with the client MAC as Ethernet source. if len(data) >= 22 and struct.unpack('!H', data[20:22])[0] == 1: - # Tailscale peers: OPi has a live WireGuard session to each peer's LAN - # endpoint. Bidirectional keepalives keep the peer MAC REACHABLE in the - # kernel ARP cache indefinitely — exclude to prevent false presence. - if src_mac not in tailscale_peer_macs: - _update_last_seen(src_mac) - # Personal devices first seen via ARP get no DHCP/Netlink event. - # Fire on_arrival so they get ARRIVED alert and occupancy counts. - if is_personal_device(src_mac) and len(data) >= 32: - with known_lock: - already_known = src_mac in known_devices - if not already_known: - spa = socket.inet_ntoa(data[28:32]) - on_arrival(src_mac, spa) + _update_last_seen(src_mac) + # Personal devices first seen via ARP get no DHCP/Netlink event. + # Fire on_arrival so they get ARRIVED alert and occupancy counts. + if is_personal_device(src_mac) and len(data) >= 32: + with known_lock: + already_known = src_mac in known_devices + if not already_known: + spa = socket.inet_ntoa(data[28:32]) + on_arrival(src_mac, spa) # Ingest ARP signal for multi-source device identity _ingest_signal(src_mac, 'arp') return @@ -1878,7 +1825,6 @@ def main() -> None: logging.info(f"Net alerter starting on interface {iface}") seed_infrastructure_ips(iface) - seed_tailscale_peers() seed_from_arp_cache() # Start monitor threads