diff --git a/interface-definitions/include/tunnel-local-remote-ip.xml.i b/interface-definitions/include/tunnel-local-remote-ip.xml.i
new file mode 100644
index 000000000..85c20f482
--- /dev/null
+++ b/interface-definitions/include/tunnel-local-remote-ip.xml.i
@@ -0,0 +1,37 @@
+<!-- included start from tunnel-local-remote-ip.xml.i -->
+<leafNode name="local-ip">
+  <properties>
+    <help>Local IP address for this tunnel</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>Local IPv4 address for this tunnel</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv6</format>
+      <description>Local IPv6 address for this tunnel</description>
+    </valueHelp>
+    <completionHelp>
+      <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
+    </completionHelp>
+    <constraint>
+      <validator name="ip-address"/>
+    </constraint>
+  </properties>
+</leafNode>
+<leafNode name="remote-ip">
+  <properties>
+    <help>Remote IP address for this tunnel</help>
+    <valueHelp>
+      <format>ipv4</format>
+      <description>Remote IPv4 address for this tunnel</description>
+    </valueHelp>
+    <valueHelp>
+      <format>ipv6</format>
+      <description>Remote IPv6 address for this tunnel</description>
+    </valueHelp>
+    <constraint>
+      <!-- does it need fixing/changing to be more restrictive ? -->
+      <validator name="ip-address"/>
+    </constraint>
+  </properties>
+</leafNode>
diff --git a/interface-definitions/include/tunnel-parameters-ip.xml.i b/interface-definitions/include/tunnel-parameters-ip.xml.i
new file mode 100644
index 000000000..c304bd3ff
--- /dev/null
+++ b/interface-definitions/include/tunnel-parameters-ip.xml.i
@@ -0,0 +1,49 @@
+<!-- included start from tunnel-parameters-ip.xml.i -->
+<node name="ip">
+  <properties>
+    <help>IPv4 specific tunnel parameters</help>
+  </properties>
+  <children>
+    <leafNode name="ttl">
+      <properties>
+        <help>Time to live field</help>
+        <valueHelp>
+          <format>0-255</format>
+          <description>Time to live (default 255)</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-255"/>
+        </constraint>
+        <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage>
+      </properties>
+      <defaultValue>255</defaultValue>
+    </leafNode>
+    <leafNode name="tos">
+      <properties>
+        <help>Type of Service (TOS)</help>
+        <valueHelp>
+          <format>0-99</format>
+          <description>Type of Service (TOS)</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-99"/>
+        </constraint>
+        <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage>
+      </properties>
+      <defaultValue>inherit</defaultValue>
+    </leafNode>
+    <leafNode name="key">
+      <properties>
+        <help>Tunnel key</help>
+        <valueHelp>
+          <format>u32</format>
+          <description>Tunnel key</description>
+        </valueHelp>
+        <constraint>
+          <validator name="numeric" argument="--range 0-4294967295"/>
+        </constraint>
+        <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage>
+      </properties>
+    </leafNode>
+  </children>
+</node>
diff --git a/interface-definitions/interfaces-erspan.xml.in b/interface-definitions/interfaces-erspan.xml.in
index 6b98b0730..afc29a658 100644
--- a/interface-definitions/interfaces-erspan.xml.in
+++ b/interface-definitions/interfaces-erspan.xml.in
@@ -1,190 +1,104 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="interfaces">
     <children>
       <tagNode name="erspan" owner="${vyos_conf_scripts_dir}/interfaces-erspan.py">
         <properties>
           <help>Encapsulated Remote SPAN over GRE and IPv4/IPv6 Tunnel Interface</help>
           <priority>310</priority>
           <constraint>
             <regex>^ersp[0-9]+$</regex>
           </constraint>
           <constraintErrorMessage>ERSPAN tunnel interface must be named erspN</constraintErrorMessage>
           <valueHelp>
             <format>erspN</format>
             <description>ERSPAN Tunnel interface name</description>
           </valueHelp>
         </properties>
         <children>
           #include <include/interface-description.xml.i>
-          #include <include/address-ipv4-ipv6.xml.i>
           #include <include/interface-disable.xml.i>
           #include <include/interface-disable-link-detect.xml.i>
-          #include <include/interface-vrf.xml.i>
           #include <include/interface-mtu-64-8024.xml.i>
-          #include <include/interface-ipv4-options.xml.i>
-          #include <include/interface-ipv6-options.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 ERSPAN tunnel</description>
-              </valueHelp>
-              <valueHelp>
-                <format>ipv6</format>
-                <description>Local IPv6 address for this tunnel [NOTICE: unavailable for mGRE tunnels]</description>
-              </valueHelp>
-              <completionHelp>
-                <script>${vyos_completion_dir}/list_local.py</script>
-              </completionHelp>
-              <constraint>
-                <validator name="ip-address"/>
-              </constraint>
-            </properties>
-          </leafNode>
-          <leafNode name="remote-ip">
-            <properties>
-              <help>Remote IP address for this ERSPAN 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>
+          #include <include/tunnel-local-remote-ip.xml.i>
           <leafNode name="encapsulation">
             <properties>
-              <help>Encapsulation of this ERSPAN tunnel interface</help>
+              <help>Encapsulation of this tunnel interface</help>
               <completionHelp>
                 <list>erspan ip6erspan</list>
               </completionHelp>
               <valueHelp>
                 <format>erspan</format>
-                <description>Encapsulated Remote SPAN over GRE and IPv4</description>
+                <description>Generic Routing Encapsulation</description>
               </valueHelp>
               <valueHelp>
                 <format>ip6erspan</format>
-                <description>Encapsulated Remote SPAN over GRE and IPv6</description>
+                <description>Generic Routing Encapsulation bridge interface</description>
               </valueHelp>
               <constraint>
                 <regex>^(erspan|ip6erspan)$</regex>
               </constraint>
               <constraintErrorMessage>Invalid encapsulation, must be one of: erspan, ip6erspan</constraintErrorMessage>
             </properties>
           </leafNode>
           <node name="parameters">
             <properties>
               <help>ERSPAN Tunnel parameters</help>
             </properties>
             <children>
