diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configquery.py | 90 | ||||
-rw-r--r-- | python/vyos/configverify.py | 63 | ||||
-rw-r--r-- | python/vyos/frr.py | 13 | ||||
-rw-r--r-- | python/vyos/ifconfig/__init__.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 6 | ||||
-rwxr-xr-x | python/vyos/ifconfig/erspan.py | 170 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 43 | ||||
-rw-r--r-- | python/vyos/ifconfig/tunnel.py | 12 | ||||
-rw-r--r-- | python/vyos/template.py | 5 | ||||
-rw-r--r-- | python/vyos/util.py | 30 | ||||
-rw-r--r-- | python/vyos/xml/kw.py | 1 | ||||
-rw-r--r-- | python/vyos/xml/load.py | 2 |
12 files changed, 215 insertions, 222 deletions
diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py new file mode 100644 index 000000000..ed7346f1f --- /dev/null +++ b/python/vyos/configquery.py @@ -0,0 +1,90 @@ +# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +''' +A small library that allows querying existence or value(s) of config +settings from op mode, and execution of arbitrary op mode commands. +''' + +from subprocess import STDOUT +from vyos.util import popen + + +class ConfigQueryError(Exception): + pass + +class GenericConfigQuery: + def __init__(self): + pass + + def exists(self, path: list): + raise NotImplementedError + + def value(self, path: list): + raise NotImplementedError + + def values(self, path: list): + raise NotImplementedError + +class GenericOpRun: + def __init__(self): + pass + + def run(self, path: list, **kwargs): + raise NotImplementedError + +class CliShellApiConfigQuery(GenericConfigQuery): + def __init__(self): + super().__init__() + + def exists(self, path: list): + cmd = ' '.join(path) + (_, err) = popen(f'cli-shell-api existsActive {cmd}') + if err: + return False + return True + + def value(self, path: list): + cmd = ' '.join(path) + (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}') + if err: + raise ConfigQueryError('No value for given path') + return out + + def values(self, path: list): + cmd = ' '.join(path) + (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}') + if err: + raise ConfigQueryError('No values for given path') + return out + +class VbashOpRun(GenericOpRun): + def __init__(self): + super().__init__() + + def run(self, path: list, **kwargs): + cmd = ' '.join(path) + (out, err) = popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs) + if err: + raise ConfigQueryError(out) + return out + +def query_context(config_query_class=CliShellApiConfigQuery, + op_run_class=VbashOpRun): + query = config_query_class() + run = op_run_class() + return query, run + + diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 7cf2cb8f9..99c472582 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -80,7 +80,7 @@ def verify_vrf(config): recurring validation of VRF configuration. """ from netifaces import interfaces - if 'vrf' in config: + if 'vrf' in config and config['vrf'] != 'default': if config['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**config)) @@ -337,18 +337,16 @@ def verify_accel_ppp_base_service(config): def verify_diffie_hellman_length(file, min_keysize): """ Verify Diffie-Hellamn keypair length given via file. It must be greater then or equal to min_keysize """ + import os + import re + from vyos.util import cmd try: keysize = str(min_keysize) except: return False - import os - import re - from vyos.util import cmd - if os.path.exists(file): - out = cmd(f'openssl dhparam -inform PEM -in {file} -text') prog = re.compile('\d+\s+bit') if prog.search(out): @@ -358,26 +356,55 @@ def verify_diffie_hellman_length(file, min_keysize): return False -def verify_route_maps(config): +def verify_common_route_maps(config): """ Common helper function used by routing protocol implementations to perform recurring validation if the specified route-map for either zebra to kernel installation exists (this is the top-level route_map key) or when a route is redistributed with a route-map that it exists! """ - if 'route_map' in config: - route_map = config['route_map'] + # XXX: This function is called in combination with a previous call to: + # tmp = conf.get_config_dict(['policy']) - see protocols_ospf.py as example. + # We should NOT call this with the key_mangling option as this would rename + # route-map hypens '-' to underscores '_' and one could no longer distinguish + # what should have been the "proper" route-map name, as foo-bar and foo_bar + # are two entire different route-map instances! + for route_map in ['route-map', 'route_map']: + if route_map not in config: + continue + tmp = config[route_map] # Check if the specified route-map exists, if not error out - if dict_search(f'policy.route_map.{route_map}', config) == None: - raise ConfigError(f'Specified route-map "{route_map}" does not exist!') + if dict_search(f'policy.route-map.{tmp}', config) == None: + raise ConfigError(f'Specified route-map "{tmp}" does not exist!') if 'redistribute' in config: for protocol, protocol_config in config['redistribute'].items(): if 'route_map' in protocol_config: - # A hyphen in a route-map name will be converted to _, take care - # about this effect during validation - route_map = protocol_config['route_map'].replace('-','_') - # Check if the specified route-map exists, if not error out - if dict_search(f'policy.route_map.{route_map}', config) == None: - raise ConfigError(f'Redistribution route-map "{route_map}" ' \ - f'for "{protocol}" does not exist!') + verify_route_map(protocol_config['route_map'], config) + +def verify_route_map(route_map_name, config): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified route-map exists! + """ + # Check if the specified route-map exists, if not error out + if dict_search(f'policy.route-map.{route_map_name}', config) == None: + raise ConfigError(f'Specified route-map "{route_map_name}" does not exist!') + +def verify_prefix_list(prefix_list, config, version=''): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified prefix-list exists! + """ + # Check if the specified prefix-list exists, if not error out + if dict_search(f'policy.prefix-list{version}.{prefix_list}', config) == None: + raise ConfigError(f'Specified prefix-list{version} "{prefix_list}" does not exist!') + +def verify_access_list(access_list, config, version=''): + """ + Common helper function used by routing protocol implementations to perform + recurring validation if a specified prefix-list exists! + """ + # Check if the specified ACL exists, if not error out + if dict_search(f'policy.access-list{version}.{access_list}', config) == None: + raise ConfigError(f'Specified access-list{version} "{access_list}" does not exist!') diff --git a/python/vyos/frr.py b/python/vyos/frr.py index 69c7a14ce..de3dbe6e9 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -68,6 +68,8 @@ Apply the new configuration: import tempfile import re from vyos import util +from vyos.util import chown +from vyos.util import cmd import logging from logging.handlers import SysLogHandler import os @@ -86,6 +88,7 @@ _frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd', path_vtysh = '/usr/bin/vtysh' path_frr_reload = '/usr/lib/frr/frr-reload.py' +path_config = '/run/frr' class FrrError(Exception): @@ -207,6 +210,16 @@ def reload_configuration(config, daemon=None): return output +def save_configuration(): + """Save FRR configuration to /run/frr/config/frr.conf + It save configuration on each commit. T3217 + """ + + cmd(f'{path_vtysh} -n -w') + + return + + def execute(command): """ Run commands inside vtysh command: str containing commands to execute inside a vtysh session diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index f5dfa8e05..e9da1e9f5 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -32,8 +32,6 @@ from vyos.ifconfig.vtun import VTunIf from vyos.ifconfig.vti import VTIIf from vyos.ifconfig.pppoe import PPPoEIf from vyos.ifconfig.tunnel import TunnelIf -from vyos.ifconfig.erspan import ERSpanIf -from vyos.ifconfig.erspan import ER6SpanIf from vyos.ifconfig.wireless import WiFiIf from vyos.ifconfig.l2tpv3 import L2TPv3If from vyos.ifconfig.macsec import MACsecIf diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 600bd3db8..14f64a8de 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -312,9 +312,15 @@ class BridgeIf(Interface): # not have any addresses configured by CLI so just flush any # remaining ones lower.flush_addrs() + # enslave interface port to bridge self.add_port(interface) + # always set private-vlan/port isolation + tmp = dict_search('isolated', interface_config) + value = 'on' if (tmp != None) else 'off' + lower.set_port_isolation(value) + # set bridge port path cost if 'cost' in interface_config: value = interface_config.get('cost') diff --git a/python/vyos/ifconfig/erspan.py b/python/vyos/ifconfig/erspan.py deleted file mode 100755 index 03b2acdbf..000000000 --- a/python/vyos/ifconfig/erspan.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see <http://www.gnu.org/licenses/>. - -# https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#erspan -# http://vger.kernel.org/lpc_net2018_talks/erspan-linux-presentation.pdf - -from copy import deepcopy - -from netaddr import EUI -from netaddr import mac_unix_expanded -from random import getrandbits - -from vyos.util import dict_search -from vyos.ifconfig.interface import Interface -from vyos.validate import assert_list - -@Interface.register -class _ERSpan(Interface): - """ - _ERSpan: private base class for ERSPAN tunnels - """ - iftype = 'erspan' - definition = { - **Interface.definition, - **{ - 'section': 'erspan', - 'prefixes': ['ersp',], - }, - } - - def __init__(self,ifname,**config): - self.config = deepcopy(config) if config else {} - super().__init__(ifname, **self.config) - - def change_options(self): - pass - - def _create(self): - pass - -class ERSpanIf(_ERSpan): - """ - ERSpanIf: private base class for ERSPAN Over GRE and IPv4 tunnels - """ - - def _create(self): - ifname = self.config['ifname'] - source_address = self.config['source_address'] - remote = self.config['remote'] - key = self.config['parameters']['ip']['key'] - version = self.config['parameters']['version'] - command = f'ip link add dev {ifname} type erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}' - - if int(version) == 1: - idx=dict_search('parameters.erspan.idx',self.config) - if idx: - command += f' erspan {idx}' - elif int(version) == 2: - direction=dict_search('parameters.erspan.direction',self.config) - if direction: - command += f' erspan_dir {direction}' - hwid=dict_search('parameters.erspan.hwid',self.config) - if hwid: - command += f' erspan_hwid {hwid}' - - ttl = dict_search('parameters.ip.ttl',self.config) - if ttl: - command += f' ttl {ttl}' - tos = dict_search('parameters.ip.tos',self.config) - if tos: - command += f' tos {tos}' - - self._cmd(command) - - def change_options(self): - ifname = self.config['ifname'] - source_address = self.config['source_address'] - remote = self.config['remote'] - key = self.config['parameters']['ip']['key'] - version = self.config['parameters']['version'] - command = f'ip link set dev {ifname} type erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}' - - if int(version) == 1: - idx=dict_search('parameters.erspan.idx',self.config) - if idx: - command += f' erspan {idx}' - elif int(version) == 2: - direction=dict_search('parameters.erspan.direction',self.config) - if direction: - command += f' erspan_dir {direction}' - hwid=dict_search('parameters.erspan.hwid',self.config) - if hwid: - command += f' erspan_hwid {hwid}' - - ttl = dict_search('parameters.ip.ttl',self.config) - if ttl: - command += f' ttl {ttl}' - tos = dict_search('parameters.ip.tos',self.config) - if tos: - command += f' tos {tos}' - - self._cmd(command) - -class ER6SpanIf(_ERSpan): - """ - ER6SpanIf: private base class for ERSPAN Over GRE and IPv6 tunnels - """ - - def _create(self): - ifname = self.config['ifname'] - source_address = self.config['source_address'] - remote = self.config['remote'] - key = self.config['parameters']['ip']['key'] - version = self.config['parameters']['version'] - command = f'ip link add dev {ifname} type ip6erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}' - - if int(version) == 1: - idx=dict_search('parameters.erspan.idx',self.config) - if idx: - command += f' erspan {idx}' - elif int(version) == 2: - direction=dict_search('parameters.erspan.direction',self.config) - if direction: - command += f' erspan_dir {direction}' - hwid=dict_search('parameters.erspan.hwid',self.config) - if hwid: - command += f' erspan_hwid {hwid}' - - ttl = dict_search('parameters.ip.ttl',self.config) - if ttl: - command += f' ttl {ttl}' - tos = dict_search('parameters.ip.tos',self.config) - if tos: - command += f' tos {tos}' - - self._cmd(command) - - def change_options(self): - ifname = self.config['ifname'] - source_address = self.config['source_address'] - remote = self.config['remote'] - key = self.config['parameters']['ip']['key'] - version = self.config['parameters']['version'] - command = f'ip link set dev {ifname} type ip6erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}' - - if int(version) == 1: - idx=dict_search('parameters.erspan.idx',self.config) - if idx: - command += f' erspan {idx}' - elif int(version) == 2: - direction=dict_search('parameters.erspan.direction',self.config) - if direction: - command += f' erspan_dir {direction}' - hwid=dict_search('parameters.erspan.hwid',self.config) - if hwid: - command += f' erspan_hwid {hwid}' - - self._cmd(command) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index fe6a3c95e..ff05cab0e 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -113,6 +113,10 @@ class Interface(Control): 'convert': lambda name: name if name else '', 'shellcmd': 'ip link set dev {ifname} alias "{value}"', }, + 'bridge_port_isolation': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'shellcmd': 'bridge link set dev {ifname} isolated {value}', + }, 'mac': { 'validate': assert_mac, 'shellcmd': 'ip link set dev {ifname} address {value}', @@ -689,6 +693,20 @@ class Interface(Control): """ self.set_interface('path_priority', priority) + def set_port_isolation(self, on_or_off): + """ + Controls whether a given port will be isolated, which means it will be + able to communicate with non-isolated ports only. By default this flag + is off. + + Use enable=1 to enable or enable=0 to disable + + Example: + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').set_port_isolation('on') + """ + self.set_interface('bridge_port_isolation', on_or_off) + def set_proxy_arp(self, enable): """ Set per interface proxy ARP configuration @@ -1051,6 +1069,10 @@ class Interface(Control): if not isinstance(state, bool): raise ValueError("Value out of range") + # https://phabricator.vyos.net/T3448 - there is (yet) no RPI support for XDP + if not os.path.exists('/usr/sbin/xdp_loader'): + return + ifname = self.config['ifname'] cmd = f'xdp_loader -d {ifname} -U --auto-mode' if state: @@ -1307,27 +1329,6 @@ class VLANIf(Interface): """ Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """ iftype = 'vlan' - def remove(self): - """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses and clear possible DHCP(v6) - client processes. - - Example: - >>> from vyos.ifconfig import Interface - >>> VLANIf('eth0.10').remove - """ - # Do we have sub interfaces (VLANs)? As interfaces need to be deleted - # "in order" starting from Q-in-Q we delete them first. - for upper in glob(f'/sys/class/net/{self.ifname}/upper*'): - # an upper interface could be named: upper_bond0.1000.1100, thus - # we need top drop the upper_ prefix - vif_c = os.path.basename(upper) - vif_c = vif_c.replace('upper_', '') - VLANIf(vif_c).remove() - - super().remove() - def _create(self): # bail out early if interface already exists if self.exists(f'{self.ifname}'): diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index e5e1300b2..08854a3b0 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -63,6 +63,10 @@ class TunnelIf(Interface): 'parameters.ip.no_pmtu_discovery' : 'nopmtudisc', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', + 'parameters.erspan.direction' : 'erspan_dir', + 'parameters.erspan.hw_id' : 'erspan_hwid', + 'parameters.erspan.index' : 'erspan', + 'parameters.erspan.version' : 'erspan_ver', } mapping_ipv6 = { 'parameters.ipv6.encaplimit' : 'encaplimit', @@ -113,8 +117,12 @@ class TunnelIf(Interface): mapping = { **self.mapping, **self.mapping_ipv4 } cmd = 'ip tunnel add {ifname} mode {encapsulation}' - if self.iftype in ['gretap', 'ip6gretap']: + if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']: cmd = 'ip link add name {ifname} type {encapsulation}' + # ERSPAN requires the serialisation of packets + if self.iftype in ['erspan', 'ip6erspan']: + cmd += ' seq' + for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like # "parameters.nolearning" - thus we need to test the nodes existence @@ -131,7 +139,7 @@ class TunnelIf(Interface): def _change_options(self): # gretap interfaces do not support changing any parameter - if self.iftype in ['gretap', 'ip6gretap']: + if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']: return if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: diff --git a/python/vyos/template.py b/python/vyos/template.py index 85e4d12b3..7810f5edd 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -207,6 +207,11 @@ def network_from_ipv4(address): cidr_prefix = ip_interface(f'{address}/{netmask}').network return address_from_cidr(cidr_prefix) +@register_filter('is_interface') +def is_interface(interface): + """ Check if parameter is a valid local interface name """ + return os.path.exists(f'/sys/class/net/{interface}') + @register_filter('is_ip') def is_ip(addr): """ Check addr if it is an IPv4 or IPv6 address """ diff --git a/python/vyos/util.py b/python/vyos/util.py index 17a7dda91..e2f4b8fc4 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -630,23 +630,26 @@ def find_device_file(device): return None -def dict_search(path, dict): - """ Traverse Python dictionary (dict) delimited by dot (.). +def dict_search(path, my_dict): + """ Traverse Python dictionary (my_dict) delimited by dot (.). Return value of key if found, None otherwise. - This is faster implementation then jmespath.search('foo.bar', dict)""" + This is faster implementation then jmespath.search('foo.bar', my_dict)""" + if not isinstance(my_dict, dict) or not path: + return None + parts = path.split('.') inside = parts[:-1] if not inside: - if path not in dict: + if path not in my_dict: return None - return dict[path] - c = dict + return my_dict[path] + c = my_dict for p in parts[:-1]: c = c.get(p, {}) return c.get(parts[-1], None) -def get_json_iface_options(interface): +def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. """ @@ -655,3 +658,16 @@ def get_json_iface_options(interface): from json import loads tmp = loads(cmd(f'ip -d -j link show {interface}'))[0] return tmp + +def get_all_vrfs(): + """ Return a dictionary of all system wide known VRF instances """ + from json import loads + tmp = loads(cmd('ip -j vrf list')) + # Result is of type [{"name":"red","table":1000},{"name":"blue","table":2000}] + # so we will re-arrange it to a more nicer representation: + # {'red': {'table': 1000}, 'blue': {'table': 2000}} + data = {} + for entry in tmp: + name = entry.pop('name') + data[name] = entry + return data diff --git a/python/vyos/xml/kw.py b/python/vyos/xml/kw.py index 64521c51a..58d47e751 100644 --- a/python/vyos/xml/kw.py +++ b/python/vyos/xml/kw.py @@ -27,7 +27,6 @@ def found(word): # root -version = '[version]' tree = '[tree]' priorities = '[priorities]' owners = '[owners]' diff --git a/python/vyos/xml/load.py b/python/vyos/xml/load.py index 1f463a5b7..0965d4220 100644 --- a/python/vyos/xml/load.py +++ b/python/vyos/xml/load.py @@ -115,7 +115,7 @@ def _format_nodes(inside, conf, xml): nodetype = 'tagNode' nodename = kw.tagNode elif 'syntaxVersion' in conf.keys(): - r[kw.version] = conf.pop('syntaxVersion')['@version'] + conf.pop('syntaxVersion') continue else: _fatal(conf.keys()) |