diff --git a/ifupdown2/addons/bond.py b/ifupdown2/addons/bond.py index f2d38de5..1dae0190 100644 --- a/ifupdown2/addons/bond.py +++ b/ifupdown2/addons/bond.py @@ -277,6 +277,7 @@ def __init__(self, *args, **kargs): except Exception as e: self.logger.info("bond: error while loading bonding module: %s" % str(e)) + # get_ifindex() always returns the primary ifname, so no need for translating self._bond_attr_ifquery_check_translate_func[Link.IFLA_BOND_PRIMARY] = self.cache.get_ifindex self._bond_attr_set_list = self._bond_attr_set_list + (('bond-primary', Link.IFLA_BOND_PRIMARY, self.cache.get_ifindex),) @@ -292,7 +293,7 @@ def __init__(self, *args, **kargs): def get_bond_slaves(self, ifaceobj): # bond-ports aliases should be translated to bond-slaves - return ifaceobj.get_attr_value_first('bond-slaves') + return self.cache.link_translate_altname(ifaceobj.get_attr_value_first('bond-slaves')) def _is_bond(self, ifaceobj): # at first link_kind is not set but once ifupdownmain diff --git a/ifupdown2/addons/bridge.py b/ifupdown2/addons/bridge.py index 4d6c091c..7c09ef5e 100644 --- a/ifupdown2/addons/bridge.py +++ b/ifupdown2/addons/bridge.py @@ -1133,20 +1133,6 @@ def get_dependent_ifacenames_running(self, ifaceobj): return None return self.cache.get_slaves(ifaceobj.name) - def _get_bridge_port_list(self, ifaceobj): - - # port list is also available in the previously - # parsed dependent list. Use that if available, instead - # of parsing port expr again - port_list = ifaceobj.lowerifaces - if port_list: - return port_list - ports = self._get_ifaceobj_bridge_ports(ifaceobj) - if ports: - return self.parse_port_list(ifaceobj.name, ports) - else: - return None - def _get_bridge_port_list_user_ordered(self, ifaceobj): # When enslaving bridge-ports we need to return the exact user # configured bridge ports list (bridge will inherit the mac of the diff --git a/ifupdown2/addons/openvswitch.py b/ifupdown2/addons/openvswitch.py index 0a124ba2..0b64e06f 100644 --- a/ifupdown2/addons/openvswitch.py +++ b/ifupdown2/addons/openvswitch.py @@ -103,6 +103,7 @@ def _get_ovs_ports (self, ifaceobj): ovs_ports.extend(port.split()) if ovs_ports: + ovs_ports = self.cache.link_translate_altnames(ovs_ports) return self.parse_port_list(ifaceobj.name, ' '.join(ovs_ports)) else: return None diff --git a/ifupdown2/addons/openvswitch_port.py b/ifupdown2/addons/openvswitch_port.py index ae8549ac..02bd737e 100644 --- a/ifupdown2/addons/openvswitch_port.py +++ b/ifupdown2/addons/openvswitch_port.py @@ -94,15 +94,28 @@ def __init__ (self, *args, **kargs): def _is_ovs_port (self, ifaceobj): ovstype = ifaceobj.get_attr_value_first ('ovs-type') - ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + ovsbridge = self._get_ovs_bridge(ifaceobj) if ovstype and ovsbridge: return True return False + def _get_ovs_bridge(self, ifaceobj): + """ + Returns the ifname of the `ovs-bridge` property. Translates altnames + to the primary ifname if needed. + :param ifaceobj: interface config object + :return: `ovs-bridge` ifname + """ + ifname = ifaceobj.get_attr_value_first('ovs-bridge') + if ifname: + return self.cache.link_translate_altname(ifname) + return None + def _get_bond_ifaces (self, ifaceobj): ovs_bonds = ifaceobj.get_attr_value_first ('ovs-bonds') if ovs_bonds: - return sorted (ovs_bonds.split ()) + ovs_bonds = self.cache.link_translate_altnames(ovs_bonds.split()) + return sorted(ovs_bonds) return None def _ovs_vsctl(self, ifaceobj, cmdlist): @@ -129,10 +142,13 @@ def _ovs_vsctl(self, ifaceobj, cmdlist): def _addport (self, ifaceobj): iface = ifaceobj.name - ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + ovsbridge = self._get_ovs_bridge(ifaceobj) ovsoptions = ifaceobj.get_attr_value_first ('ovs-options') ovstype = ifaceobj.get_attr_value_first ('ovs-type') - ovsbonds = ifaceobj.get_attr_value_first ('ovs-bonds') + + ovsbonds_list = self._get_bond_ifaces(ifaceobj) + ovsbonds = ' '.join(ovsbonds_list) if ovsbonds_list else None + ovsextra = ifaceobj.get_attr_value('ovs-extra') cmd_list = [] @@ -183,11 +199,10 @@ def _addport (self, ifaceobj): #mtu ovsmtu = ifaceobj.get_attr_value_first ('ovs-mtu') - ovsbonds_list = self._get_bond_ifaces(ifaceobj) if ovsmtu is not None: #we can't set mtu on bond fake interface, we apply it on slaves interfaces if ovstype == 'OVSBond' and ovsbonds_list is not None: - for slave in ovsbonds_list: + for slave in ovsbonds_list: cmd = "set Interface %s mtu_request=%s"%(slave,ovsmtu) cmd_list.append(cmd) @@ -207,7 +222,7 @@ def _addport (self, ifaceobj): def _delport (self, ifaceobj): iface = ifaceobj.name - ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + ovsbridge = self._get_ovs_bridge(ifaceobj) cmd = "--if-exists del-port %s %s"%(ovsbridge, iface) self._ovs_vsctl(ifaceobj, [cmd]) @@ -219,7 +234,7 @@ def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None, old_ifaceobjs= ifaceobj.link_privflags |= ifaceLinkPrivFlags.OPENVSWITCH - ovsbridge = ifaceobj.get_attr_value_first ('ovs-bridge') + ovsbridge = self._get_ovs_bridge(ifaceobj) return [ovsbridge] def _up (self, ifaceobj): diff --git a/ifupdown2/addons/vlan.py b/ifupdown2/addons/vlan.py index 4380dd8a..a53b2f9d 100644 --- a/ifupdown2/addons/vlan.py +++ b/ifupdown2/addons/vlan.py @@ -64,8 +64,17 @@ def __init__(self, *args, **kargs): Addon.__init__(self) moduleBase.__init__(self, *args, **kargs) + def _get_vlan_raw_device_first(self, ifaceobj): + """ + Returns the ifname of the `vlan-raw-device` property. Translates + altnames to the primary ifname if needed. + :param ifaceobj: interface config object + :return: `vlan-raw-device` ifname + """ + return self.cache.link_translate_altname(ifaceobj.get_attr_value_first('vlan-raw-device')) + def _is_vlan_device(self, ifaceobj): - vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + vlan_raw_device = self._get_vlan_raw_device_first(ifaceobj) if vlan_raw_device: return True elif '.' in ifaceobj.name: @@ -176,7 +185,7 @@ def _up(self, ifaceobj): vlan_exists = self.cache.link_exists(ifaceobj.name) if vlan_exists: - user_vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + user_vlan_raw_device = self._get_vlan_raw_device_first(ifaceobj) cached_vlan_raw_device = self.cache.get_lower_device_ifname(ifname) if cached_vlan_raw_device and user_vlan_raw_device and cached_vlan_raw_device != user_vlan_raw_device: @@ -239,7 +248,7 @@ def _query_check(self, ifaceobj, ifaceobjcurr): ifaceobjcurr.update_config_with_status( 'vlan-raw-device', cached_vlan_raw_device, - cached_vlan_raw_device != ifaceobj.get_attr_value_first('vlan-raw-device') + cached_vlan_raw_device != self._get_vlan_raw_device_first(ifaceobj) ) # diff --git a/ifupdown2/ifupdown/ifupdownmain.py b/ifupdown2/ifupdown/ifupdownmain.py index 51f54609..ff5631ed 100644 --- a/ifupdown2/ifupdown/ifupdownmain.py +++ b/ifupdown2/ifupdown/ifupdownmain.py @@ -228,7 +228,7 @@ def log_error(self, str): pass def link_exists(self, ifacename): - return os.path.exists('/sys/class/net/%s' %ifacename) + return self.netlink.link_exists(ifacename) def __init__(self, config={}, args=None, daemon=False, force=False, dryrun=False, nowait=False, @@ -498,14 +498,18 @@ def create_n_save_ifaceobj(self, ifacename, priv_flags=None, increfcnt=False): """ creates a iface object and adds it to the iface dictionary """ ifaceobj = iface() - ifaceobj.name = ifacename + ifaceobj.name = self.netlink.link_translate_altname(ifacename) ifaceobj.priv_flags = priv_flags ifaceobj.auto = True if not self._link_master_slave: ifaceobj.link_type = ifaceLinkType.LINK_NA if increfcnt: ifaceobj.inc_refcnt() + self.ifaceobjdict[ifacename] = [ifaceobj] + for altname in self.netlink.link_get_altnames(ifacename): + self.ifaceobjdict[ifacename] = [ifaceobj] + return ifaceobj def create_n_save_ifaceobjcurr(self, ifaceobj): @@ -513,13 +517,17 @@ def create_n_save_ifaceobjcurr(self, ifaceobj): dict containing current iface objects """ ifaceobjcurr = iface() - ifaceobjcurr.name = ifaceobj.name + ifaceobjcurr.name = self.netlink.link_translate_altname(ifaceobj.name) ifaceobjcurr.type = ifaceobj.type ifaceobjcurr.lowerifaces = ifaceobj.lowerifaces ifaceobjcurr.priv_flags = copy.deepcopy(ifaceobj.priv_flags) ifaceobjcurr.auto = ifaceobj.auto + self.ifaceobjcurrdict.setdefault(ifaceobj.name, []).append(ifaceobjcurr) + for altname in self.netlink.link_get_altnames(ifaceobj.name): + self.ifaceobjcurrdict.setdefault(altname, []).append(ifaceobjcurr) + return ifaceobjcurr def get_ifaceobjcurr(self, ifacename, idx=0): @@ -722,7 +730,7 @@ def query_lowerifaces(self, ifaceobj, ops, ifacenames, old_ifaceobjs): self.logger.warning("%s: %s: error getting dependent interfaces (%s)" % (ifaceobj.name, module, str(e))) dlist = None if dlist: ret_dlist.extend(dlist) - return list(set(ret_dlist)) + return self.netlink.link_translate_altnames(list(set(ret_dlist))) def query_upperifaces(self, ifaceobj, ops, ifacenames, type=None): """ Gets iface upperifaces by calling into respective modules """ @@ -746,7 +754,7 @@ def query_upperifaces(self, ifaceobj, ops, ifacenames, type=None): ulist = None pass if ulist: ret_ulist.extend(ulist) - return list(set(ret_ulist)) + return self.netlink.link_translate_altnames(list(set(ret_ulist))) def _remove_circular_veth_dependencies(self, ifaceobj, dlist): # if ifaceobj isn't a veth link, ignore it. @@ -1879,7 +1887,10 @@ def check_running_configuration(self, filtered_ifacenames, all=False): auto = False break - if not ifupdownflags.flags.DRYRUN and auto and not os.path.exists("/sys/class/net/%s" % ifname) and not self._is_ifaceobj_bridge_vlan(ifaceobj_list): + if (not ifupdownflags.flags.DRYRUN + and auto + and not self.link_exists(ifname) + and not self._is_ifaceobj_bridge_vlan(ifaceobj_list)): self.logger.warning("%s: interface not recognized - please check interface configuration" % ifname) def _is_ifaceobj_bridge_vlan(self, ifaceobj_list): diff --git a/ifupdown2/lib/addon.py b/ifupdown2/lib/addon.py index 47e42c76..77056b25 100644 --- a/ifupdown2/lib/addon.py +++ b/ifupdown2/lib/addon.py @@ -166,13 +166,13 @@ def __check_l3vni_bridge(self, ifaceobj): and len(self._get_ifaceobj_bridge_ports(ifaceobj, as_list=True)) == 1: ifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_l3VNI - @staticmethod - def _get_ifaceobj_bridge_ports(ifaceobj, as_list=False): + def _get_ifaceobj_bridge_ports(self, ifaceobj, as_list=False): bridge_ports = [] for brport in ifaceobj.get_attr_value('bridge-ports') or []: if brport != 'none': bridge_ports.extend(brport.split()) + bridge_ports = self.cache.link_translate_altnames(bridge_ports) if as_list: return bridge_ports @@ -185,14 +185,15 @@ def _get_bridge_port_list(self, ifaceobj): # of parsing port expr again port_list = ifaceobj.lowerifaces if port_list: - return port_list + return self.cache.link_translate_altnames(port_list) + ports = self._get_ifaceobj_bridge_ports(ifaceobj) if ports: + # _get_ifaceobj_bridge_ports() already translates altnames return self.parse_port_list(ifaceobj.name, ports) else: return None - class AddonWithIpBlackList(Addon): try: ip_blacklist = [ipnetwork.IPNetwork(ip).ip for ip in policymanager.policymanager_api.get_module_globals( diff --git a/ifupdown2/lib/nlcache.py b/ifupdown2/lib/nlcache.py index bbd2a8bc..891524e2 100644 --- a/ifupdown2/lib/nlcache.py +++ b/ifupdown2/lib/nlcache.py @@ -804,6 +804,44 @@ def get_link_info_slave_data_attribute(self, ifname, info_slave_data_attribute, except TypeError as e: return self.__handle_type_error(inspect.currentframe().f_code.co_name, ifname, str(e), return_value=default) + def link_get_altnames(self, ifname): + """ + Returns the list of altnames for the given interface. + :param ifname: + :return: list[str] + """ + proplist = self.get_link_attribute(ifname, Link.IFLA_PROP_LIST, default={}) + if Link.IFLA_ALT_IFNAME in proplist: + return proplist[Link.IFLA_ALT_IFNAME] + return [] + + def link_translate_altname(self, ifname): + """ + Translates a interface name into the respective primary interface name, + if needed. Effectively no-op if ifname is already the primary name. + :param ifname: interface name + :return: primary interface names + """ + if ifname: + return self.get_link_attribute(ifname, Link.IFLA_IFNAME, default=ifname) + return None + + def link_translate_altnames(self, ifnames): + """ + Translates all interface altnames in the given list into their respective + primary ifnames. + :param ifnames: list of interface names + :return: list of interface names which only contains primary interface names + """ + if ifnames is None: + return None + + with self._cache_lock: + return list(map( + lambda ifname: self.get_link_attribute(ifname, Link.IFLA_IFNAME, default=ifname), + ifnames + )) + ################ # MASTER & SLAVE ################ @@ -1219,6 +1257,13 @@ def add_link(self, link): # dictionaries if the master has changed or was un-enslaved. old_ifla_master = None + try: + proplist = link.get_attribute_value(Link.IFLA_PROP_LIST) + ifaltnames = proplist[Link.IFLA_ALT_IFNAME] + except (AttributeError, TypeError): + # no altnames no this link + ifaltnames = [] + with self._cache_lock: # do we have a wait event registered for RTM_NEWLINK this ifname @@ -1242,6 +1287,14 @@ def add_link(self, link): self._link_cache[ifname] = link + # For each altname, also cache the reference to the `Link` object + # it under that name. + # _cache_lock already ensures no concurrent access to the same + # interface through different names. + for altname in ifaltnames: + log.debug(f'registering {altname} as altname for {ifname}') + self._link_cache[altname] = link + ###################################################### # update helper dictionaries and handle link renamed # ###################################################### @@ -1253,6 +1306,9 @@ def add_link(self, link): # in get_ifname/get_ifindex/get_master to do the work. self._ifindex_by_ifname[ifname] = ifindex + # For mapping ifname -> ifindex, also consider altnames + for altname in ifaltnames: + self._ifindex_by_ifname[altname] = ifindex rename_detected = False old_ifname_entry_for_ifindex = self._ifname_by_ifindex.get(ifindex) @@ -1263,6 +1319,7 @@ def add_link(self, link): # renamed. We need to update the cache accordingly. rename_detected = True + # ifindex will just map to the primary ifname self._ifname_by_ifindex[ifindex] = ifname if rename_detected: @@ -1501,9 +1558,17 @@ def remove_link(self, link, link_ifname=None, link_ifindex=None): self._ignore_rtm_newlinkq.remove(ifname) except ValueError: pass + + try: + proplist = link.get_attribute_value(Link.IFLA_PROP_LIST) + ifaltnames = proplist[Link.IFLA_ALT_IFNAME] + except (AttributeError, TypeError): + # no altnames no this link + ifaltnames = [] else: ifname = link_ifname ifindex = link_ifindex + ifaltnames = [] link_ifla_master = None # when an enslaved device is removed we receive the RTM_DELLINK @@ -1532,6 +1597,17 @@ def remove_link(self, link, link_ifname=None, link_ifindex=None): # KeyError means that the link doesn't exists in the cache log.debug('del _link_cache: KeyError ifname: %s' % ifname) + # also delete altnames + for altname in ifaltnames: + try: + del self._link_cache[altname] + except KeyError: + # link is not present under the altname in the cache + log.debug(f'{altname} not present in _link_cache as altname for {ifname}?') + pass + + # for the rest of caches here, only the primary ifname is ever used + try: # like in __unslave_nolock() we need to make sure that all deleted link # have their bridge-vlans and _slaves_master entries cleared. @@ -1560,6 +1636,12 @@ def remove_link(self, link, link_ifname=None, link_ifindex=None): except KeyError: log.debug('del _ifindex_by_ifname: KeyError ifname: %s' % ifname) + for altname in ifaltnames: + try: + del self._ifindex_by_ifname[altname] + except KeyError: + log.debug('del _ifindex_by_ifname: KeyError ifaltname: %s' % altname) + try: del self._addr_cache[ifname] except KeyError: @@ -1581,6 +1663,12 @@ def remove_link(self, link, link_ifname=None, link_ifindex=None): log.debug('_masters_and_slaves[if%s].remove(%s): KeyError' % (link_ifla_master, ifname)) def _address_get_ifname_and_ifindex(self, addr): + """ + Returns the primary ifname and ifindex of the interface the given address + belongs too. + :param addr: address to inspect + :return: ifname and ifindex the specified address belongs to + """ ifindex = addr.ifindex label = addr.get_attribute_value(Address.IFA_LABEL) @@ -3139,6 +3227,20 @@ def link_set_brport_with_info_slave_data_dry_run(self, ifname, kind, ifla_info_d self.log_info_ifname_dry_run(ifname, "netlink: ip link set dev %s: bridge port attributes" % ifname) self.logger.debug("attributes: %s" % ifla_info_slave_data) + ### + + def link_exists(self, ifname): + return self.cache.link_exists(ifname) + + def link_get_altnames(self, ifname): + return self.cache.link_get_altnames(ifname) + + def link_translate_altname(self, ifname): + return self.cache.link_translate_altname(ifname) + + def link_translate_altnames(self, ifnames): + return self.cache.link_translate_altnames(ifnames) + ############################################################################ # ADDRESS ############################################################################ diff --git a/ifupdown2/nlmanager/nlmanager.py b/ifupdown2/nlmanager/nlmanager.py index 5e0cd8e2..d57ddcf1 100644 --- a/ifupdown2/nlmanager/nlmanager.py +++ b/ifupdown2/nlmanager/nlmanager.py @@ -584,6 +584,21 @@ def get_iface_name(self, ifindex): return iface.attributes[Link.IFLA_IFNAME].get_pretty_value(str) return None + def get_iface_altnames(self, ifindex): + """ + Returns the list of altnames for the specified interface. + :param ifindex: Index of the network interface + :return: List of interface altnames + """ + iface = self._get_iface_by_index(ifindex) + + if iface and Link.IFLA_PROP_LIST in iface.attributes: + proplist = iface.get_attribute_value(Link.IFLA_PROP_LIST) + if Link.IFLA_ALT_IFNAME in proplist: + return proplist[Link.IFLA_ALT_IFNAME] + + return [] + def link_dump(self, ifname=None): debug = RTM_GETLINK in self.debug msg = Link(RTM_GETLINK, debug, use_color=self.use_color) diff --git a/ifupdown2/nlmanager/nlpacket.py b/ifupdown2/nlmanager/nlpacket.py index ed463c20..efc4c4ea 100644 --- a/ifupdown2/nlmanager/nlpacket.py +++ b/ifupdown2/nlmanager/nlpacket.py @@ -3464,6 +3464,49 @@ def get_pretty_value(self, obj=None): return value_pretty +class AttributeLinkPropList(Attribute): + """ + IFLA_PROP_LIST - list of (additional) interface properties. + """ + def __init__(self, atype, string, family, logger): + Attribute.__init__(self, atype, string, logger) + self.family = family + + def decode(self, parent_msg, data): + self.decode_length_type(data) + self.value = {} + + data = self.data[4:] + + while data: + (sub_attr_length, sub_attr_type) = unpack('=HH', data[:4]) + sub_attr_end = padded_length(sub_attr_length) + + if not sub_attr_length: + self.log.error('parsed a zero length sub-attr') + continue + + (attr_name, AttrClass) = Link.attribute_to_class[sub_attr_type] + attr = AttrClass(sub_attr_type, attr_name, self.family, self.log) + attr.decode(self, data[:sub_attr_end]) + + if sub_attr_type in self.value: + self.value[sub_attr_type].append(attr.value) + else: + self.value[sub_attr_type] = [attr.value] + + data = data[sub_attr_end:] + + def get_pretty_value(self, obj=None): + if obj and callable(obj): + return obj(self.value) + + value_pretty = {} + for (sub_key, sub_value) in self.value.items(): + sub_key_pretty = "(%2d) %s" % (sub_key, Link.attribute_to_class[sub_key][0]) + value_pretty[sub_key_pretty] = sub_value + + return value_pretty class NetlinkPacket(object): """ @@ -4163,6 +4206,16 @@ class Link(NetlinkPacket, NetlinkPacket_IFLA_LINKINFO_Attributes): IFLA_NEW_IFINDEX = 49 IFLA_MIN_MTU = 50 IFLA_MAX_MTU = 51 + IFLA_PROP_LIST = 52 + IFLA_ALT_IFNAME = 53 + IFLA_PERM_ADDRESS = 54 + IFLA_PROTO_DOWN_REASON = 55 + IFLA_PARENT_DEV_NAME = 56 + IFLA_PARENT_DEV_BUS_NAME = 57 + IFLA_GRO_MAX_SIZE = 58 + IFLA_TSO_MAX_SIZE = 59 + IFLA_TSO_MAX_SEGS = 60 + IFLA_ALLMULTI = 61 attribute_to_class = { IFLA_UNSPEC : ('IFLA_UNSPEC', AttributeGeneric), @@ -4217,6 +4270,16 @@ class Link(NetlinkPacket, NetlinkPacket_IFLA_LINKINFO_Attributes): IFLA_NEW_IFINDEX : ('IFLA_NEW_IFINDEX', AttributeFourByteValue), IFLA_MIN_MTU : ('IFLA_MIN_MTU', AttributeFourByteValue), IFLA_MAX_MTU : ('IFLA_MAX_MTU', AttributeFourByteValue), + IFLA_PROP_LIST : ('IFLA_PROP_LIST', AttributeLinkPropList), + IFLA_ALT_IFNAME : ('IFLA_ALT_IFNAME', AttributeString), + IFLA_PERM_ADDRESS : ('IFLA_PERM_ADDRESS', AttributeMACAddress), + IFLA_PROTO_DOWN_REASON : ('IFLA_PROTO_DOWN_REASON', AttributeFourByteValue), + IFLA_PARENT_DEV_NAME : ('IFLA_PARENT_DEV_NAME', AttributeString), + IFLA_PARENT_DEV_BUS_NAME : ('IFLA_PARENT_DEV_BUS_NAME', AttributeString), + IFLA_GRO_MAX_SIZE : ('IFLA_GRO_MAX_SIZE', AttributeFourByteValue), + IFLA_TSO_MAX_SIZE : ('IFLA_TSO_MAX_SIZE', AttributeFourByteValue), + IFLA_TSO_MAX_SEGS : ('IFLA_TSO_MAX_SEGS', AttributeFourByteValue), + IFLA_ALLMULTI : ('IFLA_ALLMULTI', AttributeFourByteValue), } # Link flags