summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
authorThomas Mangin <thomas.mangin@exa.net.uk>2020-05-07 00:50:44 +0100
committerThomas Mangin <thomas.mangin@exa.net.uk>2020-05-07 00:50:44 +0100
commit367094ff1764116089c9359e1e949db781a4a0da (patch)
treecf3135162c8a57af652f027805d1d4a9d63cbca0 /python/vyos
parente9516e73796bbbda1be76e3f8b5d83cd84070830 (diff)
parented22334321d3b6f27b5d695a4f984257b909f78b (diff)
downloadvyos-1x-367094ff1764116089c9359e1e949db781a4a0da.tar.gz
vyos-1x-367094ff1764116089c9359e1e949db781a4a0da.zip
debug: T1230: add time information to saved debug logs
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/config.py15
-rw-r--r--python/vyos/configdict.py89
-rw-r--r--python/vyos/debug.py7
-rw-r--r--python/vyos/ifconfig/interface.py68
-rw-r--r--python/vyos/ifconfig/section.py21
-rw-r--r--python/vyos/ifconfig_vlan.py146
-rw-r--r--python/vyos/util.py71
-rw-r--r--python/vyos/validate.py66
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