gre: Tunnel encap/decap flags
[vpp.git] / test / test_gre.py
index ccd66c4..e73ba2f 100644 (file)
@@ -1,7 +1,8 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 import unittest
 
 
 import unittest
 
+import scapy.compat
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, Dot1Q, GRE
 from scapy.layers.inet import IP, UDP
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, Dot1Q, GRE
 from scapy.layers.inet import IP, UDP
@@ -9,18 +10,59 @@ from scapy.layers.inet6 import IPv6
 from scapy.volatile import RandMAC, RandIP
 
 from framework import VppTestCase, VppTestRunner
 from scapy.volatile import RandMAC, RandIP
 
 from framework import VppTestCase, VppTestRunner
-from vpp_sub_interface import VppDot1QSubint
-from vpp_gre_interface import VppGreInterface, VppGre6Interface
+from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint
+from vpp_gre_interface import VppGreInterface
+from vpp_nhrp import VppNhrp
 from vpp_ip import DpoProto
 from vpp_ip import DpoProto
-from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable
-from vpp_papi_provider import L2_VTR_OP
+from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, FibPathProto
 from util import ppp, ppc
 from util import ppp, ppc
+from vpp_papi import VppEnum
 
 
 
 
-class GreTunnelTypes:
-    TT_L3 = 0
-    TT_TEB = 1
-    TT_ERSPAN = 2
+class TestGREInputNodes(VppTestCase):
+    """ GRE Input Nodes Test Case """
+
+    def setUp(self):
+        super(TestGREInputNodes, self).setUp()
+
+        # create 3 pg interfaces - set one in a non-default table.
+        self.create_pg_interfaces(range(1))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+
+    def tearDown(self):
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.admin_down()
+        super(TestGREInputNodes, self).tearDown()
+
+    def test_gre_input_node(self):
+        """ GRE gre input nodes not registerd unless configured """
+        pkt = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+               IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) /
+               GRE())
+
+        self.pg0.add_stream(pkt)
+        self.pg_start()
+        # no tunnel created, gre-input not registered
+        err = self.statistics.get_counter(
+            '/err/ip4-input/unknown ip protocol')[0]
+        self.assertEqual(err, 1)
+        err_count = err
+
+        # create gre tunnel
+        gre_if = VppGreInterface(self, self.pg0.local_ip4, "1.1.1.2")
+        gre_if.add_vpp_config()
+
+        self.pg0.add_stream(pkt)
+        self.pg_start()
+        # tunnel created, gre-input registered
+        err = self.statistics.get_counter(
+            '/err/ip4-input/unknown ip protocol')[0]
+        # expect no new errors
+        self.assertEqual(err, err_count)
 
 
 class TestGRE(VppTestCase):
 
 
 class TestGRE(VppTestCase):
@@ -30,11 +72,15 @@ class TestGRE(VppTestCase):
     def setUpClass(cls):
         super(TestGRE, cls).setUpClass()
 
     def setUpClass(cls):
         super(TestGRE, cls).setUpClass()
 
+    @classmethod
+    def tearDownClass(cls):
+        super(TestGRE, cls).tearDownClass()
+
     def setUp(self):
         super(TestGRE, self).setUp()
 
         # create 3 pg interfaces - set one in a non-default table.
     def setUp(self):
         super(TestGRE, self).setUp()
 
         # create 3 pg interfaces - set one in a non-default table.
-        self.create_pg_interfaces(range(3))
+        self.create_pg_interfaces(range(5))
 
         self.tbl = VppIpTable(self, 1)
         self.tbl.add_vpp_config()
 
         self.tbl = VppIpTable(self, 1)
         self.tbl.add_vpp_config()
@@ -49,6 +95,10 @@ class TestGRE(VppTestCase):
         self.pg1.resolve_arp()
         self.pg2.config_ip6()
         self.pg2.resolve_ndp()
         self.pg1.resolve_arp()
         self.pg2.config_ip6()
         self.pg2.resolve_ndp()
