From 5d7ca3770f834533aa3d100820fbcee2ed834585 Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Wed, 3 Jun 2026 03:20:52 -0400 Subject: [PATCH] Development --- routlin/maintenance.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/routlin/maintenance.py b/routlin/maintenance.py index 97f9e70..7a974b9 100644 --- a/routlin/maintenance.py +++ b/routlin/maintenance.py @@ -535,24 +535,34 @@ def rotate_radius_log(radius_cfg): # Main # =================================================================== +ARP_MAX_AGE_SECS = 4 * 3600 + + def refresh_arp_cache(): try: - result = subprocess.run(['ip', 'neigh'], capture_output=True, text=True, timeout=5) - entries = {} + result = subprocess.run(['ip', '-stats', 'neigh'], capture_output=True, text=True, timeout=5) + best = {} # mac -> (used_secs, entry_dict) for line in result.stdout.splitlines(): parts = line.split() if 'lladdr' not in parts: continue - state = parts[-1] - if state in ('FAILED', 'PERMANENT', 'NOARP', 'INCOMPLETE'): + if ':' in parts[0]: # skip IPv6 continue iface = parts[2] if len(parts) > 2 else '' if iface.startswith('br-') or iface == 'docker0': continue + state = parts[-1] + if state in ('FAILED', 'PERMANENT', 'NOARP', 'INCOMPLETE'): + continue + used_match = re.search(r'used\s+(\d+)/', line) + used_secs = int(used_match.group(1)) if used_match else 0 + if used_secs > ARP_MAX_AGE_SECS: + continue idx = parts.index('lladdr') mac = parts[idx + 1].lower() - entries[mac] = {'ip': parts[0], 'state': state} - ARP_CACHE_FILE.write_text(json.dumps(entries)) + if mac not in best or used_secs < best[mac][0]: + best[mac] = (used_secs, {'ip': parts[0], 'state': state}) + ARP_CACHE_FILE.write_text(json.dumps({m: e for m, (_, e) in best.items()})) except Exception as exc: print(f"WARNING: Could not refresh ARP cache: {exc}")