diff options
author | Christian Breunig <christian@breunig.cc> | 2025-04-19 15:59:55 +0200 |
---|---|---|
committer | Christian Breunig <christian@breunig.cc> | 2025-04-22 16:06:19 +0200 |
commit | bad519f9f1004e9855e5805473e2e3e8d1fb36ec (patch) | |
tree | 25fd165e5232a784da0312d37c7e1877b62ca1cf /python | |
parent | 542e3db626ba1184743c4956a340260d0a529c92 (diff) | |
download | vyos-1x-bad519f9f1004e9855e5805473e2e3e8d1fb36ec.tar.gz vyos-1x-bad519f9f1004e9855e5805473e2e3e8d1fb36ec.zip |
interface: T7375: routes received via SLAAC are not cleared on exit
When using SLAAC for IPv6 addresses we will also receive a default route via a
RA (Router Advertisement). When we disable SLAAC on a interface the Linux
Kernel does not automatically flush all addresses nor the routes received.
The Kernel wait's until the addresses/prefixes/routes expire using their
lifestime setting.
When removing SLAAC from an interface, also remove the auto generated IPv6
address and both the default router received and the connected IP prefix of the
SLAAC advertisement.
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/ifconfig/interface.py | 47 |
1 files changed, 45 insertions, 2 deletions
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 85de0947a..baa45f5bd 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -22,6 +22,7 @@ from copy import deepcopy from glob import glob from ipaddress import IPv4Network +from ipaddress import IPv6Interface from netifaces import ifaddresses # this is not the same as socket.AF_INET/INET6 from netifaces import AF_INET @@ -911,7 +912,8 @@ class Interface(Control): return None rc = self.set_interface('ipv6_autoconf', autoconf) if autoconf == '0': - self.flush_ipv6_slaac_addrs() + flushed = self.flush_ipv6_slaac_addrs() + self.flush_ipv6_slaac_routes(flushed) return rc def add_ipv6_eui64_address(self, prefix): @@ -1313,12 +1315,13 @@ class Interface(Control): # flush all addresses self._cmd(cmd) - def flush_ipv6_slaac_addrs(self): + def flush_ipv6_slaac_addrs(self) -> list: """ Flush all IPv6 addresses installed in response to router advertisement messages from this interface. Will raise an exception on error. + Will return a list of flushed IPv6 addresses. """ netns = get_interface_namespace(self.ifname) netns_cmd = f'ip netns exec {netns}' if netns else '' @@ -1331,6 +1334,7 @@ class Interface(Control): # 'prefixlen': 64, 'scope': 'global', 'dynamic': True, # 'mngtmpaddr': True, 'protocol': 'kernel_ra', # 'valid_life_time': 2591987, 'preferred_life_time': 14387} + flushed = [] for addr_info in tmp['addr_info']: if 'protocol' not in addr_info: continue @@ -1338,8 +1342,47 @@ class Interface(Control): addr_info['scope'] == 'global'): # Flush IPv6 addresses installed by router advertisement ra_addr = f"{addr_info['local']}/{addr_info['prefixlen']}" + flushed.append(ra_addr) cmd = f'{netns_cmd} ip -6 addr del dev {self.ifname} {ra_addr}' self._cmd(cmd) + return flushed + + def flush_ipv6_slaac_routes(self, ra_addrs: list=[]) -> None: + """ + Flush IPv6 default routes installed in response to router advertisement + messages from this interface. + + Will raise an exception on error. + """ + # Do not flush default route if interface uses DHCPv6 in addition to SLAAC + if 'address' in self.config and 'dhcpv6' in self.config['address']: + return None + + # Find IPv6 connected prefixes for flushed SLAAC addresses + connected = [] + for addr in ra_addrs: + connected.append(str(IPv6Interface(addr).network)) + + netns = get_interface_namespace(self.ifname) + netns_cmd = f'ip netns exec {netns}' if netns else '' + + tmp = self._cmd(f'{netns_cmd} ip -j -6 route show dev {self.ifname}') + tmp = json.loads(tmp) + # Parse interface routes. Example data: + # {'dst': 'default', 'gateway': 'fe80::250:56ff:feb3:cdba', + # 'protocol': 'ra', 'metric': 1024, 'flags': [], 'expires': 1398, + # 'metrics': [{'hoplimit': 64}], 'pref': 'medium'} + for route in tmp: + # If it's a default route received from RA, delete it + if (dict_search('dst', route) == 'default' and + dict_search('protocol', route) == 'ra'): + self._cmd(f'{netns_cmd} ip -6 route del default via {route["gateway"]} dev {self.ifname}') + # Remove connected prefixes received from RA + if dict_search('dst', route) in connected: + # If it's a connected prefix, delete it + self._cmd(f'{netns_cmd} ip -6 route del {route["dst"]} dev {self.ifname}') + + return None def add_to_bridge(self, bridge_dict): """ |