+        self.pg3.config_ip4()
+        self.pg3.resolve_arp()
+        self.pg4.config_ip4()
+        self.pg4.resolve_arp()
 
     def tearDown(self):
         for i in self.pg_interfaces:
 
     def tearDown(self):
         for i in self.pg_interfaces:
@@ -58,26 +108,28 @@ class TestGRE(VppTestCase):
         self.pg1.set_table_ip4(0)
         super(TestGRE, self).tearDown()
 
         self.pg1.set_table_ip4(0)
         super(TestGRE, self).tearDown()
 
-    def create_stream_ip4(self, src_if, src_ip, dst_ip):
+    def create_stream_ip4(self, src_if, src_ip, dst_ip, dscp=0, ecn=0):
         pkts = []
         pkts = []
+        tos = (dscp << 2) | ecn
         for i in range(0, 257):
             info = self.create_packet_info(src_if, src_if)
             payload = self.info_to_payload(info)
             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
         for i in range(0, 257):
             info = self.create_packet_info(src_if, src_if)
             payload = self.info_to_payload(info)
             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
-                 IP(src=src_ip, dst=dst_ip) /
+                 IP(src=src_ip, dst=dst_ip, tos=tos) /
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
             pkts.append(p)
         return pkts
 
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
             pkts.append(p)
         return pkts
 
-    def create_stream_ip6(self, src_if, src_ip, dst_ip):
+    def create_stream_ip6(self, src_if, src_ip, dst_ip, dscp=0, ecn=0):
         pkts = []
         pkts = []
+        tc = (dscp << 2) | ecn
         for i in range(0, 257):
             info = self.create_packet_info(src_if, src_if)
             payload = self.info_to_payload(info)
             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
         for i in range(0, 257):
             info = self.create_packet_info(src_if, src_if)
             payload = self.info_to_payload(info)
             p = (Ether(dst=src_if.local_mac, src=src_if.remote_mac) /
-                 IPv6(src=src_ip, dst=dst_ip) /
+                 IPv6(src=src_ip, dst=dst_ip, tc=tc) /
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
@@ -146,7 +198,8 @@ class TestGRE(VppTestCase):
                  GRE() /
                  Ether(dst=RandMAC('*:*:*:*:*:*'),
                        src=RandMAC('*:*:*:*:*:*')) /
                  GRE() /
                  Ether(dst=RandMAC('*:*:*:*:*:*'),
                        src=RandMAC('*:*:*:*:*:*')) /
-                 IP(src=str(RandIP()), dst=str(RandIP())) /
+                 IP(src=scapy.compat.raw(RandIP()),
+                    dst=scapy.compat.raw(RandIP())) /
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
@@ -165,7 +218,8 @@ class TestGRE(VppTestCase):
                  Ether(dst=RandMAC('*:*:*:*:*:*'),
                        src=RandMAC('*:*:*:*:*:*')) /
                  Dot1Q(vlan=vlan) /
                  Ether(dst=RandMAC('*:*:*:*:*:*'),
                        src=RandMAC('*:*:*:*:*:*')) /
                  Dot1Q(vlan=vlan) /
-                 IP(src=str(RandIP()), dst=str(RandIP())) /
+                 IP(src=scapy.compat.raw(RandIP()),
+                    dst=scapy.compat.raw(RandIP())) /
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
                  UDP(sport=1234, dport=1234) /
                  Raw(payload))
             info.data = p.copy()
@@ -173,9 +227,11 @@ class TestGRE(VppTestCase):
         return pkts
 
     def verify_tunneled_4o4(self, src_if, capture, sent,
         return pkts
 
     def verify_tunneled_4o4(self, src_if, capture, sent,
-                            tunnel_src, tunnel_dst):
+                            tunnel_src, tunnel_dst,
+                            dscp=0, ecn=0):
 
         self.assertEqual(len(capture), len(sent))
 
         self.assertEqual(len(capture), len(sent))
