From e5b335830efe21f560383f4a2003450b42923e63 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Fri, 26 Feb 2021 19:10:08 +0100
Subject: vyos.ifconfig: T1579: remove calls to
 vyos.ifconfig.Interface.get_config()

Interface.get_config() was always a pure helper which exposed a "per interface
type" dictionary which was then fed by the caller to create interfaces by
iproute2 which required additional options during creation time.

Such interfaces had been:
 * tunnel
 * vxlan
 * geneve
 * macsec
 * wifi
 * macvlan / pseudo-ethernet

The code was always duplicated to convert from the VyOS CLI based get_config_dict()
to a dict which can be used to feed iproute2.

This path has been removed and we now always feed in the entire dictionary
retrieved by get_config_dict() or in the interfaces case, it's high-level wrapper
get_interface_dict() to the interface we wan't to create.

This also adds the - personally long awaited - possibility to get rid of the
derived tunnel classes for e.g. GRE, IPIP, IPIP6 and so on.
---
 python/vyos/configverify.py                 |   4 +-
 python/vyos/ifconfig/__init__.py            |   3 +-
 python/vyos/ifconfig/bond.py                |   6 +-
 python/vyos/ifconfig/bridge.py              |  21 ++--
 python/vyos/ifconfig/control.py             |   2 +-
 python/vyos/ifconfig/dummy.py               |   6 +-
 python/vyos/ifconfig/erspan.py              |  59 +++++-----
 python/vyos/ifconfig/ethernet.py            |   5 +-
 python/vyos/ifconfig/geneve.py              |  11 +-
 python/vyos/ifconfig/input.py               |   3 -
 python/vyos/ifconfig/interface.py           |  40 ++-----
 python/vyos/ifconfig/l2tpv3.py              |  38 ++-----
 python/vyos/ifconfig/loopback.py            |  10 +-
 python/vyos/ifconfig/macsec.py              |  17 +--
 python/vyos/ifconfig/macvlan.py             |  19 +---
 python/vyos/ifconfig/pppoe.py               |  41 -------
 python/vyos/ifconfig/tunnel.py              | 169 ++++++++++++----------------
 python/vyos/ifconfig/vti.py                 |   6 +-
 python/vyos/ifconfig/vtun.py                |   8 +-
 python/vyos/ifconfig/vxlan.py               |  75 +++++-------
 python/vyos/ifconfig/wireguard.py           |  18 +--
 python/vyos/ifconfig/wireless.py            |  27 +----
 src/conf_mode/interfaces-erspan.py          |  32 +++---
 src/conf_mode/interfaces-geneve.py          |  12 +-
 src/conf_mode/interfaces-l2tpv3.py          |  22 +---
 src/conf_mode/interfaces-macsec.py          |  10 +-
 src/conf_mode/interfaces-openvpn.py         |   5 +-
 src/conf_mode/interfaces-pseudo-ethernet.py |  12 +-
 src/conf_mode/interfaces-tunnel.py          |  40 +------
 src/conf_mode/interfaces-vxlan.py           |  13 +--
 src/conf_mode/interfaces-wireless.py        |  11 +-
 31 files changed, 215 insertions(+), 530 deletions(-)
 delete mode 100644 python/vyos/ifconfig/pppoe.py

diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 5a4d14c68..7be78b94b 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -95,11 +95,11 @@ 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')
 
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index f7b55c9dd..129524bda 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,7 +30,6 @@ 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
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..e0f72109d 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,7 +73,7 @@ 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']
@@ -82,7 +81,7 @@ class ERSpanIf(_ERSpan):
         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}'
-        
+
         if int(version) == 1:
             idx=dict_search('parameters.erspan.idx',self.config)
             if idx:
@@ -94,16 +93,16 @@ 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']
@@ -111,7 +110,7 @@ class ERSpanIf(_ERSpan):
         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}'
-        
+
         if int(version) == 1:
             idx=dict_search('parameters.erspan.idx',self.config)
             if idx:
@@ -123,21 +122,21 @@ 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']
@@ -145,7 +144,7 @@ class ER6SpanIf(_ERSpan):
         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}'
-        
+
         if int(version) == 1:
             idx=dict_search('parameters.erspan.idx',self.config)
             if idx:
@@ -157,16 +156,16 @@ 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']
@@ -174,7 +173,7 @@ class ER6SpanIf(_ERSpan):
         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}'
-        
+
         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..271919b90 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,7 +31,6 @@ def enable_to_on(value):
         return 'off'
     raise ValueError(f'expect enable or disable but got "{value}"')
 
