GBP: l3-out subnets
[vpp.git] / test / test_gbp.py
index 92480ce..4a59692 100644 (file)
@@ -196,6 +196,46 @@ class VppGbpRecirc(VppObject):
         return False
 
 
+class VppGbpExtItf(VppObject):
+    """
+    GBP ExtItfulation Interface
+    """
+
+    def __init__(self, test, itf, bd, rd):
+        self._test = test
+        self.itf = itf
+        self.bd = bd
+        self.rd = rd
+
+    def add_vpp_config(self):
+        self._test.vapi.gbp_ext_itf_add_del(
+            1,
+            self.itf.sw_if_index,
+            self.bd.bd_id,
+            self.rd.rd_id)
+        self._test.registry.register(self, self._test.logger)
+
+    def remove_vpp_config(self):
+        self._test.vapi.gbp_ext_itf_add_del(
+            0,
+            self.itf.sw_if_index,
+            self.bd.bd_id,
+            self.rd.rd_id)
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return "gbp-ext-itf:[%d]" % (self.itf.sw_if_index)
+
+    def query_vpp_config(self):
+        rs = self._test.vapi.gbp_ext_itf_dump()
+        for r in rs:
+            if r.ext_itf.sw_if_index == self.itf.sw_if_index:
+                return True
+        return False
+
+
 class VppGbpSubnet(VppObject):
     """
     GBP Subnet
@@ -365,23 +405,61 @@ class VppGbpRouteDomain(VppObject):
         return False
 
 
+class VppGbpContractNextHop():
+    def __init__(self, mac, bd, ip, rd):
+        self.mac = mac
+        self.ip = ip
+        self.bd = bd
+        self.rd = rd
+
+    def encode(self):
+        return {'ip': self.ip.encode(),
+                'mac': self.mac.encode(),
+                'bd_id': self.bd.bd.bd_id,
+                'rd_id': self.rd.rd_id}
+
+
+class VppGbpContractRule():
+    def __init__(self, action, hash_mode, nhs=[]):
+        self.action = action
+        self.hash_mode = hash_mode
+        self.nhs = nhs
+
+    def encode(self):
+        nhs = []
+        for nh in self.nhs:
+            nhs.append(nh.encode())
+        while len(nhs) < 8:
+            nhs.append({})
+        return {'action': self.action,
+                'nh_set': {
+                    'hash_mode': self.hash_mode,
+                    'n_nhs': len(self.nhs),
+                    'nhs': nhs}}
+
+
 class VppGbpContract(VppObject):
     """
     GBP Contract
     """
 
-    def __init__(self, test, src_epg, dst_epg, acl_index):
+    def __init__(self, test, src_epg, dst_epg, acl_index, rules=[]):
         self._test = test
         self.acl_index = acl_index
         self.src_epg = src_epg
         self.dst_epg = dst_epg
+        self.rules = rules
 
     def add_vpp_config(self):
+        rules = []
+        for r in self.rules:
+            rules.append(r.encode())
         self._test.vapi.gbp_contract_add_del(
             1,
             self.src_epg,
             self.dst_epg,
-            self.acl_index)
+            self.acl_index,
+            rules)
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
@@ -389,7 +467,8 @@ class VppGbpContract(VppObject):
             0,
             self.src_epg,
             self.dst_epg,
-            self.acl_index)
+            self.acl_index,
+            [])
 
     def __str__(self):
         return self.object_id()
@@ -611,6 +690,8 @@ class TestGBP(VppTestCase):
     def test_gbp(self):
         """ Group Based Policy """
 
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+
         #
         # Bridge Domains
         #
@@ -755,8 +836,6 @@ class TestGBP(VppTestCase):
             VppIpInterfaceBind(self, recirc.recirc,
                                recirc.epg.rd.t6).add_vpp_config()
 
-            self.vapi.sw_interface_set_l2_emulation(
-                recirc.recirc.sw_if_index)
             self.vapi.nat44_interface_add_del_feature(
                 recirc.recirc.sw_if_index,
                 is_inside=0,
@@ -1059,7 +1138,14 @@ class TestGBP(VppTestCase):
         rule = acl.create_rule(permit_deny=1, proto=17)
         rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
         acl_index = acl.add_vpp_config([rule, rule2])
-        c1 = VppGbpContract(self, 220, 221, acl_index)
+        c1 = VppGbpContract(
+            self, 220, 221, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
         c1.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
@@ -1071,7 +1157,15 @@ class TestGBP(VppTestCase):
         #
         # contract for the return direction
         #
-        c2 = VppGbpContract(self, 221, 220, acl_index)
+        c2 = VppGbpContract(
+            self, 221, 220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+
         c2.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
@@ -1091,7 +1185,15 @@ class TestGBP(VppTestCase):
         #
         # A uni-directional contract from EPG 220 -> 222 'L3 routed'
         #
-        c3 = VppGbpContract(self, 220, 222, acl_index)
+        c3 = VppGbpContract(
+            self, 220, 222, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+
         c3.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh gbp contract"))
@@ -1188,7 +1290,15 @@ class TestGBP(VppTestCase):
                                  dport_from=1234, dport_to=1234)
 
         acl_index2 = acl2.add_vpp_config([rule, rule2])
-        c4 = VppGbpContract(self, 220, 333, acl_index2)
+        c4 = VppGbpContract(
+            self, 220, 333, acl_index2,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+
         c4.add_vpp_config()
 
         self.send_and_expect_natted(eps[0].itf,
@@ -1221,7 +1331,15 @@ class TestGBP(VppTestCase):
         self.send_and_assert_no_replies(self.pg7,
                                         pkt_inter_epg_220_from_global * 65)
 
-        c5 = VppGbpContract(self, 333, 220, acl_index2)
+        c5 = VppGbpContract(
+            self, 333, 220, acl_index2,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+
         c5.add_vpp_config()
 
         self.send_and_expect_unnatted(self.pg7,
@@ -1297,8 +1415,6 @@ class TestGBP(VppTestCase):
                                                   is_add=0)
 
         for recirc in recircs:
-            self.vapi.sw_interface_set_l2_emulation(
-                recirc.recirc.sw_if_index, enable=0)
             self.vapi.nat44_interface_add_del_feature(
                 recirc.recirc.sw_if_index,
                 is_inside=0,
@@ -1311,6 +1427,7 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_l2(self):
         """ GBP L2 Endpoint Learning """
 
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
                    'ip': '10.0.0.1',
                    'ip6': '2001:10::2'},
@@ -1472,13 +1589,8 @@ class TestGBP(VppTestCase):
         self.sleep(2)
         for l in learnt:
             self.assertFalse(find_gbp_endpoint(self,
-                                               tep1_sw_if_index,
                                                mac=l['mac']))
 
-        self.logger.info(self.vapi.cli("show gbp endpoint"))
-        self.logger.info(self.vapi.cli("show gbp vxlan"))
-        self.logger.info(self.vapi.cli("show vxlan-gbp tunnel"))
-
         #
         # repeat. the do not learn bit is set so the EPs are not learnt
         #
@@ -1580,16 +1692,14 @@ class TestGBP(VppTestCase):
 
         #
         # static EP cannot reach the learnt EPs since there is no contract
+        # only test 1 EP as the others could timeout
         #
-        self.logger.info(self.vapi.cli("show gbp endpoint"))
-        self.logger.info(self.vapi.cli("show l2fib all"))
-        for l in learnt:
-            p = (Ether(src=ep.mac, dst=l['mac']) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        p = (Ether(src=ep.mac, dst=l['mac']) /
+             IP(dst=learnt[0]['ip'], src=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
 
-            self.send_and_assert_no_replies(self.pg0, [p], timeout=0.2)
+        self.send_and_assert_no_replies(self.pg0, [p])
 
         #
         # refresh the entries after the check for no replies above
@@ -1620,7 +1730,15 @@ class TestGBP(VppTestCase):
         rule = acl.create_rule(permit_deny=1, proto=17)
         rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
         acl_index = acl.add_vpp_config([rule, rule2])
-        c1 = VppGbpContract(self, 220, 330, acl_index)
+        c1 = VppGbpContract(
+            self, 220, 330, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+
         c1.add_vpp_config()
 
         for l in learnt:
@@ -1707,6 +1825,7 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_vlan_l2(self):
         """ GBP L2 Endpoint w/ VLANs"""
 
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
                    'ip': '10.0.0.1',
                    'ip6': '2001:10::2'},
@@ -1874,6 +1993,9 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_l3(self):
         """ GBP L3 Endpoint Learning """
 
+        self.vapi.cli("set logging class gbp debug")
+
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
         routed_src_mac = "00:22:bd:f8:19:ff"
 
@@ -1942,7 +2064,6 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
         self.logger.info(self.vapi.cli("sh gbp bridge"))
         self.logger.info(self.vapi.cli("sh gbp route"))
-        self.logger.info(self.vapi.cli("show l2fib all"))
 
         # ... and has a /32 and /128 applied
         ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
@@ -2133,7 +2254,7 @@ class TestGBP(VppTestCase):
 
         #
         # Add a route to static EP's v4 and v6 subnet
-        #  packets should be send on the v4/v6 uu=fwd interface resp.
+        #  packets should be sent on the v4/v6 uu=fwd interface resp.
         #
         se_10_24 = VppGbpSubnet(
             self, rd1, "10.0.0.0", 24,
@@ -2198,7 +2319,7 @@ class TestGBP(VppTestCase):
                                 epg_220, None,
                                 "10.0.0.88", "11.0.0.88",
                                 "2001:10::88", "3001::88",
-                                VppEnum.vl_api_gbp_endpoint_flags_t.REMOTE,
+                                ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                                 self.pg2.local_ip4,
                                 self.pg2.remote_hosts[1].ip4,
                                 mac=None)
@@ -2211,7 +2332,7 @@ class TestGBP(VppTestCase):
                                epg_220, None,
                                learnt[0]['ip'], "11.0.0.101",
                                learnt[0]['ip6'], "3001::101",
-                               VppEnum.vl_api_gbp_endpoint_flags_t.REMOTE,
+                               ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                                self.pg2.local_ip4,
                                self.pg2.remote_hosts[1].ip4,
                                mac=None)
@@ -2257,27 +2378,57 @@ class TestGBP(VppTestCase):
                 self.assertEqual(inner[IP].dst, ip)
 
         #
-        # remove the API remote EPs, they are now UU-fwd
+        # remove the API remote EPs, only API sourced is gone, the DP
+        # learnt one remains
         #
         rep_88.remove_vpp_config()
         rep_2.remove_vpp_config()
 
         self.logger.info(self.vapi.cli("show gbp endpoint"))
 
-        for ip in ips:
-            self.assertFalse(find_gbp_endpoint(self, ip=ip))
+        self.assertFalse(find_gbp_endpoint(self, ip=rep_88.ip4.address))
 
-            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IP(dst=ip, src=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(src=ep.ip4.address, dst=rep_88.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg0, [p], self.pg4)
+
+        self.assertTrue(find_gbp_endpoint(self, ip=rep_2.ip4.address))
 
-            rxs = self.send_and_expect(self.pg0, [p], self.pg4)
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(src=ep.ip4.address, dst=rep_2.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg0, [p], self.pg2)
+
+        #
+        # to appease the testcase we cannot have the registered EP stll
+        # present (because it's DP learnt) when the TC ends so wait until
+        # it is removed
+        #
+        self.sleep(2)
 
         #
         # shutdown with learnt endpoint present
         #
-        self.logger.info(self.vapi.cli("show gbp endpoint-group"))
+        p = (Ether(src=self.pg2.remote_mac,
+                   dst=self.pg2.local_mac) /
+             IP(src=self.pg2.remote_hosts[1].ip4,
+                dst=self.pg2.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=101, gpid=220, flags=0x88) /
+             Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+             IP(src=learnt[1]['ip'], dst=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rx = self.send_and_expect(self.pg2, [p], self.pg0)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip=l['ip']))
 
         #
         # TODO
@@ -2287,6 +2438,863 @@ class TestGBP(VppTestCase):
         self.pg3.unconfig_ip4()
         self.pg4.unconfig_ip4()
 
+    def test_gbp_redirect(self):
+        """ GBP Endpoint Redirect """
+
+        self.vapi.cli("set logging class gbp debug")
+
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        routed_dst_mac = "00:0c:0c:0c:0c:0c"
+        routed_src_mac = "00:22:bd:f8:19:ff"
+
+        learnt = [{'mac': '00:00:11:11:11:02',
+                   'ip': '10.0.1.2',
+                   'ip6': '2001:10::2'},
+                  {'mac': '00:00:11:11:11:03',
+                   'ip': '10.0.1.3',
+                   'ip6': '2001:10::3'}]
+
+        #
+        # lower the inactive threshold so these tests pass in a
+        # reasonable amount of time
+        #
+        self.vapi.gbp_endpoint_learn_set_inactive_threshold(1)
+
+        #
+        # IP tables
+        #
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac.address)
+
+        #
+        # Bind the BVI to the RD
+        #
+        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg7 hosts a BD's UU-fwd
+        #
+        self.pg7.config_ip4()
+        self.pg7.resolve_arp()
+
+        #
+        # a GBP bridge domains for the EPs
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
+        gbd1.add_vpp_config()
+
+        bd2 = VppBridgeDomain(self, 2)
+        bd2.add_vpp_config()
+        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
+        gbd2.add_vpp_config()
+
+        # ... and has a /32 and /128 applied
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 128)
+        ip6_addr.add_vpp_config()
+        ip4_addr = VppIpInterfaceAddress(self, gbd2.bvi, "10.0.1.128", 32)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd2.bvi, "2001:11::128", 128)
+        ip6_addr.add_vpp_config()
+
+        #
+        # The Endpoint-groups in which we are learning endpoints
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.0.128",
+                                      "2001:10::128")
+        epg_220.add_vpp_config()
+        epg_221 = VppGbpEndpointGroup(self, 221, rd1, gbd2,
+                                      None, gbd2.bvi,
+                                      "10.0.1.128",
+                                      "2001:11::128")
+        epg_221.add_vpp_config()
+        epg_222 = VppGbpEndpointGroup(self, 222, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.2.128",
+                                      "2001:12::128")
+        epg_222.add_vpp_config()
+
+        #
+        # a GBP bridge domains for the SEPs
+        #
+        bd_uu1 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   self.pg7.remote_ip4, 116)
+        bd_uu1.add_vpp_config()
+        bd_uu2 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   self.pg7.remote_ip4, 117)
+        bd_uu2.add_vpp_config()
+
+        bd3 = VppBridgeDomain(self, 3)
+        bd3.add_vpp_config()
+        gbd3 = VppGbpBridgeDomain(self, bd3, self.loop2, bd_uu1, learn=False)
+        gbd3.add_vpp_config()
+        bd4 = VppBridgeDomain(self, 4)
+        bd4.add_vpp_config()
+        gbd4 = VppGbpBridgeDomain(self, bd4, self.loop3, bd_uu2, learn=False)
+        gbd4.add_vpp_config()
+
+        #
+        # EPGs in which the service endpoints exist
+        #
+        epg_320 = VppGbpEndpointGroup(self, 320, rd1, gbd3,
+                                      None, gbd1.bvi,
+                                      "12.0.0.128",
+                                      "4001:10::128")
+        epg_320.add_vpp_config()
+        epg_321 = VppGbpEndpointGroup(self, 321, rd1, gbd4,
+                                      None, gbd2.bvi,
+                                      "12.0.1.128",
+                                      "4001:11::128")
+        epg_321.add_vpp_config()
+
+        #
+        # three local endpoints
+        #
+        ep1 = VppGbpEndpoint(self, self.pg0,
+                             epg_220, None,
+                             "10.0.0.1", "11.0.0.1",
+                             "2001:10::1", "3001:10::1")
+        ep1.add_vpp_config()
+        ep2 = VppGbpEndpoint(self, self.pg1,
+                             epg_221, None,
+                             "10.0.1.1", "11.0.1.1",
+                             "2001:11::1", "3001:11::1")
+        ep2.add_vpp_config()
+        ep3 = VppGbpEndpoint(self, self.pg2,
+                             epg_222, None,
+                             "10.0.2.2", "11.0.2.2",
+                             "2001:12::1", "3001:12::1")
+        ep3.add_vpp_config()
+
+        #
+        # service endpoints
+        #
+        sep1 = VppGbpEndpoint(self, self.pg3,
+                              epg_320, None,
+                              "12.0.0.1", "13.0.0.1",
+                              "4001:10::1", "5001:10::1")
+        sep1.add_vpp_config()
+        sep2 = VppGbpEndpoint(self, self.pg4,
+                              epg_320, None,
+                              "12.0.0.2", "13.0.0.2",
+                              "4001:10::2", "5001:10::2")
+        sep2.add_vpp_config()
+        sep3 = VppGbpEndpoint(self, self.pg5,
+                              epg_321, None,
+                              "12.0.1.1", "13.0.1.1",
+                              "4001:11::1", "5001:11::1")
+        sep3.add_vpp_config()
+        # this EP is not installed immediately
+        sep4 = VppGbpEndpoint(self, self.pg6,
+                              epg_321, None,
+                              "12.0.1.2", "13.0.1.2",
+                              "4001:11::2", "5001:11::2")
+
+        #
+        # an L2 switch packet between local EPs in different EPGs
+        #  different dest ports on each so the are LB hashed differently
+        #
+        p4 = [(Ether(src=ep1.mac, dst=ep3.mac) /
+               IP(src=ep1.ip4.address, dst=ep3.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep3.mac, dst=ep1.mac) /
+               IP(src=ep3.ip4.address, dst=ep1.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=ep3.mac) /
+               IPv6(src=ep1.ip6.address, dst=ep3.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep3.mac, dst=ep1.mac) /
+               IPv6(src=ep3.ip6.address, dst=ep1.ip6.address) /
+               UDP(sport=1234, dport=1230) /
+               Raw('\xa5' * 100))]
+
+        # should be dropped since no contract yet
+        self.send_and_assert_no_replies(self.pg0, [p4[0]])
+        self.send_and_assert_no_replies(self.pg0, [p6[0]])
+
+        #
+        # Add a contract with a rule to load-balance redirect via SEP1 and SEP2
+        # one of the next-hops is via an EP that is not known
+        #
+        acl = VppGbpAcl(self)
+        rule4 = acl.create_rule(permit_deny=1, proto=17)
+        rule6 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
+        acl_index = acl.add_vpp_config([rule4, rule6])
+
+        #
+        # test the src-ip hash mode
+        #
+        c1 = VppGbpContract(
+            self, 220, 222, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                        sep3.ip6, sep3.epg.rd),
+                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                        sep4.ip6, sep4.epg.rd)])])
+        c1.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 222, 220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                        sep3.ip6, sep3.epg.rd),
+                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                        sep4.ip6, sep4.epg.rd)])])
+        c2.add_vpp_config()
+
+        #
+        # send again with the contract preset, now packets arrive
+        # at SEP1 or SEP2 depending on the hashing
+        #
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+
+        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep2.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep2.mac)
+            self.assertEqual(rx[IP].src, ep3.ip4.address)
+            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
+
+            self.assertEqual(inner[Ether].src, routed_src_mac)
+            self.assertEqual(inner[Ether].dst, sep4.mac)
+            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
+
+        rxs = self.send_and_expect(self.pg2, p6[1] * 17, sep3.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep3.mac)
+            self.assertEqual(rx[IPv6].src, ep3.ip6.address)
+            self.assertEqual(rx[IPv6].dst, ep1.ip6.address)
+
+        #
+        # programme the unknown EP
+        #
+        sep4.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep4.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, ep3.ip6.address)
+
+        #
+        # and revert back to unprogrammed
+        #
+        sep4.remove_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
+
+            self.assertEqual(inner[Ether].src, routed_src_mac)
+            self.assertEqual(inner[Ether].dst, sep4.mac)
+            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
+
+        c1.remove_vpp_config()
+        c2.remove_vpp_config()
+
+        #
+        # test the symmetric hash mode
+        #
+        c1 = VppGbpContract(
+            self, 220, 222, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                        sep3.ip6, sep3.epg.rd),
+                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                        sep4.ip6, sep4.epg.rd)])])
+        c1.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 222, 220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                        sep3.ip6, sep3.epg.rd),
+                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                        sep4.ip6, sep4.epg.rd)])])
+        c2.add_vpp_config()
+
+        #
+        # send again with the contract preset, now packets arrive
+        # at SEP1 for both directions
+        #
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+
+        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep3.ip4.address)
+            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+
+        #
+        # programme the unknown EP for the L3 tests
+        #
+        sep4.add_vpp_config()
+
+        #
+        # an L3 switch packet between local EPs in different EPGs
+        #  different dest ports on each so the are LB hashed differently
+        #
+        p4 = [(Ether(src=ep1.mac, dst=self.router_mac.address) /
+               IP(src=ep1.ip4.address, dst=ep2.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=self.router_mac.address) /
+               IP(src=ep2.ip4.address, dst=ep1.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=self.router_mac.address) /
+               IPv6(src=ep1.ip6.address, dst=ep2.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=self.router_mac.address) /
+               IPv6(src=ep2.ip6.address, dst=ep1.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+
+        c3 = VppGbpContract(
+             self, 220, 221, acl_index,
+             [VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                 [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                        sep1.ip4, sep1.epg.rd),
+                  VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                        sep2.ip4, sep2.epg.rd)]),
+              VppGbpContractRule(
+                  VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                  VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                  [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                         sep3.ip6, sep3.epg.rd),
+                   VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                         sep4.ip6, sep4.epg.rd)])])
+        c3.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep2.ip4.address)
+
+        #
+        # learn a remote EP in EPG 221
+        #
+        vx_tun_l3 = VppGbpVxlanTunnel(
+            self, 444, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3)
+        vx_tun_l3.add_vpp_config()
+
+        c4 = VppGbpContract(
+            self, 221, 220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+        c4.add_vpp_config()
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=221, flags=0x88) /
+             Ether(src="00:22:22:22:22:33", dst=self.router_mac.address) /
+             IP(src="10.0.0.88", dst=ep1.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip="10.0.0.88"))
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=221, flags=0x88) /
+             Ether(src="00:22:22:22:22:33", dst=self.router_mac.address) /
+             IPv6(src="2001:10::88", dst=ep1.ip6.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip="2001:10::88"))
+
+        #
+        # L3 switch from local to remote EP
+        #
+        p4 = [(Ether(src=ep1.mac, dst=self.router_mac.address) /
+               IP(src=ep1.ip4.address, dst="10.0.0.88") /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=self.router_mac.address) /
+               IPv6(src=ep1.ip6.address, dst="2001:10::88") /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, "10.0.0.88")
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep4.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+
+        #
+        # test the dst-ip hash mode
+        #
+        c5 = VppGbpContract(
+            self, 220, 221, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_DST_IP,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_DST_IP,
+                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                        sep3.ip6, sep3.epg.rd),
+                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                        sep4.ip6, sep4.epg.rd)])])
+        c5.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, "10.0.0.88")
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep3.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep3.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+
+        #
+        # cleanup
+        #
+        self.pg7.unconfig_ip4()
+
+    def test_gbp_l3_out(self):
+        """ GBP L3 Out """
+
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        self.vapi.cli("set logging class gbp debug")
+
+        routed_dst_mac = "00:0c:0c:0c:0c:0c"
+        routed_src_mac = "00:22:bd:f8:19:ff"
+
+        #
+        # IP tables
+        #
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac.address)
+
+        #
+        # Bind the BVI to the RD
+        #
+        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg7 hosts a BD's BUM
+        # Pg1 some other l3 interface
+        #
+        self.pg7.config_ip4()
+        self.pg7.resolve_arp()
+
+        #
+        # a GBP external bridge domains for the EPs
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
+        gbd1.add_vpp_config()
+
+        #
+        # The Endpoint-groups in which the external endpoints exist
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.0.128",
+                                      "2001:10::128")
+        epg_220.add_vpp_config()
+
+        # the BVIs have the subnets applied ...
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 24)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 64)
+        ip6_addr.add_vpp_config()
+
+        # ... which are L3-out subnets
+        l3o_1 = VppGbpSubnet(
+            self, rd1, "10.0.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            epg=200)
+        l3o_1.add_vpp_config()
+
+        #
+        # an external interface attached to the outside world and the
+        # external BD
+        #
+        vlan_100 = VppDot1QSubint(self, self.pg0, 100)
+        vlan_100.admin_up()
+        ext_itf = VppGbpExtItf(self, vlan_100, bd1, rd1)
+        ext_itf.add_vpp_config()
+
+        #
+        # a multicast vxlan-gbp tunnel for broadcast in the BD
+        #
+        tun_bm = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   "239.1.1.1", 88,
+                                   mcast_itf=self.pg7)
+        tun_bm.add_vpp_config()
+        bp_bm = VppBridgeDomainPort(self, bd1, tun_bm,
+                                    port_type=L2_PORT_TYPE.NORMAL)
+        bp_bm.add_vpp_config()
+
+        #
+        # an unicast vxlan-gbp for inter-BD traffic
+        #
+        vx_tun_l3 = VppGbpVxlanTunnel(
+            self, 444, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3)
+        vx_tun_l3.add_vpp_config()
+
+        #
+        # packets destined to unkown addresses in the BVI's subnet
+        # are ARP'd for
+        #
+        p4 = (Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+              Dot1Q(vlan=100) /
+              IP(src="10.0.0.1", dst="10.0.0.88") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+        p6 = (Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+              Dot1Q(vlan=100) /
+              IPv6(src="2001:10::1", dst="2001:10::88") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p4 * 1, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            # self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, "239.1.1.1")
+            self.assertEqual(rx[VXLAN].vni, 88)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # policy is not applied since we don't know where it's going
+            self.assertFalse(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
+
+            self.assertTrue(inner.haslayer(ARP))
+
+        #
+        # An external Endpoint
+        #
+        eep = VppGbpEndpoint(self, vlan_100,
+                             epg_220, None,
+                             "10.0.0.1", "11.0.0.1",
+                             "2001:10::1", "3001::1",
+                             ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
+        eep.add_vpp_config()
+
+        #
+        # A remote endpoint
+        #
+        rep = VppGbpEndpoint(self, vx_tun_l3,
+                             epg_220, None,
+                             "10.0.0.101", "11.0.0.101",
+                             "2001:10::101", "3001::101",
+                             ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
+                             self.pg7.local_ip4,
+                             self.pg7.remote_ip4,
+                             mac=None)
+        rep.add_vpp_config()
+
+        #
+        # remote to external
+        #
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=220, flags=0x88) /
+             Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+             IP(src="10.0.0.101", dst="10.0.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
+
+        #
+        # A subnet reachable through the external EP
+        #
+        ip_220 = VppIpRoute(self, "10.220.0.0", 24,
+                            [VppRoutePath(eep.ip4.address,
+                                          eep.epg.bvi.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_220.add_vpp_config()
+
+        l3o_220 = VppGbpSubnet(
+            self, rd1, "10.220.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            epg=220)
+        l3o_220.add_vpp_config()
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=220, flags=0x88) /
+             Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+             IP(src="10.0.0.101", dst="10.220.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
+
+        #
+        # another external subnet, this time in a different EPG
+        #
+        ip_200 = VppIpRoute(self, "10.200.0.0", 24,
+                            [VppRoutePath(eep.ip4.address,
+                                          eep.epg.bvi.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_200.add_vpp_config()
+
+        l3o_200 = VppGbpSubnet(
+            self, rd1, "10.200.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            epg=200)
+        l3o_200.add_vpp_config()
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=220, flags=0x88) /
+             Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+             IP(src="10.0.0.101", dst="10.200.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        #
+        # packets dropped due to lack of contract.
+        #
+        rxs = self.send_and_assert_no_replies(self.pg7, p * 1)
+
+        #
+        # from the the subnet in EPG 220 beyond the external to remote
+        #
+        p4 = (Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+              Dot1Q(vlan=100) /
+              IP(src="10.220.0.1", dst=rep.ip4.address) /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p4 * 1, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 444)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+        #
+        # from the the subnet in EPG 200 beyond the external to remote
+        # dropped due to no contract
+        #
+        p4 = (Ether(src=self.pg0.remote_mac, dst=self.router_mac.address) /
+              Dot1Q(vlan=100) /
+              IP(src="10.200.0.1", dst=rep.ip4.address) /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+
+        rxs = self.send_and_assert_no_replies(self.pg0, p4 * 1)
+
+        #
+        # add a contract
+        #
+        acl = VppGbpAcl(self)
+        rule = acl.create_rule(permit_deny=1, proto=17)
+        rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
+        acl_index = acl.add_vpp_config([rule, rule2])
+        c1 = VppGbpContract(
+            self, 200, 220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 [])])
+        c1.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p4 * 1, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 444)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+        #
+        # cleanup
+        #
+        self.pg7.unconfig_ip4()
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)