+        tos = (dscp << 2) | ecn
 
         for i in range(len(capture)):
             try:
 
         for i in range(len(capture)):
             try:
@@ -187,6 +243,7 @@ class TestGRE(VppTestCase):
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
+                self.assertEqual(rx_ip.tos, tos)
 
                 rx_gre = rx[GRE]
                 rx_ip = rx_gre[IP]
 
                 rx_gre = rx[GRE]
                 rx_ip = rx_gre[IP]
@@ -202,9 +259,11 @@ class TestGRE(VppTestCase):
                 raise
 
     def verify_tunneled_6o6(self, src_if, capture, sent,
                 raise
 
     def verify_tunneled_6o6(self, src_if, capture, sent,
-                            tunnel_src, tunnel_dst):
+                            tunnel_src, tunnel_dst,
+                            dscp=0, ecn=0):
 
         self.assertEqual(len(capture), len(sent))
 
         self.assertEqual(len(capture), len(sent))
+        tc = (dscp << 2) | ecn
 
         for i in range(len(capture)):
             try:
 
         for i in range(len(capture)):
             try:
@@ -216,8 +275,9 @@ class TestGRE(VppTestCase):
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
+                self.assertEqual(rx_ip.tc, tc)
 
 
-                rx_gre = GRE(str(rx_ip[IPv6].payload))
+                rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload))
                 rx_ip = rx_gre[IPv6]
 
                 self.assertEqual(rx_ip.src, tx_ip.src)
                 rx_ip = rx_gre[IPv6]
 
                 self.assertEqual(rx_ip.src, tx_ip.src)
@@ -243,7 +303,7 @@ class TestGRE(VppTestCase):
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
-                rx_gre = GRE(str(rx_ip[IPv6].payload))
+                rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload))
                 tx_ip = tx[IP]
                 rx_ip = rx_gre[IP]
 
                 tx_ip = tx[IP]
                 rx_ip = rx_gre[IP]
 
@@ -270,7 +330,7 @@ class TestGRE(VppTestCase):
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
                 self.assertEqual(rx_ip.src, tunnel_src)
                 self.assertEqual(rx_ip.dst, tunnel_dst)
 
-                rx_gre = GRE(str(rx_ip[IP].payload))
+                rx_gre = GRE(scapy.compat.raw(rx_ip[IP].payload))
                 rx_ip = rx_gre[IPv6]
                 tx_ip = tx[IPv6]
 
                 rx_ip = rx_gre[IPv6]
                 tx_ip = tx[IPv6]
 
@@ -399,6 +459,28 @@ class TestGRE(VppTestCase):
                 self.logger.error(ppp("Tx:", tx))
                 raise
 
                 self.logger.error(ppp("Tx:", tx))
                 raise
 
+    def verify_decapped_6o6(self, src_if, capture, sent):
+        self.assertEqual(len(capture), len(sent))
+
+        for i in range(len(capture)):
+            try:
+                tx = sent[i]
+                rx = capture[i]
+
+                tx_ip = tx[IPv6]
+                rx_ip = rx[IPv6]
+                tx_gre = tx[GRE]
+                tx_ip = tx_gre[IPv6]
+
+                self.assertEqual(rx_ip.src, tx_ip.src)
+                self.assertEqual(rx_ip.dst, tx_ip.dst)
+                self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim)
+
+            except:
+                self.logger.error(ppp("Rx:", rx))
+                self.logger.error(ppp("Tx:", tx))
+                raise
+
     def test_gre(self):
         """ GRE IPv4 tunnel Tests """
 
     def test_gre(self):
         """ GRE IPv4 tunnel Tests """
 
@@ -435,7 +517,7 @@ class TestGRE(VppTestCase):
 
         #
         # Send a packet stream that is routed into the tunnel
 
         #
         # Send a packet stream that is routed into the tunnel