-
 @Interface.register
 class _Tunnel(Interface):
     """
@@ -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 = {
+        'local_ip'                        : 'local',
+        'remote_ip'                       : 'remote',
+        'source_interface'                : 'dev',
+        '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,49 @@ class _Tunnel(Interface):
             },
         }
     }
-    _create_cmd = 'ip tunnel add {ifname} mode {type}'
 
     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))
+        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 {type}'
+        if self.config['encapsulation'] == 'gre-bridge':
+            cmd = 'ip link add name {ifname} type {type}'
+        for vyos_key, iproute2_key in mapping.items():
+            # dict_search will return an empty dict "{}" for valueless nodes like
+            # "parameters.nolearning" - thus we need to test the nodes existence
+            # by using isinstance()
+            tmp = dict_search(vyos_key, self.config)
+            if isinstance(tmp, dict):
+                cmd += f' {iproute2_key}'
+            elif tmp != None:
+                cmd += f' {iproute2_key} {tmp}'
+
+        self._cmd(cmd.format(**self.config))
+
         self.set_admin_state('down')
 
     def change_options(self):
-        change = 'ip tunnel change {ifname} mode {type}'
-
-        # add " option-name option-name-value ..." for all options set
-        options = ' '.join(['{} {}'.format(k, self.config[k])
-                            for k,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))
+        if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+            mapping = { **self.mapping, **self.mapping_ipv6 }
+        else:
+            mapping = { **self.mapping, **self.mapping_ipv4 }
+
+        self.config['type'] = self.iftype
+        cmd = 'ip tunnel change {ifname} mode {type}'
+        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):
         """
@@ -151,22 +184,15 @@ class GREIf(_Tunnel):
     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
-        },
-    }
+    iftype = 'gre'
 
 # GreTap also called GRE Bridge
 class GRETapIf(_Tunnel):
     """
     GRETapIF: GreIF using TAP instead of TUN
-
     https://en.wikipedia.org/wiki/TUN/TAP
     """
+    iftype = 'gretap'
     # no multicast, ttl or tos for gretap
     definition = {
         **_Tunnel.definition,
@@ -174,15 +200,6 @@ class GRETapIf(_Tunnel):
             '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
@@ -195,16 +212,7 @@ class IP6GREIf(_Tunnel):
     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': '',
-        },
-    }
+    iftype = 'ip6gre'
 
 class IPIPIf(_Tunnel):
     """
@@ -213,15 +221,7 @@ class IPIPIf(_Tunnel):
     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
-        },
-    }
+    iftype = 'ipip'
 
 class IPIP6If(_Tunnel):
     """
@@ -230,16 +230,7 @@ class IPIP6If(_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': '',
-        },
-    }
+    iftype = 'ipip6'
 
 class IP6IP6If(IPIP6If):
     """
@@ -248,13 +239,7 @@ class IP6IP6If(IPIP6If):
     For more information please refer to:
     https://tools.ietf.org/html/rfc2473
     """
-    default = {
-        **_Tunnel.default,
-        **{
-            'type': 'ip6ip6',
-        },
-    }
-
+    iftype = 'ip6ip6'
 
 class SitIf(_Tunnel):
     """
@@ -263,12 +248,7 @@ class SitIf(_Tunnel):
     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',
-        },
-    }
+    iftype = 'sit'
 
 class Sit6RDIf(SitIf):
     """
@@ -277,14 +257,7 @@ class Sit6RDIf(SitIf):
     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' : '',
-        },
-    }
+    iftype = '6rd'
 
     def _create(self):
         # do not call _Tunnel.create, building fully here
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/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..ae28f5101 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -124,45 +124,7 @@ def apply(tunnel):
     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 = klass(**tunnel)
     tun.change_options()
     tun.update(tunnel)
 
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
-- 
cgit v1.2.3


From 8bdf5b5216ddafdcee067b5bb8e15f18799c6fe5 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Fri, 26 Feb 2021 19:10:47 +0100
Subject: vxlan: T1513: add additional EVPN related CLI options

A VXLAN tunnel may now get a TTL, TOS, Flowlabel option specified. It is also
possible to disable learning of unknown addresses into the forwarding database.
---
 .../include/interface-parameters-flowlabel.xml.i   |  1 -
 interface-definitions/interfaces-vxlan.xml.in      | 36 ++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

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/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 234770971..2afe8685a 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -60,6 +60,42 @@
               </constraint>
             </properties>
           </leafNode>