-              <node name="ip">
-                <properties>
-                  <help>IP specific ERSPAN tunnel parameters</help>
-                </properties>
-                <children>
-                  <leafNode name="ttl">
-                    <properties>
-                      <help>Time to live field</help>
-                      <valueHelp>
-                        <format>0-255</format>
-                        <description>Time to live (default 255)</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-255"/>
-                      </constraint>
-                      <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage>
-                    </properties>
-                    <defaultValue>255</defaultValue>
-                  </leafNode>
-                  <leafNode name="tos">
-                    <properties>
-                      <help>Type of Service (TOS)</help>
-                      <valueHelp>
-                        <format>0-99</format>
-                        <description>Type of Service (TOS)</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-99"/>
-                      </constraint>
-                      <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage>
-                    </properties>
-                    <defaultValue>inherit</defaultValue>
-                  </leafNode>
-                  <leafNode name="key">
-                    <properties>
-                      <help>ERSPAN Tunnel key</help>
-                      <valueHelp>
-                        <format>0-4294967295</format>
-                        <description>Tunnel key</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-4294967295"/>
-                      </constraint>
-                      <constraintErrorMessage>key must be between 0-4294967295</constraintErrorMessage>
-                    </properties>
-                  </leafNode>
-                </children>
-              </node>
+              #include <include/tunnel-parameters-ip.xml.i>
               <leafNode name="version">
                 <properties>
                   <help>ERSPAN version number setting(default:1)</help>
                   <constraint>
                     <validator name="numeric" argument="--range 1-2"/>
                   </constraint>
                   <constraintErrorMessage>The version number of ERSPAN must be 1 or 2</constraintErrorMessage>
                 </properties>
                 <defaultValue>1</defaultValue>
               </leafNode>
               <leafNode name="direction">
                 <properties>
                   <help>specifies the ERSPAN mirrored traffic's direction</help>
                   <completionHelp>
                     <list>ingress egress</list>
                   </completionHelp>
                   <valueHelp>
                     <format>ingress</format>
                     <description>Mirror ingress direction</description>
                   </valueHelp>
                   <valueHelp>
                     <format>egress</format>
                     <description>Mirror egress direction</description>
                   </valueHelp>
                   <constraint>
                     <regex>^(ingress|egress)$</regex>
                   </constraint>
                   <constraintErrorMessage>The mirror direction of ERSPAN must be ingress or egress</constraintErrorMessage>
                 </properties>
               </leafNode>
               <leafNode name="hwid">
                 <properties>
                   <help>an unique identifier of an ERSPAN v2 engine within a system</help>
                   <constraint>
                     <validator name="numeric" argument="--range 1-1048575"/>
                   </constraint>
                   <constraintErrorMessage>ERSPAN hwid must be a number(range:0-1048575)</constraintErrorMessage>
                 </properties>
               </leafNode>
               <leafNode name="idx">
                 <properties>
                   <help>specifies the ERSPAN v1 index field</help>
                   <constraint>
                     <validator name="numeric" argument="--range 0-63"/>
                   </constraint>
                   <constraintErrorMessage>ERSPAN idx must be a number(range:0-63)</constraintErrorMessage>
                 </properties>
               </leafNode>
             </children>
           </node>
         </children>
       </tagNode>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index 7fa847ab0..279c05cca 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -1,312 +1,213 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="interfaces">
     <children>
       <tagNode name="tunnel" owner="${vyos_conf_scripts_dir}/interfaces-tunnel.py">
         <properties>
           <help>Tunnel interface</help>
           <priority>380</priority>
           <constraint>
             <regex>^tun[0-9]+$</regex>
           </constraint>
           <constraintErrorMessage>tunnel interface must be named tunN</constraintErrorMessage>
           <valueHelp>
             <format>tunN</format>
             <description>Tunnel interface name</description>
           </valueHelp>
         </properties>
         <children>
           #include <include/interface-description.xml.i>
           #include <include/address-ipv4-ipv6.xml.i>
           #include <include/interface-disable.xml.i>
           #include <include/interface-disable-link-detect.xml.i>
           #include <include/interface-vrf.xml.i>
           #include <include/interface-mtu-64-8024.xml.i>
           <leafNode name="mtu">
             <defaultValue>1476</defaultValue>
           </leafNode>
           #include <include/interface-ipv4-options.xml.i>
           #include <include/interface-ipv6-options.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 [NOTICE: unavailable for mGRE tunnels]</description>
