@@ -26,6 +26,56 @@ if not logger.handlers:
2626 logger .addHandler (handler )
2727logger .setLevel (logging .INFO )
2828
29+ def find_device_from_config (interface ):
30+ # Read device from UCI config because sometimes ubus network dump is not updated yet
31+ u = EUci ()
32+ try :
33+ section = u .get_all ('network' , interface )
34+ except :
35+ return None
36+ device = section .get ('device' , '' )
37+ if device .startswith ('@' ):
38+ try :
39+ parent = u .get_all ('network' , device [1 :])
40+ except :
41+ return None
42+ return parent .get ('device' )
43+ return device
44+
45+ def is_device_up (device ):
46+ # Use JSON output to reliably inspect link state
47+ proc = subprocess .run (["/sbin/ip" , "-j" , "link" , "show" , "dev" , device ], capture_output = True , text = True )
48+ try :
49+ link_json = json .loads (proc .stdout ) if proc .stdout else []
50+ except json .JSONDecodeError :
51+ link_json = []
52+
53+ if isinstance (link_json , list ) and link_json :
54+ li = link_json [0 ]
55+ if li .get ('operstate' ) == 'UP' or 'UP' in li .get ('flags' , []):
56+ return True
57+ return False
58+
59+ def get_device_ips (device ):
60+ ipv4 = []
61+ ipv6 = []
62+ # Use JSON output to reliably inspect addresses
63+ addr_proc = subprocess .run (["/sbin/ip" , "-j" , "addr" , "show" , "dev" , device ], capture_output = True , text = True )
64+ try :
65+ addrs = json .loads (addr_proc .stdout ) if addr_proc .stdout else []
66+ except json .JSONDecodeError :
67+ addrs = []
68+
69+ for ent in addrs :
70+ for info in ent .get ('addr_info' , []):
71+ family = info .get ('family' )
72+ local = info .get ('local' , '' )
73+ if family == 'inet' :
74+ ipv4 .append (local )
75+ elif family == 'inet6' :
76+ ipv6 .append (local )
77+ return ipv4 , ipv6
78+
2979def enable_interfaces (file ):
3080 u = EUci ()
3181 with open (os .path .join (out_dir , file ), 'r' ) as f :
@@ -39,6 +89,55 @@ def enable_interfaces(file):
3989 # Return code of ifup is not reliable, so we do not check it nor log it
4090 logger .info ("Bringing up interface %s" , interface )
4191
92+ def send_gratuitous_arp (file ):
93+ # Get the mapping interface -> device
94+ proc = subprocess .run (["ubus" , "-v" , "call" , "network.interface" , "dump" ], capture_output = True , text = True )
95+ try :
96+ network_dump = json .loads (proc .stdout )
97+ except json .JSONDecodeError :
98+ logger .error ("Can't send gratuitous ARP: failed to decode JSON from network dump" )
99+ return
100+ device_map = {}
101+ for iface in network_dump .get ('interface' , []):
102+ if 'device' in iface and 'interface' in iface :
103+ device_map [iface ['interface' ]] = iface ['device' ]
104+ # Load the file with the interfaces to send gratuitous ARP for
105+ with open (os .path .join (out_dir , file ), 'r' ) as f :
106+ interfaces = json .load (f )
107+ for interface in interfaces :
108+ device = device_map .get (interface , find_device_from_config (interface ))
109+ if not device :
110+ logger .error ("Can't send gratuitous ARP: no device found for interface %s" , interface )
111+ continue
112+ # Check if device is up
113+ max_attempts = 10
114+ ready = False
115+ # First check if device is up
116+ for _ in range (max_attempts ):
117+ if is_device_up (device ):
118+ ready = True
119+ break
120+ time .sleep (0.5 )
121+ if not ready :
122+ logger .error ("Can't send gratuitous ARP: device %s for interface %s is down" , device , interface )
123+ continue
124+ # Check if device has IP address
125+ for _ in range (max_attempts ):
126+ # Ignore IPv6 which has a different ARP tools (ndisc6)
127+ ipv4 , _ = get_device_ips (device )
128+ if len (ipv4 ) > 0 :
129+ for ip in ipv4 :
130+ # Send gratuitous ARP to update switches ARP tables
131+ # Wait for the device to be up and to have the IP address (timeout ~10s)
132+ proca = subprocess .run (["/usr/bin/arping" , "-U" , "-I" , device , "-c" , "1" , ip ], capture_output = True , text = True )
133+ if proca .returncode == 0 :
134+ logger .info ("Sent gratuitous ARP for IP %s on interface %s: success" , ip , interface )
135+ else :
136+ logger .info ("Sent gratuitous ARP for IP %s on interface %s: fail, %s" , ip , interface , proca .stderr .strip ())
137+ return
138+ time .sleep (0.5 )
139+
140+
42141def enable_hotspot_mac ():
43142 u = EUci ()
44143 devices = utils .get_all_by_type (u , 'network' , 'device' )
@@ -60,4 +159,5 @@ if __name__ == "__main__":
60159 enable_interfaces ('wg_interfaces' )
61160 enable_interfaces ('ipsec_interfaces' )
62161 enable_hotspot_mac ()
162+ send_gratuitous_arp ('wan_interfaces' )
63163 subprocess .run (["/sbin/reload_config" ], capture_output = True )
0 commit comments