From a1afabff0fa8be39c917cd2fd879c4b68db49f42 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 11:22:11 +0200 Subject: interface: T2367: optimize flow and detriplicate add/del_addr functions - detriplicate list appending - detriplicate returns - use if-elif-else - move check if address is already added to beginning - move caching in variable to after address assignment so a failed assignment won't cache the address --- python/vyos/ifconfig/interface.py | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index de5ca369f..5b3da228e 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -638,6 +638,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 +650,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 - return False + # add to cache + self._addr.append(addr) + + return True def del_addr(self, addr): """ @@ -693,24 +690,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): """ -- cgit v1.2.3 From 208414293f341923990ff3a30f1d0eddbf94b14f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 11:55:00 +0200 Subject: vlan: T2241: cleanup vlan_to_dict function Remove one unnecessary call to conf.get_level() --- python/vyos/configdict.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index e1b704a31..943092ceb 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -416,13 +416,13 @@ 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() + level = conf.get_level() + # get the '100' in 'interfaces bonding bond0 vif-s 100' + vlan['id'] = level[-1] # if this is a not within vif-s node, we are done - if current_level[-2] != 'vif-s': + if level[-2] != 'vif-s': return vlan # ethertype is mandatory on vif-s nodes and only exists here! -- cgit v1.2.3 From 9bfbb24ae649d7cedc2002f7df95a0aa0fececdc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:25:56 +0200 Subject: util: T2241: add func that gets interface name from current config level --- python/vyos/util.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'python') diff --git a/python/vyos/util.py b/python/vyos/util.py index 92b6f7992..c708c1eb4 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -498,3 +498,27 @@ 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] -- cgit v1.2.3 From c1ad2a6461fc2e767d69567be9647150c3310569 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:28:11 +0200 Subject: configdict: T2241: get interface name in intf/vlan_from_dict This is needed as later functions depend on it --- python/vyos/configdict.py | 2 ++ src/conf_mode/interfaces-bonding.py | 1 - src/conf_mode/interfaces-ethernet.py | 1 - src/conf_mode/interfaces-pseudo-ethernet.py | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 943092ceb..cd3364c94 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,6 +23,7 @@ from copy import deepcopy from vyos import ConfigError from vyos.ifconfig import Interface +from vyos.util import ifname_from_config def retrieve_config(path_hash, base_path, config): @@ -197,6 +198,7 @@ def intf_to_dict(conf, default): """ intf = deepcopy(default) + intf['intf'] = ifname_from_config(conf) # retrieve configured interface addresses if conf.exists('address'): diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index a174e33e4..76caefabe 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -119,7 +119,6 @@ def get_config(): conf.set_level(cfg_base) bond, disabled = intf_to_dict(conf, default_config_data) - bond['intf'] = ifname # ARP link monitoring frequency in milliseconds if conf.exists('arp-monitor interval'): diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 3ddd394d7..c7f935ca6 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -92,7 +92,6 @@ def get_config(): conf.set_level(cfg_base) eth, disabled = intf_to_dict(conf, default_config_data) - eth['intf'] = ifname # disable ethernet flow control (pause frames) if conf.exists('disable-flow-control'): diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index f0f893b44..67250ec9f 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -85,7 +85,6 @@ def get_config(): conf.set_level(cfg_base) peth, disabled = intf_to_dict(conf, default_config_data) - peth['intf'] = ifname # ARP cache entry timeout in seconds if conf.exists(['ip', 'arp-cache-timeout']): -- cgit v1.2.3 From 900e75e387939a1d1d4d5b0b79809b8bb2305b91 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:46:47 +0200 Subject: validate: T2241: rewrite is_bridge_member to generic is_member - rewrite the function to support both bridge and bonding interface types, if the type is passed it searches only that type, otherwise it searches both - move is_member check out of the deleted condition - move is_member check to intf_from_dict for interfaces that use it --- python/vyos/configdict.py | 3 ++ python/vyos/validate.py | 44 ++++++++++++++++++++--------- src/conf_mode/interfaces-bonding.py | 3 -- src/conf_mode/interfaces-dummy.py | 7 +++-- src/conf_mode/interfaces-geneve.py | 7 +++-- src/conf_mode/interfaces-openvpn.py | 7 +++-- src/conf_mode/interfaces-pseudo-ethernet.py | 5 +--- src/conf_mode/interfaces-tunnel.py | 4 +-- src/conf_mode/interfaces-vxlan.py | 7 +++-- src/conf_mode/interfaces-wireguard.py | 7 +++-- src/conf_mode/interfaces-wireless.py | 7 +++-- src/conf_mode/interfaces-wirelessmodem.py | 7 +++-- 12 files changed, 65 insertions(+), 43 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index cd3364c94..ab72aac6c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -23,6 +23,7 @@ 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 @@ -268,6 +269,8 @@ def intf_to_dict(conf, default): # 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'): diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 446f6e4ca..eb3f8bf52 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -241,25 +241,43 @@ 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 diff --git a/src/conf_mode/interfaces-bonding.py b/src/conf_mode/interfaces-bonding.py index 76caefabe..0fc8cfa6c 100755 --- a/src/conf_mode/interfaces-bonding.py +++ b/src/conf_mode/interfaces-bonding.py @@ -25,7 +25,6 @@ from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.config import Config from vyos.util import call, cmd -from vyos.validate import is_bridge_member from vyos import ConfigError default_config_data = { @@ -111,8 +110,6 @@ def get_config(): bond = deepcopy(default_config_data) bond['intf'] = ifname bond['deleted'] = True - # check if interface is member if a bridge - bond['is_bridge_member'] = is_bridge_member(conf, ifname) return bond # set new configuration level diff --git a/src/conf_mode/interfaces-dummy.py b/src/conf_mode/interfaces-dummy.py index 23eaa4ecb..7bc834be5 100755 --- a/src/conf_mode/interfaces-dummy.py +++ b/src/conf_mode/interfaces-dummy.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.ifconfig import DummyIf from vyos.configdict import list_diff from vyos.config import Config -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -47,11 +47,12 @@ def get_config(): dummy['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if we are a member of any bridge + dummy['is_bridge_member'] = is_member(conf, dummy['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces dummy ' + dummy['intf']): dummy['deleted'] = True - # check if interface is member if a bridge - dummy['is_bridge_member'] = is_bridge_member(conf, dummy['intf']) return dummy # set new configuration level diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 708a64474..98f0672c5 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -22,7 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.ifconfig import GeneveIf -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -49,11 +49,12 @@ def get_config(): geneve['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + geneve['is_bridge_member'] = is_member(conf, geneve['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces geneve ' + geneve['intf']): geneve['deleted'] = True - # check if interface is member if a bridge - geneve['is_bridge_member'] = is_bridge_member(conf, geneve['intf']) return geneve # set new configuration level diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 029bc1d69..23a690bf2 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -29,7 +29,7 @@ from vyos.configdict import list_diff from vyos.ifconfig import VTunIf from vyos.template import render from vyos.util import call, chown, chmod_600, chmod_755 -from vyos.validate import is_addr_assigned, is_bridge_member, is_ipv4 +from vyos.validate import is_addr_assigned, is_member, is_ipv4 from vyos import ConfigError user = 'openvpn' @@ -199,11 +199,12 @@ def get_config(): openvpn['intf'] = os.environ['VYOS_TAGNODE_VALUE'] openvpn['auth_user_pass_file'] = f"/run/openvpn/{openvpn['intf']}.pw" + # check if interface is member of a bridge + openvpn['is_bridge_member'] = is_member(conf, openvpn['intf'], 'bridge') + # Check if interface instance has been removed if not conf.exists('interfaces openvpn ' + openvpn['intf']): openvpn['deleted'] = True - # check if interface is member if a bridge - openvpn['is_bridge_member'] = is_bridge_member(conf, openvpn['intf']) return openvpn # Check if we belong to any bridge interface diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index 67250ec9f..f9523ca8b 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -21,10 +21,9 @@ from sys import exit from netifaces import interfaces from vyos.config import Config -from vyos.configdict import list_diff, vlan_to_dict, intf_to_dict, add_to_dict +from vyos.configdict import list_diff, intf_to_dict, add_to_dict from vyos.ifconfig import MACVLANIf, Section from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config -from vyos.validate import is_bridge_member from vyos import ConfigError default_config_data = { @@ -77,8 +76,6 @@ def get_config(): if not conf.exists(cfg_base): peth = deepcopy(default_config_data) peth['deleted'] = True - # check if interface is member if a bridge - peth['is_bridge_member'] = is_bridge_member(conf, ifname) return peth # set new configuration level diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index fc084814a..1916d2de2 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -25,7 +25,7 @@ from vyos.config import Config from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf from vyos.ifconfig.afi import IP4, IP6 from vyos.configdict import list_diff -from vyos.validate import is_ipv4, is_ipv6, is_bridge_member +from vyos.validate import is_ipv4, is_ipv6, is_member from vyos import ConfigError from vyos.dicts import FixedDict @@ -410,7 +410,7 @@ def get_config(): options['tunnel'] = {} # check for bridges - options['bridge'] = is_bridge_member(conf, ifname) + options['bridge'] = is_member(conf, ifname, 'bridge') options['interfaces'] = interfaces() for name in ct: diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 74eae4281..334e418ab 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -22,7 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.ifconfig import VXLANIf, Interface -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -62,11 +62,12 @@ def get_config(): vxlan['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + vxlan['is_bridge_member'] = is_member(conf, vxlan['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces vxlan ' + vxlan['intf']): vxlan['deleted'] = True - # check if interface is member if a bridge - vxlan['is_bridge_member'] = is_bridge_member(conf, vxlan['intf']) return vxlan # set new configuration level diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 01f84260d..98d5fcb27 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -25,7 +25,7 @@ from vyos.config import Config from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf from vyos.util import chown, chmod_750, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError kdir = r'/config/auth/wireguard' @@ -78,11 +78,12 @@ def get_config(): wg = deepcopy(default_config_data) wg['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + wg['is_bridge_member'] = is_member(conf, wg['intf'], 'bridge') + # Check if interface has been removed if not conf.exists(base + [wg['intf']]): wg['deleted'] = True - # check if interface is member if a bridge - wg['is_bridge_member'] = is_bridge_member(conf, wg['intf']) return wg conf.set_level(base + [wg['intf']]) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index 148a7f6e0..04125ff31 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -29,7 +29,7 @@ from vyos.ifconfig import WiFiIf, Section from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config from vyos.template import render from vyos.util import chown, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -134,12 +134,13 @@ def get_config(): wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + # check if interface is member if a bridge + wifi['is_bridge_member'] = is_member(conf, wifi['intf'], 'bridge') + # check if wireless interface has been removed cfg_base = 'interfaces wireless ' + wifi['intf'] if not conf.exists(cfg_base): wifi['deleted'] = True - # check if interface is member if a bridge - wifi['is_bridge_member'] = is_bridge_member(conf, wifi['intf']) # we can not bail out early as wireless interface can not be removed # Kernel will complain with: RTNETLINK answers: Operation not supported. # Thus we need to remove individual settings diff --git a/src/conf_mode/interfaces-wirelessmodem.py b/src/conf_mode/interfaces-wirelessmodem.py index a3a2a2648..03832f345 100755 --- a/src/conf_mode/interfaces-wirelessmodem.py +++ b/src/conf_mode/interfaces-wirelessmodem.py @@ -23,7 +23,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.template import render from vyos.util import chown, chmod_755, cmd, call -from vyos.validate import is_bridge_member +from vyos.validate import is_member from vyos import ConfigError default_config_data = { @@ -64,11 +64,12 @@ def get_config(): wwan['logfile'] = f"/var/log/vyatta/ppp_{wwan['intf']}.log" wwan['chat_script'] = f"/etc/ppp/peers/chat.{wwan['intf']}" + # check if interface is member if a bridge + wwan['is_bridge_member'] = is_member(conf, wwan['intf'], 'bridge') + # Check if interface has been removed if not conf.exists('interfaces wirelessmodem ' + wwan['intf']): wwan['deleted'] = True - # check if interface is member if a bridge - wwan['is_bridge_member'] = is_bridge_member(conf, wwan['intf']) return wwan # set new configuration level -- cgit v1.2.3 From 64d3d94f35453bfaf596c27a0fc0f3fa78cc7260 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:50:42 +0200 Subject: intf_from_dict: T2241: move getting mac code so it's sorted alphabetically --- python/vyos/configdict.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index ab72aac6c..97ba8937c 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -266,9 +266,6 @@ def intf_to_dict(conf, default): 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') @@ -277,6 +274,10 @@ def intf_to_dict(conf, default): 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')) @@ -350,7 +351,7 @@ def intf_to_dict(conf, default): # 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(): -- cgit v1.2.3 From da413b6aec002b37a20443632bab08f5db89f854 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 13:57:35 +0200 Subject: vlan: T2241: add checks for bridge membership --- python/vyos/configdict.py | 2 +- python/vyos/ifconfig_vlan.py | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 97ba8937c..0648d8646 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -26,7 +26,6 @@ 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): """ Retrieves a VyOS config as a dict according to a declarative description @@ -130,6 +129,7 @@ vlan_default = { 'ipv6_dup_addr_detect': 1, 'ingress_qos': '', 'ingress_qos_changed': False, + 'is_bridge_member': False, 'mac': '', 'mtu': 1500, 'vif_c': [], diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index ee009f7f9..079118df6 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -103,9 +103,15 @@ def verify_vlan_config(config): 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['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. @@ -121,17 +127,28 @@ def verify_vlan_config(config): 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') + + 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!')) for vif_c in vif_s['vif_c']: # 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') + 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!')) -- cgit v1.2.3 From c113977f917ec56fe2b8707bfe8e381b4c181d7d Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 15:38:30 +0200 Subject: util: T2241: add get_bridge_member_config function Function that parses the config of a bridge member into a dict that is needed to apply all port config when adding a port to a bridge. Needed because other interfaces will be adding themselves to the bridge outside of the bridge conf_mode script and they need a common place to get their config. Can't be put as method of BridgeIf as we can't invoke it without it creating the bridge (create=False raises an exception), we need to get the configuration before we create the interface. --- python/vyos/util.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'python') diff --git a/python/vyos/util.py b/python/vyos/util.py index c708c1eb4..e598e0ff3 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -522,3 +522,50 @@ def ifname_from_config(conf): # 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 -- cgit v1.2.3 From e35807b209e3de77b09082a74d2b0f0b1a802206 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 15:47:11 +0200 Subject: interface: T2241: add function to add self to bridge Will be called by all interface scripts to re-add themselves to a bridge after deleting and recreating themselves. --- python/vyos/ifconfig/interface.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 5b3da228e..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): @@ -718,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 -- cgit v1.2.3 From 963dcd509fa491a30d0dd6266d827fb60bb9e27b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 16:01:53 +0200 Subject: vlan: T2241: fix falling out of bridge when changing settings Previously, set_vrf was always called, which uses the same master and nomaster commands as bridge, so it removed the interface from the bridge. - add checks to make VRF and bridge membership mutually exclusive - always re-add the interface back to any bridge it is part of in case it is deleted and recreated (e.g. changing egress/ingress-qos) --- python/vyos/ifconfig_vlan.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'python') diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 079118df6..bc43ff6db 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -63,8 +63,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,6 +94,10 @@ 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- @@ -103,7 +109,6 @@ def verify_vlan_config(config): if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif['vrf']: if vif['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif["vrf"]}" does not exist') @@ -127,7 +132,6 @@ def verify_vlan_config(config): if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif_s['vrf']: if vif_s['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_s["vrf"]}" does not exist') @@ -142,7 +146,6 @@ def verify_vlan_config(config): if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') - if vif_c['vrf']: if vif_c['vrf'] not in interfaces(): raise ConfigError(f'VRF "{vif_c["vrf"]}" does not exist') -- cgit v1.2.3 From d6e65c5b14b1049ba346cbaa7c05c12cacfd0495 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 22:54:53 +0200 Subject: configdict: T2241: don't add default IPv6 EUI64 if member of a bridge Bridge members should not have addresses assigned. --- python/vyos/configdict.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 0648d8646..5ab831258 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -344,8 +344,9 @@ def intf_to_dict(conf, default): 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 set or if member of a bridge + if ( conf.exists('ipv6 address no-default-link-local') + or intf.get('is_bridge_member') ): intf['ipv6_eui64_prefix_remove'].append('fe80::/64') else: # add the link-local by default to make IPv6 work -- cgit v1.2.3 From faa490ad122eeb1de02947bb4593539f0f476bd3 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 22:56:30 +0200 Subject: vlan: T2241: make address and bridge membership mutually exclusive Bridge members should not have addresses assigned. --- python/vyos/ifconfig_vlan.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index bc43ff6db..eb7a369ec 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -109,6 +109,14 @@ def verify_vlan_config(config): if vif['dhcpv6_prm_only'] and vif['dhcpv6_temporary']: raise ConfigError('DHCPv6 temporary and parameters-only options are mutually exclusive!') + 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') @@ -132,6 +140,14 @@ def verify_vlan_config(config): if vif_s['dhcpv6_prm_only'] and vif_s['dhcpv6_temporary']: 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') @@ -146,6 +162,14 @@ def verify_vlan_config(config): if vif_c['dhcpv6_prm_only'] and vif_c['dhcpv6_temporary']: 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') -- cgit v1.2.3 From 1c93e3895787641a207d09c562f2703fddc77b8f Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Sun, 3 May 2020 20:59:35 +0200 Subject: validate: T2241: add func that checks if an interface has a configured address --- python/vyos/validate.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'python') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index eb3f8bf52..e083604c5 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -282,3 +282,25 @@ def is_member(conf, interface, intftype=None): 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 -- cgit v1.2.3 From 109dbf9789bdc0b1e35b2b5ebd2fd0ce2a1ebe4a Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 17:36:47 +0200 Subject: ifconfig: T2241: fix section _basename vlan stripping Previously the function returned the correct basename only for vif interfaces as it stopped at the 2nd dot. If we had a vif-s vif-c interface 'eth0.1.2' it would return 'eth0.'. It is now fixed to strip both vif-s and vif-c if 'vlan=True' (default). --- python/vyos/ifconfig/section.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 092236fef..2d77c42cc 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 -- cgit v1.2.3 From 6f784231283ec3a63335145e914f2b15e28795dc Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Fri, 1 May 2020 17:40:58 +0200 Subject: ifconfig: section: T2241: add get_config_path function Add a function that converts an interface name to its config path. For example: 'eth0.1.2' -> 'ethernet eth0 vif-s 1 vif-c 2' --- python/vyos/ifconfig/section.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py index 2d77c42cc..926c22e8a 100644 --- a/python/vyos/ifconfig/section.py +++ b/python/vyos/ifconfig/section.py @@ -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 -- cgit v1.2.3 From 5fdd21714536a69348adbfdb97370c3201b401be Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 14:50:59 +0200 Subject: config: T2427: always return copies of lists Since lists in python are assigned by reference, taking the return value from these functions and modifying it will modify all other return values of functions that called the function before and did not explicitly copy it. To be safe, always make a copy of lists before returning them. --- python/vyos/config.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'python') 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) -- cgit v1.2.3 From 7a1c1fef7677e68d5a92c8538eda74a45b47a3c9 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 15:48:47 +0200 Subject: configdict: T2427: do not remove all addresses when disabling interface Commit 3fdf0093a introduced code that removed all addresses from an interface when that interface is disabled. This is wrong, as other configured services may be listening on these addresses and may fail to start if their configured address isn't present. It also caused a commit error when applying dhcp-server configuration: DHCP server configuration error! None of configured DHCP subnets does not have appropriate primary IP address on any broadcast interface. This commit reverts it to prior behavior, which was to just put the interface admin down and leave all addresses configured, other than the IPv6 'fe80::EUI-64/64' link-local, which it deletes, as the interface may not have a MAC if it's put down. --- python/vyos/configdict.py | 55 ++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 5ab831258..ceacf86fc 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -201,10 +201,6 @@ def intf_to_dict(conf, default): intf = deepcopy(default) intf['intf'] = ifname_from_config(conf) - # retrieve configured interface addresses - if conf.exists('address'): - intf['address'] = conf.return_values('address') - # retrieve interface description if conf.exists('description'): intf['description'] = conf.return_value('description') @@ -258,10 +254,6 @@ 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 @@ -304,49 +296,44 @@ 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 or if member of a bridge + # 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.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 @@ -358,7 +345,7 @@ def intf_to_dict(conf, default): 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 -- cgit v1.2.3 From 023b6b772ee6d6853e1c0e585b7f7d207e9926ce Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 15:57:31 +0200 Subject: vlan: T2427: convert vlan config variables from lists to dicts Previously all vlan configs, which are dicts, were appended to a simple list, with the distinguishing 'id' stored inside the dicts themselves. This worked, but wasn't ideal. This commit converts them to dicts, where the key is the VLAN ID and value the config dict of that VLAN. This makes it posible to access single VLANs by their ID (key) and we can for-loop and get both the ID and config with: 'for vif_id, vif in conf["vif"].items():' --- python/vyos/configdict.py | 12 ++++-------- python/vyos/ifconfig_vlan.py | 25 +++++++++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index ceacf86fc..666497389 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -132,7 +132,7 @@ vlan_default = { 'is_bridge_member': False, 'mac': '', 'mtu': 1500, - 'vif_c': [], + 'vif_c': {}, 'vif_c_remove': [], 'vrf': '' } @@ -400,7 +400,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) @@ -411,12 +412,8 @@ def add_to_dict(conf, disabled, ifdict, section, key): def vlan_to_dict(conf, default=vlan_default): vlan, disabled = intf_to_dict(conf, default) - level = conf.get_level() - # get the '100' in 'interfaces bonding bond0 vif-s 100' - vlan['id'] = level[-1] - # if this is a not within vif-s node, we are done - if level[-2] != 'vif-s': + if conf.get_level()[-2] != 'vif-s': return vlan # ethertype is mandatory on vif-s nodes and only exists here! @@ -428,7 +425,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/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index eb7a369ec..54736ec6f 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -104,7 +104,8 @@ def verify_vlan_config(config): 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!') @@ -131,14 +132,19 @@ def verify_vlan_config(config): 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!') + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are mutually ' + 'exclusive!')) if ( vif_s['is_bridge_member'] and ( vif_s['address'] @@ -157,10 +163,13 @@ def verify_vlan_config(config): 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!')) - for vif_c in vif_s['vif_c']: + # 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!') + raise ConfigError(( + 'DHCPv6 temporary and parameters-only options are ' + 'mutually exclusive!')) if ( vif_c['is_bridge_member'] and ( vif_c['address'] -- cgit v1.2.3 From 312cffce31cf3a411a87559bf99f78cde5facd60 Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:03:49 +0200 Subject: vlan: T2427: move code that applies VLANs to interface to common function --- python/vyos/ifconfig_vlan.py | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'python') diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 54736ec6f..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 -- cgit v1.2.3 From 0a3147631094dba44523bd439586086c1bf6a93b Mon Sep 17 00:00:00 2001 From: Jernej Jakob Date: Tue, 5 May 2020 16:04:54 +0200 Subject: configdict: T2427: clarify code comments --- python/vyos/configdict.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 666497389..5ca369f66 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -375,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'] = [] @@ -389,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 -- cgit v1.2.3 From faec542493eedf989f0703a1b4cedb74025bd9e2 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Wed, 6 May 2020 16:24:46 +0100 Subject: debug: T2426: remove invisible characters when printing --- python/vyos/debug.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'python') diff --git a/python/vyos/debug.py b/python/vyos/debug.py index 1a042cbb4..91431a7bb 100644 --- a/python/vyos/debug.py +++ b/python/vyos/debug.py @@ -81,10 +81,17 @@ def enabled(flag): return _fromenv(flag) or _fromfile(flag) +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' -- cgit v1.2.3