+          <node name="parameters">
+            <properties>
+              <help>VXLAN tunnel parameters</help>
+            </properties>
+            <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>
-- 
cgit v1.2.3


From 8f100189086102458ff8e4f61f842cf44a6bf8aa Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 27 Feb 2021 16:04:38 +0100
Subject: tunnel: T3364: rename encapsulation mode "gre-bridge" to "gretap"

The following list shows the mapping of VyOS tunnel encapsulation modes to the
corresponding Linux modes.

VyOS        Linux
gre         gre
gre-bridge  gretap
ipip        ipip
ipip6       ipip6
ip6ip6      ip6ip6
ip6gre      ip6gre
sit         sit

Besides gre-bridge this is pretty consistent. As bridge interfaces are also
called tap interfaces gre-bridge will be renamed to gretap to make the
post-processing much easier.

This means (in detail) that there are no more child classes of _Tunnel and
there will be now one geneirc TunnelIf class handling all sorts of encapsulation.
---
 interface-definitions/interfaces-tunnel.xml.in  |  10 +-
 python/vyos/configverify.py                     |   7 +-
 python/vyos/ifconfig/__init__.py                |   9 +-
 python/vyos/ifconfig/tunnel.py                  | 132 +++++-------------------
 smoketest/configs/tunnel-broker                 | 103 ++++++++++++++++++
 smoketest/scripts/cli/test_interfaces_tunnel.py |  65 ++++++++----
 src/conf_mode/interfaces-tunnel.py              |  53 +++++-----
 src/migration-scripts/interfaces/19-to-20       |  51 +++++++++
 8 files changed, 263 insertions(+), 167 deletions(-)
 create mode 100644 smoketest/configs/tunnel-broker
 create mode 100755 src/migration-scripts/interfaces/19-to-20

diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 7a97980a2..bb23ba933 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -79,15 +79,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 +110,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/python/vyos/configverify.py b/python/vyos/configverify.py
index 7be78b94b..8286a735c 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -124,10 +124,11 @@ def verify_tunnel(config):
         if 'remote_ip' in config and not is_ipv4(config['remote_ip']):
             raise ConfigError(f'{error_ipv4} remote-ip')
 
