diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/config.py | 15 | ||||
-rw-r--r-- | python/vyos/configdict.py | 89 | ||||
-rw-r--r-- | python/vyos/debug.py | 7 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 68 | ||||
-rw-r--r-- | python/vyos/ifconfig/section.py | 21 | ||||
-rw-r--r-- | python/vyos/ifconfig_vlan.py | 146 | ||||
-rw-r--r-- | python/vyos/util.py | 71 | ||||
-rw-r--r-- | python/vyos/validate.py | 66 |
8 files changed, 361 insertions, 122 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index 75055a603..0bc6be12a 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -155,7 +155,7 @@ class Config(object): ``exists("system name-server"`` without ``set_level``. Args: - path (str): relative config path + path (str|list): relative config path """ # Make sure there's always a space between default path (level) # and path supplied as method argument @@ -166,7 +166,7 @@ class Config(object): else: self._level = [] elif isinstance(path, list): - self._level = path + self._level = path.copy() else: raise TypeError("Level path must be either a whitespace-separated string or a list") @@ -177,7 +177,7 @@ class Config(object): Returns: str: current edit level """ - return(self._level) + return(self._level.copy()) def exists(self, path): """ @@ -386,7 +386,7 @@ class Config(object): values = [] if not values: - return(default) + return(default.copy()) else: return(values) @@ -407,7 +407,7 @@ class Config(object): nodes = [] if not nodes: - return(default) + return(default.copy()) else: return(nodes) @@ -448,7 +448,6 @@ class Config(object): else: return(value) - def return_effective_values(self, path, default=[]): """ Retrieve all values of a multi-value node in a running (effective) config @@ -465,7 +464,7 @@ class Config(object): values = [] if not values: - return(default) + return(default.copy()) else: return(values) @@ -488,6 +487,6 @@ class Config(object): nodes = [] if not nodes: - return(default) + return(default.copy()) else: return(nodes) diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e1b704a31..5ca369f66 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,7 +23,8 @@ from copy import deepcopy from vyos import ConfigError from vyos.ifconfig import Interface - +from vyos.validate import is_member +from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): """ @@ -128,9 +129,10 @@ vlan_default = { 'ipv6_dup_addr_detect': 1, 'ingress_qos': '', 'ingress_qos_changed': False, + 'is_bridge_member': False, 'mac': '', 'mtu': 1500, - 'vif_c': [], + 'vif_c': {}, 'vif_c_remove': [], 'vrf': '' } @@ -197,10 +199,7 @@ def intf_to_dict(conf, default): """ intf = deepcopy(default) - - # retrieve configured interface addresses - if conf.exists('address'): - intf['address'] = conf.return_values('address') + intf['intf'] = ifname_from_config(conf) # retrieve interface description if conf.exists('description'): @@ -255,23 +254,22 @@ def intf_to_dict(conf, default): if conf.exists('ipv6 address autoconf'): intf['ipv6_autoconf'] = 1 - # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - if conf.exists('ipv6 address eui64'): - intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') - # Disable IPv6 forwarding on this interface if conf.exists('ipv6 disable-forwarding'): intf['ipv6_forwarding'] = 0 - # Media Access Control (MAC) address - if conf.exists('mac'): - intf['mac'] = conf.return_value('mac') + # check if interface is member of a bridge + intf['is_bridge_member'] = is_member(conf, intf['intf'], 'bridge') # IPv6 Duplicate Address Detection (DAD) tries if conf.exists('ipv6 dup-addr-detect-transmits'): intf['ipv6_dup_addr_detect'] = int( conf.return_value('ipv6 dup-addr-detect-transmits')) + # Media Access Control (MAC) address + if conf.exists('mac'): + intf['mac'] = conf.return_value('mac') + # Maximum Transmission Unit (MTU) if conf.exists('mtu'): intf['mtu'] = int(conf.return_value('mtu')) @@ -298,60 +296,56 @@ def intf_to_dict(conf, default): if intf['ingress_qos'] != conf.return_effective_value('ingress-qos'): intf['ingress_qos_changed'] = True - disabled = disable_state(conf) + # Get the interface addresses + intf['address'] = conf.return_values('address') - # Get the interface IPs - eff_addr = conf.return_effective_values('address') - act_addr = conf.return_values('address') + # addresses to remove - difference between effective and working config + intf['address_remove'] = list_diff( + conf.return_effective_values('address'), + intf['address'] + ) # Get prefixes for IPv6 addressing based on MAC address (EUI-64) - eff_eui = conf.return_effective_values('ipv6 address eui64') - act_eui = conf.return_values('ipv6 address eui64') + intf['ipv6_eui64_prefix'] = conf.return_values('ipv6 address eui64') + + # EUI64 to remove - difference between effective and working config + intf['ipv6_eui64_prefix_remove'] = list_diff( + conf.return_effective_values('ipv6 address eui64'), + intf['ipv6_eui64_prefix'] + ) - # Determine what should stay or be removed + # Determine if the interface should be disabled + disabled = disable_state(conf) if disabled == disable.both: # was and is still disabled intf['disable'] = True - intf['address'] = [] - intf['address_remove'] = [] - intf['ipv6_eui64_prefix'] = [] - intf['ipv6_eui64_prefix_remove'] = [] elif disabled == disable.now: # it is now disable but was not before intf['disable'] = True - intf['address'] = [] - intf['address_remove'] = eff_addr - intf['ipv6_eui64_prefix'] = [] - intf['ipv6_eui64_prefix_remove'] = eff_eui elif disabled == disable.was: # it was disable but not anymore intf['disable'] = False - intf['address'] = act_addr - intf['address_remove'] = [] - intf['ipv6_eui64_prefix'] = act_eui - intf['ipv6_eui64_prefix_remove'] = [] else: # normal change intf['disable'] = False - intf['address'] = act_addr - intf['address_remove'] = list_diff(eff_addr, act_addr) - intf['ipv6_eui64_prefix'] = act_eui - intf['ipv6_eui64_prefix_remove'] = list_diff(eff_eui, act_eui) - # Remove the default link-local address if set. - if conf.exists('ipv6 address no-default-link-local'): + # Remove the default link-local address if no-default-link-local is set, + # if member of a bridge or if disabled (it may not have a MAC if it's down) + if ( conf.exists('ipv6 address no-default-link-local') + or intf.get('is_bridge_member') + or intf['disable'] ): intf['ipv6_eui64_prefix_remove'].append('fe80::/64') else: # add the link-local by default to make IPv6 work intf['ipv6_eui64_prefix'].append('fe80::/64') - # Find out if MAC has changed + # If MAC has changed, remove and re-add all IPv6 EUI64 addresses try: interface = Interface(intf['intf'], create=False) if intf['mac'] and intf['mac'] != interface.get_mac(): intf['ipv6_eui64_prefix_remove'] += intf['ipv6_eui64_prefix'] except Exception: - # If the interface does not exists, it can not have changed + # If the interface does not exist, it could not have changed pass return intf, disable @@ -381,8 +375,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): # the section to parse for vlan sections = [] - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed from the bond + # determine which interfaces to add or remove based on disable state if disabled == disable.both: # was and is still disabled ifdict[f'{key}_remove'] = [] @@ -395,7 +388,7 @@ def add_to_dict(conf, disabled, ifdict, section, key): sections = active else: # normal change - # get vif-s interfaces (currently effective) - to determine which vif-s + # get interfaces (currently effective) - to determine which # interface is no longer present and needs to be removed ifdict[f'{key}_remove'] = list_diff(effect, active) sections = active @@ -406,7 +399,8 @@ def add_to_dict(conf, disabled, ifdict, section, key): for s in sections: # set config level to vif interface conf.set_level(current_level + [section, s]) - ifdict[f'{key}'].append(vlan_to_dict(conf)) + # add the vlan config as a key (vlan id) - value (config) pair + ifdict[key][s] = vlan_to_dict(conf) # re-set configuration level to leave things as found conf.set_level(current_level) @@ -416,13 +410,9 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): vlan, disabled = intf_to_dict(conf, default) - # get the '100' in 'interfaces bonding bond0 vif-s 100 - vlan['id'] = conf.get_level()[-1] - - current_level = conf.get_level() # if this is a not within vif-s node, we are done - if current_level[-2] != 'vif-s': + if conf.get_level()[-2] != 'vif-s': return vlan # ethertype is mandatory on vif-s nodes and only exists here! @@ -434,7 +424,6 @@ def vlan_to_dict(conf, default=vlan_default): # check if there is a Q-in-Q vlan customer interface # and call this function recursively - add_to_dict(conf, disable, vlan, 'vif-c', 'vif_c') return vlan diff --git a/python/vyos/debug.py b/python/vyos/debug.py index 60e5291c1..6ce42b173 100644 --- a/python/vyos/debug.py +++ b/python/vyos/debug.py @@ -86,10 +86,17 @@ def _timed(message): return f'{now} {message}' +def _remove_invisible(string): + for char in ('\0', '\a', '\b', '\f', '\v'): + string = string.replace(char, '') + return string + + def _format(flag, message): """ format a log message """ + message = _remove_invisible(message) return f'DEBUG/{flag.upper():<7} {message}\n' diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index de5ca369f..7b42e3399 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -42,6 +42,7 @@ from vyos.ifconfig.control import Control from vyos.ifconfig.dhcp import DHCP from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.operational import Operational +from vyos.ifconfig import Section class Interface(Control): @@ -638,6 +639,10 @@ class Interface(Control): # XXX: normalize/compress with ipaddress if calling functions don't? # is subnet mask always passed, and in the same way? + # do not add same address twice + if addr in self._addr: + return False + # we can't have both DHCP and static IPv4 addresses assigned for a in self._addr: if ( ( addr == 'dhcp' and a != 'dhcpv6' and is_ipv4(a) ) or @@ -646,27 +651,20 @@ class Interface(Control): "Can't configure both static IPv4 and DHCP address " "on the same interface")) - # do not add same address twice - if addr in self._addr: - return False - # add to interface if addr == 'dhcp': - self._addr.append(addr) self.dhcp.v4.set() - return True - - if addr == 'dhcpv6': - self._addr.append(addr) + elif addr == 'dhcpv6': self.dhcp.v6.set() - return True - - if not is_intf_addr_assigned(self.ifname, addr): - self._addr.append(addr) + elif not is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr add "{addr}" dev "{self.ifname}"') - return True + else: + return False + + # add to cache + self._addr.append(addr) - return False + return True def del_addr(self, addr): """ @@ -693,24 +691,21 @@ class Interface(Control): ['2001:db8::ffff/64'] """ - # remove from cache (dhcp, and dhcpv6 can not be in it) - if addr in self._addr: - self._addr.remove(addr) - # remove from interface if addr == 'dhcp': self.dhcp.v4.delete() - return True - - if addr == 'dhcpv6': + elif addr == 'dhcpv6': self.dhcp.v6.delete() - return True - - if is_intf_addr_assigned(self.ifname, addr): + elif is_intf_addr_assigned(self.ifname, addr): self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"') - return True + else: + return False + + # remove from cache + if addr in self._addr: + self._addr.remove(addr) - return False + return True def flush_addrs(self): """ @@ -724,3 +719,22 @@ class Interface(Control): # flush all addresses self._cmd(f'ip addr flush dev "{self.ifname}"') + + def add_to_bridge(self, br): + """ + Adds the interface to the bridge with the passed port config. + + Returns False if bridge doesn't exist. + """ + + # check if the bridge exists (on boot it doesn't) + if br not in Section.interfaces('bridge'): + return False + + self.flush_addrs() + # add interface to bridge - use Section.klass to get BridgeIf class + Section.klass(br)(br, create=False).add_port(self.ifname) + + # TODO: port config (STP) + + return True diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 092236fef..926c22e8a 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -54,7 +54,7 @@ class Section: name = name.rstrip('0123456789') name = name.rstrip('.') if vlan: - name = name.rstrip('0123456789') + name = name.rstrip('0123456789.') return name @classmethod @@ -137,3 +137,22 @@ class Section: eth, lo, vxlan, dum, ... """ return list(cls._prefixes.keys()) + + @classmethod + def get_config_path(cls, name): + """ + get config path to interface with .vif or .vif-s.vif-c + example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2' + Returns False if interface name is invalid (not found in sections) + """ + sect = cls.section(name) + if sect: + splinterface = name.split('.') + intfpath = f'{sect} {splinterface[0]}' + if len(splinterface) == 2: + intfpath += f' vif {splinterface[1]}' + elif len(splinterface) == 3: + intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}' + return intfpath + else: + return False diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ee009f7f9..09fb8c802 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -16,6 +16,53 @@ from netifaces import interfaces from vyos import ConfigError +def apply_all_vlans(intf, intfconfig): + """ + Function applies all VLANs to the passed interface. + + intf: object of Interface class + intfconfig: dict with interface configuration + """ + # remove no longer required service VLAN interfaces (vif-s) + for vif_s in intfconfig['vif_s_remove']: + intf.del_vlan(vif_s) + + # create service VLAN interfaces (vif-s) + for vif_s_id, vif_s in intfconfig['vif_s'].items(): + s_vlan = intf.add_vlan(vif_s_id, ethertype=vif_s['ethertype']) + apply_vlan_config(s_vlan, vif_s) + + # remove no longer required client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c in intfconfig['vif_c_remove']: + s_vlan.del_vlan(vif_c) + + # create client VLAN interfaces (vif-c) + # on lower service VLAN interface + for vif_c_id, vif_c in vif_s['vif_c'].items(): + c_vlan = s_vlan.add_vlan(vif_c_id) + apply_vlan_config(c_vlan, vif_c) + + # remove no longer required VLAN interfaces (vif) + for vif in intfconfig['vif_remove']: + intf.del_vlan(vif) + + # create VLAN interfaces (vif) + for vif_id, vif in intfconfig['vif'].items(): + # QoS priority mapping can only be set during interface creation + # so we delete the interface first if required. + if vif['egress_qos_changed'] or vif['ingress_qos_changed']: + try: + # on system bootup the above condition is true but the interface + # does not exists, which throws an exception, but that's legal + intf.del_vlan(vif_id) + except: + pass + + vlan = intf.add_vlan(vif_id, ingress_qos=vif['ingress_qos'], egress_qos=vif['egress_qos']) + apply_vlan_config(vlan, vif) + + def apply_vlan_config(vlan, config): """ Generic function to apply a VLAN configuration from a dictionary @@ -63,8 +110,10 @@ def apply_vlan_config(vlan, config): # Maximum Transmission Unit (MTU) vlan.set_mtu(config['mtu']) - # assign/remove VRF - vlan.set_vrf(config['vrf']) + # assign/remove VRF (ONLY when not a member of a bridge, + # otherwise 'nomaster' removes it from it) + if not config['is_bridge_member']: + vlan.set_vrf(config['vrf']) # Delete old IPv6 EUI64 addresses before changing MAC for addr in config['ipv6_eui64_prefix_remove']: @@ -92,46 +141,97 @@ def apply_vlan_config(vlan, config): for addr in config['address']: vlan.add_addr(addr) + # re-add ourselves to any bridge we might have fallen out of + if config['is_bridge_member']: + vlan.add_to_bridge(config['is_bridge_member']) + def verify_vlan_config(config): """ Generic function to verify VLAN config consistency. Instead of re- implementing this function in multiple places use single source \o/ """ - for vif in config['vif']: + # config['vif'] is a dict with ids as keys and config dicts as values + for vif in config['vif'].values(): # DHCPv6 parameters-only and temporary address are mutually exclusive if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - vrf_name = vif['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') + if ( vif['is_bridge_member'] + and ( vif['address'] + or vif['ipv6_eui64_prefix'] + or vif['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif interface {vif["intf"]} ' + f'which is a member of bridge {vif["is_bridge_member"]}')) + + if vif['vrf']: + if vif['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') + + if vif['is_bridge_member']: + raise ConfigError(( + f'vif {vif["intf"]} cannot be member of VRF {vif["vrf"]} ' + f'and bridge {vif["is_bridge_member"]} at the same time!')) # e.g. wireless interface has no vif_s support # thus we bail out eraly. if 'vif_s' not in config.keys(): return - for vif_s in config['vif_s']: - for vif in config['vif']: - if vif['id'] == vif_s['id']: - raise ConfigError('Can not use identical ID on vif and vif-s interface') + # config['vif_s'] is a dict with ids as keys and config dicts as values + for vif_s_id, vif_s in config['vif_s'].items(): + for vif_id, vif in config['vif'].items(): + if vif_id == vif_s_id: + raise ConfigError(( + f'Cannot use identical ID on vif "{vif["intf"]}" ' + f'and vif-s "{vif_s}"')) # DHCPv6 parameters-only and temporary address are mutually exclusive if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - - vrf_name = vif_s['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') - - for vif_c in vif_s['vif_c']: + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are mutually ' + 'exclusive!')) + + if ( vif_s['is_bridge_member'] + and ( vif_s['address'] + or vif_s['ipv6_eui64_prefix'] + or vif_s['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif-s interface {vif_s["intf"]} ' + f'which is a member of bridge {vif_s["is_bridge_member"]}')) + + if vif_s['vrf']: + if vif_s['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') + + if vif_s['is_bridge_member']: + raise ConfigError(( + f'vif-s {vif_s["intf"]} cannot be member of VRF {vif_s["vrf"]} ' + f'and bridge {vif_s["is_bridge_member"]} at the same time!')) + + # vif_c is a dict with ids as keys and config dicts as values + for vif_c in vif_s['vif_c'].values(): # DHCPv6 parameters-only and temporary address are mutually exclusive if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: - raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - - vrf_name = vif_c['vrf'] - if vrf_name and vrf_name not in interfaces(): - raise ConfigError(f'VRF "{vrf_name}" does not exist') - + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are ' + 'mutually exclusive!')) + + if ( vif_c['is_bridge_member'] + and ( vif_c['address'] + or vif_c['ipv6_eui64_prefix'] + or vif_c['ipv6_autoconf'] ) ): + raise ConfigError(( + f'Cannot assign address to vif-c interface {vif_c["intf"]} ' + f'which is a member of bridge {vif_c["is_bridge_member"]}')) + + if vif_c['vrf']: + if vif_c['vrf'] not in interfaces(): + raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') + + if vif_c['is_bridge_member']: + raise ConfigError(( + f'vif-c {vif_c["intf"]} cannot be member of VRF {vif_c["vrf"]} ' + f'and bridge {vif_c["is_bridge_member"]} at the same time!')) diff --git a/python/vyos/util.py b/python/vyos/util.py index 92b6f7992..e598e0ff3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -498,3 +498,74 @@ def get_half_cpus(): if cpu > 1: cpu /= 2 return int(cpu) + +def ifname_from_config(conf): + """ + Gets interface name with VLANs from current config level. + Level must be at the interface whose name we want. + + Example: + >>> from vyos.util import ifname_from_config + >>> from vyos.config import Config + >>> conf = Config() + >>> conf.set_level('interfaces ethernet eth0 vif-s 1 vif-c 2') + >>> ifname_from_config(conf) + 'eth0.1.2' + """ + level = conf.get_level() + + # vlans + if level[-2] == 'vif' or level[-2] == 'vif-s': + return level[-3] + '.' + level[-1] + if level[-2] == 'vif-c': + return level[-5] + '.' + level[-3] + '.' + level[-1] + + # no vlans + return level[-1] + +def get_bridge_member_config(conf, br, intf): + """ + Gets bridge port (member) configuration + + Arguments: + conf: Config + br: bridge name + intf: interface name + + Returns: + dict with the configuration + False if bridge or bridge port doesn't exist + """ + old_level = conf.get_level() + conf.set_level([]) + + bridge = f'interfaces bridge {br}' + member = f'{bridge} member interface {intf}' + if not ( conf.exists(bridge) and conf.exists(member) ): + return False + + # default bridge port configuration + # cost and priority initialized with linux defaults + # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} + # after adding interface to bridge after reboot + memberconf = { + 'cost': 100, + 'priority': 32, + 'arp_cache_tmo': 30, + 'disable_link_detect': 1, + } + + if conf.exists(f'{member} cost'): + memberconf['cost'] = int(conf.return_value(f'{member} cost')) + + if conf.exists(f'{member} priority'): + memberconf['priority'] = int(conf.return_value(f'{member} priority')) + + if conf.exists(f'{bridge} ip arp-cache-timeout'): + memberconf['arp_cache_tmo'] = int(conf.return_value(f'{bridge} ip arp-cache-timeout')) + + if conf.exists(f'{bridge} disable-link-detect'): + memberconf['disable_link_detect'] = 2 + + conf.set_level(old_level) + return memberconf diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 446f6e4ca..e083604c5 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -241,26 +241,66 @@ def assert_mac(m): if octets[:5] == (0, 0, 94, 0, 1): raise ValueError(f'{m} is a VRRP MAC address') -def is_bridge_member(conf, interface): +def is_member(conf, interface, intftype=None): """ - Checks if passed interfaces is part of a bridge device or not. - - Returns a tuple: - None -> Interface not a bridge member - Bridge -> Interface is a member of this bridge + Checks if passed interface is member of other interface of specified type. + intftype is optional, if not passed it will search all known types + (currently bridge and bonding) + + Returns: + None -> Interface is not a member + interface name -> Interface is a member of this interface + False -> interface type cannot have members """ ret_val = None - old_level = conf.get_level() + + if intftype not in ['bonding', 'bridge', None]: + raise ValueError(( + f'unknown interface type "{intftype}" or it cannot ' + f'have member interfaces')) + + intftype = ['bonding', 'bridge'] if intftype == None else [intftype] # set config level to root + old_level = conf.get_level() conf.set_level([]) - base = ['interfaces', 'bridge'] - for bridge in conf.list_nodes(base): - members = conf.list_nodes(base + [bridge, 'member', 'interface']) - if interface in members: - ret_val = bridge - break + + for it in intftype: + base = 'interfaces ' + it + for intf in conf.list_nodes(base): + memberintf = f'{base} {intf} member interface' + if conf.is_tag(memberintf): + if interface in conf.list_nodes(memberintf): + ret_val = intf + break + elif conf.is_leaf(memberintf): + if ( conf.exists(memberintf) and + interface in conf.return_values(memberintf) ): + ret_val = intf + break old_level = conf.set_level(old_level) return ret_val +def has_address_configured(conf, intf): + """ + Checks if interface has an address configured. + Checks the following config nodes: + 'address', 'ipv6 address eui64', 'ipv6 address autoconf' + + Returns True if interface has address configured, False if it doesn't. + """ + from vyos.ifconfig import Section + ret = False + + old_level = conf.get_level() + conf.set_level([]) + + intfpath = 'interfaces ' + Section.get_config_path(intf) + if ( conf.exists(f'{intfpath} address') or + conf.exists(f'{intfpath} ipv6 address autoconf') or + conf.exists(f'{intfpath} ipv6 address eui64') ): + ret = True + + conf.set_level(old_level) + return ret |