-        #  - they are all dropped since the tunnel's desintation IP
+        #  - they are all dropped since the tunnel's destintation IP
         #    is unresolved - or resolves via the default route - which
         #    which is a drop.
         #
         #    is unresolved - or resolves via the default route - which
         #    which is a drop.
         #
@@ -529,8 +611,7 @@ class TestGRE(VppTestCase):
             self, "2001::1", 128,
             [VppRoutePath("::",
                           gre_if.sw_if_index,
             self, "2001::1", 128,
             [VppRoutePath("::",
                           gre_if.sw_if_index,
-                          proto=DpoProto.DPO_PROTO_IP6)],
-            is_ip6=1)
+                          proto=DpoProto.DPO_PROTO_IP6)])
         route6_via_tun.add_vpp_config()
 
         tx = self.create_stream_ip6(self.pg0, "2001::2", "2001::1")
         route6_via_tun.add_vpp_config()
 
         tx = self.create_stream_ip6(self.pg0, "2001::2", "2001::1")
@@ -561,25 +642,22 @@ class TestGRE(VppTestCase):
         #  - assign an IP Address
         #  - Add a route via the tunnel
         #
         #  - assign an IP Address
         #  - Add a route via the tunnel
         #
-        gre_if = VppGre6Interface(self,
-                                  self.pg2.local_ip6,
-                                  "1002::1")
+        gre_if = VppGreInterface(self,
+                                 self.pg2.local_ip6,
+                                 "1002::1")
         gre_if.add_vpp_config()
         gre_if.admin_up()
         gre_if.config_ip6()
 
         gre_if.add_vpp_config()
         gre_if.admin_up()
         gre_if.config_ip6()
 
-        route_via_tun = VppIpRoute(
-            self, "4004::1", 128,
-            [VppRoutePath("0::0",
-                          gre_if.sw_if_index,
-                          proto=DpoProto.DPO_PROTO_IP6)],
-            is_ip6=1)
+        route_via_tun = VppIpRoute(self, "4004::1", 128,
+                                   [VppRoutePath("0::0",
+                                                 gre_if.sw_if_index)])
 
         route_via_tun.add_vpp_config()
 
         #
         # Send a packet stream that is routed into the tunnel
 
         route_via_tun.add_vpp_config()
 
         #
         # Send a packet stream that is routed into the tunnel
-        #  - they are all dropped since the tunnel's desintation IP
+        #  - they are all dropped since the tunnel's destintation IP
         #    is unresolved - or resolves via the default route - which
         #    which is a drop.
         #
         #    is unresolved - or resolves via the default route - which
         #    which is a drop.
         #
@@ -591,12 +669,9 @@ class TestGRE(VppTestCase):
         #
         # Add a route that resolves the tunnel's destination
         #
         #
         # Add a route that resolves the tunnel's destination
         #
-        route_tun_dst = VppIpRoute(
-            self, "1002::1", 128,
-            [VppRoutePath(self.pg2.remote_ip6,
-                          self.pg2.sw_if_index,
-                          proto=DpoProto.DPO_PROTO_IP6)],
-            is_ip6=1)
+        route_tun_dst = VppIpRoute(self, "1002::1", 128,
+                                   [VppRoutePath(self.pg2.remote_ip6,
+                                                 self.pg2.sw_if_index)])
         route_tun_dst.add_vpp_config()
 
         #
         route_tun_dst.add_vpp_config()
 
         #
@@ -652,6 +727,8 @@ class TestGRE(VppTestCase):
     def test_gre_vrf(self):
         """ GRE tunnel VRF Tests """
 
     def test_gre_vrf(self):
         """ GRE tunnel VRF Tests """
 
+        e = VppEnum.vl_api_tunnel_encap_decap_flags_t
+
         #
         # Create an L3 GRE tunnel whose destination is in the non-default
         # table. The underlay is thus non-default - the overlay is still
         #
         # Create an L3 GRE tunnel whose destination is in the non-default
         # table. The underlay is thus non-default - the overlay is still
