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}")