diff options
Diffstat (limited to 'python/vyos/ifconfig/tunnel.py')
-rw-r--r-- | python/vyos/ifconfig/tunnel.py | 269 |
1 files changed, 120 insertions, 149 deletions
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 6f9d7abbf..e40756cc7 100644 --- a/python/vyos/ifconfig/tunnel.py +++ b/python/vyos/ifconfig/tunnel.py @@ -1,4 +1,4 @@ -# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io> +# 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 @@ -16,13 +16,12 @@ # 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 copy import deepcopy - 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): @@ -32,11 +31,10 @@ def enable_to_on(value): return 'off' raise ValueError(f'expect enable or disable but got "{value}"') - @Interface.register -class _Tunnel(Interface): +class TunnelIf(Interface): """ - _Tunnel: private base class for tunnels + 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 """ @@ -49,54 +47,127 @@ class _Tunnel(Interface): }, } + # 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.ignore_df' : 'ignore-df', + '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}', - }, - }} - - _create_cmd = 'ip tunnel add {ifname} mode {type}' + _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, **config): - self.config = deepcopy(config) if config else {} - super().__init__(ifname, **config) + 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): - # add " option-name option-name-value ..." for all options set - options = " ".join(["{} {}".format(k, self.config[k]) - for k in self.options if k in self.config and self.config[k]]) - self._cmd('{} {}'.format(self._create_cmd.format(**self.config), options)) - self.set_admin_state('down') + 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', '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)) - def change_options(self): - change = 'ip tunnel change {ifname} mode {type}' - - # add " option-name option-name-value ..." for all options set - options = " ".join(["{} {}".format(k, self.config[k]) - for k in self.options if k in self.config and self.config[k]]) - self._cmd('{} {}'.format(change.format(**self.config), options)) + self.set_admin_state('down') - @classmethod - def get_config(cls): - return dict(zip(cls.options, ['']*len(cls.options))) + def _change_options(self): + # gretap interfaces do not support changing any parameter + 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() @@ -113,113 +184,13 @@ class _Tunnel(Interface): mac.dialect = mac_unix_expanded return str(mac) -class GREIf(_Tunnel): - """ - GRE: Generic Routing Encapsulation - - For more information please refer to: - RFC1701, RFC1702, RFC2784 - https://tools.ietf.org/html/rfc2784 - https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre.c - """ - - default = {'type': 'gre'} - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - -# GreTap also called GRE Bridge -class GRETapIf(_Tunnel): - """ - GRETapIF: GreIF using TAP instead of TUN - - https://en.wikipedia.org/wiki/TUN/TAP - """ - - # no multicast, ttl or tos for gretap - default = {'type': 'gretap'} - options = ['local', 'remote', 'ttl', 'tos', 'key'] - - _create_cmd = 'ip link add name {ifname} type {type}' - - def change_options(self): - pass - -class IP6GREIf(_Tunnel): - """ - IP6Gre: IPv6 Support for Generic Routing Encapsulation (GRE) - - For more information please refer to: - https://tools.ietf.org/html/rfc7676 - https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre6.c - """ - - default = {'type': 'ip6gre'} - options = ['local', 'remote', 'dev', 'encaplimit', - 'hoplimit', 'tclass', 'flowlabel'] - -class IPIPIf(_Tunnel): - """ - IPIP: IP Encapsulation within IP - - For more information please refer to: - https://tools.ietf.org/html/rfc2003 - """ - - # IPIP does not allow to pass multicast, unlike GRE - # but the interface itself can be set with multicast - - default = {'type': 'ipip'} - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - -class IPIP6If(_Tunnel): - """ - IPIP6: IPv4 over IPv6 tunnel - - For more information please refer to: - https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_ip6tnl.c - """ - - default = {'type': 'ipip6'} - options = ['local', 'remote', 'dev', 'encaplimit', - 'hoplimit', 'tclass', 'flowlabel'] - -class IP6IP6If(IPIP6If): - """ - IP6IP6: IPv6 over IPv6 tunnel - - For more information please refer to: - https://tools.ietf.org/html/rfc2473 - """ - default = {'type': 'ip6ip6'} - - -class SitIf(_Tunnel): - """ - Sit: Simple Internet Transition - - For more information please refer to: - https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_iptnl.c - """ - - default = {'type': 'sit'} - options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key'] - -class Sit6RDIf(SitIf): - """ - Sit6RDIf: Simple Internet Transition with 6RD - - https://en.wikipedia.org/wiki/IPv6_rapid_deployment - """ - # TODO: check if key can really be used with 6RD - options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix'] - - def _create(self): - # do not call _Tunnel.create, building fully here - - create = 'ip tunnel add {ifname} mode {type} remote {remote}' - self._cmd(create.format(**self.config)) - self.set_interface('state','down') + 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() - set6rd = 'ip tunnel 6rd dev {ifname} 6rd-prefix {6rd-prefix}' - if '6rd-relay-prefix' in self.config: - set6rd += ' 6rd-relay-prefix {6rd-relay-prefix}' - self._cmd(set6rd.format(**self.config)) + # call base class first + super().update(config) |