From 54a8b9e72616a949249bea2667adcc486a45b06f Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Wed, 29 May 2024 16:41:28 -0600 Subject: [PATCH 1/7] feat: backport DAD handling to stable branch --- ifupdown2/addons/address.py | 93 ++++++++++++++++++++++++++- ifupdown2/ifupdownaddons/LinkUtils.py | 9 ++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index f576dc02..c877e161 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -5,6 +5,9 @@ # import socket +import json +import time +import subprocess from ipaddr import IPNetwork, IPv4Network, IPv6Network, _BaseV6 @@ -111,6 +114,20 @@ class address(moduleBase): 'validvals': ['yes', 'no'], 'default' : 'no', 'example' : ['mpls-enable yes']}, + 'dad-attempts': { + 'help': 'Number of attempts to settle DAD (0 to disable DAD). ' + 'To use this feature, the ipv6_dad_handling_enabled ' + 'module global must be set to true', + 'example': ['dad-attempts 0'], + 'default': '60', + }, + 'dad-interval': { + 'help': 'DAD state polling interval in seconds. ' + 'To use this feature, the ipv6_dad_handling_enabled ' + 'module global must be set to true', + 'example': ['dad-interval 0.5'], + 'default': '0.1', + }, 'ipv6-addrgen': { 'help': 'enable disable ipv6 link addrgenmode', 'validvals': ['on', 'off'], @@ -137,6 +154,13 @@ def __init__(self, *args, **kargs): 'enable_l3_iface_forwarding_checks' ) ) + self.ipv6_dad_handling_enabled = utils.get_boolean_from_string( + policymanager.policymanager_api.get_module_globals( + self.__class__.__name__, + 'ipv6_dad_handling_enabled' + ), + default=False + ) if not self.default_mtu: self.default_mtu = '1500' @@ -338,12 +362,16 @@ def _inet_address_convert_to_cidr(self, ifaceobjlist): attrs = {} for a in ['broadcast', 'pointopoint', 'scope', - 'preferred-lifetime']: + 'preferred-lifetime', 'nodad']: aval = ifaceobj.get_attr_value_n(a, addr_index) if aval: attrs[a] = aval if attrs: + try: + attrs['nodad'] = bool(attrs['nodad']) + except KeyError: + pass newaddr_attrs[newaddr]= attrs return (True, newaddrs, newaddr_attrs) @@ -359,7 +387,9 @@ def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): newaddr_attrs.get(newaddrs[addr_index], {}).get('scope'), newaddr_attrs.get(newaddrs[addr_index], - {}).get('preferred-lifetime')) + {}).get('preferred-lifetime'), + newaddr_attrs.get(newaddrs[addr_index], + {}).get('nodad')) else: self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index]) except Exception, e: @@ -866,6 +896,18 @@ def _up(self, ifaceobj, ifaceobj_getfunc=None): self.up_hwaddress(ifaceobj) + # settle dad + if not self.ipv6_dad_handling_enabled: + return + if not self.cache.link_exists(ifaceobj.name): + return + ifname = ifaceobj.name + ifaceobjs = self._get_ifaceobjs(ifaceobj, ifaceobj_getfunc) + addr_supported, user_addrs_list = self.__get_ip_addr_with_attributes(ifaceobjs, ifname) + if not addr_supported: + return + self._settle_dad(ifaceobj, [ip for ip, _ in user_addrs_list if ip.version == 6]) + gateways = ifaceobj.get_attr_value('gateway') if not gateways: gateways = [] @@ -1212,3 +1254,50 @@ def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None): else: op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) + + def _settle_dad(self, ifaceobj, ips): + """ Settle dad for any given ips """ + def ip_addr_list(what): + raw = json.loads(utils.exec_commandl([ + 'ip', '-j', '-o', '-6', 'address', 'list', 'dev', + ifaceobj.name, what + ])) + addr_infos = (x for t in raw for x in t.get('addr_info', [])) + ip_list = [f'{x["local"]}/{x["prefixlen"]}' for x in addr_infos if x] + return ip_list + + def get_param(key, default=None): + return (ifaceobj.get_attr_value_first(key) + or policymanager.policymanager_api.get_iface_default( + self.__class__.__name__, ifaceobj.name, key) + or default) + + interval = float(get_param('dad-interval', '0.1')) # 0.1: ifupdown default value + attempts = int(get_param('dad-attempts', '60')) # 60: ifupdown default value + if not attempts or not ips: + return + try: + for _attempt in range(0, attempts): + tentative = ip_addr_list('tentative') + if all(str(ip) not in tentative for ip in ips): + break + time.sleep(interval) + else: + timeout = ','.join(ip for ip in ips if str(ip) not in tentative) + self.logger.warning('address: %s: dad timeout "%s"', ifaceobj.name, timeout) + return + failure = ip_addr_list('dadfailed') + if failure: + self.logger.warning('address: %s: dad failure "%s"', ifaceobj.name, ','.join(failure)) + except subprocess.CalledProcessError as exc: + self.logger.error('address: %s: could not settle dad %s', ifaceobj.name, str(exc)) + + def _get_ifaceobjs(self, ifaceobj, ifaceobj_getfunc): + squash_addr_config = ifupdownconfig.config.get("addr_config_squash", "0") == "1" + if not squash_addr_config: + return [ifaceobj] # no squash, returns current ifaceobj + if not ifaceobj.flags & ifaceobj.YOUNGEST_SIBLING: + return [] # when squash is present, work only on the youngest sibling + if ifaceobj.flags & iface.HAS_SIBLINGS: + return ifaceobj_getfunc(ifaceobj.name) # get sibling interfaces + return [ifaceobj] diff --git a/ifupdown2/ifupdownaddons/LinkUtils.py b/ifupdown2/ifupdownaddons/LinkUtils.py index 2b29bfeb..243e5b4b 100644 --- a/ifupdown2/ifupdownaddons/LinkUtils.py +++ b/ifupdown2/ifupdownaddons/LinkUtils.py @@ -852,7 +852,8 @@ def link_show(ifacename=None): '-o', '-d', 'link', 'show']) def addr_add(self, ifacename, address, broadcast=None, - peer=None, scope=None, preferred_lifetime=None, metric=None): + peer=None, scope=None, preferred_lifetime=None, metric=None, + nodad=False): if not address: return cmd = 'addr add %s' % address @@ -864,6 +865,8 @@ def addr_add(self, ifacename, address, broadcast=None, cmd += ' scope %s' % scope if preferred_lifetime: cmd += ' preferred_lft %s' % preferred_lifetime + if nodad: + cmd += ' nodad' cmd += ' dev %s' % ifacename if metric: @@ -1022,7 +1025,7 @@ def compare_user_config_vs_running_state(running_addrs, user_addrs): return running_ipobj == (ip4 + ip6) - def addr_add_multiple(self, ifaceobj, ifacename, addrs, purge_existing=False, metric=None): + def addr_add_multiple(self, ifaceobj, ifacename, addrs, purge_existing=False, metric=None, nodad=False): # purges address if purge_existing: # if perfmode is not set and also if iface has no sibling @@ -1049,7 +1052,7 @@ def addr_add_multiple(self, ifaceobj, ifacename, addrs, purge_existing=False, me self.logger.warning('%s: %s' % (ifacename, str(e))) for a in addrs: try: - self.addr_add(ifacename, a, metric=metric) + self.addr_add(ifacename, a, metric=metric, nodad=nodad) except Exception, e: self.logger.error(str(e)) From d91b8b5deb02f1abd538ad6b90241e8aac5d9872 Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Thu, 30 May 2024 13:21:06 -0600 Subject: [PATCH 2/7] this works but has bugs --- ifupdown2/addons/address.py | 11 +++++++---- ifupdown2/ifupdownaddons/LinkUtils.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index c877e161..7da392b7 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -369,7 +369,7 @@ def _inet_address_convert_to_cidr(self, ifaceobjlist): if attrs: try: - attrs['nodad'] = bool(attrs['nodad']) + attrs['nodad'] = bool(int(attrs['nodad'])) except KeyError: pass newaddr_attrs[newaddr]= attrs @@ -379,6 +379,10 @@ def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): for addr_index in range(0, len(newaddrs)): try: if newaddr_attrs: + if ifaceobj.addr_family[addr_index] == "inet6": + nodad = newaddr_attrs.get(newaddrs[addr_index], {}).get('nodad') + else: + nodad = False self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], newaddr_attrs.get(newaddrs[addr_index], {}).get('broadcast'), @@ -388,8 +392,7 @@ def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): {}).get('scope'), newaddr_attrs.get(newaddrs[addr_index], {}).get('preferred-lifetime'), - newaddr_attrs.get(newaddrs[addr_index], - {}).get('nodad')) + nodad=nodad) else: self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index]) except Exception, e: @@ -1263,7 +1266,7 @@ def ip_addr_list(what): ifaceobj.name, what ])) addr_infos = (x for t in raw for x in t.get('addr_info', [])) - ip_list = [f'{x["local"]}/{x["prefixlen"]}' for x in addr_infos if x] + ip_list = ['%s/%s' % (x["local"], {x["prefixlen"]}) for x in addr_infos if x] return ip_list def get_param(key, default=None): diff --git a/ifupdown2/ifupdownaddons/LinkUtils.py b/ifupdown2/ifupdownaddons/LinkUtils.py index 243e5b4b..e92b149c 100644 --- a/ifupdown2/ifupdownaddons/LinkUtils.py +++ b/ifupdown2/ifupdownaddons/LinkUtils.py @@ -865,12 +865,12 @@ def addr_add(self, ifacename, address, broadcast=None, cmd += ' scope %s' % scope if preferred_lifetime: cmd += ' preferred_lft %s' % preferred_lifetime - if nodad: - cmd += ' nodad' cmd += ' dev %s' % ifacename if metric: cmd += ' metric %s' % metric + if nodad: + cmd += ' nodad' if LinkUtils.ipbatch and not LinkUtils.ipbatch_pause: self.add_to_batch(cmd) From b75ce6493a5140d766351ad6db8e7b0b9aeb17ad Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Thu, 30 May 2024 16:37:01 -0600 Subject: [PATCH 3/7] better --- ifupdown2/addons/address.py | 36 +++++++++++---------------- ifupdown2/ifupdown/policymanager.py | 1 - ifupdown2/ifupdownaddons/LinkUtils.py | 3 ++- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 7da392b7..37a64bd9 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -362,16 +362,12 @@ def _inet_address_convert_to_cidr(self, ifaceobjlist): attrs = {} for a in ['broadcast', 'pointopoint', 'scope', - 'preferred-lifetime', 'nodad']: + 'preferred-lifetime', 'dad-attempts', 'dad-interval']: aval = ifaceobj.get_attr_value_n(a, addr_index) if aval: attrs[a] = aval if attrs: - try: - attrs['nodad'] = bool(int(attrs['nodad'])) - except KeyError: - pass newaddr_attrs[newaddr]= attrs return (True, newaddrs, newaddr_attrs) @@ -379,10 +375,11 @@ def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): for addr_index in range(0, len(newaddrs)): try: if newaddr_attrs: - if ifaceobj.addr_family[addr_index] == "inet6": - nodad = newaddr_attrs.get(newaddrs[addr_index], {}).get('nodad') - else: - nodad = False + nodad = False + if self.ipv6_dad_handling_enabled and ifaceobj.addr_family[addr_index] == "inet6": + dad_attempts = newaddr_attrs.get(newaddrs[addr_index], {}).get('dad-attempts') + if dad_attempts == "0": + nodad = True self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], newaddr_attrs.get(newaddrs[addr_index], {}).get('broadcast'), @@ -801,7 +798,7 @@ def _sysctl_config(self, ifaceobj): if not setting_default_value: ifaceobj.status = ifaceStatus.ERROR self.logger.error('%s: %s' %(ifaceobj.name, str(e))) - + def process_mtu(self, ifaceobj, ifaceobj_getfunc): mtu = ifaceobj.get_attr_value_first('mtu') @@ -900,16 +897,10 @@ def _up(self, ifaceobj, ifaceobj_getfunc=None): self.up_hwaddress(ifaceobj) # settle dad - if not self.ipv6_dad_handling_enabled: - return - if not self.cache.link_exists(ifaceobj.name): - return - ifname = ifaceobj.name - ifaceobjs = self._get_ifaceobjs(ifaceobj, ifaceobj_getfunc) - addr_supported, user_addrs_list = self.__get_ip_addr_with_attributes(ifaceobjs, ifname) - if not addr_supported: - return - self._settle_dad(ifaceobj, [ip for ip, _ in user_addrs_list if ip.version == 6]) + if self.ipv6_dad_handling_enabled and self.ipcmd.link_exists(ifaceobj.name): + addrlist = ifaceobj.get_attr_value('address') + if any((":"in ip for ip in addrlist)): + self._settle_dad(ifaceobj, [ip for ip in addrlist if ":" in ip]) gateways = ifaceobj.get_attr_value('gateway') if not gateways: @@ -1266,7 +1257,7 @@ def ip_addr_list(what): ifaceobj.name, what ])) addr_infos = (x for t in raw for x in t.get('addr_info', [])) - ip_list = ['%s/%s' % (x["local"], {x["prefixlen"]}) for x in addr_infos if x] + ip_list = ['%s/%s' % (x["local"], x["prefixlen"]) for x in addr_infos if x] return ip_list def get_param(key, default=None): @@ -1280,6 +1271,7 @@ def get_param(key, default=None): if not attempts or not ips: return try: + for _attempt in range(0, attempts): tentative = ip_addr_list('tentative') if all(str(ip) not in tentative for ip in ips): @@ -1294,7 +1286,7 @@ def get_param(key, default=None): self.logger.warning('address: %s: dad failure "%s"', ifaceobj.name, ','.join(failure)) except subprocess.CalledProcessError as exc: self.logger.error('address: %s: could not settle dad %s', ifaceobj.name, str(exc)) - + def _get_ifaceobjs(self, ifaceobj, ifaceobj_getfunc): squash_addr_config = ifupdownconfig.config.get("addr_config_squash", "0") == "1" if not squash_addr_config: diff --git a/ifupdown2/ifupdown/policymanager.py b/ifupdown2/ifupdown/policymanager.py index 4dadc0d8..d7507457 100644 --- a/ifupdown2/ifupdown/policymanager.py +++ b/ifupdown2/ifupdown/policymanager.py @@ -177,7 +177,6 @@ def get_module_globals(self,module_name=None,attr=None): We first check the user_policy_array and return that value. But if the user did not specify an override, we use the system_policy_array. ''' - if (not attr or not module_name): return None # users can specify defaults to override the systemwide settings diff --git a/ifupdown2/ifupdownaddons/LinkUtils.py b/ifupdown2/ifupdownaddons/LinkUtils.py index e92b149c..5195eab6 100644 --- a/ifupdown2/ifupdownaddons/LinkUtils.py +++ b/ifupdown2/ifupdownaddons/LinkUtils.py @@ -853,7 +853,7 @@ def link_show(ifacename=None): def addr_add(self, ifacename, address, broadcast=None, peer=None, scope=None, preferred_lifetime=None, metric=None, - nodad=False): + nodad=None): if not address: return cmd = 'addr add %s' % address @@ -869,6 +869,7 @@ def addr_add(self, ifacename, address, broadcast=None, if metric: cmd += ' metric %s' % metric + if nodad: cmd += ' nodad' From 503f72802d0d6a5e75c5df5da3a068c751cd7e0a Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Thu, 30 May 2024 16:55:06 -0600 Subject: [PATCH 4/7] whitespace --- ifupdown2/addons/address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 37a64bd9..638a3c7f 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -798,7 +798,7 @@ def _sysctl_config(self, ifaceobj): if not setting_default_value: ifaceobj.status = ifaceStatus.ERROR self.logger.error('%s: %s' %(ifaceobj.name, str(e))) - + def process_mtu(self, ifaceobj, ifaceobj_getfunc): mtu = ifaceobj.get_attr_value_first('mtu') From cce68c83fae6c606aa70ed5d1f30965f654490b7 Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Mon, 24 Jun 2024 11:07:34 -0600 Subject: [PATCH 5/7] Update ifupdown2/addons/address.py Co-authored-by: Adrien Banlin --- ifupdown2/addons/address.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 638a3c7f..610528d0 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -1257,7 +1257,7 @@ def ip_addr_list(what): ifaceobj.name, what ])) addr_infos = (x for t in raw for x in t.get('addr_info', [])) - ip_list = ['%s/%s' % (x["local"], x["prefixlen"]) for x in addr_infos if x] + ip_list = ['{local}/{prefixlen}'.format(**x) for x in addr_infos if x] return ip_list def get_param(key, default=None): From e16e0ae458d0d5168a447558180ab854c313ac3a Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Mon, 24 Jun 2024 11:08:21 -0600 Subject: [PATCH 6/7] Update ifupdown2/addons/address.py Co-authored-by: Adrien Banlin --- ifupdown2/addons/address.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 610528d0..7f0a7113 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -898,9 +898,10 @@ def _up(self, ifaceobj, ifaceobj_getfunc=None): # settle dad if self.ipv6_dad_handling_enabled and self.ipcmd.link_exists(ifaceobj.name): - addrlist = ifaceobj.get_attr_value('address') - if any((":"in ip for ip in addrlist)): - self._settle_dad(ifaceobj, [ip for ip in addrlist if ":" in ip]) + self._settle_dad( + ifaceobj, + [a for a in ifaceobj.get_attr_value('address') if ':' in a] + ) gateways = ifaceobj.get_attr_value('gateway') if not gateways: From 2296574e216fb57f998f0106d643ddd7dcc23b4d Mon Sep 17 00:00:00 2001 From: Ryan Addessi Date: Mon, 24 Jun 2024 11:10:24 -0600 Subject: [PATCH 7/7] Update ifupdown2/addons/address.py Co-authored-by: Adrien Banlin --- ifupdown2/addons/address.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py index 7f0a7113..5d6393ca 100644 --- a/ifupdown2/addons/address.py +++ b/ifupdown2/addons/address.py @@ -378,8 +378,7 @@ def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): nodad = False if self.ipv6_dad_handling_enabled and ifaceobj.addr_family[addr_index] == "inet6": dad_attempts = newaddr_attrs.get(newaddrs[addr_index], {}).get('dad-attempts') - if dad_attempts == "0": - nodad = True + nodad = dad_attempts == "0" self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], newaddr_attrs.get(newaddrs[addr_index], {}).get('broadcast'),