From ebefa38b9fa946fde82a4c9b55122c037598143b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 1 Jul 2020 19:06:52 +0200 Subject: ethernet: ifconfig: T2653: move to get_config_dict() The current VyOS CLI parser code written in Python contains a ton of duplicates which I can also hold myself accountable for - or maybe mainly me - depends on the angle of judge. While providing a new update() method in vyos.ifconfig.interfaces() this is extended for ethernet based interfaces which also supports 802.1q, 802.1ad VLANs. This commit migrates the existing codebase for an ethernet based interfaces and implements the missing parts for VLANs. Adding or migrating other interfaces (e.g. bridge or bond) will become much easier as they must reuse the entire functionality - we now walk towards a single codepath. Thanks for all who made this combined effort possible! Signed-off-by: Christian Poessinger --- python/vyos/configverify.py | 53 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 32129a048..36b10c956 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -41,14 +41,14 @@ def verify_vrf(config): def verify_address(config): """ - Common helper function used by interface implementations to - perform recurring validation of IP address assignmenr - when interface also is part of a bridge. + Common helper function used by interface implementations to perform + recurring validation of IP address assignment when interface is part + of a bridge or bond. """ if {'is_bridge_member', 'address'} <= set(config): raise ConfigError( - f'Cannot assign address to interface "{ifname}" as it is a ' - f'member of bridge "{is_bridge_member}"!'.format(**config)) + 'Cannot assign address to interface "{ifname}" as it is a ' + 'member of bridge "{is_bridge_member}"!'.format(**config)) def verify_bridge_delete(config): @@ -62,6 +62,15 @@ def verify_bridge_delete(config): 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) +def verify_interface_exists(config): + """ + Common helper function used by interface implementations to perform + recurring validation if an interface actually exists. + """ + from netifaces import interfaces + if not config['ifname'] in interfaces(): + raise ConfigError(f'Interface "{ifname}" does not exist!' + .format(**config)) def verify_source_interface(config): """ @@ -76,3 +85,37 @@ def verify_source_interface(config): if not config['source_interface'] in interfaces(): raise ConfigError(f'Source interface {source_interface} does not ' f'exist'.format(**config)) + +def verify_dhcpv6(config): + """ + Common helper function used by interface implementations to perform + recurring validation of DHCPv6 options which are mutually exclusive. + """ + if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + +def verify_vlan_config(config): + """ + Common helper function used by interface implementations to perform + recurring validation of interface VLANs + """ + # 802.1q VLANs + for vlan in config.get('vif', {}).keys(): + vlan = config['vif'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + # 802.1ad (Q-in-Q) VLANs + for vlan in config.get('vif_s', {}).keys(): + vlan = config['vif_s'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) + + for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + vlan = config['vif_c'][vlan] + verify_dhcpv6(vlan) + verify_address(vlan) + verify_vrf(vlan) -- cgit v1.2.3 From e57d76e86f7e5280eb065e98552c7d6395805c01 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 25 Jul 2020 17:53:32 +0200 Subject: vyos.configverify: T2653: fix some formatting issues --- python/vyos/configverify.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 36b10c956..8e06d16f2 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -79,12 +79,12 @@ def verify_source_interface(config): required by e.g. peth/MACvlan, MACsec ... """ from netifaces import interfaces - if not 'source_interface' in config.keys(): + if 'source_interface' not in config: raise ConfigError('Physical source-interface required for ' 'interface "{ifname}"'.format(**config)) - if not config['source_interface'] in interfaces(): - raise ConfigError(f'Source interface {source_interface} does not ' - f'exist'.format(**config)) + if config['source_interface'] not in interfaces(): + raise ConfigError('Source interface {source_interface} does not ' + 'exist'.format(**config)) def verify_dhcpv6(config): """ -- cgit v1.2.3 From dfbbdd2950c3db3f56649ff602a86b8bf17de748 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 1 Aug 2020 10:49:59 +0200 Subject: ifconfig: T2752: fix string format in verify_interface_exists() We do not have a formatted string here thus the "f" keyword is wrong and triggered an exception. --- python/vyos/configverify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 8e06d16f2..bb590a514 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -69,7 +69,7 @@ def verify_interface_exists(config): """ from netifaces import interfaces if not config['ifname'] in interfaces(): - raise ConfigError(f'Interface "{ifname}" does not exist!' + raise ConfigError('Interface "{ifname}" does not exist!' .format(**config)) def verify_source_interface(config): -- cgit v1.2.3 From 10fd2aa6ab205ef22e0bd3eef3767cb1ab00e164 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 15 Aug 2020 19:17:29 +0200 Subject: vyos.configverify: no need to call .keys() when searching dict --- python/vyos/configverify.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index bb590a514..d1519b0ac 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -29,11 +29,11 @@ def verify_vrf(config): recurring validation of VRF configuration. """ from netifaces import interfaces - if 'vrf' in config.keys(): + if 'vrf' in config: if config['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) - if 'is_bridge_member' in config.keys(): + if 'is_bridge_member' in config: raise ConfigError( 'Interface "{ifname}" cannot be both a member of VRF "{vrf}" ' 'and bridge "{is_bridge_member}"!'.format(**config)) @@ -57,7 +57,7 @@ def verify_bridge_delete(config): perform recurring validation of IP address assignmenr when interface also is part of a bridge. """ - if 'is_bridge_member' in config.keys(): + if 'is_bridge_member' in config: raise ConfigError( 'Interface "{ifname}" cannot be deleted as it is a ' 'member of bridge "{is_bridge_member}"!'.format(**config)) @@ -101,20 +101,20 @@ def verify_vlan_config(config): recurring validation of interface VLANs """ # 802.1q VLANs - for vlan in config.get('vif', {}).keys(): + for vlan in config.get('vif', {}): vlan = config['vif'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) # 802.1ad (Q-in-Q) VLANs - for vlan in config.get('vif_s', {}).keys(): + for vlan in config.get('vif_s', {}): vlan = config['vif_s'][vlan] verify_dhcpv6(vlan) verify_address(vlan) verify_vrf(vlan) - for vlan in config.get('vif_s', {}).get('vif_c', {}).keys(): + for vlan in config.get('vif_s', {}).get('vif_c', {}): vlan = config['vif_c'][vlan] verify_dhcpv6(vlan) verify_address(vlan) -- cgit v1.2.3 From 20ef1aab793504cf4956dedeeadaf528933b7ccf Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 13:06:37 +0200 Subject: vyos.configverify: T2677: extend verify_dhcpv6() for non duplicate sla-ids --- python/vyos/configverify.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index d1519b0ac..264dd1c30 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -91,9 +91,26 @@ def verify_dhcpv6(config): Common helper function used by interface implementations to perform recurring validation of DHCPv6 options which are mutually exclusive. """ - if {'parameters_only', 'temporary'} <= set(config.get('dhcpv6_options', {})): - raise ConfigError('DHCPv6 temporary and parameters-only options ' - 'are mutually exclusive!') + if 'dhcpv6_options' in config: + from vyos.util import vyos_dict_search + if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): + raise ConfigError('DHCPv6 temporary and parameters-only options ' + 'are mutually exclusive!') + + # It is not allowed to have duplicate SLA-IDs as those identify an + # assigned IPv6 subnet from a delegated prefix + for pd in vyos_dict_search(config, 'dhcpv6_options.pd'): + sla_ids = [] + for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): + sla_id = vyos_dict_search(config, + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + sla_ids.append(sla_id) + + # Check for duplicates + duplicates = [x for n, x in enumerate(sla_ids) if x in sla_ids[:n]] + if duplicates: + raise ConfigError('Site-Level Aggregation Identifier (SLA-ID) ' + 'must be unique per prefix-delegation!') def verify_vlan_config(config): """ -- cgit v1.2.3 From aa25690c83c4812c92490d29b564dd0330b24d34 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 23 Aug 2020 14:22:41 +0200 Subject: T2755: convert jmespath.search() to vyos_dict_search() for performance --- python/vyos/configdict.py | 6 +++--- python/vyos/configverify.py | 9 +++++---- python/vyos/ifconfig/bond.py | 6 +++--- python/vyos/ifconfig/bridge.py | 9 ++++----- python/vyos/ifconfig/ethernet.py | 12 ++++++------ python/vyos/ifconfig/interface.py | 33 +++++++++++++++++---------------- python/vyos/util.py | 2 +- 7 files changed, 39 insertions(+), 38 deletions(-) (limited to 'python/vyos/configverify.py') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index c1e93955e..bd8624ced 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -17,7 +17,6 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion. """ import os -import jmespath from enum import Enum from copy import deepcopy @@ -226,8 +225,9 @@ def get_interface_dict(config, base, ifname=''): Will return a dictionary with the necessary interface configuration """ - from vyos.xml import defaults + from vyos.util import vyos_dict_search from vyos.validate import is_member + from vyos.xml import defaults if not ifname: # determine tagNode instance @@ -273,7 +273,7 @@ def get_interface_dict(config, base, ifname=''): # XXX: T2636 workaround: convert string to a list with one element if isinstance(eui64, str): eui64 = [eui64] - tmp = jmespath.search('ipv6.address', dict) + tmp = vyos_dict_search('ipv6.address', dict) if not tmp: dict.update({'ipv6': {'address': {'eui64_old': eui64}}}) else: diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 264dd1c30..7e1930878 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -93,17 +93,18 @@ def verify_dhcpv6(config): """ if 'dhcpv6_options' in config: from vyos.util import vyos_dict_search + if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']): raise ConfigError('DHCPv6 temporary and parameters-only options ' 'are mutually exclusive!') # It is not allowed to have duplicate SLA-IDs as those identify an # assigned IPv6 subnet from a delegated prefix - for pd in vyos_dict_search(config, 'dhcpv6_options.pd'): + for pd in vyos_dict_search('dhcpv6_options.pd', config): sla_ids = [] - for interface in vyos_dict_search(config, f'dhcpv6_options.pd.{pd}.interface'): - sla_id = vyos_dict_search(config, - f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id') + for interface in vyos_dict_search(f'dhcpv6_options.pd.{pd}.interface', config): + sla_id = vyos_dict_search( + f'dhcpv6_options.pd.{pd}.interface.{interface}.sla_id', config) sla_ids.append(sla_id) # Check for duplicates diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 193cea321..64407401b 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -14,12 +14,12 @@ # License along with this library. If not, see . import os -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.util import cmd +from vyos.util import vyos_dict_search from vyos.validate import assert_list from vyos.validate import assert_positive @@ -336,7 +336,7 @@ class BondIf(Interface): self.set_arp_ip_target('-' + addr) # Add configured ARP target addresses - value = jmespath.search('arp_monitor.target', config) + value = vyos_dict_search('arp_monitor.target', config) if isinstance(value, str): value = [value] if value: @@ -359,7 +359,7 @@ class BondIf(Interface): if value: self.set_mode(value) # Add (enslave) interfaces to bond - value = jmespath.search('member.interface', config) + value = vyos_dict_search('member.interface', config) if value: for interface in value: # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 466e6b682..4c76fe996 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -13,13 +13,12 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . -import jmespath - from vyos.ifconfig.interface import Interface from vyos.ifconfig.stp import STP from vyos.validate import assert_boolean from vyos.validate import assert_positive from vyos.util import cmd +from vyos.util import vyos_dict_search @Interface.register class BridgeIf(Interface): @@ -223,18 +222,18 @@ class BridgeIf(Interface): self.set_stp(value) # enable or disable IGMP querier - tmp = jmespath.search('igmp.querier', config) + tmp = vyos_dict_search('igmp.querier', config) value = '1' if (tmp != None) else '0' self.set_multicast_querier(value) # remove interface from bridge - tmp = jmespath.search('member.interface_remove', config) + tmp = vyos_dict_search('member.interface_remove', config) if tmp: for member in tmp: self.del_port(member) STPBridgeIf = STP.enable(BridgeIf) - tmp = jmespath.search('member.interface', config) + tmp = vyos_dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): # if we've come here we already verified the interface diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index b2f701e00..17c1bd64d 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -15,12 +15,12 @@ import os import re -import jmespath from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLAN from vyos.validate import assert_list from vyos.util import run +from vyos.util import vyos_dict_search @Interface.register @VLAN.enable @@ -268,27 +268,27 @@ class EthernetIf(Interface): self.set_flow_control(value) # GRO (generic receive offload) - tmp = jmespath.search('offload_options.generic_receive', config) + tmp = vyos_dict_search('offload_options.generic_receive', config) value = tmp if (tmp != None) else 'off' self.set_gro(value) # GSO (generic segmentation offload) - tmp = jmespath.search('offload_options.generic_segmentation', config) + tmp = vyos_dict_search('offload_options.generic_segmentation', config) value = tmp if (tmp != None) else 'off' self.set_gso(value) # scatter-gather option - tmp = jmespath.search('offload_options.scatter_gather', config) + tmp = vyos_dict_search('offload_options.scatter_gather', config) value = tmp if (tmp != None) else 'off' self.set_sg(value) # TSO (TCP segmentation offloading) - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_tso(value) # UDP fragmentation offloading - tmp = jmespath.search('offload_options.udp_fragmentation', config) + tmp = vyos_dict_search('offload_options.udp_fragmentation', config) value = tmp if (tmp != None) else 'off' self.set_ufo(value) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 537c4bc2d..67ba973c4 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -34,6 +34,7 @@ from vyos.configdict import list_diff from vyos.configdict import dict_merge from vyos.template import render from vyos.util import mac2eui64 +from vyos.util import vyos_dict_search from vyos.validate import is_ipv4 from vyos.validate import is_ipv6 from vyos.validate import is_intf_addr_assigned @@ -824,7 +825,7 @@ class Interface(Control): lease_file = f'{config_base}_{ifname}.leases' if enable and 'disable' not in self._config: - if jmespath.search('dhcp_options.host_name', self._config) == None: + if vyos_dict_search('dhcp_options.host_name', self._config) == None: # read configured system hostname. # maybe change to vyos hostd client ??? hostname = 'vyos' @@ -907,7 +908,7 @@ class Interface(Control): # always ensure DHCPv6 client is stopped (when not configured as client # for IPv6 address or prefix delegation - dhcpv6pd = jmespath.search('dhcpv6_options.pd', config) + dhcpv6pd = vyos_dict_search('dhcpv6_options.pd', config) if 'dhcpv6' not in new_addr or dhcpv6pd == None: self.del_addr('dhcpv6') @@ -935,59 +936,59 @@ class Interface(Control): self.set_vrf(config.get('vrf', '')) # Configure ARP cache timeout in milliseconds - has default value - tmp = jmespath.search('ip.arp_cache_timeout', config) + tmp = vyos_dict_search('ip.arp_cache_timeout', config) value = tmp if (tmp != None) else '30' self.set_arp_cache_tmo(value) # Configure ARP filter configuration - tmp = jmespath.search('ip.disable_arp_filter', config) + tmp = vyos_dict_search('ip.disable_arp_filter', config) value = '0' if (tmp != None) else '1' self.set_arp_filter(value) # Configure ARP accept - tmp = jmespath.search('ip.enable_arp_accept', config) + tmp = vyos_dict_search('ip.enable_arp_accept', config) value = '1' if (tmp != None) else '0' self.set_arp_accept(value) # Configure ARP announce - tmp = jmespath.search('ip.enable_arp_announce', config) + tmp = vyos_dict_search('ip.enable_arp_announce', config) value = '1' if (tmp != None) else '0' self.set_arp_announce(value) # Configure ARP ignore - tmp = jmespath.search('ip.enable_arp_ignore', config) + tmp = vyos_dict_search('ip.enable_arp_ignore', config) value = '1' if (tmp != None) else '0' self.set_arp_ignore(value) # Enable proxy-arp on this interface - tmp = jmespath.search('ip.enable_proxy_arp', config) + tmp = vyos_dict_search('ip.enable_proxy_arp', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp(value) # Enable private VLAN proxy ARP on this interface - tmp = jmespath.search('ip.proxy_arp_pvlan', config) + tmp = vyos_dict_search('ip.proxy_arp_pvlan', config) value = '1' if (tmp != None) else '0' self.set_proxy_arp_pvlan(value) # IPv6 forwarding - tmp = jmespath.search('ipv6.disable_forwarding', config) + tmp = vyos_dict_search('ipv6.disable_forwarding', config) value = '0' if (tmp != None) else '1' self.set_ipv6_forwarding(value) # IPv6 router advertisements - tmp = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_dict_search('ipv6.address.autoconf', config) value = '2' if (tmp != None) else '1' if 'dhcpv6' in new_addr: value = '2' self.set_ipv6_accept_ra(value) # IPv6 address autoconfiguration - tmp = jmespath.search('ipv6.address.autoconf', config) + tmp = vyos_dict_search('ipv6.address.autoconf', config) value = '1' if (tmp != None) else '0' self.set_ipv6_autoconf(value) # IPv6 Duplicate Address Detection (DAD) tries - tmp = jmespath.search('ipv6.dup_addr_detect_transmits', config) + tmp = vyos_dict_search('ipv6.dup_addr_detect_transmits', config) value = tmp if (tmp != None) else '1' self.set_ipv6_dad_messages(value) @@ -996,7 +997,7 @@ class Interface(Control): self.set_mtu(config.get('mtu')) # Delete old IPv6 EUI64 addresses before changing MAC - tmp = jmespath.search('ipv6.address.eui64_old', config) + tmp = vyos_dict_search('ipv6.address.eui64_old', config) if tmp: for addr in tmp: self.del_ipv6_eui64_address(addr) @@ -1011,7 +1012,7 @@ class Interface(Control): self.set_mac(mac) # Manage IPv6 link-local addresses - tmp = jmespath.search('ipv6.address.no_default_link_local', config) + tmp = vyos_dict_search('ipv6.address.no_default_link_local', config) # we must check explicitly for None type as if the key is set we will # get an empty dict () if tmp is not None: @@ -1020,7 +1021,7 @@ class Interface(Control): self.add_ipv6_eui64_address('fe80::/64') # Add IPv6 EUI-based addresses - tmp = jmespath.search('ipv6.address.eui64', config) + tmp = vyos_dict_search('ipv6.address.eui64', config) if tmp: # XXX: T2636 workaround: convert string to a list with one element if isinstance(tmp, str): diff --git a/python/vyos/util.py b/python/vyos/util.py index 4cc25764b..84aa16791 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -673,7 +673,7 @@ def find_device_file(device): return None -def vyos_dict_search(dict, path): +def vyos_dict_search(path, dict): """ Traverse Python dictionary (dict) delimited by dot (.). Return value of key if found, None otherwise. -- cgit v1.2.3