diff options
50 files changed, 662 insertions, 866 deletions
| diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in index 3b5843b53..ff632e1d1 100644 --- a/interface-definitions/dns-domain-name.xml.in +++ b/interface-definitions/dns-domain-name.xml.in @@ -44,7 +44,7 @@          <properties>            <help>System domain name</help>            <constraint> -            <regex>[A-Za-z0-9][-.A-Za-z0-9]*</regex> +            <validator name="fqdn"/>            </constraint>          </properties>        </leafNode> diff --git a/interface-definitions/include/interface-parameters-flowlabel.xml.i b/interface-definitions/include/interface-parameters-flowlabel.xml.i index ae65c27c9..0723c4b47 100644 --- a/interface-definitions/include/interface-parameters-flowlabel.xml.i +++ b/interface-definitions/include/interface-parameters-flowlabel.xml.i @@ -11,6 +11,5 @@      </constraint>      <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage>    </properties> -  <defaultValue>inherit</defaultValue>  </leafNode>  <!-- included end --> diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i index ab3c6d72a..c57d39b6b 100644 --- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i +++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i @@ -26,7 +26,27 @@          #include <include/radius-server-port.xml.i>        </children>      </tagNode> -    #include <include/source-address-ipv4-ipv6.xml.i> +    <leafNode name="source-address"> +      <properties> +        <help>Source IP address used to initiate connection</help> +        <completionHelp> +          <script>${vyos_completion_dir}/list_local_ips.sh --both</script> +        </completionHelp> +        <valueHelp> +          <format>ipv4</format> +          <description>IPv4 source address</description> +        </valueHelp> +        <valueHelp> +          <format>ipv6</format> +          <description>IPv6 source address</description> +        </valueHelp> +        <constraint> +          <validator name="ipv4-address"/> +          <validator name="ipv6-address"/> +        </constraint> +        <multi/> +      </properties> +    </leafNode>    </children>  </node>  <!-- included end --> diff --git a/interface-definitions/include/source-address-ipv4-ipv6.xml.i b/interface-definitions/include/source-address-ipv4-ipv6.xml.i index 4da4698c2..93cfd7c63 100644 --- a/interface-definitions/include/source-address-ipv4-ipv6.xml.i +++ b/interface-definitions/include/source-address-ipv4-ipv6.xml.i @@ -14,10 +14,8 @@        <description>IPv6 source address</description>      </valueHelp>      <constraint> -      <validator name="ipv4-address"/> -      <validator name="ipv6-address"/> +      <validator name="ip-address"/>      </constraint> -    <multi/>    </properties>  </leafNode>  <!-- included end --> diff --git a/interface-definitions/include/tunnel-local-remote-ip.xml.i b/interface-definitions/include/tunnel-local-remote-ip.xml.i deleted file mode 100644 index 85c20f482..000000000 --- a/interface-definitions/include/tunnel-local-remote-ip.xml.i +++ /dev/null @@ -1,37 +0,0 @@ -<!-- included start from tunnel-local-remote-ip.xml.i --> -<leafNode name="local-ip"> -  <properties> -    <help>Local IP address for this tunnel</help> -    <valueHelp> -      <format>ipv4</format> -      <description>Local IPv4 address for this tunnel</description> -    </valueHelp> -    <valueHelp> -      <format>ipv6</format> -      <description>Local IPv6 address for this tunnel</description> -    </valueHelp> -    <completionHelp> -      <script>${vyos_completion_dir}/list_local_ips.sh --both</script> -    </completionHelp> -    <constraint> -      <validator name="ip-address"/> -    </constraint> -  </properties> -</leafNode> -<leafNode name="remote-ip"> -  <properties> -    <help>Remote IP address for this tunnel</help> -    <valueHelp> -      <format>ipv4</format> -      <description>Remote IPv4 address for this tunnel</description> -    </valueHelp> -    <valueHelp> -      <format>ipv6</format> -      <description>Remote IPv6 address for this tunnel</description> -    </valueHelp> -    <constraint> -      <!-- does it need fixing/changing to be more restrictive ? --> -      <validator name="ip-address"/> -    </constraint> -  </properties> -</leafNode> diff --git a/interface-definitions/include/tunnel-remote.xml.i b/interface-definitions/include/tunnel-remote.xml.i new file mode 100644 index 000000000..d5b50d3f6 --- /dev/null +++ b/interface-definitions/include/tunnel-remote.xml.i @@ -0,0 +1,18 @@ +<!-- included start from tunnel-remote.xml.i --> +<leafNode name="remote"> +  <properties> +    <help>Tunnel remote address</help> +    <valueHelp> +      <format>ipv4</format> +      <description>Tunnel remote IPv4 address</description> +    </valueHelp> +    <valueHelp> +      <format>ipv6</format> +      <description>Tunnel remote IPv6 address</description> +    </valueHelp> +    <constraint> +      <validator name="ip-address"/> +    </constraint> +  </properties> +</leafNode> +<!-- included end --> diff --git a/interface-definitions/interfaces-erspan.xml.in b/interface-definitions/interfaces-erspan.xml.in index e36a64d3a..2394d3534 100644 --- a/interface-definitions/interfaces-erspan.xml.in +++ b/interface-definitions/interfaces-erspan.xml.in @@ -20,7 +20,8 @@            #include <include/interface-disable.xml.i>            #include <include/interface-disable-link-detect.xml.i>            #include <include/interface-mtu-64-8024.xml.i> -          #include <include/tunnel-local-remote-ip.xml.i> +          #include <include/source-address-ipv4-ipv6.xml.i> +          #include <include/tunnel-remote.xml.i>            <leafNode name="encapsulation">              <properties>                <help>Encapsulation of this tunnel interface</help> diff --git a/interface-definitions/interfaces-geneve.xml.in b/interface-definitions/interfaces-geneve.xml.in index 1064b2c18..5894f580c 100644 --- a/interface-definitions/interfaces-geneve.xml.in +++ b/interface-definitions/interfaces-geneve.xml.in @@ -23,18 +23,7 @@            #include <include/interface-ipv6-options.xml.i>            #include <include/interface-mac.xml.i>            #include <include/interface-mtu-1450-16000.xml.i> -          <leafNode name="remote"> -            <properties> -              <help>Remote address of GENEVE tunnel</help> -              <valueHelp> -                <format>ipv4</format> -                <description>Remote address of GENEVE tunnel</description> -              </valueHelp> -              <constraint> -                <validator name="ipv4-address"/> -              </constraint> -            </properties> -          </leafNode> +          #include <include/tunnel-remote.xml.i>            #include <include/vni.xml.i>          </children>        </tagNode> diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 94d78c6dd..3f2e5bb69 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -28,14 +28,18 @@                  <properties>                    <help>Cipher suite used</help>                    <completionHelp> -                    <list>gcm-aes-128</list> +                    <list>gcm-aes-128 gcm-aes-256</list>                    </completionHelp>                    <valueHelp>                      <format>gcm-aes-128</format>                      <description>Galois/Counter Mode of AES cipher with 128-bit key (default)</description>                    </valueHelp> +                  <valueHelp> +                    <format>gcm-aes-256</format> +                    <description>Galois/Counter Mode of AES cipher with 256-bit key</description> +                  </valueHelp>                    <constraint> -                    <regex>(gcm-aes-128)</regex> +                    <regex>^(gcm-aes-128|gcm-aes-256)$</regex>                    </constraint>                  </properties>                </leafNode> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index 7a97980a2..c2d03c5ea 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -27,7 +27,8 @@            </leafNode>            #include <include/interface-ipv4-options.xml.i>            #include <include/interface-ipv6-options.xml.i> -          #include <include/tunnel-local-remote-ip.xml.i> +          #include <include/source-address-ipv4-ipv6.xml.i> +          #include <include/tunnel-remote.xml.i>            <leafNode name="source-interface">              <properties>                <help>Physical Interface used for underlaying traffic</help> @@ -79,15 +80,15 @@              <properties>                <help>Encapsulation of this tunnel interface</help>                <completionHelp> -                <list>gre gre-bridge ip6gre ip6ip6 ipip ipip6 sit</list> +                <list>gre gretap ip6gre ip6ip6 ipip ipip6 sit</list>                </completionHelp>                <valueHelp>                  <format>gre</format>                  <description>Generic Routing Encapsulation</description>                </valueHelp>                <valueHelp> -                <format>gre-bridge</format> -                <description>Generic Routing Encapsulation bridge interface</description> +                <format>gretap</format> +                <description>Generic Routing Encapsulation (virtual L2 tunnel)</description>                </valueHelp>                <valueHelp>                  <format>ip6gre</format> @@ -110,9 +111,9 @@                  <description>Simple Internet Transition encapsulation</description>                </valueHelp>                <constraint> -                <regex>^(gre|gre-bridge|ip6gre|ip6ip6|ipip|ipip6|sit)$</regex> +                <regex>^(gre|gretap|ip6gre|ip6ip6|ipip|ipip6|sit)$</regex>                </constraint> -              <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gre-bridge, ipip, sit, ipip6, ip6ip6, ip6gre</constraintErrorMessage> +              <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gretap, ipip, sit, ipip6, ip6ip6, ip6gre</constraintErrorMessage>              </properties>            </leafNode>            <leafNode name="multicast"> diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index 234770971..a2ff34186 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -37,29 +37,47 @@            </leafNode>            #include <include/interface-ipv4-options.xml.i>            #include <include/interface-ipv6-options.xml.i> -          #include <include/source-address-ipv4.xml.i> -          #include <include/source-interface.xml.i>            #include <include/interface-mac.xml.i>            #include <include/interface-mtu-1200-16000.xml.i>            <leafNode name="mtu">              <defaultValue>1450</defaultValue>            </leafNode> -          <leafNode name="remote"> +          <node name="parameters">              <properties> -              <help>Remote address of VXLAN tunnel</help> -              <valueHelp> -                <format>ipv4</format> -                <description>Remote IPv4 address of VXLAN tunnel</description> -              </valueHelp> -              <valueHelp> -                <format>ipv6</format> -                <description>Remote IPv6 address of VXLAN tunnel</description> -              </valueHelp> -              <constraint> -                <validator name="ip-address"/> -              </constraint> +              <help>VXLAN tunnel parameters</help>              </properties> -          </leafNode> +            <children> +              <node name="ip"> +                <properties> +                  <help>IPv4 specific tunnel parameters</help> +                </properties> +                <children> +                  <leafNode name="df"> +                    <properties> +                      <help>Specifies the usage of the do not fragment (DF) bit</help> +                      <valueless/> +                    </properties> +                  </leafNode> +                  #include <include/interface-parameters-tos.xml.i> +                  #include <include/interface-parameters-ttl.xml.i> +                </children> +              </node> +              <node name="ipv6"> +                <properties> +                  <help>IPv6 specific tunnel parameters</help> +                </properties> +                <children> +                  #include <include/interface-parameters-flowlabel.xml.i> +                </children> +              </node> +              <leafNode name="nolearning"> +                <properties> +                  <help>Do not add unknown addresses into forwarding database</help> +                  <valueless/> +                </properties> +              </leafNode> +            </children> +          </node>            <leafNode name="port">              <properties>                <help>Destination port of VXLAN tunnel (default: 8472)</help> @@ -73,6 +91,9 @@              </properties>              <defaultValue>8472</defaultValue>            </leafNode> +          #include <include/source-address-ipv4-ipv6.xml.i> +          #include <include/source-interface.xml.i> +          #include <include/tunnel-remote.xml.i>            #include <include/vni.xml.i>          </children>        </tagNode> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 5a4d14c68..db3e7cc57 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -95,41 +95,42 @@ def verify_tunnel(config):      """      from vyos.template import is_ipv4      from vyos.template import is_ipv6 -     +      if 'encapsulation' not in config:          raise ConfigError('Must configure the tunnel encapsulation for '\                            '{ifname}!'.format(**config)) -     -    if 'local_ip' not in config and 'dhcp_interface' not in config: -        raise ConfigError('local-ip is mandatory for tunnel') -    if 'remote_ip' not in config and config['encapsulation'] != 'gre': -        raise ConfigError('remote-ip is mandatory for tunnel') +    if 'source_address' not in config and 'dhcp_interface' not in config: +        raise ConfigError('source-address is mandatory for tunnel') + +    if 'remote' not in config and config['encapsulation'] != 'gre': +        raise ConfigError('remote ip address is mandatory for tunnel') -    if {'local_ip', 'dhcp_interface'} <= set(config): -        raise ConfigError('Can not use both local-ip and dhcp-interface') +    if {'source_address', 'dhcp_interface'} <= set(config): +        raise ConfigError('Can not use both source-address and dhcp-interface')      if config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre', 'ip6erspan']:          error_ipv6 = 'Encapsulation mode requires IPv6' -        if 'local_ip' in config and not is_ipv6(config['local_ip']): -            raise ConfigError(f'{error_ipv6} local-ip') +        if 'source_address' in config and not is_ipv6(config['source_address']): +            raise ConfigError(f'{error_ipv6} source-address') -        if 'remote_ip' in config and not is_ipv6(config['remote_ip']): -            raise ConfigError(f'{error_ipv6} remote-ip') +        if 'remote' in config and not is_ipv6(config['remote']): +            raise ConfigError(f'{error_ipv6} remote')      else:          error_ipv4 = 'Encapsulation mode requires IPv4' -        if 'local_ip' in config and not is_ipv4(config['local_ip']): -            raise ConfigError(f'{error_ipv4} local-ip') +        if 'source_address' in config and not is_ipv4(config['source_address']): +            raise ConfigError(f'{error_ipv4} source-address') -        if 'remote_ip' in config and not is_ipv4(config['remote_ip']): -            raise ConfigError(f'{error_ipv4} remote-ip') +        if 'remote' in config and not is_ipv4(config['remote']): +            raise ConfigError(f'{error_ipv4} remote address') -    if config['encapsulation'] in ['sit', 'gre-bridge']: +    if config['encapsulation'] in ['sit', 'gretap']:          if 'source_interface' in config: -            raise ConfigError('Option source-interface can not be used with ' \ -                              'encapsulation "sit" or "gre-bridge"') +            encapsulation = config['encapsulation'] +            raise ConfigError(f'Option source-interface can not be used with ' \ +                              f'encapsulation "{encapsulation}"!')      elif config['encapsulation'] == 'gre': -        if 'local_ip' in config and is_ipv6(config['local_ip']): +        if 'source_address' in config and is_ipv6(config['source_address']):              raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')  def verify_eapol(config): diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index f7b55c9dd..9d797d7f1 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -30,15 +30,7 @@ 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 GREIf -from vyos.ifconfig.tunnel import GRETapIf -from vyos.ifconfig.tunnel import IP6GREIf -from vyos.ifconfig.tunnel import IPIPIf -from vyos.ifconfig.tunnel import IPIP6If -from vyos.ifconfig.tunnel import IP6IP6If -from vyos.ifconfig.tunnel import SitIf -from vyos.ifconfig.tunnel import Sit6RDIf +from vyos.ifconfig.tunnel import TunnelIf  from vyos.ifconfig.erspan import ERSpanIf  from vyos.ifconfig.erspan import ER6SpanIf  from vyos.ifconfig.wireless import WiFiIf diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index 709222b09..28b5da3ee 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.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 @@ -31,9 +31,7 @@ class BondIf(Interface):      monitoring may be performed.      """ -    default = { -        'type': 'bond', -    } +    iftype = 'bond'      definition = {          **Interface.definition,          ** { diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index 1bd617a05..69f652547 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -34,10 +34,7 @@ class BridgeIf(Interface):      The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard.      """ - -    default = { -        'type': 'bridge', -    } +    iftype = 'bridge'      definition = {          **Interface.definition,          **{ @@ -283,21 +280,21 @@ class BridgeIf(Interface):          if int(vlan_filter):              add_vlan = []              cur_vlan_ids = get_vlan_ids(ifname) -             +              tmp = dict_search('vif', config)              if tmp:                  for vif, vif_config in tmp.items():                      add_vlan.append(vif) -             +              # Remove redundant VLANs from the system              for vlan in list_diff(cur_vlan_ids, add_vlan):                  cmd = f'bridge vlan del dev {ifname} vid {vlan} self'                  self._cmd(cmd) -             +              for vlan in add_vlan:                  cmd = f'bridge vlan add dev {ifname} vid {vlan} self'                  self._cmd(cmd) -             +              # VLAN of bridge parent interface is always 1              # VLAN 1 is the default VLAN for all unlabeled packets              cmd = f'bridge vlan add dev {ifname} vid 1 pvid untagged self' @@ -337,7 +334,7 @@ class BridgeIf(Interface):                      native_vlan_id = None                      allowed_vlan_ids= []                      cur_vlan_ids = get_vlan_ids(interface) -                     +                      if 'native_vlan' in interface_config:                          vlan_id = interface_config['native_vlan']                          add_vlan.append(vlan_id) @@ -353,12 +350,12 @@ class BridgeIf(Interface):                              else:                                  add_vlan.append(vlan)                                  allowed_vlan_ids.append(vlan) -                     +                      # Remove redundant VLANs from the system                      for vlan in list_diff(cur_vlan_ids, add_vlan):                          cmd = f'bridge vlan del dev {interface} vid {vlan} master'                          self._cmd(cmd) -                     +                      for vlan in allowed_vlan_ids:                          cmd = f'bridge vlan add dev {interface} vid {vlan} master'                          self._cmd(cmd) diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py index 43136f361..d41dfef47 100644 --- a/python/vyos/ifconfig/control.py +++ b/python/vyos/ifconfig/control.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py index 19ef9d304..0019fc52b 100644 --- a/python/vyos/ifconfig/dummy.py +++ b/python/vyos/ifconfig/dummy.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -23,9 +23,7 @@ class DummyIf(Interface):      packets through without actually transmitting them.      """ -    default = { -        'type': 'dummy', -    } +    iftype = 'dummy'      definition = {          **Interface.definition,          **{ diff --git a/python/vyos/ifconfig/erspan.py b/python/vyos/ifconfig/erspan.py index 50230e14a..9e24cf6cd 100755 --- a/python/vyos/ifconfig/erspan.py +++ b/python/vyos/ifconfig/erspan.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 @@ -31,12 +31,7 @@ class _ERSpan(Interface):      """      _ERSpan: private base class for ERSPAN tunnels      """ -    default = { -        **Interface.default, -        **{ -            'type': 'erspan', -        } -    } +    iftype = 'erspan'      definition = {          **Interface.definition,          **{ @@ -44,18 +39,23 @@ class _ERSpan(Interface):              'prefixes': ['ersp',],          },      } -     -    options = ['local_ip','remote_ip','encapsulation','parameters'] -     +      def __init__(self,ifname,**config):          self.config = deepcopy(config) if config else {}          super().__init__(ifname, **self.config) -     +      def change_options(self):          pass -     +      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. """ + +        # call base class first +        super().update(config) +          # Enable/Disable of an interface must always be done at the end of the          # derived class to make use of the ref-counting set_admin_state()          # function. We will only enable the interface if 'up' was called as @@ -63,10 +63,9 @@ class _ERSpan(Interface):          # as certain parameters can only be changed when the interface is          # in admin-down state. This ensures the link does not flap during          # reconfiguration. -        super().update(config)          state = 'down' if 'disable' in config else 'up'          self.set_admin_state(state) -     +      def _create(self):          pass @@ -74,15 +73,15 @@ class ERSpanIf(_ERSpan):      """      ERSpanIf: private base class for ERSPAN Over GRE and IPv4 tunnels      """ -     +      def _create(self):          ifname = self.config['ifname'] -        local_ip = self.config['local_ip'] -        remote_ip = self.config['remote_ip'] +        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 {local_ip} remote {remote_ip} seq key {key} erspan_ver {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: @@ -94,24 +93,24 @@ class ERSpanIf(_ERSpan):              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'] -        local_ip = self.config['local_ip'] -        remote_ip = self.config['remote_ip'] +        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 {local_ip} remote {remote_ip} seq key {key} erspan_ver {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: @@ -123,29 +122,29 @@ class ERSpanIf(_ERSpan):              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'] -        local_ip = self.config['local_ip'] -        remote_ip = self.config['remote_ip'] +        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 {local_ip} remote {remote_ip} seq key {key} erspan_ver {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: @@ -157,24 +156,24 @@ class ER6SpanIf(_ERSpan):              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'] -        local_ip = self.config['local_ip'] -        remote_ip = self.config['remote_ip'] +        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 {local_ip} remote {remote_ip} seq key {key} erspan_ver {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: @@ -186,5 +185,5 @@ class ER6SpanIf(_ERSpan):              hwid=dict_search('parameters.erspan.hwid',self.config)              if hwid:                  command += f' erspan_hwid {hwid}' -                 +          self._cmd(command) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 547b54b84..aca0aeead 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -26,10 +26,7 @@ class EthernetIf(Interface):      """      Abstraction of a Linux Ethernet Interface      """ - -    default = { -        'type': 'ethernet', -    } +    iftype = 'ethernet'      definition = {          **Interface.definition,          **{ diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index 5c4597be8..1b3ee0dc9 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -26,14 +26,7 @@ class GeneveIf(Interface):      https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve      https://lwn.net/Articles/644938/      """ - -    default = { -        'type': 'geneve', -        'vni': 0, -        'remote': '', -    } -    options = Interface.options + \ -        ['vni', 'remote'] +    iftype = 'geneve'      definition = {          **Interface.definition,          **{ diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py index a6e566d87..db7d2b6b4 100644 --- a/python/vyos/ifconfig/input.py +++ b/python/vyos/ifconfig/input.py @@ -17,9 +17,6 @@ from vyos.ifconfig.interface import Interface  @Interface.register  class InputIf(Interface): -    default = { -        'type': '', -    }      definition = {          **Interface.definition,          **{ diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 4bdabd432..9c0ee2ab1 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.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 @@ -60,7 +60,6 @@ class Interface(Control):      options = ['debug', 'create']      required = []      default = { -        'type': '',          'debug': True,          'create': True,      } @@ -232,26 +231,21 @@ class Interface(Control):          >>> from vyos.ifconfig import Interface          >>> i = Interface('eth0')          """ +        self.config = deepcopy(kargs) +        self.config['ifname'] = self.ifname = ifname -        self.config = deepcopy(self.default) -        for k in self.options: -            if k in kargs: -                self.config[k] = kargs[k] - -        # make sure the ifname is the first argument and not from the dict -        self.config['ifname'] = ifname          self._admin_state_down_cnt = 0          # we must have updated config before initialising the Interface          super().__init__(**kargs) -        self.ifname = ifname          if not self.exists(ifname): -            # Any instance of Interface, such as Interface('eth0') -            # can be used safely to access the generic function in this class -            # as 'type' is unset, the class can not be created -            if not self.config['type']: +            # Any instance of Interface, such as Interface('eth0') can be used +            # safely to access the generic function in this class as 'type' is +            # unset, the class can not be created +            if not self.iftype:                  raise Exception(f'interface "{ifname}" not found') +            self.config['type'] = self.iftype              # Should an Instance of a child class (EthernetIf, DummyIf, ..)              # be required, then create should be set to False to not accidentally create it. @@ -1300,17 +1294,7 @@ class Interface(Control):  class VLANIf(Interface):      """ Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """ -    default = { -        'type': 'vlan', -        'source_interface': '', -        'vlan_id': '', -        'protocol': '', -        'ingress_qos': '', -        'egress_qos': '', -    } - -    options = Interface.options + \ -        ['source_interface', 'vlan_id', 'protocol', 'ingress_qos', 'egress_qos'] +    iftype = 'vlan'      def remove(self):          """ @@ -1339,11 +1323,11 @@ class VLANIf(Interface):              return          cmd = 'ip link add link {source_interface} name {ifname} type vlan id {vlan_id}' -        if self.config['protocol']: +        if 'protocol' in self.config:              cmd += ' protocol {protocol}' -        if self.config['ingress_qos']: +        if 'ingress_qos' in self.config:              cmd += ' ingress-qos-map {ingress_qos}' -        if self.config['egress_qos']: +        if 'egress_qos' in self.config:              cmd += ' egress-qos-map {egress_qos}'          self._cmd(cmd.format(**self.config)) diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py index 8ed3d5afb..34f8cd4d3 100644 --- a/python/vyos/ifconfig/l2tpv3.py +++ b/python/vyos/ifconfig/l2tpv3.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -24,19 +24,7 @@ class L2TPv3If(Interface):      either hot standby or load balancing services. Additionally, link integrity      monitoring may be performed.      """ - -    default = { -        'type': 'l2tp', -        'peer_tunnel_id': '', -        'local_port': 0, -        'remote_port': 0, -        'encapsulation': 'udp', -        'local_address': '', -        'remote_address': '', -        'session_id': '', -        'tunnel_id': '', -        'peer_session_id': '' -    } +    iftype = 'l2tp'      definition = {          **Interface.definition,          **{ @@ -45,20 +33,16 @@ class L2TPv3If(Interface):              'bridgeable': True,          }      } -    options = Interface.options + \ -        ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port', -         'encapsulation', 'local_address', 'remote_address', 'session_id', -         'peer_session_id']      def _create(self):          # create tunnel interface          cmd = 'ip l2tp add tunnel tunnel_id {tunnel_id}'          cmd += ' peer_tunnel_id {peer_tunnel_id}' -        cmd += ' udp_sport {local_port}' -        cmd += ' udp_dport {remote_port}' +        cmd += ' udp_sport {source_port}' +        cmd += ' udp_dport {destination_port}'          cmd += ' encap {encapsulation}' -        cmd += ' local {local_address}' -        cmd += ' remote {remote_address}' +        cmd += ' local {local_ip}' +        cmd += ' remote {remote_ip}'          self._cmd(cmd.format(**self.config))          # setup session @@ -82,20 +66,20 @@ class L2TPv3If(Interface):          >>> i.remove()          """ -        if self.exists(self.config['ifname']): +        if self.exists(self.ifname):              # interface is always A/D down. It needs to be enabled explicitly              self.set_admin_state('down') -            if self.config['tunnel_id'] and self.config['session_id']: +            if {'tunnel_id', 'session_id'} <= set(self.config):                  cmd = 'ip l2tp del session tunnel_id {tunnel_id}'                  cmd += ' session_id {session_id}'                  self._cmd(cmd.format(**self.config)) -            if self.config['tunnel_id']: +            if 'tunnel_id' in self.config:                  cmd = 'ip l2tp del tunnel tunnel_id {tunnel_id}'                  self._cmd(cmd.format(**self.config)) -     -     + +      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 diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py index 0e632d826..d323feed8 100644 --- a/python/vyos/ifconfig/loopback.py +++ b/python/vyos/ifconfig/loopback.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -22,9 +22,8 @@ class LoopbackIf(Interface):      uses to communicate with itself.      """      _persistent_addresses = ['127.0.0.1/8', '::1/128'] -    default = { -        'type': 'loopback', -    } +    iftype = 'loopback' +      definition = {          **Interface.definition,          **{ @@ -33,9 +32,6 @@ class LoopbackIf(Interface):              'bridgeable': True,          }      } - -    name = 'loopback' -      def remove(self):          """          Loopback interface can not be deleted from operating system. We can diff --git a/python/vyos/ifconfig/macsec.py b/python/vyos/ifconfig/macsec.py index 456686ea6..c15273080 100644 --- a/python/vyos/ifconfig/macsec.py +++ b/python/vyos/ifconfig/macsec.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -27,12 +27,7 @@ class MACsecIf(Interface):      other security solutions such as IPsec (layer 3) or TLS (layer 4), as all      those solutions are used for their own specific use cases.      """ - -    default = { -        'type': 'macsec', -        'security_cipher': '', -        'source_interface': '' -    } +    iftype = 'macsec'      definition = {          **Interface.definition,          **{ @@ -40,8 +35,6 @@ class MACsecIf(Interface):              'prefixes': ['macsec', ],          },      } -    options = Interface.options + \ -        ['security_cipher', 'source_interface']      def _create(self):          """ @@ -49,9 +42,9 @@ class MACsecIf(Interface):          down by default.          """          # create tunnel interface -        cmd  = 'ip link add link {source_interface} {ifname} type {type}' -        cmd += ' cipher {security_cipher}' -        self._cmd(cmd.format(**self.config)) +        cmd  = 'ip link add link {source_interface} {ifname} type {type}'.format(**self.config) +        cmd += f' cipher {self.config["security"]["cipher"]}' +        self._cmd(cmd)          # interface is always A/D down. It needs to be enabled explicitly          self.set_admin_state('down') diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index 2447fec77..73e2eea9e 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.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 @@ -20,13 +20,7 @@ class MACVLANIf(Interface):      """      Abstraction of a Linux MACvlan interface      """ - -    default = { -        'type': 'macvlan', -        'address': '', -        'source_interface': '', -        'mode': '', -    } +    iftype = 'macvlan'      definition = {          **Interface.definition,          **{ @@ -34,17 +28,10 @@ class MACVLANIf(Interface):              'prefixes': ['peth', ],          },      } -    options = Interface.options + \ -        ['source_interface', 'mode']      def _create(self):          # please do not change the order when assembling the command -        cmd = 'ip link add {ifname}' -        if self.config['source_interface']: -            cmd += ' link {source_interface}' -        cmd += ' type macvlan' -        if self.config['mode']: -            cmd += ' mode {mode}' +        cmd = 'ip link add {ifname} link {source_interface} type {type} mode {mode}'          self._cmd(cmd.format(**self.config))      def set_mode(self, mode): diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py deleted file mode 100644 index 787245696..000000000 --- a/python/vyos/ifconfig/pppoe.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020 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.interface import Interface - - -@Interface.register -class PPPoEIf(Interface): -    default = { -        'type': 'pppoe', -    } -    definition = { -        **Interface.definition, -        **{ -            'section': 'pppoe', -            'prefixes': ['pppoe', ], -        }, -    } - -    # stub this interface is created in the configure script - -    def _create(self): -        # we can not create this interface as it is managed outside -        pass - -    def _delete(self): -        # we can not create this interface as it is managed outside -        pass diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py index 4320bf8bc..bb940b0cf 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      """ @@ -48,16 +46,30 @@ class _Tunnel(Interface):          },      } -    default = { -        'local' : '', -        'remote': '', -        'dev' : '', -        'ttl' : '', -        'tos' : '', -        'key' : '', -        'raw' : '', +    # 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', +    } +    mapping_ipv6 = { +        'parameters.ipv6.encaplimit'      : 'encaplimit', +        'parameters.ipv6.flowlabel'       : 'flowlabel', +        'parameters.ipv6.hoplimit'        : 'hoplimit', +        'parameters.ipv6.tclass'          : 'tclass',      } -    options = Interface.options + list(default.keys())      # TODO: This is surely used for more than tunnels      # TODO: could be refactored elsewhere @@ -76,28 +88,67 @@ class _Tunnel(Interface):              },          }      } -    _create_cmd = 'ip tunnel add {ifname} mode {type}' + +    def __init__(self, ifname, **kargs): +        self.iftype = kargs['encapsulation'] +        super().__init__(ifname, **kargs) + +        # The gretap interface has the possibility to act as L2 bridge +        if self.iftype == 'gretap': +            # no multicast, ttl or tos for gretap +            self.definition = { +                **TunnelIf.definition, +                **{ +                    'bridgeable': True, +                }, +            } +      def _create(self): -        # add " option-name option-name-value ..." for all options set -        options = ' '.join(['{} {}'.format(k, self.config[k]) -                            for k,v in self.config.items() if v and k not in -                                                    ['ifname', 'type', 'raw']]) -        if 'raw' in self.config: -            options += ' ' + ' '.join(self.config['raw']) -        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 == 'gretap': +            cmd = 'ip link add name {ifname} type {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 change_options(self): -        change = 'ip tunnel change {ifname} mode {type}' +        self.set_admin_state('down') -        # add " option-name option-name-value ..." for all options set -        options = ' '.join(['{} {}'.format(k, self.config[k]) -                            for k,v in self.config.items() if v and k not in -                                                    ['ifname', 'type', 'raw']]) -        if 'raw' in self.config: -            options += ' ' + ' '.join(self.config['raw']) -        self._cmd('{} {}'.format(change.format(**self.config), options)) +    def _change_options(self): +        # gretap interfaces do not support changing any parameter +        if self.iftype == 'gretap': +            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):          """ @@ -128,6 +179,8 @@ class _Tunnel(Interface):          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) @@ -141,159 +194,3 @@ class _Tunnel(Interface):          # reconfiguration.          state = 'down' if 'disable' in config else 'up'          self.set_admin_state(state) - -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 = { -        **_Tunnel.default, -        **{ -            'type': 'gre', -            'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command -        }, -    } - -# 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 -    definition = { -        **_Tunnel.definition, -        **{ -            'bridgeable': True, -        }, -    } -    default = { -        'type': 'gretap', -        'local': '', -        'remote': '', -        'dev': '', -        'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command -    } - -    _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 = { -        **_Tunnel.default, -        **{ -            'type': 'ip6gre', -            '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 = { -        **_Tunnel.default, -        **{ -            'type': 'ipip', -            'raw' : ['pmtudisc'], # parameters that we can pass raw to ip command -        }, -    } - -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 = { -        **_Tunnel.default, -        **{ -            'type': 'ipip6', -            '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 = { -        **_Tunnel.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 = { -        **_Tunnel.default, -        **{ -            'type': 'sit', -        }, -    } - -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 -    default = { -        **_Tunnel.default, -        **{ -            'type': '6rd', -            '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') - -        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)) diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py index d0745898c..e2090c889 100644 --- a/python/vyos/ifconfig/vti.py +++ b/python/vyos/ifconfig/vti.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# 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 @@ -17,9 +17,7 @@ from vyos.ifconfig.interface import Interface  @Interface.register  class VTIIf(Interface): -    default = { -        'type': 'vti', -    } +    iftype = 'vti'      definition = {          **Interface.definition,          **{ diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py index 99a592b3e..8f027cf9d 100644 --- a/python/vyos/ifconfig/vtun.py +++ b/python/vyos/ifconfig/vtun.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -17,10 +17,7 @@ from vyos.ifconfig.interface import Interface  @Interface.register  class VTunIf(Interface): -    default = { -        'type': 'vtun', -        'device_type': 'tun', -    } +    iftype = 'vtun'      definition = {          **Interface.definition,          **{ @@ -29,7 +26,6 @@ class VTunIf(Interface):              'bridgeable': True,          },      } -    options = Interface.options + ['device_type']      def _create(self):          """ Depending on OpenVPN operation mode the interface is created diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index ad1f605ed..291332a77 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -15,6 +15,7 @@  from vyos import ConfigError  from vyos.ifconfig.interface import Interface +from vyos.util import dict_search  @Interface.register  class VXLANIf(Interface): @@ -38,16 +39,7 @@ class VXLANIf(Interface):      https://www.kernel.org/doc/Documentation/networking/vxlan.txt      """ -    default = { -        'type': 'vxlan', -        'group': '', -        'port': 8472,   # The Linux implementation of VXLAN pre-dates -                        # the IANA's selection of a standard destination port -        'remote': '', -        'source_address': '', -        'source_interface': '', -        'vni': 0 -    } +    iftype = 'vxlan'      definition = {          **Interface.definition,          **{ @@ -56,44 +48,35 @@ class VXLANIf(Interface):              'bridgeable': True,          }      } -    options = Interface.options + \ -        ['group', 'remote', 'source_interface', 'port', 'vni', 'source_address'] - -    mapping = { -        'ifname': 'add', -        'vni':    'id', -        'port':   'dstport', -        'source_address': 'local', -        'source_interface': 'dev', -    }      def _create(self): -        cmdline = ['ifname', 'type', 'vni', 'port'] - -        if self.config['source_address']: -            cmdline.append('source_address') - -        if self.config['remote']: -            cmdline.append('remote') - -        if self.config['group'] or self.config['source_interface']: -            if self.config['group'] and self.config['source_interface']: -                cmdline.append('group') -                cmdline.append('source_interface') -            else: -                ifname = self.config['ifname'] -                raise ConfigError( -                    f'VXLAN "{ifname}" is missing mandatory underlay multicast' -                     'group or source interface for a multicast network.') - -        cmd = 'ip link' -        for key in cmdline: -            value = self.config.get(key, '') -            if not value: -                continue -            cmd += ' {} {}'.format(self.mapping.get(key, key), value) +        # 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 +        mapping = { +            'source_address'            : 'local', +            'source_interface'          : 'dev', +            'remote'                    : 'remote', +            'group'                     : 'group', +            'parameters.ip.tos'         : 'tos', +            'parameters.ip.ttl'         : 'ttl', +            'parameters.ipv6.flowlabel' : 'flowlabel', +            'parameters.nolearning'     : 'nolearning', +        } -        self._cmd(cmd) +        cmd = 'ip link add {ifname} type {type} id {vni} dstport {port}' +        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 update(self, config):          """ General helper function which works on a dictionary retrived by diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 9ee798ee8..f377e2b1d 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -1,4 +1,4 @@ -# Copyright 2019 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 @@ -148,18 +148,7 @@ class WireGuardOperational(Operational):  @Interface.register  class WireGuardIf(Interface):      OperationalClass = WireGuardOperational - -    default = { -        'type': 'wireguard', -        'port': 0, -        'private_key': None, -        'pubkey': None, -        'psk': '', -        'allowed_ips': [], -        'fwmark': 0x00, -        'endpoint': None, -        'keepalive': 0 -    } +    iftype = 'wireguard'      definition = {          **Interface.definition,          **{ @@ -168,9 +157,6 @@ class WireGuardIf(Interface):              'bridgeable': False,          }      } -    options = Interface.options + \ -        ['port', 'private_key', 'pubkey', 'psk', -         'allowed_ips', 'fwmark', 'endpoint', 'keepalive']      def get_mac(self):          """ diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py index 37703d242..6c0eeec44 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -1,4 +1,4 @@ -# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-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 @@ -20,11 +20,7 @@ class WiFiIf(Interface):      """      Handle WIFI/WLAN interfaces.      """ - -    default = { -        'type': 'wifi', -        'phy': 'phy0' -    } +    iftype = 'wifi'      definition = {          **Interface.definition,          **{ @@ -33,14 +29,10 @@ class WiFiIf(Interface):              'bridgeable': True,          }      } -    options = Interface.options + \ -        ['phy', 'op_mode'] -      def _create(self):          # all interfaces will be added in monitor mode -        cmd = 'iw phy {phy} interface add {ifname} type monitor' \ -            .format(**self.config) -        self._cmd(cmd) +        cmd = 'iw phy {physical_device} interface add {ifname} type monitor' +        self._cmd(cmd.format(**self.config))          # wireless interface is administratively down by default          self.set_admin_state('down') @@ -81,14 +73,3 @@ class WiFiIf(Interface):          # reconfiguration.          state = 'down' if 'disable' in config else 'up'          self.set_admin_state(state) - - -@Interface.register -class WiFiModemIf(WiFiIf): -    definition = { -        **WiFiIf.definition, -        **{ -            'section': 'wirelessmodem', -            'prefixes': ['wlm', ], -        } -    } diff --git a/smoketest/configs/tunnel-broker b/smoketest/configs/tunnel-broker new file mode 100644 index 000000000..54e63abda --- /dev/null +++ b/smoketest/configs/tunnel-broker @@ -0,0 +1,103 @@ +interfaces { +    dummy dum0 { +        address 192.0.2.0/32 +    } +    dummy dum1 { +        address 192.0.2.1/32 +    } +    dummy dum2 { +        address 192.0.2.2/32 +    } +    dummy dum3 { +        address 192.0.2.3/32 +    } +    dummy dum4 { +        address 192.0.2.4/32 +    } +    ethernet eth0 { +        duplex auto +        smp-affinity auto +        speed auto +        address 172.18.202.10/24 +    } +    tunnel tun100 { +        address 172.16.0.1/30 +        encapsulation gre-bridge +        local-ip 192.0.2.0 +        remote-ip 192.0.2.100 +    } +    tunnel tun200 { +        address 172.16.0.5/30 +        encapsulation gre +        local-ip 192.0.2.1 +        remote-ip 192.0.2.101 +    } +    tunnel tun300 { +        address 172.16.0.9/30 +        encapsulation ipip +        local-ip 192.0.2.2 +        remote-ip 192.0.2.102 +    } +    tunnel tun400 { +        address 172.16.0.13/30 +        encapsulation gre-bridge +        local-ip 192.0.2.3 +        remote-ip 192.0.2.103 +    } +    tunnel tun500 { +        address 172.16.0.17/30 +        encapsulation gre +        local-ip 192.0.2.4 +        remote-ip 192.0.2.104 +    } +} +protocols { +    static { +        route 0.0.0.0/0 { +            next-hop 172.18.202.1 { +                distance 10 +            } +        } +    } +} +system { +    config-management { +        commit-revisions 100 +    } +    console { +        device ttyS0 { +            speed 115200 +        } +    } +    host-name vyos +    login { +        user vyos { +            authentication { +                encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 +                plaintext-password "" +            } +        } +    } +    ntp { +        server 0.pool.ntp.org { +        } +        server 1.pool.ntp.org { +        } +        server 2.pool.ntp.org { +        } +    } +    syslog { +        global { +            facility all { +                level info +            } +            facility protocols { +                level debug +            } +        } +    } +} + +/* Warning: Do not remove the following line. */ +/* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@10:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ +/* Release version: 1.2.6-S1 */ diff --git a/smoketest/scripts/cli/test_interfaces_erspan.py b/smoketest/scripts/cli/test_interfaces_erspan.py index c180f0a34..d0814f2fb 100755 --- a/smoketest/scripts/cli/test_interfaces_erspan.py +++ b/smoketest/scripts/cli/test_interfaces_erspan.py @@ -27,51 +27,6 @@ mtu = 1500  def erspan_conf(interface):      tmp = cmd(f'ip -d -j link show {interface}') -    ''' -    [ -        { -            "ifindex": 17, -            "link": null, -            "ifname": "ersp0", -            "flags": [ -                "BROADCAST", -                "MULTICAST" -            ], -            "mtu": 1450, -            "qdisc": "noop", -            "operstate": "DOWN", -            "linkmode": "DEFAULT", -            "group": "default", -            "txqlen": 1000, -            "link_type": "ether", -            "address": "22:27:14:7b:0d:79", -            "broadcast": "ff:ff:ff:ff:ff:ff", -            "promiscuity": 0, -            "min_mtu": 68, -            "max_mtu": 0, -            "linkinfo": { -                "info_kind": "erspan", -                "info_data": { -                    "remote": "10.2.2.2", -                    "local": "10.1.1.1", -                    "ttl": 0, -                    "pmtudisc": true, -                    "ikey": "0.0.0.123", -                    "okey": "0.0.0.123", -                    "iseq": true, -                    "oseq": true, -                    "erspan_index": 0, -                    "erspan_ver": 1 -                } -            }, -            "inet6_addr_gen_mode": "eui64", -            "num_tx_queues": 1, -            "num_rx_queues": 1, -            "gso_max_size": 65536, -            "gso_max_segs": 65535 -        } -    ] -    '''      return json.loads(tmp)[0]  class ERSPanTunnelInterfaceTest(BasicInterfaceTest.BaseTest): @@ -96,8 +51,8 @@ class ERSPanTunnelInterfaceTest(BasicInterfaceTest.BaseTest):          key = 123          self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) -        self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) -        self.session.set(self._base_path + [interface, 'remote-ip', self.remote_v4]) +        self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) +        self.session.set(self._base_path + [interface, 'remote', self.remote_v4])          self.session.set(self._base_path + [interface, 'parameters', 'ip' , 'key', str(key)])          self.session.commit() @@ -107,8 +62,8 @@ class ERSPanTunnelInterfaceTest(BasicInterfaceTest.BaseTest):          self.assertEqual(encapsulation, conf['linkinfo']['info_kind'])          self.assertEqual(mtu, conf['mtu']) -        self.assertEqual(self.local_v4, conf['linkinfo']['info_data']['local']) -        self.assertEqual(self.remote_v4,     conf['linkinfo']['info_data']['remote']) +        self.assertEqual(self.local_v4,  conf['linkinfo']['info_data']['local']) +        self.assertEqual(self.remote_v4, conf['linkinfo']['info_data']['remote'])      def test_erspan_ipv6(self): @@ -117,8 +72,8 @@ class ERSPanTunnelInterfaceTest(BasicInterfaceTest.BaseTest):          key = 123          self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) -        self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) -        self.session.set(self._base_path + [interface, 'remote-ip', self.remote_v6]) +        self.session.set(self._base_path + [interface, 'source-address', self.local_v6]) +        self.session.set(self._base_path + [interface, 'remote', self.remote_v6])          self.session.set(self._base_path + [interface, 'parameters', 'ip' , 'key', str(key)])          self.session.commit() @@ -128,8 +83,8 @@ class ERSPanTunnelInterfaceTest(BasicInterfaceTest.BaseTest):          self.assertEqual(encapsulation, conf['linkinfo']['info_kind'])          self.assertEqual(mtu, conf['mtu']) -        self.assertEqual(self.local_v6, conf['linkinfo']['info_data']['local']) -        self.assertEqual(self.remote_v6,     conf['linkinfo']['info_data']['remote']) +        self.assertEqual(self.local_v6,  conf['linkinfo']['info_data']['local']) +        self.assertEqual(self.remote_v6, conf['linkinfo']['info_data']['remote'])  if __name__ == '__main__':      unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index 3a3e7bff3..d6bef993a 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -14,6 +14,7 @@  # 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  import re  import unittest @@ -22,6 +23,7 @@ from netifaces import interfaces  from vyos.configsession import ConfigSessionError  from vyos.ifconfig import Section +from vyos.util import cmd  from vyos.util import read_file  from vyos.util import process_named_running @@ -30,6 +32,16 @@ def get_config_value(interface, key):      tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)      return tmp[0] +def get_cipher(interface): +    """ Returns the used encapsulation protocol for given interface. +        If interface does not exist, None is returned. +    """ +    if not os.path.exists(f'/sys/class/net/{interface}'): +        return None +    from json import loads +    tmp = loads(cmd(f'ip -d -j link show {interface}'))[0] +    return tmp['linkinfo']['info_data']['cipher_suite'].lower() +  class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):      @classmethod      def setUpClass(cls): @@ -107,8 +119,30 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):              # Check for running process              self.assertTrue(process_named_running('wpa_supplicant')) -    def test_macsec_mandatory_options(self): +    def test_macsec_gcm_aes_128(self):          interface = 'macsec1' +        cipher = 'gcm-aes-128' +        self.session.set(self._base_path + [interface]) + +        # check validate() - source interface is mandatory +        with self.assertRaises(ConfigSessionError): +            self.session.commit() +        self.session.set(self._base_path + [interface, 'source-interface', 'eth0']) + +        # check validate() - cipher is mandatory +        with self.assertRaises(ConfigSessionError): +            self.session.commit() +        self.session.set(self._base_path + [interface, 'security', 'cipher', cipher]) + +        # final commit and verify +        self.session.commit() +        self.assertIn(interface, interfaces()) +        self.assertIn(interface, interfaces()) +        self.assertEqual(cipher, get_cipher(interface)) + +    def test_macsec_gcm_aes_256(self): +        interface = 'macsec4' +        cipher = 'gcm-aes-256'          self.session.set(self._base_path + [interface])          # check validate() - source interface is mandatory @@ -119,11 +153,12 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):          # check validate() - cipher is mandatory          with self.assertRaises(ConfigSessionError):              self.session.commit() -        self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128']) +        self.session.set(self._base_path + [interface, 'security', 'cipher', cipher])          # final commit and verify          self.session.commit()          self.assertIn(interface, interfaces()) +        self.assertEqual(cipher, get_cipher(interface))      def test_macsec_source_interface(self):          # Ensure source-interface can bot be part of any other bond or bridge diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index a9250e3e5..cc8fbd527 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -20,6 +20,7 @@ import json  from vyos.configsession import ConfigSession  from vyos.configsession import ConfigSessionError  from vyos.util import cmd +from vyos.template import inc_ip  from base_interfaces_test import BasicInterfaceTest @@ -70,8 +71,8 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):          cls.local_v4 = '192.0.2.1'          cls.local_v6 = '2001:db8::1'          cls._options = { -            'tun10': ['encapsulation ipip', 'remote-ip 192.0.2.10', 'local-ip ' + cls.local_v4], -            'tun20': ['encapsulation gre',  'remote-ip 192.0.2.20', 'local-ip ' + cls.local_v4], +            '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) @@ -90,25 +91,25 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):          interface = f'tun1000'          local_if_addr = f'10.10.200.1/24' -        for encapsulation in ['ipip', 'sit', 'gre', 'gre-bridge']: +        for encapsulation in ['ipip', 'sit', 'gre', 'gretap']:              self.session.set(self._base_path + [interface, 'address', local_if_addr])              self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) -            self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) -            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) +            self.session.set(self._base_path + [interface, 'source-address', self.local_v6]) +            self.session.set(self._base_path + [interface, 'remote', remote_ip6]) -            # Encapsulation mode requires IPv4 local-ip +            # Encapsulation mode requires IPv4 source-address              with self.assertRaises(ConfigSessionError):                  self.session.commit() -            self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) +            self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) -            # Encapsulation mode requires IPv4 local-ip +            # Encapsulation mode requires IPv4 remote              with self.assertRaises(ConfigSessionError):                  self.session.commit() -            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) +            self.session.set(self._base_path + [interface, 'remote', remote_ip4])              self.session.set(self._base_path + [interface, 'source-interface', source_if]) -            # Source interface can not be used with sit and gre-bridge -            if encapsulation in ['sit', 'gre-bridge']: +            # Source interface can not be used with sit and gretap +            if encapsulation in ['sit', 'gretap']:                  with self.assertRaises(ConfigSessionError):                      self.session.commit()                  self.session.delete(self._base_path + [interface, 'source-interface']) @@ -117,17 +118,14 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):              self.session.commit()              conf = tunnel_conf(interface) -            self.assertEqual(interface, conf['ifname']) -            self.assertEqual(mtu, conf['mtu']) - -            if encapsulation not in ['sit', 'gre-bridge']: +            if encapsulation not in ['sit', 'gretap']:                  self.assertEqual(source_if, conf['link']) -                self.assertEqual(encapsulation, conf['link_type']) -            elif encapsulation in ['gre-bridge']: -                self.assertEqual('ether', conf['link_type']) +            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.assertEqual(remote_ip4,    conf['linkinfo']['info_data']['remote'])              self.assertTrue(conf['linkinfo']['info_data']['pmtudisc'])              # cleanup this instance @@ -143,18 +141,18 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):          for encapsulation in ['ipip6', 'ip6ip6', 'ip6gre']:              self.session.set(self._base_path + [interface, 'address', local_if_addr])              self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) -            self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) -            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) +            self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) +            self.session.set(self._base_path + [interface, 'remote', remote_ip4]) -            # Encapsulation mode requires IPv6 local-ip +            # Encapsulation mode requires IPv6 source-address              with self.assertRaises(ConfigSessionError):                  self.session.commit() -            self.session.set(self._base_path + [interface, 'local-ip', self.local_v6]) +            self.session.set(self._base_path + [interface, 'source-address', self.local_v6]) -            # Encapsulation mode requires IPv6 local-ip +            # Encapsulation mode requires IPv6 remote              with self.assertRaises(ConfigSessionError):                  self.session.commit() -            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6]) +            self.session.set(self._base_path + [interface, 'remote', remote_ip6])              # Configure Tunnel Source interface              self.session.set(self._base_path + [interface, 'source-interface', source_if]) @@ -167,14 +165,15 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):              self.assertEqual(mtu, conf['mtu'])              self.assertEqual(source_if, conf['link']) -            # remap encapsulation protocol(s) -            if encapsulation in ['ipip6', 'ip6ip6']: -                encapsulation = 'tunnel6' -            elif encapsulation in ['ip6gre']: -                encapsulation = 'gre6' +            # Not applicable for ip6gre +            if 'proto' in conf['linkinfo']['info_data']: +                self.assertEqual(encapsulation, conf['linkinfo']['info_data']['proto']) -            self.assertEqual(encapsulation, conf['link_type']) +            # 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']) @@ -183,18 +182,18 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):              self.session.commit()      def test_tunnel_verify_local_dhcp(self): -        # We can not use local-ip and dhcp-interface at the same time +        # 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.session.set(self._base_path + [interface, 'address', local_if_addr])          self.session.set(self._base_path + [interface, 'encapsulation', 'gre']) -        self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) -        self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) +        self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) +        self.session.set(self._base_path + [interface, 'remote', remote_ip4])          self.session.set(self._base_path + [interface, 'dhcp-interface', 'eth0']) -        # local-ip and dhcp-interface can not be used at the same time +        # source-address and dhcp-interface can not be used at the same time          with self.assertRaises(ConfigSessionError):              self.session.commit()          self.session.delete(self._base_path + [interface, 'dhcp-interface']) @@ -209,8 +208,8 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):          tos = '20'          self.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) -        self.session.set(self._base_path + [interface, 'local-ip', self.local_v4]) -        self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4]) +        self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) +        self.session.set(self._base_path + [interface, 'remote', remote_ip4])          self.session.set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery'])          self.session.set(self._base_path + [interface, 'parameters', 'ip', 'key', gre_key]) @@ -222,11 +221,41 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):          conf = tunnel_conf(interface)          self.assertEqual(mtu,           conf['mtu'])          self.assertEqual(interface,     conf['ifname']) -        self.assertEqual(encapsulation, conf['link_type']) +        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.session.set(self._base_path + [interface, 'encapsulation', encapsulation]) +        self.session.set(self._base_path + [interface, 'source-address', self.local_v4]) +        self.session.set(self._base_path + [interface, 'remote', remote_ip4]) + +        # Check if commit is ok +        self.session.commit() + +        conf = tunnel_conf(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.session.set(self._base_path + [interface, 'remote', new_remote]) +        # Check if commit is ok +        self.session.commit() + +        conf = tunnel_conf(interface) +        self.assertEqual(new_remote,    conf['linkinfo']['info_data']['remote']) +  if __name__ == '__main__': -    unittest.main(verbosity=2) +    unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index fcc1b15ce..adbe7138b 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -27,8 +27,9 @@ class VXLANInterfaceTest(BasicInterfaceTest.BaseTest):          cls._test_mtu = True          cls._base_path = ['interfaces', 'vxlan']          cls._options = { -            'vxlan0': ['vni 10', 'remote 127.0.0.2'], -            'vxlan1': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], +            'vxlan10': ['vni 10', 'remote 127.0.0.2'], +            'vxlan20': ['vni 20', 'group 239.1.1.1', 'source-interface eth0'], +            'vxlan30': ['vni 30', 'remote 2001:db8:2000::1', 'source-address 2001:db8:1000::1'],          }          cls._interfaces = list(cls._options) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index db7b2dda4..e74e22a5c 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -200,6 +200,7 @@ class TestServiceDHCPServer(unittest.TestCase):      def test_dhcp_single_pool_static_mapping(self):          shared_net_name = 'SMOKE-2' +        domain_name = 'private'          pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]          # we use the first subnet IP address as default gateway diff --git a/src/conf_mode/interfaces-erspan.py b/src/conf_mode/interfaces-erspan.py index 2d65b834c..97ae3cf55 100755 --- a/src/conf_mode/interfaces-erspan.py +++ b/src/conf_mode/interfaces-erspan.py @@ -48,7 +48,7 @@ def get_config(config=None):          conf = Config()      base = ['interfaces', 'erspan']      erspan = get_interface_dict(conf, base) -     +      tmp = leaf_node_changed(conf, ['encapsulation'])      if tmp:          erspan.update({'encapsulation_changed': {}}) @@ -58,30 +58,30 @@ def get_config(config=None):  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  -     +    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 @@ -90,14 +90,8 @@ def apply(erspan):      # We need to re-map the tunnel encapsulation proto to a valid interface class      encap = erspan['encapsulation']      klass = dispatch[encap] -     -    conf = deepcopy(erspan) -     -    conf.update(klass.get_config()) -     -    del conf['ifname'] -     -    erspan_tunnel = klass(erspan['ifname'],**conf) + +    erspan_tunnel = klass(**erspan)      erspan_tunnel.change_options()      erspan_tunnel.update(erspan) diff --git a/src/conf_mode/interfaces-geneve.py b/src/conf_mode/interfaces-geneve.py index 979a5612e..2a63b60aa 100755 --- a/src/conf_mode/interfaces-geneve.py +++ b/src/conf_mode/interfaces-geneve.py @@ -72,18 +72,8 @@ def apply(geneve):          g.remove()      if 'deleted' not in geneve: -        # This is a special type of interface which needs additional parameters -        # when created using iproute2. Instead of passing a ton of arguments, -        # use a dictionary provided by the interface class which holds all the -        # options necessary. -        conf = GeneveIf.get_config() - -        # Assign GENEVE instance configuration parameters to config dict -        conf['vni'] = geneve['vni'] -        conf['remote'] = geneve['remote'] -          # Finally create the new interface -        g = GeneveIf(geneve['ifname'], **conf) +        g = GeneveIf(**geneve)          g.update(geneve)      return None diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py index 7b3afa058..5bae66074 100755 --- a/src/conf_mode/interfaces-l2tpv3.py +++ b/src/conf_mode/interfaces-l2tpv3.py @@ -83,34 +83,16 @@ def generate(l2tpv3):      return None  def apply(l2tpv3): -    # This is a special type of interface which needs additional parameters -    # when created using iproute2. Instead of passing a ton of arguments, -    # use a dictionary provided by the interface class which holds all the -    # options necessary. -    conf = L2TPv3If.get_config() -      # Check if L2TPv3 interface already exists      if l2tpv3['ifname'] in interfaces():          # L2TPv3 is picky when changing tunnels/sessions, thus we can simply          # always delete it first. -        conf['session_id'] = l2tpv3['session_id'] -        conf['tunnel_id'] = l2tpv3['tunnel_id'] -        l = L2TPv3If(l2tpv3['ifname'], **conf) +        l = L2TPv3If(**l2tpv3)          l.remove()      if 'deleted' not in l2tpv3: -        conf['peer_tunnel_id'] = l2tpv3['peer_tunnel_id'] -        conf['local_port'] = l2tpv3['source_port'] -        conf['remote_port'] = l2tpv3['destination_port'] -        conf['encapsulation'] = l2tpv3['encapsulation'] -        conf['local_address'] = l2tpv3['local_ip'] -        conf['remote_address'] = l2tpv3['remote_ip'] -        conf['session_id'] = l2tpv3['session_id'] -        conf['tunnel_id'] = l2tpv3['tunnel_id'] -        conf['peer_session_id'] = l2tpv3['peer_session_id'] -          # Finally create the new interface -        l = L2TPv3If(l2tpv3['ifname'], **conf) +        l = L2TPv3If(**l2tpv3)          l.update(l2tpv3)      return None diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index bfebed7e4..eab69f36e 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -115,17 +115,9 @@ def apply(macsec):              os.unlink(wpa_suppl_conf.format(**macsec))      else: -        # This is a special type of interface which needs additional parameters -        # when created using iproute2. Instead of passing a ton of arguments, -        # use a dictionary provided by the interface class which holds all the -        # options necessary. -        conf = MACsecIf.get_config() -        conf['source_interface'] = macsec['source_interface'] -        conf['security_cipher'] = macsec['security']['cipher'] -          # It is safe to "re-create" the interface always, there is a sanity          # check that the interface will only be create if its non existent -        i = MACsecIf(macsec['ifname'], **conf) +        i = MACsecIf(**macsec)          i.update(macsec)          call('systemctl restart wpa_supplicant-macsec@{source_interface}' diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index ee6f05fcd..4afb85526 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -502,10 +502,7 @@ def apply(openvpn):      # existed - nevertheless, spawn new OpenVPN process      call(f'systemctl start openvpn@{interface}.service') -    conf = VTunIf.get_config() -    conf['device_type'] = openvpn['device_type'] - -    o = VTunIf(interface, **conf) +    o = VTunIf(**openvpn)      o.update(openvpn)      return None diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py index ddbef56ac..34a054837 100755 --- a/src/conf_mode/interfaces-pseudo-ethernet.py +++ b/src/conf_mode/interfaces-pseudo-ethernet.py @@ -75,19 +75,9 @@ def apply(peth):      if 'mode_old' in peth:          MACVLANIf(peth['ifname']).remove() -    # This is a special type of interface which needs additional parameters -    # when created using iproute2. Instead of passing a ton of arguments, -    # use a dictionary provided by the interface class which holds all the -    # options necessary. -    conf = MACVLANIf.get_config() - -    # Assign MACVLAN instance configuration parameters to config dict -    conf['source_interface'] = peth['source_interface'] -    conf['mode'] = peth['mode'] -      # It is safe to "re-create" the interface always, there is a sanity check      # that the interface will only be create if its non existent -    p = MACVLANIf(peth['ifname'], **conf) +    p = MACVLANIf(**peth)      p.update(peth)      return None diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index 87da214a8..2d2f29f94 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -31,21 +31,25 @@ 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 GREIf -from vyos.ifconfig import GRETapIf -from vyos.ifconfig import IPIPIf -from vyos.ifconfig import IP6GREIf -from vyos.ifconfig import IPIP6If -from vyos.ifconfig import IP6IP6If -from vyos.ifconfig import SitIf -from vyos.ifconfig import Sit6RDIf +from vyos.ifconfig import TunnelIf  from vyos.template import is_ipv4  from vyos.template import is_ipv6 +from vyos.util import cmd  from vyos.util import dict_search  from vyos import ConfigError  from vyos import airbag  airbag.enable() +def get_tunnel_encapsulation(interface): +    """ Returns the used encapsulation protocol for given interface. +        If interface does not exist, None is returned. +    """ +    if not os.path.exists(f'/sys/class/net/{interface}'): +        return None +    from json import loads +    tmp = loads(cmd(f'ip -d -j link show {interface}'))[0] +    return tmp['linkinfo']['info_kind'] +  def get_config(config=None):      """      Retrive CLI config as dictionary. Dictionary can never be empty, as at least @@ -79,8 +83,8 @@ def verify(tunnel):          return None      if 'encapsulation' not in tunnel: -        raise ConfigError('Must configure the tunnel encapsulation for '\ -                          '{ifname}!'.format(**tunnel)) +        error = 'Must configure encapsulation for "{ifname}"!' +        raise ConfigError(error.format(**tunnel))      verify_mtu_ipv6(tunnel)      verify_address(tunnel) @@ -103,67 +107,20 @@ def generate(tunnel):      return None  def apply(tunnel): -    if 'deleted' in tunnel or 'encapsulation_changed' in tunnel: -        if tunnel['ifname'] in interfaces(): -            tmp = Interface(tunnel['ifname']) +    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 = get_tunnel_encapsulation(interface) + +    if 'deleted' in tunnel or 'encapsulation_changed' in tunnel or encap == 'gretap': +        if interface in interfaces(): +            tmp = Interface(interface)              tmp.remove()          if 'deleted' in tunnel:              return None -    dispatch = { -        'gre': GREIf, -        'gre-bridge': GRETapIf, -        'ipip': IPIPIf, -        'ipip6': IPIP6If, -        'ip6ip6': IP6IP6If, -        'ip6gre': IP6GREIf, -        'sit': SitIf, -    } - -    # We need to re-map the tunnel encapsulation proto to a valid interface class -    encap = tunnel['encapsulation'] -    klass = dispatch[encap] - -    # This is a special type of interface which needs additional parameters -    # when created using iproute2. Instead of passing a ton of arguments, -    # use a dictionary provided by the interface class which holds all the -    # options necessary. -    conf = klass.get_config() - -    # Copy/re-assign our dictionary values to values understood by the -    # derived _Tunnel classes -    mapping = { -        # this                       :  get_config() -        'local_ip'                   : 'local', -        'remote_ip'                  : 'remote', -        'source_interface'           : 'dev', -        'parameters.ip.ttl'          : 'ttl', -        'parameters.ip.tos'          : 'tos', -        'parameters.ip.key'          : 'key', -    } - -    # Add additional IPv6 options if tunnel is IPv6 aware -    if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']: -        mappingv6 = { -            # this                       :  get_config() -            'parameters.ipv6.encaplimit' : 'encaplimit', -            'parameters.ipv6.flowlabel'  : 'flowlabel', -            'parameters.ipv6.hoplimit'   : 'hoplimit', -            'parameters.ipv6.tclass'     : 'flowlabel' -        } -        mapping.update(mappingv6) - -    for our_key, their_key in mapping.items(): -        if dict_search(our_key, tunnel) and their_key in conf: -            conf[their_key] = dict_search(our_key, tunnel) - -    if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None: -        if 'pmtudisc' in conf['raw']: -            conf['raw'].remove('pmtudisc') -        conf['raw'].append('nopmtudisc') - -    tun = klass(tunnel['ifname'], **conf) -    tun.change_options() +    tun = TunnelIf(**tunnel)      tun.update(tunnel)      return None diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 9a6d72772..8e6247a30 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -90,19 +90,8 @@ def apply(vxlan):          v.remove()      if 'deleted' not in vxlan: -        # This is a special type of interface which needs additional parameters -        # when created using iproute2. Instead of passing a ton of arguments, -        # use a dictionary provided by the interface class which holds all the -        # options necessary. -        conf = VXLANIf.get_config() - -        # Assign VXLAN instance configuration parameters to config dict -        for tmp in ['vni', 'group', 'source_address', 'source_interface', 'remote', 'port']: -            if tmp in vxlan: -                conf[tmp] = vxlan[tmp] -          # Finally create the new interface -        v = VXLANIf(vxlan['ifname'], **conf) +        v = VXLANIf(**vxlan)          v.update(vxlan)      return None diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index b25fcd4e0..7b3de6e8a 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -255,17 +255,8 @@ def apply(wifi):      if 'deleted' in wifi:          WiFiIf(interface).remove()      else: -        # This is a special type of interface which needs additional parameters -        # when created using iproute2. Instead of passing a ton of arguments, -        # use a dictionary provided by the interface class which holds all the -        # options necessary. -        conf = WiFiIf.get_config() - -        # Assign WiFi instance configuration parameters to config dict -        conf['phy'] = wifi['physical_device'] -          # Finally create the new interface -        w = WiFiIf(interface, **conf) +        w = WiFiIf(**wifi)          w.update(wifi)          # Enable/Disable interface - interface is always placed in diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20 new file mode 100755 index 000000000..ed2780b92 --- /dev/null +++ b/src/migration-scripts/interfaces/19-to-20 @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 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/>. + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + +if __name__ == '__main__': +    if (len(argv) < 1): +        print("Must specify file name!") +        exit(1) + +    file_name = argv[1] +    with open(file_name, 'r') as f: +        config_file = f.read() + +    config = ConfigTree(config_file) +    base = ['interfaces', 'tunnel'] + +    if not config.exists(base): +        # Nothing to do +        exit(0) + +    # +    # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap +    # Migrate "interface tunnel <tunX> local-ip" to source-address +    # Migrate "interface tunnel <tunX> remote-ip" to remote +    for interface in config.list_nodes(base): +        encap_path = base + [interface, 'encapsulation'] +        if config.exists(encap_path): +            tmp = config.return_value(encap_path) +            if tmp == 'gre-bridge': +                config.set(encap_path, value='gretap') + +        local_ip_path = base + [interface, 'local-ip'] +        if config.exists(local_ip_path): +            config.rename(local_ip_path, 'source-address') + +        remote_ip_path = base + [interface, 'remote-ip'] +        if config.exists(remote_ip_path): +            config.rename(remote_ip_path, 'remote') + +    try: +        with open(file_name, 'w') as f: +            f.write(config.to_string()) +    except OSError as e: +        print("Failed to save the modified config: {}".format(e)) +        exit(1) diff --git a/src/validators/fqdn b/src/validators/fqdn index 66276c093..a4027e4ca 100755 --- a/src/validators/fqdn +++ b/src/validators/fqdn @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2020 VyOS maintainers and contributors +# 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 @@ -17,8 +17,7 @@  import re  import sys -# pattern copied from: https://www.regextester.com/103452 -pattern = "(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)" +pattern = '[A-Za-z0-9][-.A-Za-z0-9]*'  if __name__ == '__main__':      if len(sys.argv) != 2: | 