-    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']):
             raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 129524bda..9d797d7f1 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -30,14 +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.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/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 271919b90..a74d50646 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -32,9 +32,9 @@ def enable_to_on(value):
     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
     """
@@ -89,15 +89,30 @@ class _Tunnel(Interface):
         }
     }
 
+    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):
         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 {type}'
-        if self.config['encapsulation'] == 'gre-bridge':
-            cmd = 'ip link add name {ifname} type {type}'
+        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
@@ -112,14 +127,17 @@ class _Tunnel(Interface):
 
         self.set_admin_state('down')
 
-    def change_options(self):
+    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 }
 
-        self.config['type'] = self.iftype
-        cmd = 'ip tunnel change {ifname} mode {type}'
+        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
@@ -161,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)
@@ -174,99 +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
-    """
-    iftype = 'gre'
-
-# GreTap also called GRE Bridge
-class GRETapIf(_Tunnel):
-    """
-    GRETapIF: GreIF using TAP instead of TUN
-    https://en.wikipedia.org/wiki/TUN/TAP
-    """
-    iftype = 'gretap'
-    # no multicast, ttl or tos for gretap
-    definition = {
-        **_Tunnel.definition,
-        **{
-            'bridgeable': True,
-        },
-    }
-
-    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
-    """
-    iftype = 'ip6gre'
-
-class IPIPIf(_Tunnel):
-    """
-    IPIP: IP Encapsulation within IP
-
-    For more information please refer to:
-    https://tools.ietf.org/html/rfc2003
-    """
-    iftype = 'ipip'
-
-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
-    """
-    iftype = 'ipip6'
-
-class IP6IP6If(IPIP6If):
-    """
-    IP6IP6: IPv6 over IPv6 tunnel
-
-    For more information please refer to:
-    https://tools.ietf.org/html/rfc2473
-    """
-    iftype = '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
-    """
-    iftype = '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
-    iftype = '6rd'
-
-    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/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_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py
index a9250e3e5..0bbc807db 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
 
@@ -90,7 +91,7 @@ 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])
@@ -107,8 +108,8 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
             self.session.set(self._base_path + [interface, 'remote-ip', 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
@@ -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'])
 
@@ -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, 'local-ip', self.local_v4])
+        self.session.set(self._base_path + [interface, 'remote-ip', 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 (inc host by 2
+        new_remote = inc_ip(remote_ip4, 2)
+        self.session.set(self._base_path + [interface, 'remote-ip', 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)
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index ae28f5101..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,29 +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]
-
-    tun = klass(**tunnel)
-    tun.change_options()
+    tun = TunnelIf(**tunnel)
     tun.update(tunnel)
 
     return None
diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20
new file mode 100755
index 000000000..be42cdd61
--- /dev/null
+++ b/src/migration-scripts/interfaces/19-to-20
@@ -0,0 +1,51 @@
+#!/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
+    for interface in config.list_nodes(base):
+        path = base + [interface, 'encapsulation']
+        if config.exists(path):
+            tmp = config.return_value(path)
+            if tmp == 'gre-bridge':
+                config.set(path, value='gretap')
+
+    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)
-- 
cgit v1.2.3


From 582b718221c67ddb71e39fbad0a72241761304a9 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 27 Feb 2021 21:37:15 +0100
Subject: tunnel: T3366: rename local-ip to source-address

Streamline the CLI configuration where we try to use source-address when
creating connections which are especially sourced from a discrete address.
---
 .../include/radius-server-ipv4-ipv6.xml.i          | 22 ++++++++++++++-
 .../include/source-address-ipv4-ipv6.xml.i         |  1 -
 .../include/tunnel-local-remote-ip.xml.i           | 20 +-------------
 python/vyos/configverify.py                        | 18 ++++++------
 python/vyos/ifconfig/tunnel.py                     |  4 +--
 smoketest/scripts/cli/test_interfaces_tunnel.py    | 32 +++++++++++-----------
 src/migration-scripts/interfaces/19-to-20          | 13 ++++++---
 7 files changed, 58 insertions(+), 52 deletions(-)

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..004e04f7b 100644
--- a/interface-definitions/include/source-address-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/source-address-ipv4-ipv6.xml.i
@@ -17,7 +17,6 @@
       <validator name="ipv4-address"/>
       <validator name="ipv6-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
index 85c20f482..f86e1dd8c 100644
--- a/interface-definitions/include/tunnel-local-remote-ip.xml.i
+++ b/interface-definitions/include/tunnel-local-remote-ip.xml.i
@@ -1,23 +1,5 @@
 <!-- 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>
+#include <include/source-address-ipv4-ipv6.xml.i>
 <leafNode name="remote-ip">
   <properties>
     <help>Remote IP address for this tunnel</help>
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 8286a735c..c901ccbc5 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -100,26 +100,26 @@ def verify_tunnel(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 'source_address' not in config and 'dhcp_interface' not in config:
+        raise ConfigError('source-address is mandatory for tunnel')
 
     if 'remote_ip' not in config and config['encapsulation'] != 'gre':
         raise ConfigError('remote-ip 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')
     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')
@@ -130,7 +130,7 @@ def verify_tunnel(config):
             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/tunnel.py b/python/vyos/ifconfig/tunnel.py
index a74d50646..2820e2563 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -51,9 +51,9 @@ class TunnelIf(Interface):
     # - https://man7.org/linux/man-pages/man8/ip-link.8.html
     # - https://man7.org/linux/man-pages/man8/ip-tunnel.8.html
     mapping = {
-        'local_ip'                        : 'local',
-        'remote_ip'                       : 'remote',
+        'source_address'                  : 'local',
         'source_interface'                : 'dev',
+        'remote_ip'                       : 'remote',
         'parameters.ip.key'               : 'key',
         'parameters.ip.tos'               : 'tos',
         'parameters.ip.ttl'               : 'ttl',
diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py
index 0bbc807db..cf7e7aac9 100755
--- a/smoketest/scripts/cli/test_interfaces_tunnel.py
+++ b/smoketest/scripts/cli/test_interfaces_tunnel.py
@@ -71,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-ip 192.0.2.10', 'source-address ' + cls.local_v4],
+            'tun20': ['encapsulation gre',  'remote-ip 192.0.2.20', 'source-address ' + cls.local_v4],
         }
         cls._interfaces = list(cls._options)
 
@@ -94,15 +94,15 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
         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, 'source-address', self.local_v6])
             self.session.set(self._base_path + [interface, 'remote-ip', 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-ip
             with self.assertRaises(ConfigSessionError):
                 self.session.commit()
             self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
@@ -141,15 +141,15 @@ 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, 'source-address', self.local_v4])
             self.session.set(self._base_path + [interface, 'remote-ip', 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-ip
             with self.assertRaises(ConfigSessionError):
                 self.session.commit()
             self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6])
@@ -182,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, 'source-address', self.local_v4])
         self.session.set(self._base_path + [interface, 'remote-ip', 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'])
@@ -208,7 +208,7 @@ 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, 'source-address', self.local_v4])
         self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
 
         self.session.set(self._base_path + [interface, 'parameters', 'ip', 'no-pmtu-discovery'])
@@ -234,7 +234,7 @@ 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, 'source-address', self.local_v4])
         self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
 
         # Check if commit is ok
@@ -258,4 +258,4 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
         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/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20
index be42cdd61..1727ac4dc 100755
--- a/src/migration-scripts/interfaces/19-to-20
+++ b/src/migration-scripts/interfaces/19-to-20
@@ -36,12 +36,17 @@ if __name__ == '__main__':
 
     #
     # Migrate "interface tunnel <tunX> encapsulation gre-bridge" to gretap
+    # Migrate "interface tunnel <tunX> local-ip" to source-address
     for interface in config.list_nodes(base):
-        path = base + [interface, 'encapsulation']
-        if config.exists(path):
-            tmp = config.return_value(path)
+        encap_path = base + [interface, 'encapsulation']
+        if config.exists(encap_path):
+            tmp = config.return_value(encap_path)
             if tmp == 'gre-bridge':
-                config.set(path, value='gretap')
+                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')
 
     try:
         with open(file_name, 'w') as f:
-- 
cgit v1.2.3


From 857294427afba3259e683f2360c735f0f4be32b6 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 27 Feb 2021 21:49:00 +0100
Subject: tunnel: T3366: rename remote-ip to remote

Streamline the CLI configuration where we try to use remote on other interfaces
like vxlan, geneve.
---
 .../include/tunnel-local-remote-ip.xml.i           | 19 -------
 interface-definitions/include/tunnel-remote.xml.i  | 18 +++++++
 interface-definitions/interfaces-erspan.xml.in     |  3 +-
 interface-definitions/interfaces-geneve.xml.in     | 13 +----
 interface-definitions/interfaces-tunnel.xml.in     |  3 +-
 interface-definitions/interfaces-vxlan.xml.in      | 17 +-----
 python/vyos/configverify.py                        | 12 ++---
 python/vyos/ifconfig/erspan.py                     | 24 ++++-----
 python/vyos/ifconfig/tunnel.py                     |  2 +-
 smoketest/scripts/cli/test_interfaces_erspan.py    | 61 +++-------------------
 smoketest/scripts/cli/test_interfaces_tunnel.py    | 26 ++++-----
 src/migration-scripts/interfaces/19-to-20          |  5 ++
 12 files changed, 69 insertions(+), 134 deletions(-)
 delete mode 100644 interface-definitions/include/tunnel-local-remote-ip.xml.i
 create mode 100644 interface-definitions/include/tunnel-remote.xml.i

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 f86e1dd8c..000000000
--- a/interface-definitions/include/tunnel-local-remote-ip.xml.i
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- included start from tunnel-local-remote-ip.xml.i -->
-#include <include/source-address-ipv4-ipv6.xml.i>
-<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-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index bb23ba933..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>
diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in
index 2afe8685a..efe6218e1 100644
--- a/interface-definitions/interfaces-vxlan.xml.in
+++ b/interface-definitions/interfaces-vxlan.xml.in
@@ -44,22 +44,7 @@
           <leafNode name="mtu">
             <defaultValue>1450</defaultValue>
           </leafNode>
-          <leafNode name="remote">
-            <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>
-            </properties>
-          </leafNode>
+          #include <include/tunnel-remote.xml.i>
           <node name="parameters">
             <properties>
               <help>VXLAN tunnel parameters</help>
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index c901ccbc5..db3e7cc57 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -103,8 +103,8 @@ def verify_tunnel(config):
     if 'source_address' not in config and 'dhcp_interface' not in config:
         raise ConfigError('source-address is mandatory for tunnel')
 
-    if 'remote_ip' not in config and config['encapsulation'] != 'gre':
-        raise ConfigError('remote-ip is mandatory for tunnel')
+    if 'remote' not in config and config['encapsulation'] != 'gre':
+        raise ConfigError('remote ip address is mandatory for tunnel')
 
     if {'source_address', 'dhcp_interface'} <= set(config):
         raise ConfigError('Can not use both source-address and dhcp-interface')
@@ -114,15 +114,15 @@ def verify_tunnel(config):
         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 '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', 'gretap']:
         if 'source_interface' in config:
diff --git a/python/vyos/ifconfig/erspan.py b/python/vyos/ifconfig/erspan.py
index e0f72109d..9e24cf6cd 100755
--- a/python/vyos/ifconfig/erspan.py
+++ b/python/vyos/ifconfig/erspan.py
@@ -76,11 +76,11 @@ class ERSpanIf(_ERSpan):
 
     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)
@@ -105,11 +105,11 @@ class ERSpanIf(_ERSpan):
 
     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)
@@ -139,11 +139,11 @@ class ER6SpanIf(_ERSpan):
 
     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)
@@ -168,11 +168,11 @@ class ER6SpanIf(_ERSpan):
 
     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)
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 2820e2563..bb940b0cf 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -53,7 +53,7 @@ class TunnelIf(Interface):
     mapping = {
         'source_address'                  : 'local',
         'source_interface'                : 'dev',
-        'remote_ip'                       : 'remote',
+        'remote'                          : 'remote',
         'parameters.ip.key'               : 'key',
         'parameters.ip.tos'               : 'tos',
         'parameters.ip.ttl'               : 'ttl',
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_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py
index cf7e7aac9..cc8fbd527 100755
--- a/smoketest/scripts/cli/test_interfaces_tunnel.py
+++ b/smoketest/scripts/cli/test_interfaces_tunnel.py
@@ -71,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', 'source-address ' + cls.local_v4],
-            'tun20': ['encapsulation gre',  'remote-ip 192.0.2.20', 'source-address ' + 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)
 
@@ -95,17 +95,17 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
             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, 'source-address', self.local_v6])
-            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6])
+            self.session.set(self._base_path + [interface, 'remote', remote_ip6])
 
             # Encapsulation mode requires IPv4 source-address
             with self.assertRaises(ConfigSessionError):
                 self.session.commit()
             self.session.set(self._base_path + [interface, 'source-address', self.local_v4])
 
-            # Encapsulation mode requires IPv4 remote-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 gretap
@@ -142,17 +142,17 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
             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, 'source-address', self.local_v4])
-            self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
+            self.session.set(self._base_path + [interface, 'remote', remote_ip4])
 
             # Encapsulation mode requires IPv6 source-address
             with self.assertRaises(ConfigSessionError):
                 self.session.commit()
             self.session.set(self._base_path + [interface, 'source-address', self.local_v6])
 
-            # Encapsulation mode requires IPv6 remote-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])
@@ -190,7 +190,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
         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, 'source-address', self.local_v4])
-        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, 'dhcp-interface', 'eth0'])
 
         # source-address and dhcp-interface can not be used at the same time
@@ -209,7 +209,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
 
         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-ip', remote_ip4])
+        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])
@@ -235,7 +235,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
 
         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-ip', remote_ip4])
+        self.session.set(self._base_path + [interface, 'remote', remote_ip4])
 
         # Check if commit is ok
         self.session.commit()
@@ -248,9 +248,9 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
         self.assertEqual(remote_ip4,    conf['linkinfo']['info_data']['remote'])
         self.assertEqual(0,             conf['linkinfo']['info_data']['ttl'])
 
-        # Change remote-ip (inc host by 2
+        # Change remote ip address (inc host by 2
         new_remote = inc_ip(remote_ip4, 2)
-        self.session.set(self._base_path + [interface, 'remote-ip', new_remote])
+        self.session.set(self._base_path + [interface, 'remote', new_remote])
         # Check if commit is ok
         self.session.commit()
 
diff --git a/src/migration-scripts/interfaces/19-to-20 b/src/migration-scripts/interfaces/19-to-20
index 1727ac4dc..ed2780b92 100755
--- a/src/migration-scripts/interfaces/19-to-20
+++ b/src/migration-scripts/interfaces/19-to-20
@@ -37,6 +37,7 @@ if __name__ == '__main__':
     #
     # 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):
@@ -48,6 +49,10 @@ if __name__ == '__main__':
         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())
-- 
cgit v1.2.3


From 5bcc549edeaeaa767d77a68b33751e834d467c34 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 27 Feb 2021 22:59:00 +0100
Subject: macsec: T3368: add support for gcm-aes-256 cipher

---
 interface-definitions/interfaces-macsec.xml.in  |  8 +++--
 smoketest/scripts/cli/test_interfaces_macsec.py | 39 +++++++++++++++++++++++--
 2 files changed, 43 insertions(+), 4 deletions(-)

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/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
-- 
cgit v1.2.3