diff --git a/data/configd-include.json b/data/configd-include.json index eed858363..4959e5020 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -1,71 +1,70 @@ [ "bcast_relay.py", "dhcp_relay.py", "dhcpv6_relay.py", "dns_forwarding.py", "dynamic_dns.py", "firewall_options.py", "host_name.py", "https.py", "igmp_proxy.py", "intel_qat.py", "interfaces-bonding.py", "interfaces-bridge.py", "interfaces-dummy.py", "interfaces-ethernet.py", "interfaces-geneve.py", "interfaces-l2tpv3.py", "interfaces-loopback.py", "interfaces-macsec.py", "interfaces-openvpn.py", "interfaces-pppoe.py", "interfaces-pseudo-ethernet.py", "interfaces-tunnel.py", -"interfaces-erspan.py", "interfaces-vxlan.py", "interfaces-wireguard.py", "interfaces-wireless.py", "interfaces-wirelessmodem.py", "ipsec-settings.py", "lldp.py", "nat.py", "nat66.py", "ntp.py", "policy-local-route.py", "protocols_bfd.py", "protocols_bgp.py", "protocols_igmp.py", "protocols_isis.py", "protocols_mpls.py", "protocols_ospf.py", "protocols_ospfv3.py", "protocols_pim.py", "protocols_rip.py", "protocols_ripng.py", "protocols_static.py", "protocols_static_multicast.py", "salt-minion.py", "service_console-server.py", "service_ids_fastnetmon.py", "service_ipoe-server.py", "service_mdns-repeater.py", "service_pppoe-server.py", "service_router-advert.py", "ssh.py", "system-ip.py", "system-ipv6.py", "system-login-banner.py", "system-option.py", "system-syslog.py", "system-timezone.py", "system_console.py", "system_lcd.py", "task_scheduler.py", "tftp_server.py", "vpn_l2tp.py", "vpn_pptp.py", "vpn_sstp.py", "vrf.py", "vrrp.py", "vyos_cert.py" ] diff --git a/interface-definitions/interfaces-erspan.xml.in b/interface-definitions/interfaces-erspan.xml.in deleted file mode 100644 index 769899415..000000000 --- a/interface-definitions/interfaces-erspan.xml.in +++ /dev/null @@ -1,114 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> - <node name="interfaces"> - <children> - <tagNode name="erspan" owner="${vyos_conf_scripts_dir}/interfaces-erspan.py"> - <properties> - <help>Encapsulated Remote SPAN over GRE and IPv4/IPv6 Tunnel Interface</help> - <priority>310</priority> - <constraint> - <regex>^ersp[0-9]+$</regex> - </constraint> - <constraintErrorMessage>ERSPAN tunnel interface must be named erspN</constraintErrorMessage> - <valueHelp> - <format>erspN</format> - <description>ERSPAN Tunnel interface name</description> - </valueHelp> - </properties> - <children> - #include <include/interface/interface-description.xml.i> - #include <include/interface/interface-disable.xml.i> - #include <include/interface/interface-disable-link-detect.xml.i> - #include <include/interface/interface-mtu-64-8024.xml.i> - #include <include/source-address-ipv4-ipv6.xml.i> - #include <include/interface/tunnel-remote.xml.i> - <leafNode name="encapsulation"> - <properties> - <help>Encapsulation of this tunnel interface</help> - <completionHelp> - <list>erspan ip6erspan</list> - </completionHelp> - <valueHelp> - <format>erspan</format> - <description>Generic Routing Encapsulation</description> - </valueHelp> - <valueHelp> - <format>ip6erspan</format> - <description>Generic Routing Encapsulation bridge interface</description> - </valueHelp> - <constraint> - <regex>^(erspan|ip6erspan)$</regex> - </constraint> - <constraintErrorMessage>Invalid encapsulation, must be one of: erspan, ip6erspan</constraintErrorMessage> - </properties> - </leafNode> - <node name="parameters"> - <properties> - <help>ERSPAN Tunnel parameters</help> - </properties> - <children> - <node name="ip"> - <properties> - <help>IPv4 specific tunnel parameters</help> - </properties> - <children> - #include <include/interface/interface-parameters-key.xml.i> - #include <include/interface/interface-parameters-tos.xml.i> - #include <include/interface/interface-parameters-ttl.xml.i> - </children> - </node> - <leafNode name="version"> - <properties> - <help>ERSPAN version number setting(default:1)</help> - <constraint> - <validator name="numeric" argument="--range 1-2"/> - </constraint> - <constraintErrorMessage>The version number of ERSPAN must be 1 or 2</constraintErrorMessage> - </properties> - <defaultValue>1</defaultValue> - </leafNode> - <leafNode name="direction"> - <properties> - <help>Specifies mirrored traffic direction</help> - <completionHelp> - <list>ingress egress</list> - </completionHelp> - <valueHelp> - <format>ingress</format> - <description>Mirror ingress direction</description> - </valueHelp> - <valueHelp> - <format>egress</format> - <description>Mirror egress direction</description> - </valueHelp> - <constraint> - <regex>^(ingress|egress)$</regex> - </constraint> - <constraintErrorMessage>The mirror direction of ERSPAN must be ingress or egress</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="hwid"> - <properties> - <help>an unique identifier of an ERSPAN v2 engine within a system</help> - <constraint> - <validator name="numeric" argument="--range 1-1048575"/> - </constraint> - <constraintErrorMessage>ERSPAN hwid must be a number(range:0-1048575)</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="idx"> - <properties> - <help>specifies the ERSPAN v1 index field</help> - <constraint> - <validator name="numeric" argument="--range 0-63"/> - </constraint> - <constraintErrorMessage>ERSPAN idx must be a number(range:0-63)</constraintErrorMessage> - </properties> - </leafNode> - </children> - </node> - </children> - </tagNode> - </children> - </node> -</interfaceDefinition> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index e3aad2719..e4bdcb3d7 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -1,228 +1,311 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="interfaces"> <children> <tagNode name="tunnel" owner="${vyos_conf_scripts_dir}/interfaces-tunnel.py"> <properties> <help>Tunnel interface</help> <priority>380</priority> <constraint> <regex>^tun[0-9]+$</regex> </constraint> <constraintErrorMessage>tunnel interface must be named tunN</constraintErrorMessage> <valueHelp> <format>tunN</format> <description>Tunnel interface name</description> </valueHelp> </properties> <children> #include <include/interface/interface-description.xml.i> #include <include/interface/address-ipv4-ipv6.xml.i> #include <include/interface/interface-disable.xml.i> #include <include/interface/interface-disable-link-detect.xml.i> #include <include/interface/interface-vrf.xml.i> #include <include/interface/interface-mtu-64-8024.xml.i> <leafNode name="mtu"> <defaultValue>1476</defaultValue> </leafNode> #include <include/interface/interface-ipv4-options.xml.i> #include <include/interface/interface-ipv6-options.xml.i> #include <include/source-address-ipv4-ipv6.xml.i> #include <include/interface/tunnel-remote.xml.i> <leafNode name="source-interface"> <properties> <help>Physical Interface used for underlaying traffic</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> </properties> </leafNode> <leafNode name="6rd-prefix"> <properties> <help>6rd network prefix</help> <valueHelp> <format>ipv6</format> <description>IPv6 address and prefix length</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> </leafNode> <leafNode name="6rd-relay-prefix"> <properties> <help>6rd relay prefix</help> <valueHelp> <format>ipv4net</format> <description>IPv4 prefix of interface for 6rd</description> </valueHelp> <constraint> <validator name="ipv4-prefix"/> </constraint> </properties> </leafNode> <leafNode name="dhcp-interface"> <properties> <help>dhcp interface</help> <valueHelp> <format>interface</format> <description>DHCP interface that supplies the local IP address for this tunnel</description> </valueHelp> <completionHelp> <script>${vyos_completion_dir}/list_interfaces.py</script> </completionHelp> <constraint> <regex>^(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+$</regex> </constraint> </properties> </leafNode> <leafNode name="encapsulation"> <properties> <help>Encapsulation of this tunnel interface</help> <completionHelp> - <list>gre gretap ip6gre ip6gretap ip6ip6 ipip ipip6 sit</list> + <list>erspan gre gretap ip6erspan ip6gre ip6gretap ip6ip6 ipip ipip6 sit</list> </completionHelp> + <valueHelp> + <format>erspan</format> + <description>Encapsulated Remote Switched Port Analyzer</description> + </valueHelp> <valueHelp> <format>gre</format> <description>Generic Routing Encapsulation</description> </valueHelp> <valueHelp> <format>gretap</format> <description>Generic Routing Encapsulation (virtual L2 tunnel)</description> </valueHelp> + <valueHelp> + <format>ip6erspan</format> + <description>Encapsulated Remote Switched Port Analyzer over IPv6 network</description> + </valueHelp> <valueHelp> <format>ip6gre</format> <description>GRE over IPv6 network</description> </valueHelp> <valueHelp> <format>ip6gretap</format> <description>Generic Routing Encapsulation over IPv6 (virtual L2 tunnel)</description> </valueHelp> <valueHelp> <format>ip6ip6</format> <description>IP6 in IP6 encapsulation</description> </valueHelp> <valueHelp> <format>ipip</format> <description>IP in IP encapsulation</description> </valueHelp> <valueHelp> <format>ipip6</format> <description>IP in IP6 encapsulation</description> </valueHelp> <valueHelp> <format>sit</format> <description>Simple Internet Transition encapsulation</description> </valueHelp> <constraint> - <regex>^(gre|gretap|ip6gre|ip6gretap|ip6ip6|ipip|ipip6|sit)$</regex> + <regex>^(erspan|gre|gretap|ip6erspan|ip6gre|ip6gretap|ip6ip6|ipip|ipip6|sit)$</regex> </constraint> - <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gretap, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6</constraintErrorMessage> + <constraintErrorMessage>Invalid encapsulation, must be one of: erspan, gre, gretap, ip6erspan, ip6gre, ip6gretap, ipip, sit, ipip6 or ip6ip6</constraintErrorMessage> </properties> </leafNode> <leafNode name="multicast"> <properties> <help>Multicast operation over tunnel</help> <completionHelp> <list>enable disable</list> </completionHelp> <valueHelp> <format>enable</format> <description>Enable Multicast</description> </valueHelp> <valueHelp> <format>disable</format> <description>Disable Multicast (default)</description> </valueHelp> <constraint> <regex>^(enable|disable)$</regex> </constraint> <constraintErrorMessage>Must be 'disable' or 'enable'</constraintErrorMessage> </properties> </leafNode> <node name="parameters"> <properties> <help>Tunnel parameters</help> </properties> <children> + <node name="erspan"> + <properties> + <help>ERSPAN Tunnel parameters</help> + </properties> + <children> + +<!--- +Temporary disabled b/c of https://github.com/shemminger/iproute2/issues/41 + <leafNode name="direction"> + <properties> + <help>Specifies mirrored traffic direction</help> + <completionHelp> + <list>ingress egress</list> + </completionHelp> + <valueHelp> + <format>ingress</format> + <description>Mirror ingress direction</description> + </valueHelp> + <valueHelp> + <format>egress</format> + <description>Mirror egress direction</description> + </valueHelp> + <constraint> + <regex>^(ingress|egress)$</regex> + </constraint> + </properties> + </leafNode> + <leafNode name="hw-id"> + <properties> + <help>Unique identifier of ERSPAN engine within a system</help> + <valueHelp> + <format>0-1048575</format> + <description>Unique identifier of ERSPAN engine</description> + </valueHelp> + <constraint> +fix double hyphen below ... + <validator name="numeric" argument="- -range 0-1048575"/> + </constraint> + </properties> + </leafNode> +--> + <leafNode name="index"> + <properties> + <help>Specifify ERSPAN version 1 index field</help> + <valueHelp> + <format>0-63</format> + <description>Platform-depedent field for specifying port number and direction</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-63"/> + </constraint> + </properties> + </leafNode> + <leafNode name="version"> + <properties> + <help>Protocol version</help> + <valueHelp> + <format>1</format> + <description>ERSPAN Type II</description> + </valueHelp> +<!-- +Temporary disabled b/c of https://github.com/shemminger/iproute2/issues/41 + <valueHelp> + <format>2</format> + <description>ERSPAN Type III</description> + </valueHelp> +--> + <constraint> + <validator name="numeric" argument="--range 1-1"/> + </constraint> + </properties> + <defaultValue>1</defaultValue> + </leafNode> + </children> + </node> <node name="ip"> <properties> <help>IPv4 specific tunnel parameters</help> </properties> <children> <leafNode name="no-pmtu-discovery"> <properties> <help>Disable path MTU discovery</help> <valueless/> </properties> </leafNode> #include <include/interface/interface-parameters-key.xml.i> #include <include/interface/interface-parameters-tos.xml.i> #include <include/interface/interface-parameters-ttl.xml.i> </children> </node> <node name="ipv6"> <properties> <help>IPv6 specific tunnel parameters</help> </properties> <children> <leafNode name="encaplimit"> <properties> <help>Set fixed encapsulation limit</help> <completionHelp> <list>none</list> </completionHelp> <valueHelp> <format>0-255</format> <description>Encaplimit (default: 4)</description> </valueHelp> <valueHelp> <format>none</format> <description>Encaplimit disabled</description> </valueHelp> <constraint> <regex>^(none)$</regex> <validator name="numeric" argument="--range 0-255"/> </constraint> <constraintErrorMessage>Tunnel encaplimit must be 0-255 or none</constraintErrorMessage> </properties> <defaultValue>4</defaultValue> </leafNode> #include <include/interface/interface-parameters-flowlabel.xml.i> <leafNode name="hoplimit"> <properties> <help>Hoplimit</help> <valueHelp> <format>0-255</format> <description>Hoplimit (default 64)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-255"/> </constraint> <constraintErrorMessage>hoplimit must be between 0-255</constraintErrorMessage> </properties> <defaultValue>64</defaultValue> </leafNode> <leafNode name="tclass"> <properties> <help>Traffic class (Tclass)</help> <valueHelp> <format>0x0-0x0FFFFF</format> <description>Traffic class, 'inherit' or hex value</description> </valueHelp> <constraint> <regex>(0x){0,1}(0?[0-9A-Fa-f]{1,2})</regex> </constraint> <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage> </properties> <defaultValue>inherit</defaultValue> </leafNode> </children> </node> </children> </node> </children> </tagNode> </children> </node> </interfaceDefinition> 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 @@ -1,39 +1,37 @@ # 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/>. from vyos.ifconfig.section import Section from vyos.ifconfig.control import Control from vyos.ifconfig.interface import Interface from vyos.ifconfig.operational import Operational from vyos.ifconfig.vrrp import VRRP from vyos.ifconfig.bond import BondIf from vyos.ifconfig.bridge import BridgeIf from vyos.ifconfig.dummy import DummyIf from vyos.ifconfig.ethernet import EthernetIf from vyos.ifconfig.geneve import GeneveIf from vyos.ifconfig.loopback import LoopbackIf from vyos.ifconfig.macvlan import MACVLANIf from vyos.ifconfig.vxlan import VXLANIf from vyos.ifconfig.wireguard import WireGuardIf 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/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/tunnel.py b/python/vyos/ifconfig/tunnel.py index e5e1300b2..08854a3b0 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -1,188 +1,196 @@ # 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/ # https://community.hetzner.com/tutorials/linux-setup-gre-tunnel from netaddr import EUI from netaddr import mac_unix_expanded from random import getrandbits from vyos.ifconfig.interface import Interface from vyos.util import dict_search from vyos.validate import assert_list def enable_to_on(value): if value == 'enable': return 'on' if value == 'disable': return 'off' raise ValueError(f'expect enable or disable but got "{value}"') @Interface.register class TunnelIf(Interface): """ Tunnel: private base class for tunnels https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/tunnel.c https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ip6tunnel.c """ definition = { **Interface.definition, **{ 'section': 'tunnel', 'prefixes': ['tun',], }, } # This table represents a mapping from VyOS internal config dict to # arguments used by iproute2. For more information please refer to: # - https://man7.org/linux/man-pages/man8/ip-link.8.html # - https://man7.org/linux/man-pages/man8/ip-tunnel.8.html mapping = { 'source_address' : 'local', 'source_interface' : 'dev', 'remote' : 'remote', 'parameters.ip.key' : 'key', 'parameters.ip.tos' : 'tos', 'parameters.ip.ttl' : 'ttl', } mapping_ipv4 = { 'parameters.ip.key' : 'key', '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', 'parameters.ipv6.flowlabel' : 'flowlabel', 'parameters.ipv6.hoplimit' : 'hoplimit', 'parameters.ipv6.tclass' : 'tclass', } # TODO: This is surely used for more than tunnels # TODO: could be refactored elsewhere _command_set = { **Interface._command_set, **{ 'multicast': { 'validate': lambda v: assert_list(v, ['enable', 'disable']), 'convert': enable_to_on, 'shellcmd': 'ip link set dev {ifname} multicast {value}', }, 'allmulticast': { 'validate': lambda v: assert_list(v, ['enable', 'disable']), 'convert': enable_to_on, 'shellcmd': 'ip link set dev {ifname} allmulticast {value}', }, } } def __init__(self, ifname, **kargs): # T3357: we do not have the 'encapsulation' in kargs when calling this # class from op-mode like "show interfaces tunnel" if 'encapsulation' in kargs: self.iftype = kargs['encapsulation'] # The gretap interface has the possibility to act as L2 bridge if self.iftype in ['gretap', 'ip6gretap']: # no multicast, ttl or tos for gretap self.definition = { **TunnelIf.definition, **{ 'bridgeable': True, }, } super().__init__(ifname, **kargs) def _create(self): if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: mapping = { **self.mapping, **self.mapping_ipv6 } else: 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 # by using isinstance() tmp = dict_search(vyos_key, self.config) if isinstance(tmp, dict): cmd += f' {iproute2_key}' elif tmp != None: cmd += f' {iproute2_key} {tmp}' self._cmd(cmd.format(**self.config)) self.set_admin_state('down') 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']: mapping = { **self.mapping, **self.mapping_ipv6 } else: mapping = { **self.mapping, **self.mapping_ipv4 } cmd = 'ip tunnel change {ifname} mode {encapsulation}' 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 # by using isinstance() tmp = dict_search(vyos_key, self.config) if isinstance(tmp, dict): cmd += f' {iproute2_key}' elif tmp != None: cmd += f' {iproute2_key} {tmp}' self._cmd(cmd.format(**self.config)) def get_mac(self): """ Get current interface MAC (Media Access Contrl) address used. NOTE: Tunnel interfaces have no "MAC" address by default. The content of the 'address' file in /sys/class/net/device contains the local-ip thus we generate a random MAC address instead Example: >>> from vyos.ifconfig import Interface >>> Interface('eth0').get_mac() '00:50:ab:cd:ef:00' """ # we choose 40 random bytes for the MAC address, this gives # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A') tmp = EUI(getrandbits(48)).value # set locally administered bit in MAC address tmp |= 0xf20000000000 # convert integer to "real" MAC address representation mac = EUI(hex(tmp).split('x')[-1]) # change dialect to use : as delimiter instead of - mac.dialect = mac_unix_expanded return str(mac) def update(self, config): """ General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered interface setup code and provide a single point of entry when workin on any interface. """ # Adjust iproute2 tunnel parameters if necessary self._change_options() # call base class first super().update(config) diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index 6af31ddff..0e021b385 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -1,236 +1,322 @@ #!/usr/bin/env python3 # # Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.util import get_interface_config from vyos.template import inc_ip remote_ip4 = '192.0.2.100' remote_ip6 = '2001:db8::ffff' source_if = 'dum2222' mtu = 1476 class TunnelInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): cls._test_ip = True cls._test_ipv6 = True cls._test_mtu = True cls._base_path = ['interfaces', 'tunnel'] cls.local_v4 = '192.0.2.1' cls.local_v6 = '2001:db8::1' cls._options = { 'tun10': ['encapsulation ipip', 'remote 192.0.2.10', 'source-address ' + cls.local_v4], 'tun20': ['encapsulation gre', 'remote 192.0.2.20', 'source-address ' + cls.local_v4], } cls._interfaces = list(cls._options) # call base-classes classmethod super(cls, cls).setUpClass() def setUp(self): super().setUp() self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) self.cli_set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) def tearDown(self): self.cli_delete(['interfaces', 'dummy', source_if]) super().tearDown() def test_ipv4_encapsulations(self): # When running tests ensure that for certain encapsulation types the # local and remote IP address is actually an IPv4 address interface = f'tun1000' local_if_addr = f'10.10.200.1/24' for encapsulation in ['ipip', 'sit', 'gre', 'gretap']: self.cli_set(self._base_path + [interface, 'address', local_if_addr]) self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) # Encapsulation mode requires IPv4 source-address with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) # Encapsulation mode requires IPv4 remote with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) self.cli_set(self._base_path + [interface, 'source-interface', source_if]) # Source interface can not be used with sit and gretap if encapsulation in ['sit', 'gretap']: with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(self._base_path + [interface, 'source-interface']) # Check if commit is ok self.cli_commit() conf = get_interface_config(interface) if encapsulation not in ['sit', 'gretap']: self.assertEqual(source_if, conf['link']) self.assertEqual(interface, conf['ifname']) self.assertEqual(mtu, conf['mtu']) self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) self.assertTrue(conf['linkinfo']['info_data']['pmtudisc']) # cleanup this instance self.cli_delete(self._base_path + [interface]) self.cli_commit() def test_ipv6_encapsulations(self): # When running tests ensure that for certain encapsulation types the # local and remote IP address is actually an IPv6 address interface = f'tun1010' local_if_addr = f'10.10.200.1/24' for encapsulation in ['ipip6', 'ip6ip6', 'ip6gre', 'ip6gretap']: self.cli_set(self._base_path + [interface, 'address', local_if_addr]) self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) # Encapsulation mode requires IPv6 source-address with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) # Encapsulation mode requires IPv6 remote with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) # Configure Tunnel Source interface self.cli_set(self._base_path + [interface, 'source-interface', source_if]) # Source interface can not be used with ip6gretap if encapsulation in ['ip6gretap']: with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(self._base_path + [interface, 'source-interface']) # Check if commit is ok self.cli_commit() conf = get_interface_config(interface) if encapsulation not in ['ip6gretap']: self.assertEqual(source_if, conf['link']) self.assertEqual(interface, conf['ifname']) self.assertEqual(mtu, conf['mtu']) # Not applicable for ip6gre if 'proto' in conf['linkinfo']['info_data']: self.assertEqual(encapsulation, conf['linkinfo']['info_data']['proto']) # remap encapsulation protocol(s) only for ipip6, ip6ip6 if encapsulation in ['ipip6', 'ip6ip6']: encapsulation = 'ip6tnl' self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) # cleanup this instance self.cli_delete(self._base_path + [interface]) self.cli_commit() def test_tunnel_verify_local_dhcp(self): # We can not use source-address and dhcp-interface at the same time interface = f'tun1020' local_if_addr = f'10.0.0.1/24' self.cli_set(self._base_path + [interface, 'address', local_if_addr]) self.cli_set(self._base_path + [interface, 'encapsulation', 'gre']) self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) self.cli_set(self._base_path + [interface, 'dhcp-interface', 'eth0']) # source-address and dhcp-interface can not be used at the same time with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(self._base_path + [interface, 'dhcp-interface']) # Check if commit is ok self.cli_commit() def test_tunnel_parameters_gre(self): interface = f'tun1030' gre_key = '10' encapsulation = 'gre' tos = '20' self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery']) self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', gre_key]) self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'tos', tos]) # Check if commit is ok self.cli_commit() conf = get_interface_config(interface) self.assertEqual(mtu, conf['mtu']) self.assertEqual(interface, conf['ifname']) self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) self.assertFalse( conf['linkinfo']['info_data']['pmtudisc']) def test_gretap_parameters_change(self): interface = f'tun1040' gre_key = '10' encapsulation = 'gretap' tos = '20' self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) # Check if commit is ok self.cli_commit() conf = get_interface_config(interface) self.assertEqual(mtu, conf['mtu']) self.assertEqual(interface, conf['ifname']) self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) # Change remote ip address (inc host by 2 new_remote = inc_ip(remote_ip4, 2) self.cli_set(self._base_path + [interface, 'remote', new_remote]) # Check if commit is ok self.cli_commit() conf = get_interface_config(interface) self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + def test_erspan(self): + interface = f'tun1070' + encapsulation = 'erspan' + ip_key = '77' + idx = '20' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v4]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip4]) + + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'index', idx]) + + # ERSPAN requires ip key parameter + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', ip_key]) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip4, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + self.assertEqual(int(idx), conf['linkinfo']['info_data']['erspan_index']) + self.assertEqual(1, conf['linkinfo']['info_data']['erspan_ver']) + self.assertTrue( conf['linkinfo']['info_data']['iseq']) + self.assertTrue( conf['linkinfo']['info_data']['oseq']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip4, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + + def test_ip6erspan(self): + interface = f'tun1070' + encapsulation = 'ip6erspan' + ip_key = '77' + erspan_ver = '2' + direction = 'ingres' + + self.cli_set(self._base_path + [interface, 'encapsulation', encapsulation]) + self.cli_set(self._base_path + [interface, 'source-address', self.local_v6]) + self.cli_set(self._base_path + [interface, 'remote', remote_ip6]) + + self.cli_set(self._base_path + [interface, 'parameters', 'erspan', 'index', '10']) + + # ERSPAN requires ip key parameter + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'parameters', 'ip', 'key', ip_key]) + + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(mtu, conf['mtu']) + self.assertEqual(interface, conf['ifname']) + self.assertEqual(encapsulation, conf['linkinfo']['info_kind']) + self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) + self.assertEqual(remote_ip6, conf['linkinfo']['info_data']['remote']) + self.assertEqual(0, conf['linkinfo']['info_data']['ttl']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['ikey']) + self.assertEqual(f'0.0.0.{ip_key}', conf['linkinfo']['info_data']['okey']) + self.assertEqual(1, conf['linkinfo']['info_data']['erspan_ver']) + self.assertTrue( conf['linkinfo']['info_data']['iseq']) + self.assertTrue( conf['linkinfo']['info_data']['oseq']) + + # Change remote ip address (inc host by 2 + new_remote = inc_ip(remote_ip6, 2) + self.cli_set(self._base_path + [interface, 'remote', new_remote]) + # Check if commit is ok + self.cli_commit() + + conf = get_interface_config(interface) + self.assertEqual(new_remote, conf['linkinfo']['info_data']['remote']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/interfaces-erspan.py b/src/conf_mode/interfaces-erspan.py deleted file mode 100755 index 97ae3cf55..000000000 --- a/src/conf_mode/interfaces-erspan.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018-2020 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os - -from sys import exit -from copy import deepcopy -from netifaces import interfaces - -from vyos.config import Config -from vyos.configdict import dict_merge -from vyos.configdict import get_interface_dict -from vyos.configdict import node_changed -from vyos.configdict import leaf_node_changed -from vyos.configverify import verify_mtu_ipv6 -from vyos.configverify import verify_tunnel -from vyos.ifconfig import Interface -from vyos.ifconfig import ERSpanIf -from vyos.ifconfig import ER6SpanIf -from vyos.template import is_ipv4 -from vyos.template import is_ipv6 -from vyos.util import dict_search -from vyos import ConfigError -from vyos import airbag -airbag.enable() - -def get_config(config=None): - """ - Retrive CLI config as dictionary. Dictionary can never be empty, as at least - the interface name will be added or a deleted flag - """ - if config: - conf = config - else: - conf = Config() - base = ['interfaces', 'erspan'] - erspan = get_interface_dict(conf, base) - - tmp = leaf_node_changed(conf, ['encapsulation']) - if tmp: - erspan.update({'encapsulation_changed': {}}) - - return erspan - -def verify(erspan): - if 'deleted' in erspan: - return None - - if 'encapsulation' not in erspan: - raise ConfigError('Unable to detect the following ERSPAN tunnel encapsulation'\ - '{ifname}!'.format(**erspan)) - - verify_mtu_ipv6(erspan) - verify_tunnel(erspan) - - key = dict_search('parameters.ip.key',erspan) - if key == None: - raise ConfigError('parameters.ip.key is mandatory for ERSPAN tunnel') - - -def generate(erspan): - return None - -def apply(erspan): - if 'deleted' in erspan or 'encapsulation_changed' in erspan: - if erspan['ifname'] in interfaces(): - tmp = Interface(erspan['ifname']) - tmp.remove() - if 'deleted' in erspan: - return None - - dispatch = { - 'erspan': ERSpanIf, - 'ip6erspan': ER6SpanIf - } - - # We need to re-map the tunnel encapsulation proto to a valid interface class - encap = erspan['encapsulation'] - klass = dispatch[encap] - - erspan_tunnel = klass(**erspan) - erspan_tunnel.change_options() - erspan_tunnel.update(erspan) - - return None - -if __name__ == '__main__': - try: - c = get_config() - generate(c) - verify(c) - apply(c) - except ConfigError as e: - print(e) - exit(1) diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index cab94a5b0..4e6c8a9ab 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -1,132 +1,148 @@ #!/usr/bin/env python3 # # Copyright (C) 2018-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os from sys import exit from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import get_interface_dict from vyos.configdict import node_changed from vyos.configdict import leaf_node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_interface_exists from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_vrf from vyos.configverify import verify_tunnel from vyos.ifconfig import Interface from vyos.ifconfig import TunnelIf from vyos.template import is_ipv4 from vyos.template import is_ipv6 from vyos.util import get_interface_config from vyos.util import dict_search from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ if config: conf = config else: conf = Config() base = ['interfaces', 'tunnel'] tunnel = get_interface_dict(conf, base) tmp = leaf_node_changed(conf, ['encapsulation']) if tmp: tunnel.update({'encapsulation_changed': {}}) # We must check if our interface is configured to be a DMVPN member nhrp_base = ['protocols', 'nhrp', 'tunnel'] conf.set_level(nhrp_base) nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True) if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())}) + if 'encapsulation' in tunnel and tunnel['encapsulation'] not in ['erspan', 'ip6erspan']: + del tunnel['parameters']['erspan'] + return tunnel def verify(tunnel): if 'deleted' in tunnel: verify_bridge_delete(tunnel) if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']: raise ConfigError('Tunnel used for NHRP, it can not be deleted!') return None - if 'encapsulation' not in tunnel: - error = 'Must configure encapsulation for "{ifname}"!' - raise ConfigError(error.format(**tunnel)) + verify_tunnel(tunnel) + + if tunnel['encapsulation'] in ['erspan', 'ip6erspan']: + if dict_search('parameters.ip.key', tunnel) == None: + raise ConfigError('ERSPAN requires ip key parameter!') + + # this is a default field + ver = int(tunnel['parameters']['erspan']['version']) + if ver == 1: + if 'hw_id' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support hw-id!') + if 'direction' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 1 does not support direction!') + elif ver == 2: + if 'idx' in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 does not index parameter!') + if 'direction' not in tunnel['parameters']['erspan']: + raise ConfigError('ERSPAN version 2 requires direction to be set!') verify_mtu_ipv6(tunnel) verify_address(tunnel) verify_vrf(tunnel) - verify_tunnel(tunnel) if 'source_interface' in tunnel: verify_interface_exists(tunnel['source_interface']) # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default # values, thus the keys are always present. if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None: if dict_search('parameters.ip.ttl', tunnel) != '0': raise ConfigError('Disabled PMTU requires TTL set to "0"!') if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: raise ConfigError('Can not disable PMTU discovery for given encapsulation') - def generate(tunnel): return None def apply(tunnel): interface = tunnel['ifname'] # If a gretap tunnel is already existing we can not "simply" change local or # remote addresses. This returns "Operation not supported" by the Kernel. # There is no other solution to destroy and recreate the tunnel. encap = '' remote = '' tmp = get_interface_config(interface) if tmp: encap = dict_search('linkinfo.info_kind', tmp) remote = dict_search('linkinfo.info_data.remote', tmp) - if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or - encap in ['gretap', 'ip6gretap'] or remote in ['any']): + if ('deleted' in tunnel or 'encapsulation_changed' in tunnel or encap in + ['gretap', 'ip6gretap', 'erspan', 'ip6erspan'] or remote in ['any']): if interface in interfaces(): tmp = Interface(interface) tmp.remove() if 'deleted' in tunnel: return None tun = TunnelIf(**tunnel) tun.update(tunnel) return None if __name__ == '__main__': try: c = get_config() generate(c) verify(c) apply(c) except ConfigError as e: print(e) exit(1)