@@ -659,9 +736,13 @@ class TestGRE(VppTestCase):
         #  - set it admin up
         #  - assign an IP Addres
         #
         #  - set it admin up
         #  - assign an IP Addres
         #
-        gre_if = VppGreInterface(self, self.pg1.local_ip4,
-                                 "2.2.2.2",
-                                 outer_fib_id=1)
+        gre_if = VppGreInterface(
+            self, self.pg1.local_ip4,
+            "2.2.2.2",
+            outer_table_id=1,
+            flags=(e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP |
+                   e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_ECN))
+
         gre_if.add_vpp_config()
         gre_if.admin_up()
         gre_if.config_ip4()
         gre_if.add_vpp_config()
         gre_if.admin_up()
         gre_if.config_ip4()
@@ -689,10 +770,12 @@ class TestGRE(VppTestCase):
         #  - packets are GRE encapped
         #
         self.vapi.cli("clear trace")
         #  - packets are GRE encapped
         #
         self.vapi.cli("clear trace")
-        tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "9.9.9.9")
+        tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "9.9.9.9",
+                                    dscp=5, ecn=3)
         rx = self.send_and_expect(self.pg0, tx, self.pg1)
         self.verify_tunneled_4o4(self.pg1, rx, tx,
         rx = self.send_and_expect(self.pg0, tx, self.pg1)
         self.verify_tunneled_4o4(self.pg1, rx, tx,
-                                 self.pg1.local_ip4, "2.2.2.2")
+                                 self.pg1.local_ip4, "2.2.2.2",
+                                 dscp=5, ecn=3)
 
         #
         # Send tunneled packets that match the created tunnel and
 
         #
         # Send tunneled packets that match the created tunnel and
@@ -756,10 +839,12 @@ class TestGRE(VppTestCase):
         #
         gre_if1 = VppGreInterface(self, self.pg0.local_ip4,
                                   "2.2.2.2",
         #
         gre_if1 = VppGreInterface(self, self.pg0.local_ip4,
                                   "2.2.2.2",
-                                  type=GreTunnelTypes.TT_TEB)
+                                  type=(VppEnum.vl_api_gre_tunnel_type_t.
+                                        GRE_API_TUNNEL_TYPE_TEB))
         gre_if2 = VppGreInterface(self, self.pg0.local_ip4,
                                   "2.2.2.3",
         gre_if2 = VppGreInterface(self, self.pg0.local_ip4,
                                   "2.2.2.3",
-                                  type=GreTunnelTypes.TT_TEB)
+                                  type=(VppEnum.vl_api_gre_tunnel_type_t.
+                                        GRE_API_TUNNEL_TYPE_TEB))
         gre_if1.add_vpp_config()
         gre_if2.add_vpp_config()
 
         gre_if1.add_vpp_config()
         gre_if2.add_vpp_config()
 
@@ -824,12 +909,12 @@ class TestGRE(VppTestCase):
         # Configure both to pop thier respective VLAN tags,
         # so that during the x-coonect they will subsequently push
         #
         # Configure both to pop thier respective VLAN tags,
         # so that during the x-coonect they will subsequently push
         #
-        self.vapi.l2_interface_vlan_tag_rewrite(gre_if_12.sw_if_index,
-                                                L2_VTR_OP.L2_POP_1,
-                                                12)
-        self.vapi.l2_interface_vlan_tag_rewrite(gre_if_11.sw_if_index,
-                                                L2_VTR_OP.L2_POP_1,
-                                                11)
+        self.vapi.l2_interface_vlan_tag_rewrite(
+            sw_if_index=gre_if_12.sw_if_index, vtr_op=L2_VTR_OP.L2_POP_1,
+            push_dot1q=12)
+        self.vapi.l2_interface_vlan_tag_rewrite(
+            sw_if_index=gre_if_11.sw_if_index, vtr_op=L2_VTR_OP.L2_POP_1,
+            push_dot1q=11)
 
         #
         # Send traffic in both directiond - expect the VLAN tags to
 
         #
         # Send traffic in both directiond - expect the VLAN tags to
