Remove Tailscale exclusion machinery — root cause was a bad .env label
b6:cc was mislabeled as 'Dane iPhone' causing it to be treated as a personal device. Removing the label fixes the problem without needing any Tailscale-specific code. Keep mDNS and ARP arrival fixes.
This commit is contained in:
@@ -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 = {} # {mac: float} — updated by passive ARP/mDNS capture
|
||||||
last_seen_lock = threading.Lock()
|
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
|
# Interface flap detection — suppresses departure storms
|
||||||
_flap_window = [] # [(timestamp, mac)]
|
_flap_window = [] # [(timestamp, mac)]
|
||||||
_flap_lock = threading.Lock()
|
_flap_lock = threading.Lock()
|
||||||
@@ -836,50 +831,6 @@ def seed_from_arp_cache():
|
|||||||
logging.error(f"Failed to seed from ARP cache: {e}")
|
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:
|
def send_alert(msg: str) -> None:
|
||||||
"""Send alert to Matrix room.
|
"""Send alert to Matrix room.
|
||||||
|
|
||||||
@@ -1606,10 +1557,6 @@ def parse_frame(data: bytes) -> None:
|
|||||||
# ARP replies (opcode 2) may be AP ARP proxy forgeries: the AP answers
|
# 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.
|
# 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:
|
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)
|
_update_last_seen(src_mac)
|
||||||
# Personal devices first seen via ARP get no DHCP/Netlink event.
|
# Personal devices first seen via ARP get no DHCP/Netlink event.
|
||||||
# Fire on_arrival so they get ARRIVED alert and occupancy counts.
|
# Fire on_arrival so they get ARRIVED alert and occupancy counts.
|
||||||
@@ -1878,7 +1825,6 @@ def main() -> None:
|
|||||||
logging.info(f"Net alerter starting on interface {iface}")
|
logging.info(f"Net alerter starting on interface {iface}")
|
||||||
|
|
||||||
seed_infrastructure_ips(iface)
|
seed_infrastructure_ips(iface)
|
||||||
seed_tailscale_peers()
|
|
||||||
seed_from_arp_cache()
|
seed_from_arp_cache()
|
||||||
|
|
||||||
# Start monitor threads
|
# Start monitor threads
|
||||||
|
|||||||
Reference in New Issue
Block a user