-              </valueHelp>
-              <completionHelp>
-                <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
-              </completionHelp>
-              <constraint>
-                <validator name="ip-address"/>
-              </constraint>
-            </properties>
-          </leafNode>
-          <leafNode name="remote-ip">
-            <properties>
-              <help>Remote IP address for this tunnel</help>
-              <valueHelp>
-                <format>ipv4</format>
-                <description>Remote IPv4 address for this tunnel</description>
-              </valueHelp>
-              <valueHelp>
-                <format>ipv6</format>
-                <description>Remote IPv6 address for this tunnel</description>
-              </valueHelp>
-              <constraint>
-                <!-- does it need fixing/changing to be more restrictive ? -->
-                <validator name="ip-address"/>
-              </constraint>
-            </properties>
-          </leafNode>
+          #include <include/tunnel-local-remote-ip.xml.i>
           <leafNode name="source-interface">
             <properties>
               <help>Physical Interface used for underlaying traffic</help>
               <completionHelp>
                 <script>${vyos_completion_dir}/list_interfaces.py</script>
               </completionHelp>
             </properties>
           </leafNode>
           <leafNode name="6rd-prefix">
             <properties>
               <help>6rd network prefix</help>
               <valueHelp>
                 <format>ipv6</format>
                 <description>IPv6 address and prefix length</description>
               </valueHelp>
               <constraint>
                 <validator name="ipv6-prefix"/>
               </constraint>
             </properties>
           </leafNode>
           <leafNode name="6rd-relay-prefix">
             <properties>
               <help>6rd relay prefix</help>
               <valueHelp>
                 <format>ipv4net</format>
                 <description>IPv4 prefix of interface for 6rd</description>
               </valueHelp>
               <constraint>
                 <validator name="ipv4-prefix"/>
               </constraint>
             </properties>
           </leafNode>
           <leafNode name="dhcp-interface">
             <properties>
               <help>dhcp interface</help>
               <valueHelp>
                 <format>interface</format>
                 <description>DHCP interface that supplies the local IP address for this tunnel</description>
               </valueHelp>
               <completionHelp>
                 <script>${vyos_completion_dir}/list_interfaces.py</script>
               </completionHelp>
               <constraint>
                 <regex>^(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+$</regex>
               </constraint>
             </properties>
           </leafNode>
           <leafNode name="encapsulation">
             <properties>
               <help>Encapsulation of this tunnel interface</help>
               <completionHelp>
                 <list>gre gre-bridge 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>
               </valueHelp>
               <valueHelp>
                 <format>ip6gre</format>
                 <description>GRE over IPv6 network</description>
               </valueHelp>
               <valueHelp>
                 <format>ip6ip6</format>
                 <description>IP6 in IP6 encapsulation</description>
               </valueHelp>
               <valueHelp>
                 <format>ipip</format>
                 <description>IP in IP encapsulation</description>
               </valueHelp>
               <valueHelp>
                 <format>ipip6</format>
                 <description>IP in IP6 encapsulation</description>
               </valueHelp>
               <valueHelp>
                 <format>sit</format>
                 <description>Simple Internet Transition encapsulation</description>
               </valueHelp>
               <constraint>
                 <regex>^(gre|gre-bridge|ip6gre|ip6ip6|ipip|ipip6|sit)$</regex>
               </constraint>
               <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gre-bridge, ipip, sit, ipip6, ip6ip6, ip6gre</constraintErrorMessage>
             </properties>
           </leafNode>
           <leafNode name="multicast">
             <properties>
               <help>Multicast operation over tunnel</help>
               <completionHelp>
                 <list>enable disable</list>
               </completionHelp>
               <valueHelp>
                 <format>enable</format>
                 <description>Enable Multicast</description>
               </valueHelp>
               <valueHelp>
                 <format>disable</format>
                 <description>Disable Multicast (default)</description>
               </valueHelp>
               <constraint>
                 <regex>^(enable|disable)$</regex>
               </constraint>
               <constraintErrorMessage>Must be 'disable' or 'enable'</constraintErrorMessage>
             </properties>
           </leafNode>
           <node name="parameters">
             <properties>
               <help>Tunnel parameters</help>
             </properties>
             <children>
-              <node name="ip">
-                <properties>
-                  <help>IPv4 specific tunnel parameters</help>
-                </properties>
-                <children>
-                  <leafNode name="no-pmtu-discovery">
-                    <properties>
-                      <help>Disable path MTU discovery</help>
-                      <valueless/>
-                    </properties>
-                  </leafNode>
-                  <leafNode name="ttl">
-                    <properties>
-                      <help>Time to live (default: 0)</help>
-                      <valueHelp>
-                        <format>0</format>
-                        <description>Copy value from original IP header</description>
-                      </valueHelp>
-                      <valueHelp>
-                        <format>1-255</format>
-                        <description>Time to Live</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-255"/>
-                      </constraint>
-                      <constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage>
-                    </properties>
-                    <defaultValue>0</defaultValue>
-                  </leafNode>
-                  <leafNode name="tos">
-                    <properties>
-                      <help>Type of Service (default: 0)</help>
-                      <completionHelp>
-                        <list>inherit</list>
-                      </completionHelp>
-                      <valueHelp>
-                        <format>0</format>
-                        <description>Copy value from original IP header</description>
-                      </valueHelp>
-                      <valueHelp>
-                        <format>1-99</format>
-                        <description>Type of Service (TOS)</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-99"/>
-                      </constraint>
-                      <constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage>
-                    </properties>
-                    <defaultValue>0</defaultValue>
-                  </leafNode>
-                  <leafNode name="key">
-                    <properties>
-                      <help>Tunnel key</help>
-                      <valueHelp>
-                        <format>u32</format>
-                        <description>Tunnel key</description>
-                      </valueHelp>
-                      <constraint>
-                        <validator name="numeric" argument="--range 0-4294967295"/>
-                      </constraint>
-                      <constraintErrorMessage>Key must be in range 0-4294967295</constraintErrorMessage>
-                    </properties>
-                  </leafNode>
-                </children>
-              </node>
+              #include <include/tunnel-parameters-ip.xml.i>
               <node name="ipv6">
                 <properties>
                   <help>IPv6 specific tunnel parameters</help>
                 </properties>
                 <children>
                   <leafNode name="encaplimit">
                     <properties>
                       <help>Encaplimit field</help>
                       <valueHelp>
                         <format>0-255</format>
                         <description>Encaplimit (default: 4)</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 0-255"/>
                       </constraint>
                       <constraintErrorMessage>key must be between 0-255</constraintErrorMessage>
                     </properties>
                     <defaultValue>4</defaultValue>
                   </leafNode>
                   <leafNode name="flowlabel">
                     <properties>
                       <help>Flowlabel</help>
                       <valueHelp>
                         <format>0x0-0x0FFFFF</format>
                         <description>Tunnel key, 'inherit' or hex value</description>
                       </valueHelp>
                       <constraint>
                         <regex>(0x){0,1}(0?[0-9A-Fa-f]{1,5})</regex>
                       </constraint>
                       <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage>
                     </properties>
                     <defaultValue>inherit</defaultValue>
                   </leafNode>
                   <leafNode name="hoplimit">
                     <properties>
                       <help>Hoplimit</help>
                       <valueHelp>
                         <format>0-255</format>
                         <description>Hoplimit (default 64)</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 0-255"/>
                       </constraint>
                       <constraintErrorMessage>hoplimit must be between 0-255</constraintErrorMessage>
                     </properties>
                     <defaultValue>64</defaultValue>
                   </leafNode>
                   <leafNode name="tclass">
                     <properties>
                       <help>Traffic class (Tclass)</help>
                       <valueHelp>
                         <format>0x0-0x0FFFFF</format>
                         <description>Traffic class, 'inherit' or hex value</description>
                       </valueHelp>
                       <constraint>
                         <regex>(0x){0,1}(0?[0-9A-Fa-f]{1,2})</regex>
                       </constraint>
                       <constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage>
                     </properties>
                     <defaultValue>inherit</defaultValue>
                   </leafNode>
                 </children>
               </node>
             </children>
           </node>
         </children>
       </tagNode>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index a888791ba..e71e4e1c5 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -1,332 +1,375 @@
 # 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/>.
 
 # The sole purpose of this module is to hold common functions used in
 # all kinds of implementations to verify the CLI configuration.
 # It is started by migrating the interfaces to the new get_config_dict()
 # approach which will lead to a lot of code that can be reused.
 
 # NOTE: imports should be as local as possible to the function which
 # makes use of it!
 
 from vyos import ConfigError
 from vyos.util import dict_search
 
 def verify_mtu(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation if the specified MTU can be used by the underlaying
     hardware.
     """
     from vyos.ifconfig import Interface
     if 'mtu' in config:
         mtu = int(config['mtu'])
 
         tmp = Interface(config['ifname'])
         min_mtu = tmp.get_min_mtu()
         max_mtu = tmp.get_max_mtu()
 
         if mtu < min_mtu:
             raise ConfigError(f'Interface MTU too low, ' \
                               f'minimum supported MTU is {min_mtu}!')
         if mtu > max_mtu:
             raise ConfigError(f'Interface MTU too high, ' \
                               f'maximum supported MTU is {max_mtu}!')
 
 def verify_mtu_ipv6(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation if the specified MTU can be used when IPv6 is
     configured on the interface. IPv6 requires a 1280 bytes MTU.
     """
     from vyos.template import is_ipv6
     if 'mtu' in config:
         # IPv6 minimum required link mtu
         min_mtu = 1280
         if int(config['mtu']) < min_mtu:
             interface = config['ifname']
             error_msg = f'IPv6 address will be configured on interface "{interface}" ' \
                         f'thus the minimum MTU requirement is {min_mtu}!'
 
             for address in (dict_search('address', config) or []):
                 if address in ['dhcpv6'] or is_ipv6(address):
                     raise ConfigError(error_msg)
 
             tmp = dict_search('ipv6.address', config)
             if tmp and 'no_default_link_local' not in tmp:
                 raise ConfigError('link-local ' + error_msg)
 
             if tmp and 'autoconf' in tmp:
                 raise ConfigError(error_msg)
 
             if tmp and 'eui64' in tmp:
                 raise ConfigError(error_msg)
 
 def verify_vrf(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of VRF configuration.
     """
     from netifaces import interfaces
     if 'vrf' in config:
         if config['vrf'] not in interfaces():
             raise ConfigError('VRF "{vrf}" does not exist'.format(**config))
 
         if 'is_bridge_member' in config:
             raise ConfigError(
                 'Interface "{ifname}" cannot be both a member of VRF "{vrf}" '
                 'and bridge "{is_bridge_member}"!'.format(**config))
 
+def verify_tunnel(config):
+    """
+    This helper is used to verify the common part of the tunnel
+    """
+    from vyos.template import is_ipv4
+    from vyos.template import is_ipv6
+    
+    if 'encapsulation' not in config:
+        raise ConfigError('Must configure the tunnel encapsulation for '\
+                          '{ifname}!'.format(**config))
+    
+    if 'local_ip' not in config and 'dhcp_interface' not in config:
+        raise ConfigError('local-ip is mandatory for tunnel')
+
+    if 'remote_ip' not in config and config['encapsulation'] != 'gre':
+        raise ConfigError('remote-ip is mandatory for tunnel')
+
+    if {'local_ip', 'dhcp_interface'} <= set(config):
+        raise ConfigError('Can not use both local-ip 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 '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 '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 'source_interface' in config:
+            raise ConfigError('Option source-interface can not be used with ' \
+                              'encapsulation "sit" or "gre-bridge"')
+    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')
+
 def verify_eapol(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of EAPoL configuration.
     """
     if 'eapol' in config:
         if not {'cert_file', 'key_file'} <= set(config['eapol']):
             raise ConfigError('Both cert and key-file must be specified '\
                               'when using EAPoL!')
 
 def verify_mirror(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of mirror interface configuration.
 
     It makes no sense to mirror traffic back at yourself!
     """
     if 'mirror' in config:
         for direction, mirror_interface in config['mirror'].items():
             if mirror_interface == config['ifname']:
                 raise ConfigError(f'Can not mirror "{direction}" traffic back ' \
                                    'the originating interface!')
 
 
 def verify_address(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of IP address assignment when interface is part
     of a bridge or bond.
     """
     if {'is_bridge_member', 'address'} <= set(config):
         raise ConfigError(
             'Cannot assign address to interface "{ifname}" as it is a '
             'member of bridge "{is_bridge_member}"!'.format(**config))
 
 
 def verify_bridge_delete(config):
     """
     Common helper function used by interface implementations to
     perform recurring validation of IP address assignmenr
     when interface also is part of a bridge.
     """
     if 'is_bridge_member' in config:
         raise ConfigError(
             'Interface "{ifname}" cannot be deleted as it is a '
             'member of bridge "{is_bridge_member}"!'.format(**config))
 
 def verify_interface_exists(ifname):
     """
     Common helper function used by interface implementations to perform
     recurring validation if an interface actually exists.
     """
     from netifaces import interfaces
     if ifname not in interfaces():
         raise ConfigError(f'Interface "{ifname}" does not exist!')
 
 def verify_source_interface(config):
     """
     Common helper function used by interface implementations to
     perform recurring validation of the existence of a source-interface
     required by e.g. peth/MACvlan, MACsec ...
     """
     from netifaces import interfaces
     if 'source_interface' not in config:
         raise ConfigError('Physical source-interface required for '
                           'interface "{ifname}"'.format(**config))
 
     if config['source_interface'] not in interfaces():
         raise ConfigError('Specified source-interface {source_interface} does '
                           'not exist'.format(**config))
 
     if 'source_interface_is_bridge_member' in config:
         raise ConfigError('Invalid source-interface {source_interface}. Interface '
                           'is already a member of bridge '
                           '{source_interface_is_bridge_member}'.format(**config))
 
     if 'source_interface_is_bond_member' in config:
         raise ConfigError('Invalid source-interface {source_interface}. Interface '
                           'is already a member of bond '
                           '{source_interface_is_bond_member}'.format(**config))
 
 def verify_dhcpv6(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of DHCPv6 options which are mutually exclusive.
     """
     if 'dhcpv6_options' in config:
         from vyos.util import dict_search
 
         if {'parameters_only', 'temporary'} <= set(config['dhcpv6_options']):
             raise ConfigError('DHCPv6 temporary and parameters-only options '
                               'are mutually exclusive!')
 
         # It is not allowed to have duplicate SLA-IDs as those identify an
         # assigned IPv6 subnet from a delegated prefix
         for pd in dict_search('dhcpv6_options.pd', config):
             sla_ids = []
             interfaces = dict_search(f'dhcpv6_options.pd.{pd}.interface', config)
 
             if not interfaces:
                 raise ConfigError('DHCPv6-PD requires an interface where to assign '
                                   'the delegated prefix!')
 
             for count, interface in enumerate(interfaces):
                 if 'sla_id' in interfaces[interface]:
                     sla_ids.append(interfaces[interface]['sla_id'])
                 else:
                     sla_ids.append(str(count))
 
             # Check for duplicates
             duplicates = [x for n, x in enumerate(sla_ids) if x in sla_ids[:n]]
             if duplicates:
                 raise ConfigError('Site-Level Aggregation Identifier (SLA-ID) '
                                   'must be unique per prefix-delegation!')
 
 def verify_vlan_config(config):
     """
     Common helper function used by interface implementations to perform
     recurring validation of interface VLANs
     """
     # 802.1q VLANs
     for vlan in config.get('vif', {}):
         vlan = config['vif'][vlan]
         verify_dhcpv6(vlan)
         verify_address(vlan)
         verify_vrf(vlan)
 
     # 802.1ad (Q-in-Q) VLANs
     for s_vlan in config.get('vif_s', {}):
         s_vlan = config['vif_s'][s_vlan]
         verify_dhcpv6(s_vlan)
         verify_address(s_vlan)
         verify_vrf(s_vlan)
 
         for c_vlan in s_vlan.get('vif_c', {}):
             c_vlan = s_vlan['vif_c'][c_vlan]
             verify_dhcpv6(c_vlan)
             verify_address(c_vlan)
             verify_vrf(c_vlan)
 
 def verify_accel_ppp_base_service(config):
     """
     Common helper function which must be used by all Accel-PPP services based
     on get_config_dict()
     """
     # vertify auth settings
     if dict_search('authentication.mode', config) == 'local':
         if not dict_search('authentication.local_users', config):
             raise ConfigError('PPPoE local auth mode requires local users to be configured!')
 
         for user in dict_search('authentication.local_users.username', config):
             user_config = config['authentication']['local_users']['username'][user]
 
             if 'password' not in user_config:
                 raise ConfigError(f'Password required for local user "{user}"')
 
             if 'rate_limit' in user_config:
                 # if up/download is set, check that both have a value
                 if not {'upload', 'download'} <= set(user_config['rate_limit']):
                     raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \
                                       'direction but both upload and download must be given!')
 
     elif dict_search('authentication.mode', config) == 'radius':
         if not dict_search('authentication.radius.server', config):
             raise ConfigError('RADIUS authentication requires at least one server')
 
         for server in dict_search('authentication.radius.server', config):
             radius_config = config['authentication']['radius']['server'][server]
             if 'key' not in radius_config:
                 raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
 
     if 'gateway_address' not in config:
         raise ConfigError('PPPoE server requires gateway-address to be configured!')
 
     if 'name_server_ipv4' in config:
         if len(config['name_server_ipv4']) > 2:
             raise ConfigError('Not more then two IPv4 DNS name-servers ' \
                               'can be configured')
 
     if 'name_server_ipv6' in config:
         if len(config['name_server_ipv6']) > 3:
             raise ConfigError('Not more then three IPv6 DNS name-servers ' \
                               'can be configured')
 
     if 'client_ipv6_pool' in config:
         ipv6_pool = config['client_ipv6_pool']
         if 'delegate' in ipv6_pool:
             if 'prefix' not in ipv6_pool:
                 raise ConfigError('IPv6 "delegate" also requires "prefix" to be defined!')
 
             for delegate in ipv6_pool['delegate']:
                 if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]:
                     raise ConfigError('delegation-prefix length required!')
 
 def verify_diffie_hellman_length(file, min_keysize):
     """ Verify Diffie-Hellamn keypair length given via file. It must be greater
     then or equal to min_keysize """
 
     try:
         keysize = str(min_keysize)
     except:
         return False
 
     import os
     import re
     from vyos.util import cmd
 
     if os.path.exists(file):
 
         out = cmd(f'openssl dhparam -inform PEM -in {file} -text')
         prog = re.compile('\d+\s+bit')
         if prog.search(out):
             bits = prog.search(out)[0].split()[0]
             if int(bits) >= int(min_keysize):
                 return True
 
     return False
 
 def verify_route_maps(config):
     """
     Common helper function used by routing protocol implementations to perform
     recurring validation if the specified route-map for either zebra to kernel
     installation exists (this is the top-level route_map key) or when a route
     is redistributed with a route-map that it exists!
     """
     if 'route_map' in config:
         route_map = config['route_map']
         # Check if the specified route-map exists, if not error out
         if dict_search(f'policy.route_map.{route_map}', config) == None:
             raise ConfigError(f'Specified route-map "{route_map}" does not exist!')
 
     if 'redistribute' in config:
         for protocol, protocol_config in config['redistribute'].items():
             if 'route_map' in protocol_config:
                 # A hyphen in a route-map name will be converted to _, take care
                 # about this effect during validation
                 route_map = protocol_config['route_map'].replace('-','_')
                 # Check if the specified route-map exists, if not error out
                 if dict_search(f'policy.route_map.{route_map}', config) == None:
                     raise ConfigError(f'Redistribution route-map "{route_map}" ' \
                                       f'for "{protocol}" does not exist!')
diff --git a/python/vyos/ifconfig/erspan.py b/python/vyos/ifconfig/erspan.py
index 848840144..50230e14a 100755
--- a/python/vyos/ifconfig/erspan.py
+++ b/python/vyos/ifconfig/erspan.py
@@ -1,190 +1,190 @@
 # Copyright 2019-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/>.
 
 # https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#erspan
 # http://vger.kernel.org/lpc_net2018_talks/erspan-linux-presentation.pdf
 
 from copy import deepcopy
 
 from netaddr import EUI
 from netaddr import mac_unix_expanded
 from random import getrandbits
 
 from vyos.util import dict_search
 from vyos.ifconfig.interface import Interface
 from vyos.validate import assert_list
 
 @Interface.register
 class _ERSpan(Interface):
     """
     _ERSpan: private base class for ERSPAN tunnels
     """
     default = {
         **Interface.default,
         **{
             'type': 'erspan',
         }
     }
     definition = {
         **Interface.definition,
         **{
             'section': 'erspan',
             '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):
         
         # 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
         # often as 'down'. This is required by some interface implementations
         # 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
 
 class ERSpanIf(_ERSpan):
     """
     ERSpanIf: private base class for ERSPAN Over GRE and IPv4 tunnels
     """
     
     def _create(self):
         ifname = self.config['ifname']
         local_ip = self.config['local_ip']
         remote_ip = self.config['remote_ip']
         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:
                 command += f' erspan {idx}'
         elif int(version) == 2:
             direction=dict_search('parameters.erspan.direction',self.config)
             if direction:
                 command += f' erspan_dir {direction}'
             hwid=dict_search('parameters.erspan.hwid',self.config)
             if hwid:
                 command += f' erspan_hwid {hwid}'
         
         ttl = dict_search('parameters.ip.ttl',self.config)
         if ttl:
             command += f' ttl {ttl}'
         tos = dict_search('parameters.ip.tos',self.config)
         if tos:
             command += f' tos {tos}'
                 
         self._cmd(command)
     
     def change_options(self):
         ifname = self.config['ifname']
         local_ip = self.config['local_ip']
         remote_ip = self.config['remote_ip']
         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:
                 command += f' erspan {idx}'
         elif int(version) == 2:
             direction=dict_search('parameters.erspan.direction',self.config)
             if direction:
                 command += f' erspan_dir {direction}'
             hwid=dict_search('parameters.erspan.hwid',self.config)
             if hwid:
                 command += f' erspan_hwid {hwid}'
         
         ttl = dict_search('parameters.ip.ttl',self.config)
         if ttl:
             command += f' ttl {ttl}'
         tos = dict_search('parameters.ip.tos',self.config)
         if tos:
             command += f' tos {tos}'
                 
         self._cmd(command)
 
 class ER6SpanIf(_ERSpan):
     """
     ER6SpanIf: private base class for ERSPAN Over GRE and IPv6 tunnels
     """
     
     def _create(self):
         ifname = self.config['ifname']
         local_ip = self.config['local_ip']
         remote_ip = self.config['remote_ip']
         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:
                 command += f' erspan {idx}'
         elif int(version) == 2:
             direction=dict_search('parameters.erspan.direction',self.config)
             if direction:
                 command += f' erspan_dir {direction}'
             hwid=dict_search('parameters.erspan.hwid',self.config)
             if hwid:
                 command += f' erspan_hwid {hwid}'
         
         ttl = dict_search('parameters.ip.ttl',self.config)
         if ttl:
             command += f' ttl {ttl}'
         tos = dict_search('parameters.ip.tos',self.config)
         if tos:
             command += f' tos {tos}'
                 
         self._cmd(command)
     
-    def change_options(self, config):
+    def change_options(self):
         ifname = self.config['ifname']
         local_ip = self.config['local_ip']
         remote_ip = self.config['remote_ip']
         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:
                 command += f' erspan {idx}'
         elif int(version) == 2:
             direction=dict_search('parameters.erspan.direction',self.config)
             if direction:
                 command += f' erspan_dir {direction}'
             hwid=dict_search('parameters.erspan.hwid',self.config)
             if hwid:
                 command += f' erspan_hwid {hwid}'
                 
         self._cmd(command)
diff --git a/smoketest/scripts/cli/test_interfaces_erspan.py b/smoketest/scripts/cli/test_interfaces_erspan.py
index d21b3ab9b..c180f0a34 100755
--- a/smoketest/scripts/cli/test_interfaces_erspan.py
+++ b/smoketest/scripts/cli/test_interfaces_erspan.py
@@ -1,161 +1,135 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import unittest
 import json
 
 from vyos.configsession import ConfigSession
 from vyos.configsession import ConfigSessionError
 from vyos.util import cmd
 
 from base_interfaces_test import BasicInterfaceTest
 
 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):
     def setUp(self):
         super().setUp()
 
         self._base_path = ['interfaces', 'erspan']
         self._test_mtu = True
 
         self.local_v4 = '10.1.1.1'
         self.local_v6 = '2001:db8::1'
         self.remote_v4 = '10.2.2.2'
         self.remote_v6 = '2001:db9::1'
 
     def tearDown(self):
         self.session.delete(['interfaces', 'erspan'])
         super().tearDown()
 
     def test_erspan_ipv4(self):
         interface = 'ersp100'
         encapsulation = 'erspan'
-        address_v4 = '10.1.1.1/24'
         key = 123
 
-        self.session.set(self._base_path + [interface, 'address', address_v4])
-
-        # Must configure the ERSPAN tunnel encapsulation for ersp100
-        with self.assertRaises(ConfigSessionError):
-            self.session.commit()
         self.session.set(self._base_path + [interface, 'encapsulation', encapsulation])
-
-        # local-ip is mandatory for ERSPAN tunnel
-        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, 'remote-ip', self.remote_v4])
-
         self.session.set(self._base_path + [interface, 'parameters', 'ip' , 'key', str(key)])
 
         self.session.commit()
 
         conf = erspan_conf(interface)
         self.assertEqual(interface, conf['ifname'])
         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'])
 
 
     def test_erspan_ipv6(self):
         interface = 'ersp1000'
         encapsulation = 'ip6erspan'
-        address_v6 = '2001:db8::1/24'
         key = 123
 
-        self.session.set(self._base_path + [interface, 'address', address_v6])
-
-        # Must configure the ERSPAN tunnel encapsulation for ersp100
-        with self.assertRaises(ConfigSessionError):
-            self.session.commit()
         self.session.set(self._base_path + [interface, 'encapsulation', encapsulation])
-
-        # local-ip is mandatory for ERSPAN tunnel
-        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, 'remote-ip', self.remote_v6])
-
         self.session.set(self._base_path + [interface, 'parameters', 'ip' , 'key', str(key)])
 
         self.session.commit()
 
         conf = erspan_conf(interface)
         self.assertEqual(interface, conf['ifname'])
         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'])
 
 if __name__ == '__main__':
-    unittest.main()
+    unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-erspan.py b/src/conf_mode/interfaces-erspan.py
index 1bb5a4a9d..2d65b834c 100755
--- a/src/conf_mode/interfaces-erspan.py
+++ b/src/conf_mode/interfaces-erspan.py
@@ -1,144 +1,114 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2020 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 from copy import deepcopy
 from netifaces import interfaces
 
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.configdict import get_interface_dict
 from vyos.configdict import node_changed
 from vyos.configdict import leaf_node_changed
-from vyos.configverify import verify_vrf
-from vyos.configverify import verify_address
 from vyos.configverify import verify_mtu_ipv6
+from vyos.configverify import verify_tunnel
 from vyos.ifconfig import Interface
 from vyos.ifconfig import ERSpanIf
 from vyos.ifconfig import ER6SpanIf
 from vyos.template import is_ipv4
 from vyos.template import is_ipv6
 from vyos.util import dict_search
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 def get_config(config=None):
     """
     Retrive CLI config as dictionary. Dictionary can never be empty, as at least
     the interface name will be added or a deleted flag
     """
     if config:
         conf = config
     else:
         conf = Config()
     base = ['interfaces', 'erspan']
     erspan = get_interface_dict(conf, base)
     
     tmp = leaf_node_changed(conf, ['encapsulation'])
-    if tmp: 
+    if tmp:
         erspan.update({'encapsulation_changed': {}})
 
     return erspan
 
 def verify(erspan):
     if 'deleted' in erspan:
         return None
     
     if 'encapsulation' not in erspan:
-        raise ConfigError('Must configure the ERSPAN tunnel encapsulation for '\
+        raise ConfigError('Unable to detect the following ERSPAN tunnel encapsulation'\
                           '{ifname}!'.format(**erspan))
 
     verify_mtu_ipv6(erspan)
-    verify_address(erspan)
-    verify_vrf(erspan)
-
-    if 'local_ip' not in erspan:
-        raise ConfigError('local-ip is mandatory for ERSPAN tunnel')
-
-    if 'remote_ip' not in erspan:
-        raise ConfigError('remote-ip is mandatory for ERSPAN tunnel')
-
-    if erspan['encapsulation'] in ['ip6erspan']:
-        error_ipv6 = 'Encapsulation mode requires IPv6'
-        if 'local_ip' in erspan and not is_ipv6(erspan['local_ip']):
-            raise ConfigError(f'{error_ipv6} local-ip')
-
-        if 'remote_ip' in erspan and not is_ipv6(erspan['remote_ip']):
-            raise ConfigError(f'{error_ipv6} remote-ip')
-    else:
-        error_ipv4 = 'Encapsulation mode requires IPv4'
-        if 'local_ip' in erspan and not is_ipv4(erspan['local_ip']):
-            raise ConfigError(f'{error_ipv4} local-ip')
-
-        if 'remote_ip' in erspan and not is_ipv4(erspan['remote_ip']):
-            raise ConfigError(f'{error_ipv4} remote-ip')
-    
-    if 'parameters' not in erspan:
-        raise ConfigError('parameters is mandatory for ERSPAN tunnel')
+    verify_tunnel(erspan)
     
     key = dict_search('parameters.ip.key',erspan)
     if key == None:
         raise ConfigError('parameters.ip.key is mandatory for ERSPAN tunnel')
-    
-    if erspan['encapsulation'] == 'erspan':
-        if 'local_ip' in erspan and is_ipv6(erspan['local_ip']):
-            raise ConfigError('Can not use local IPv6 address is for ERSPAN tunnels')
             
 
 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
     }
 
     # 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.change_options()
     erspan_tunnel.update(erspan)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         generate(c)
         verify(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f03bc9d5d..034bd6dd1 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -1,210 +1,180 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2018-2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 from netifaces import interfaces
 
 from vyos.config import Config
 from vyos.configdict import dict_merge
 from vyos.configdict import get_interface_dict
 from vyos.configdict import node_changed
 from vyos.configdict import leaf_node_changed
 from vyos.configverify import verify_address
 from vyos.configverify import verify_bridge_delete
 from vyos.configverify import verify_interface_exists
 from vyos.configverify import verify_mtu_ipv6
 from vyos.configverify import verify_vrf
+from vyos.configverify import verify_tunnel
 from vyos.ifconfig import Interface
 from vyos.ifconfig import 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.template import is_ipv4
 from vyos.template import is_ipv6
 from vyos.util import dict_search
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 def get_config(config=None):
     """
     Retrive CLI config as dictionary. Dictionary can never be empty, as at least
     the interface name will be added or a deleted flag
     """
     if config:
         conf = config
     else:
         conf = Config()
     base = ['interfaces', 'tunnel']
     tunnel = get_interface_dict(conf, base)
 
     tmp = leaf_node_changed(conf, ['encapsulation'])
     if tmp: tunnel.update({'encapsulation_changed': {}})
 
     # We must check if our interface is configured to be a DMVPN member
     nhrp_base = ['protocols', 'nhrp', 'tunnel']
     conf.set_level(nhrp_base)
     nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
     if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())})
 
     return tunnel
 
 def verify(tunnel):
     if 'deleted' in tunnel:
         verify_bridge_delete(tunnel)
 
         if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']:
             raise ConfigError('Tunnel used for NHRP, it can not be deleted!')
 
         return None
 
     if 'encapsulation' not in tunnel:
         raise ConfigError('Must configure the tunnel encapsulation for '\
                           '{ifname}!'.format(**tunnel))
 
     verify_mtu_ipv6(tunnel)
     verify_address(tunnel)
     verify_vrf(tunnel)
-
-    if 'local_ip' not in tunnel and 'dhcp_interface' not in tunnel:
-        raise ConfigError('local-ip is mandatory for tunnel')
-
-    if 'remote_ip' not in tunnel and tunnel['encapsulation'] != 'gre':
-        raise ConfigError('remote-ip is mandatory for tunnel')
-
-    if {'local_ip', 'dhcp_interface'} <= set(tunnel):
-        raise ConfigError('Can not use both local-ip and dhcp-interface')
-
-    if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
-        error_ipv6 = 'Encapsulation mode requires IPv6'
-        if 'local_ip' in tunnel and not is_ipv6(tunnel['local_ip']):
-            raise ConfigError(f'{error_ipv6} local-ip')
-
-        if 'remote_ip' in tunnel and not is_ipv6(tunnel['remote_ip']):
-            raise ConfigError(f'{error_ipv6} remote-ip')
-    else:
-        error_ipv4 = 'Encapsulation mode requires IPv4'
-        if 'local_ip' in tunnel and not is_ipv4(tunnel['local_ip']):
-            raise ConfigError(f'{error_ipv4} local-ip')
-
-        if 'remote_ip' in tunnel and not is_ipv4(tunnel['remote_ip']):
-            raise ConfigError(f'{error_ipv4} remote-ip')
-
-    if tunnel['encapsulation'] in ['sit', 'gre-bridge']:
-        if 'source_interface' in tunnel:
-            raise ConfigError('Option source-interface can not be used with ' \
-                              'encapsulation "sit" or "gre-bridge"')
-    elif tunnel['encapsulation'] == 'gre':
-        if 'local_ip' in tunnel and is_ipv6(tunnel['local_ip']):
-            raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')
+    verify_tunnel(tunnel)
 
     if 'source_interface' in tunnel:
         verify_interface_exists(tunnel['source_interface'])
 
     # TTL != 0 and nopmtudisc are incompatible, parameters and ip use default
     # values, thus the keys are always present.
     if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None:
         if dict_search('parameters.ip.ttl', tunnel) != '0':
             raise ConfigError('Disabled PMTU requires TTL set to "0"!')
         if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
             raise ConfigError('Can not disable PMTU discovery for given encapsulation')
 
 
 def generate(tunnel):
     return None
 
 def apply(tunnel):
     if 'deleted' in tunnel or 'encapsulation_changed' in tunnel:
         if tunnel['ifname'] in interfaces():
             tmp = Interface(tunnel['ifname'])
             tmp.remove()
         if 'deleted' in tunnel:
             return None
 
     dispatch = {
         'gre': GREIf,
         'gre-bridge': GRETapIf,
         'ipip': IPIPIf,
         'ipip6': IPIP6If,
         'ip6ip6': IP6IP6If,
         'ip6gre': IP6GREIf,
         'sit': SitIf,
     }
 
     # We need to re-map the tunnel encapsulation proto to a valid interface class
     encap = tunnel['encapsulation']
     klass = dispatch[encap]
 
     # This is a special type of interface which needs additional parameters
     # when created using iproute2. Instead of passing a ton of arguments,
     # use a dictionary provided by the interface class which holds all the
     # options necessary.
     conf = klass.get_config()
 
     # Copy/re-assign our dictionary values to values understood by the
     # derived _Tunnel classes
     mapping = {
         # this                       :  get_config()
         'local_ip'                   : 'local',
         'remote_ip'                  : 'remote',
         'source_interface'           : 'dev',
         'parameters.ip.ttl'          : 'ttl',
         'parameters.ip.tos'          : 'tos',
         'parameters.ip.key'          : 'key',
         'parameters.ipv6.encaplimit' : 'encaplimit'
     }
 
     # Add additional IPv6 options if tunnel is IPv6 aware
     if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
         mappingv6 = {
             # this                       :  get_config()
             'parameters.ipv6.encaplimit' : 'encaplimit',
             'parameters.ipv6.flowlabel'  : 'flowlabel',
             'parameters.ipv6.hoplimit'   : 'hoplimit',
             'parameters.ipv6.tclass'     : 'flowlabel'
         }
         mapping.update(mappingv6)
 
     for our_key, their_key in mapping.items():
         if dict_search(our_key, tunnel) and their_key in conf:
             conf[their_key] = dict_search(our_key, tunnel)
 
     if dict_search('parameters.ip.no_pmtu_discovery', tunnel) != None:
         if 'pmtudisc' in conf['raw']:
             conf['raw'].remove('pmtudisc')
         conf['raw'].append('nopmtudisc')
 
     tun = klass(tunnel['ifname'], **conf)
     tun.change_options()
     tun.update(tunnel)
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         generate(c)
         verify(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)