Skip to content

Commit ca5447c

Browse files
committed
fix(ns-ha): improve gratuitous ARP
Changes: - wait for the device to be up, wait maximum 5 seconds per interface - wait for IP to be configured into the device, wait maximum 5 seconds per interface
1 parent 6c95139 commit ca5447c

File tree

1 file changed

+66
-18
lines changed

1 file changed

+66
-18
lines changed

packages/ns-ha/files/ns-ha-enable

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,40 @@ if not logger.handlers:
2626
logger.addHandler(handler)
2727
logger.setLevel(logging.INFO)
2828

29+
def is_device_up(device):
30+
# Use JSON output to reliably inspect link state
31+
proc = subprocess.run(["/sbin/ip", "-j", "link", "show", "dev", device], capture_output=True, text=True)
32+
try:
33+
link_json = json.loads(proc.stdout) if proc.stdout else []
34+
except json.JSONDecodeError:
35+
link_json = []
36+
37+
if isinstance(link_json, list) and link_json:
38+
li = link_json[0]
39+
if li.get('operstate') == 'UP' or 'UP' in li.get('flags', []):
40+
return True
41+
return False
42+
43+
def get_device_ips(device):
44+
ipv4 = []
45+
ipv6 = []
46+
# Use JSON output to reliably inspect addresses
47+
addr_proc = subprocess.run(["/sbin/ip", "-j", "addr", "show", "dev", device], capture_output=True, text=True)
48+
try:
49+
addrs = json.loads(addr_proc.stdout) if addr_proc.stdout else []
50+
except json.JSONDecodeError:
51+
addrs = []
52+
53+
for ent in addrs:
54+
for info in ent.get('addr_info', []):
55+
family = info.get('family')
56+
local = info.get('local', '')
57+
if family == 'inet':
58+
ipv4.append(local)
59+
elif family == 'inet6':
60+
ipv6.append(local)
61+
return ipv4, ipv6
62+
2963
def enable_interfaces(file):
3064
u = EUci()
3165
with open(os.path.join(out_dir, file), 'r') as f:
@@ -55,24 +89,38 @@ def send_gratuitous_arp(file):
5589
with open(os.path.join(out_dir, file), 'r') as f:
5690
interfaces = json.load(f)
5791
for interface in interfaces:
58-
if 'ipaddr' in interfaces[interface]:
59-
device = device_map.get(interface)
60-
if not device:
61-
logger.error("Can't send gratuitous ARP: no device found for interface %s", interface)
62-
continue
63-
# It should not happen, but ipaddr can contain multiple IPs
64-
ipaddr = interfaces[interface]['ipaddr']
65-
if isinstance(ipaddr, str):
66-
ipaddr = [ipaddr]
67-
for ip in ipaddr:
68-
# Remove /mask if present
69-
ip = ip.split('/')[0]
70-
# Send gratuitous ARP to update switches ARP tables
71-
aproc = subprocess.run(["/usr/bin/arping", "-c", "1", "-U", "-I", device, ip], capture_output=True, text=True)
72-
if aproc.returncode == 0:
73-
logger.info("Sending gratuitous ARP on interface %s (%s) for IP %s: success", interface, device, ip)
74-
else:
75-
logger.error("Sending gratuitous ARP on interface %s (%s) for IP %s: fail, %s", interface, device, ip, aproc.stderr.strip())
92+
device = device_map.get(interface)
93+
if not device:
94+
logger.error("Can't send gratuitous ARP: no device found for interface %s", interface)
95+
continue
96+
# Check if device is up
97+
max_attempts = 10
98+
ready = False
99+
# First check if device is up
100+
for _ in range(max_attempts):
101+
if is_device_up(device):
102+
ready = True
103+
break
104+
time.sleep(0.5)
105+
if not ready:
106+
logger.error("Can't send gratuitous ARP: device %s for interface %s is down", device, interface)
107+
continue
108+
# Check if device has IP address
109+
for _ in range(max_attempts):
110+
# Ignore IPv6 which has a different ARP tools (ndisc6)
111+
ipv4, _ = get_device_ips(device)
112+
if len(ipv4) > 0:
113+
for ip in ipv4:
114+
# Send gratuitous ARP to update switches ARP tables
115+
# Wait for the device to be up and to have the IP address (timeout ~10s)
116+
proc = subprocess.run(["/usr/bin/arping", "-U", "-I", device, "-c", "3", ip], capture_output=True, text=True)
117+
if proc.returncode == 0:
118+
logger.info("Sent gratuitous ARP for IP %s on interface %s: success", ip, interface)
119+
else:
120+
logger.info("Sent gratuitous ARP for IP %s on interface %s: fail, %s", ip, interface, proc.stderr.strip())
121+
break
122+
time.sleep(0.5)
123+
76124

77125
def enable_hotspot_mac():
78126
u = EUci()

0 commit comments

Comments
 (0)