@@ -926,6 +1011,202 @@ class TestGRE(VppTestCase):
         route_via_tun_2.remove_vpp_config()
         gre_if.remove_vpp_config()
 
         route_via_tun_2.remove_vpp_config()
         gre_if.remove_vpp_config()
 
+    def test_mgre(self):
+        """ mGRE IPv4 tunnel Tests """
+
+        for itf in self.pg_interfaces[3:]:
+            #
+            # one underlay nh for each overlay/tunnel peer
+            #
+            itf.generate_remote_hosts(4)
+            itf.configure_ipv4_neighbors()
+
+            #
+            # Create an L3 GRE tunnel.
+            #  - set it admin up
+            #  - assign an IP Addres
+            #  - Add a route via the tunnel
+            #
+            gre_if = VppGreInterface(self,
+                                     itf.local_ip4,
+                                     "0.0.0.0",
+                                     mode=(VppEnum.vl_api_tunnel_mode_t.
+                                           TUNNEL_API_MODE_MP))
+            gre_if.add_vpp_config()
+            gre_if.admin_up()
+            gre_if.config_ip4()
+            gre_if.generate_remote_hosts(4)
+
+            self.logger.info(self.vapi.cli("sh adj"))
+            self.logger.info(self.vapi.cli("sh ip fib"))
+
+            #
+            # ensure we don't match to the tunnel if the source address
+            # is all zeros
+            #
+            tx = self.create_tunnel_stream_4o4(self.pg0,
+                                               "0.0.0.0",
+                                               itf.local_ip4,
+                                               self.pg0.local_ip4,
+                                               self.pg0.remote_ip4)
+            self.send_and_assert_no_replies(self.pg0, tx)
+
+            #
+            # for-each peer
+            #
+            for ii in range(1, 4):
+                route_addr = "4.4.4.%d" % ii
+
+                #
+                # route traffic via the peer
+                #
+                route_via_tun = VppIpRoute(
+                    self, route_addr, 32,
+                    [VppRoutePath(gre_if._remote_hosts[ii].ip4,
+                                  gre_if.sw_if_index)])
+                route_via_tun.add_vpp_config()
+
+                #
+                # Add a NHRP entry resolves the peer
+                #
+                nhrp = VppNhrp(self, gre_if,
+                               gre_if._remote_hosts[ii].ip4,
+                               itf._remote_hosts[ii].ip4)
+                nhrp.add_vpp_config()
+
+                #
+                # Send a packet stream that is routed into the tunnel
+                #  - packets are GRE encapped
+                #
+                tx_e = self.create_stream_ip4(self.pg0, "5.5.5.5", route_addr)
+                rx = self.send_and_expect(self.pg0, tx_e, itf)
+                self.verify_tunneled_4o4(self.pg0, rx, tx_e,
+                                         itf.local_ip4,
+                                         itf._remote_hosts[ii].ip4)
+
+                tx_i = self.create_tunnel_stream_4o4(self.pg0,
+                                                     itf._remote_hosts[ii].ip4,
+                                                     itf.local_ip4,
+                                                     self.pg0.local_ip4,
+                                                     self.pg0.remote_ip4)
+                rx = self.send_and_expect(self.pg0, tx_i, self.pg0)
+                self.verify_decapped_4o4(self.pg0, rx, tx_i)
+
+                #
+                # delete and re-add the NHRP
+                #
+                nhrp.remove_vpp_config()
+                self.send_and_assert_no_replies(self.pg0, tx_e)
+                self.send_and_assert_no_replies(self.pg0, tx_i)
+
+                nhrp.add_vpp_config()
+                rx = self.send_and_expect(self.pg0, tx_e, itf)
+                self.verify_tunneled_4o4(self.pg0, rx, tx_e,
+                                         itf.local_ip4,
+                                         itf._remote_hosts[ii].ip4)
+                rx = self.send_and_expect(self.pg0, tx_i, self.pg0)
+                self.verify_decapped_4o4(self.pg0, rx, tx_i)
+
+            gre_if.admin_down()
+            gre_if.unconfig_ip4()
+
+    def test_mgre6(self):
+        """ mGRE IPv6 tunnel Tests """
+
+        self.pg0.config_ip6()
+        self.pg0.resolve_ndp()
+
+        e = VppEnum.vl_api_tunnel_encap_decap_flags_t
+
+        for itf in self.pg_interfaces[3:]:
+            #
+            # one underlay nh for each overlay/tunnel peer
+            #
+            itf.config_ip6()
+            itf.generate_remote_hosts(4)
+            itf.configure_ipv6_neighbors()
+
+            #
+            # Create an L3 GRE tunnel.
+            #  - set it admin up
+            #  - assign an IP Addres
+            #  - Add a route via the tunnel
+            #
+            gre_if = VppGreInterface(
+                self,
+                itf.local_ip6,
+                "::",
+                mode=(VppEnum.vl_api_tunnel_mode_t.
+                      TUNNEL_API_MODE_MP),
+                flags=e.TUNNEL_API_ENCAP_DECAP_FLAG_ENCAP_COPY_DSCP)
+
+            gre_if.add_vpp_config()
+            gre_if.admin_up()
+            gre_if.config_ip6()
+            gre_if.generate_remote_hosts(4)
+
+            #
+            # for-each peer
+            #
+            for ii in range(1, 4):
+                route_addr = "4::%d" % ii
+
+                #
+                # Add a NHRP entry resolves the peer
+                #
+                nhrp = VppNhrp(self, gre_if,
+                               gre_if._remote_hosts[ii].ip6,
+                               itf._remote_hosts[ii].ip6)
+                nhrp.add_vpp_config()
+
+                #
+                # route traffic via the peer
+                #
+                route_via_tun = VppIpRoute(
+                    self, route_addr, 128,
+                    [VppRoutePath(gre_if._remote_hosts[ii].ip6,
+                                  gre_if.sw_if_index)])
+                route_via_tun.add_vpp_config()
+
+                #
+                # Send a packet stream that is routed into the tunnel
+                #  - packets are GRE encapped
+                #
+                tx_e = self.create_stream_ip6(self.pg0, "5::5", route_addr,
+                                              dscp=2, ecn=1)
+                rx = self.send_and_expect(self.pg0, tx_e, itf)
+                self.verify_tunneled_6o6(self.pg0, rx, tx_e,
+                                         itf.local_ip6,
+                                         itf._remote_hosts[ii].ip6,
+                                         dscp=2)
+                tx_i = self.create_tunnel_stream_6o6(self.pg0,
+                                                     itf._remote_hosts[ii].ip6,
+                                                     itf.local_ip6,
+                                                     self.pg0.local_ip6,
+                                                     self.pg0.remote_ip6)
+                rx = self.send_and_expect(self.pg0, tx_i, self.pg0)
+                self.verify_decapped_6o6(self.pg0, rx, tx_i)
+
+                #
+                # delete and re-add the NHRP
+                #
+                nhrp.remove_vpp_config()
+                self.send_and_assert_no_replies(self.pg0, tx_e)
+
+                nhrp.add_vpp_config()
+                rx = self.send_and_expect(self.pg0, tx_e, itf)
+                self.verify_tunneled_6o6(self.pg0, rx, tx_e,
+                                         itf.local_ip6,
+                                         itf._remote_hosts[ii].ip6,
+                                         dscp=2)
+                rx = self.send_and_expect(self.pg0, tx_i, self.pg0)
+                self.verify_decapped_6o6(self.pg0, rx, tx_i)
+
+            gre_if.admin_down()
+            gre_if.unconfig_ip4()
+            itf.unconfig_ip6()
+        self.pg0.unconfig_ip6()
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)