l2: BD ARP termination entry API update
[vpp.git] / test / test_gbp.py
index 4304303..0580fff 100644 (file)
@@ -5,27 +5,30 @@ import unittest
 
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
-from scapy.layers.inet import IP, UDP
-from scapy.layers.inet6 import IPv6, ICMPv6ND_NS,  ICMPv6NDOptSrcLLAddr, \
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6NDOptSrcLLAddr, \
     ICMPv6ND_NA
 from scapy.utils6 import in6_getnsma, in6_getnsmac
 from scapy.layers.vxlan import VXLAN
-from scapy.data import ETH_P_IP, ETH_P_IPV6
+from scapy.data import ETH_P_IP, ETH_P_IPV6, ETH_P_ARP
 from scapy.utils import inet_pton, inet_ntop
 
 from framework import VppTestCase, VppTestRunner
 from vpp_object import VppObject
 from vpp_interface import VppInterface
 from vpp_ip_route import VppIpRoute, VppRoutePath, VppIpTable, \
-    VppIpInterfaceAddress, VppIpInterfaceBind, find_route
+    VppIpInterfaceAddress, VppIpInterfaceBind, find_route, FibPathProto, \
+    FibPathType
 from vpp_l2 import VppBridgeDomain, VppBridgeDomainPort, \
-    VppBridgeDomainArpEntry, VppL2FibEntry, find_bridge_domain_port
-from vpp_sub_interface import VppDot1QSubint
+    VppBridgeDomainArpEntry, VppL2FibEntry, find_bridge_domain_port, VppL2Vtr
+from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint
 from vpp_ip import VppIpAddress, VppIpPrefix
 from vpp_papi import VppEnum, MACAddress
-from vpp_papi_provider import L2_VTR_OP
 from vpp_vxlan_gbp_tunnel import find_vxlan_gbp_tunnel, INDEX_INVALID, \
     VppVxlanGbpTunnel
+from vpp_neighbor import VppNeighbor
+
+NUM_PKTS = 67
 
 
 def find_gbp_endpoint(test, sw_if_index=None, ip=None, mac=None):
@@ -67,10 +70,6 @@ class VppGbpEndpoint(VppObject):
     def mac(self):
         return str(self.vmac)
 
-    @property
-    def mac(self):
-        return self.itf.remote_mac
-
     @property
     def ip4(self):
         return self._ip4
@@ -134,9 +133,6 @@ class VppGbpEndpoint(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_endpoint_del(self.handle)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-endpoint:[%d==%d:%s:%d]" % (self.handle,
                                                 self.itf.sw_if_index,
@@ -175,9 +171,6 @@ class VppGbpRecirc(VppObject):
             self.epg.sclass,
             self.is_ext)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-recirc:[%d]" % (self.recirc.sw_if_index)
 
@@ -215,9 +208,6 @@ class VppGbpExtItf(VppObject):
             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)
 
@@ -233,14 +223,15 @@ class VppGbpSubnet(VppObject):
     """
     GBP Subnet
     """
+
     def __init__(self, test, rd, address, address_len,
-                 type, sw_if_index=None, epg=None):
+                 type, sw_if_index=None, sclass=None):
         self._test = test
         self.rd_id = rd.rd_id
         self.prefix = VppIpPrefix(address, address_len)
         self.type = type
         self.sw_if_index = sw_if_index
-        self.epg = epg
+        self.sclass = sclass
 
     def add_vpp_config(self):
         self._test.vapi.gbp_subnet_add_del(
@@ -249,7 +240,7 @@ class VppGbpSubnet(VppObject):
             self.prefix.encode(),
             self.type,
             sw_if_index=self.sw_if_index if self.sw_if_index else 0xffffffff,
-            sclass=self.epg.sclass if self.epg else 0xffff)
+            sclass=self.sclass if self.sclass else 0xffff)
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
@@ -259,9 +250,6 @@ class VppGbpSubnet(VppObject):
             self.prefix.encode(),
             self.type)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-subnet:[%d-%s]" % (self.rd_id, self.prefix)
 
@@ -269,8 +257,8 @@ class VppGbpSubnet(VppObject):
         ss = self._test.vapi.gbp_subnet_dump()
         for s in ss:
             if s.subnet.rd_id == self.rd_id and \
-               s.subnet.type == self.type and \
-               s.subnet.prefix == self.prefix:
+                    s.subnet.type == self.type and \
+                    s.subnet.prefix == self.prefix:
                 return True
         return False
 
@@ -317,9 +305,6 @@ class VppGbpEndpointGroup(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_endpoint_group_del(self.sclass)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-endpoint-group:[%d]" % (self.vnid)
 
@@ -337,7 +322,7 @@ class VppGbpBridgeDomain(VppObject):
     """
 
     def __init__(self, test, bd, bvi, uu_fwd=None,
-                 bm_flood=None, learn=True):
+                 bm_flood=None, learn=True, uu_drop=False, bm_drop=False):
         self._test = test
         self.bvi = bvi
         self.uu_fwd = uu_fwd
@@ -349,6 +334,10 @@ class VppGbpBridgeDomain(VppObject):
             self.learn = e.GBP_BD_API_FLAG_NONE
         else:
             self.learn = e.GBP_BD_API_FLAG_DO_NOT_LEARN
+        if (uu_drop):
+            self.learn |= e.GBP_BD_API_FLAG_UU_FWD_DROP
+        if (bm_drop):
+            self.learn |= e.GBP_BD_API_FLAG_MCAST_DROP
 
     def add_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_add(
@@ -362,9 +351,6 @@ class VppGbpBridgeDomain(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_del(self.bd.bd_id)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-bridge-domain:[%d]" % (self.bd.bd_id)
 
@@ -401,9 +387,6 @@ class VppGbpRouteDomain(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_route_domain_del(self.rd_id)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-route-domain:[%d]" % (self.rd_id)
 
@@ -453,52 +436,61 @@ class VppGbpContract(VppObject):
     GBP Contract
     """
 
-    def __init__(self, test, src_epg, dst_epg, acl_index,
+    def __init__(self, test, sclass, dclass, acl_index,
                  rules, allowed_ethertypes):
         self._test = test
         self.acl_index = acl_index
-        self.src_epg = src_epg
-        self.dst_epg = dst_epg
+        self.sclass = sclass
+        self.dclass = dclass
         self.rules = rules
         self.allowed_ethertypes = allowed_ethertypes
+        while (len(self.allowed_ethertypes) < 16):
+            self.allowed_ethertypes.append(0)
 
     def add_vpp_config(self):
         rules = []
         for r in self.rules:
             rules.append(r.encode())
-        self._test.vapi.gbp_contract_add_del(
+        r = self._test.vapi.gbp_contract_add_del(
             1,
-            self.src_epg.sclass,
-            self.dst_epg.sclass,
+            self.sclass,
+            self.dclass,
             self.acl_index,
             rules,
             self.allowed_ethertypes)
+        self.stats_index = r.stats_index
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_contract_add_del(
             0,
-            self.src_epg.sclass,
-            self.dst_epg.sclass,
+            self.sclass,
+            self.dclass,
             self.acl_index,
-            [], [])
-
-    def __str__(self):
-        return self.object_id()
+            [],
+            self.allowed_ethertypes)
 
     def object_id(self):
-        return "gbp-contract:[%d:%s:%d]" % (self.src_epg.sclass,
-                                            self.dst_epg.sclass,
+        return "gbp-contract:[%d:%s:%d]" % (self.sclass,
+                                            self.dclass,
                                             self.acl_index)
 
     def query_vpp_config(self):
         cs = self._test.vapi.gbp_contract_dump()
         for c in cs:
-            if c.contract.sclass == self.src_epg.sclass \
-               and c.contract.dclass == self.dst_epg.sclass:
+            if c.contract.sclass == self.sclass \
+                    and c.contract.dclass == self.dclass:
                 return True
         return False
 
+    def get_drop_stats(self):
+        c = self._test.statistics.get_counter("/net/gbp/contract/drop")
+        return c[0][self.stats_index]
+
+    def get_permit_stats(self):
+        c = self._test.statistics.get_counter("/net/gbp/contract/permit")
+        return c[0][self.stats_index]
+
 
 class VppGbpVxlanTunnel(VppInterface):
     """
@@ -525,9 +517,6 @@ class VppGbpVxlanTunnel(VppInterface):
     def remove_vpp_config(self):
         self._test.vapi.gbp_vxlan_tunnel_del(self.vni)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-vxlan:%d" % (self.sw_if_index)
 
@@ -545,8 +534,8 @@ class VppGbpAcl(VppObject):
         self.acl_index = 4294967295
 
     def create_rule(self, is_ipv6=0, permit_deny=0, proto=-1,
-                    s_prefix=0, s_ip='\x00\x00\x00\x00', sport_from=0,
-                    sport_to=65535, d_prefix=0, d_ip='\x00\x00\x00\x00',
+                    s_prefix=0, s_ip=b'\x00\x00\x00\x00', sport_from=0,
+                    sport_to=65535, d_prefix=0, d_ip=b'\x00\x00\x00\x00',
                     dport_from=0, dport_to=65535):
         if proto == -1 or proto == 0:
             sport_to = 0
@@ -569,16 +558,13 @@ class VppGbpAcl(VppObject):
 
         reply = self._test.vapi.acl_add_replace(self.acl_index,
                                                 r=rules,
-                                                tag='GBPTest')
+                                                tag=b'GBPTest')
         self.acl_index = reply.acl_index
         return self.acl_index
 
     def remove_vpp_config(self):
         self._test.vapi.acl_del(self.acl_index)
 
-    def __str__(self):
-        return self.object_id()
-
     def object_id(self):
         return "gbp-acl:[%d]" % (self.acl_index)
 
@@ -593,6 +579,18 @@ class VppGbpAcl(VppObject):
 class TestGBP(VppTestCase):
     """ GBP Test Case """
 
+    @property
+    def config_flags(self):
+        return VppEnum.vl_api_nat_config_flags_t
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestGBP, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestGBP, cls).tearDownClass()
+
     def setUp(self):
         super(TestGBP, self).setUp()
 
@@ -702,6 +700,24 @@ class TestGBP(VppTestCase):
             self.assertEqual(r[IPv6].src, src_ip)
         return rx
 
+    def send_and_expect_no_arp(self, src, tx, dst):
+        self.pg_send(src, tx)
+        dst.get_capture(0, timeout=1)
+        dst.assert_nothing_captured(remark="")
+        timeout = 0.1
+
+    def send_and_expect_arp(self, src, tx, dst):
+        rx = self.send_and_expect(src, tx, dst)
+
+        for r in rx:
+            self.assertEqual(r[Ether].src, tx[0][Ether].src)
+            self.assertEqual(r[Ether].dst, tx[0][Ether].dst)
+            self.assertEqual(r[ARP].psrc, tx[0][ARP].psrc)
+            self.assertEqual(r[ARP].pdst, tx[0][ARP].pdst)
+            self.assertEqual(r[ARP].hwsrc, tx[0][ARP].hwsrc)
+            self.assertEqual(r[ARP].hwdst, tx[0][ARP].hwdst)
+        return rx
+
     def test_gbp(self):
         """ Group Based Policy """
 
@@ -805,12 +821,13 @@ class TestGBP(VppTestCase):
                     self.router_mac.packed)
 
                 # The BVIs are NAT inside interfaces
-                self.vapi.nat44_interface_add_del_feature(epg.bvi.sw_if_index,
-                                                          is_inside=1,
-                                                          is_add=1)
-                self.vapi.nat66_add_del_interface(epg.bvi.sw_if_index,
-                                                  is_inside=1,
-                                                  is_add=1)
+                flags = self.config_flags.NAT_IS_INSIDE
+                self.vapi.nat44_interface_add_del_feature(
+                    sw_if_index=epg.bvi.sw_if_index,
+                    flags=flags, is_add=1)
+                self.vapi.nat66_add_del_interface(
+                    is_add=1, flags=flags,
+                    sw_if_index=epg.bvi.sw_if_index)
 
             if_ip4 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip4, 32)
             if_ip6 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip6, 128)
@@ -824,10 +841,10 @@ class TestGBP(VppTestCase):
             # add the BD ARP termination entry for BVI IP
             epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
-                                                     epg.bvi_ip4)
+                                                     epg.bvi_ip4.address)
             epg.bd_arp_ip6 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
-                                                     epg.bvi_ip6)
+                                                     epg.bvi_ip6.address)
             epg.bd_arp_ip4.add_vpp_config()
             epg.bd_arp_ip6.add_vpp_config()
 
@@ -842,13 +859,10 @@ class TestGBP(VppTestCase):
                                recirc.epg.rd.t6).add_vpp_config()
 
             self.vapi.nat44_interface_add_del_feature(
-                recirc.recirc.sw_if_index,
-                is_inside=0,
-                is_add=1)
+                sw_if_index=recirc.recirc.sw_if_index, is_add=1)
             self.vapi.nat66_add_del_interface(
-                recirc.recirc.sw_if_index,
-                is_inside=0,
-                is_add=1)
+                is_add=1,
+                sw_if_index=recirc.recirc.sw_if_index)
 
             recirc.add_vpp_config()
 
@@ -868,14 +882,19 @@ class TestGBP(VppTestCase):
             for (ip, fip) in zip(ep.ips, ep.fips):
                 # Add static mappings for each EP from the 10/8 to 11/8 network
                 if ip.af == AF_INET:
-                    self.vapi.nat44_add_del_static_mapping(ip.bytes,
-                                                           fip.bytes,
-                                                           vrf_id=0,
-                                                           addr_only=1)
+                    flags = self.config_flags.NAT_IS_ADDR_ONLY
+                    self.vapi.nat44_add_del_static_mapping(
+                        is_add=1,
+                        local_ip_address=ip.bytes,
+                        external_ip_address=fip.bytes,
+                        external_sw_if_index=0xFFFFFFFF,
+                        vrf_id=0,
+                        flags=flags)
                 else:
-                    self.vapi.nat66_add_del_static_mapping(ip.bytes,
-                                                           fip.bytes,
-                                                           vrf_id=0)
+                    self.vapi.nat66_add_del_static_mapping(
+                        local_ip_address=ip.bytes,
+                        external_ip_address=fip.bytes,
+                        vrf_id=0, is_add=1)
 
             # VPP EP create ...
             ep.add_vpp_config()
@@ -898,17 +917,18 @@ class TestGBP(VppTestCase):
 
             # add the BD ARP termination entry for floating IP
             for fip in ep.fips:
-                ba = VppBridgeDomainArpEntry(self, epg_nat.bd.bd, ep.mac, fip)
+                ba = VppBridgeDomainArpEntry(self, epg_nat.bd.bd, ep.mac,
+                                             fip.address)
                 ba.add_vpp_config()
 
                 # floating IPs route via EPG recirc
-                r = VppIpRoute(self, fip.address, fip.length,
-                               [VppRoutePath(fip.address,
-                                             ep.recirc.recirc.sw_if_index,
-                                             is_dvr=1,
-                                             proto=fip.dpo_proto)],
-                               table_id=20,
-                               is_ip6=fip.is_ip6)
+                r = VppIpRoute(
+                    self, fip.address, fip.length,
+                    [VppRoutePath(fip.address,
+                                  ep.recirc.recirc.sw_if_index,
+                                  type=FibPathType.FIB_PATH_TYPE_DVR,
+                                  proto=fip.dpo_proto)],
+                    table_id=20)
                 r.add_vpp_config()
 
             # L2 FIB entries in the NAT EPG BD to bridge the packets from
@@ -994,7 +1014,8 @@ class TestGBP(VppTestCase):
                                  UDP(sport=1234, dport=1234) /
                                  Raw('\xa5' * 100))
 
-        self.send_and_assert_no_replies(self.pg0, pkt_intra_epg_220_ip4 * 65)
+        self.send_and_assert_no_replies(self.pg0,
+                                        pkt_intra_epg_220_ip4 * NUM_PKTS)
 
         pkt_inter_epg_222_ip6 = (Ether(src=self.pg0.remote_mac,
                                        dst=str(self.router_mac)) /
@@ -1002,7 +1023,8 @@ class TestGBP(VppTestCase):
                                       dst="2001:10::99") /
                                  UDP(sport=1234, dport=1234) /
                                  Raw('\xa5' * 100))
-        self.send_and_assert_no_replies(self.pg0, pkt_inter_epg_222_ip6 * 65)
+        self.send_and_assert_no_replies(self.pg0,
+                                        pkt_inter_epg_222_ip6 * NUM_PKTS)
 
         #
         # Add the subnet routes
@@ -1033,13 +1055,13 @@ class TestGBP(VppTestCase):
         s63.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_intra_epg_220_ip4 * 65,
+                                     pkt_intra_epg_220_ip4 * NUM_PKTS,
                                      eps[0].epg.uplink)
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_inter_epg_222_ip4 * 65,
+                                     pkt_inter_epg_222_ip4 * NUM_PKTS,
                                      eps[0].epg.uplink)
         self.send_and_expect_bridged6(eps[0].itf,
-                                      pkt_inter_epg_222_ip6 * 65,
+                                      pkt_inter_epg_222_ip6 * NUM_PKTS,
                                       eps[0].epg.uplink)
 
         self.logger.info(self.vapi.cli("sh ip fib 11.0.0.2"))
@@ -1064,7 +1086,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_intra_epg_220_to_uplink * 65,
+                                     pkt_intra_epg_220_to_uplink * NUM_PKTS,
                                      eps[0].epg.uplink)
         # ... and nowhere else
         self.pg1.get_capture(0, timeout=0.1)
@@ -1078,7 +1100,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(eps[2].itf,
-                                     pkt_intra_epg_221_to_uplink * 65,
+                                     pkt_intra_epg_221_to_uplink * NUM_PKTS,
                                      eps[2].epg.uplink)
 
         #
@@ -1092,7 +1114,7 @@ class TestGBP(VppTestCase):
                                          Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg4,
-                                     pkt_intra_epg_220_from_uplink * 65,
+                                     pkt_intra_epg_220_from_uplink * NUM_PKTS,
                                      self.pg0)
 
         #
@@ -1106,10 +1128,12 @@ class TestGBP(VppTestCase):
                          UDP(sport=1234, dport=1234) /
                          Raw('\xa5' * 100))
 
-        self.send_and_expect_bridged(self.pg0, pkt_intra_epg * 65, self.pg1)
+        self.send_and_expect_bridged(self.pg0,
+                                     pkt_intra_epg * NUM_PKTS,
+                                     self.pg1)
 
         #
-        # in the abscense of policy, endpoints in the different EPG
+        # in the absence of policy, endpoints in the different EPG
         # cannot communicate
         #
         pkt_inter_epg_220_to_221 = (Ether(src=self.pg0.remote_mac,
@@ -1132,9 +1156,9 @@ class TestGBP(VppTestCase):
                                     Raw('\xa5' * 100))
 
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_221 * 65)
+                                        pkt_inter_epg_220_to_221 * NUM_PKTS)
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_222 * 65)
+                                        pkt_inter_epg_220_to_222 * NUM_PKTS)
 
         #
         # A uni-directional contract from EPG 220 -> 221
@@ -1144,43 +1168,48 @@ class TestGBP(VppTestCase):
         rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
         acl_index = acl.add_vpp_config([rule, rule2])
         c1 = VppGbpContract(
-            self, epgs[0], epgs[1], acl_index,
+            self, epgs[0].sclass, epgs[1].sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_inter_epg_220_to_221 * 65,
+                                     pkt_inter_epg_220_to_221 * NUM_PKTS,
                                      eps[2].itf)
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_222 * 65)
+                                        pkt_inter_epg_220_to_222 * NUM_PKTS)
 
         #
         # contract for the return direction
         #
         c2 = VppGbpContract(
-            self, epgs[1], epgs[0], acl_index,
+            self, epgs[1].sclass, epgs[0].sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_inter_epg_220_to_221 * 65,
+                                     pkt_inter_epg_220_to_221 * NUM_PKTS,
                                      eps[2].itf)
         self.send_and_expect_bridged(eps[2].itf,
-                                     pkt_inter_epg_221_to_220 * 65,
+                                     pkt_inter_epg_221_to_220 * NUM_PKTS,
                                      eps[0].itf)
 
+        ds = c2.get_drop_stats()
+        self.assertEqual(ds['packets'], 0)
+        ps = c2.get_permit_stats()
+        self.assertEqual(ps['packets'], NUM_PKTS)
+
         #
         # the contract does not allow non-IP
         #
@@ -1195,26 +1224,26 @@ class TestGBP(VppTestCase):
         # not in the contract.
         #
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_222 * 65)
+                                        pkt_inter_epg_220_to_222 * NUM_PKTS)
 
         #
         # A uni-directional contract from EPG 220 -> 222 'L3 routed'
         #
         c3 = VppGbpContract(
-            self, epgs[0], epgs[2], acl_index,
+            self, epgs[0].sclass, epgs[2].sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c3.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh gbp contract"))
 
         self.send_and_expect_routed(eps[0].itf,
-                                    pkt_inter_epg_220_to_222 * 65,
+                                    pkt_inter_epg_220_to_222 * NUM_PKTS,
                                     eps[3].itf,
                                     str(self.router_mac))
 
@@ -1227,11 +1256,11 @@ class TestGBP(VppTestCase):
         acl.remove_vpp_config()
 
         self.send_and_assert_no_replies(eps[2].itf,
-                                        pkt_inter_epg_221_to_220 * 65)
+                                        pkt_inter_epg_221_to_220 * NUM_PKTS)
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_221 * 65)
+                                        pkt_inter_epg_220_to_221 * NUM_PKTS)
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_intra_epg * 65,
+                                     pkt_intra_epg * NUM_PKTS,
                                      eps[1].itf)
 
         #
@@ -1243,33 +1272,33 @@ class TestGBP(VppTestCase):
             self, rd0, "0.0.0.0", 0,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=recirc_nat.recirc.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         se2 = VppGbpSubnet(
             self, rd0, "11.0.0.0", 8,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=recirc_nat.recirc.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         se16 = VppGbpSubnet(
             self, rd0, "::", 0,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=recirc_nat.recirc.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         # in the NAT RD an external subnet via the NAT EPG's uplink
         se3 = VppGbpSubnet(
             self, rd20, "0.0.0.0", 0,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=epg_nat.uplink.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         se36 = VppGbpSubnet(
             self, rd20, "::", 0,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=epg_nat.uplink.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         se4 = VppGbpSubnet(
             self, rd20, "11.0.0.0", 8,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_STITCHED_EXTERNAL,
             sw_if_index=epg_nat.uplink.sw_if_index,
-            epg=epg_nat)
+            sclass=epg_nat.sclass)
         se1.add_vpp_config()
         se2.add_vpp_config()
         se16.add_vpp_config()
@@ -1284,7 +1313,7 @@ class TestGBP(VppTestCase):
                                        eps[0].fip6))
 
         #
-        # From an EP to an outside addess: IN2OUT
+        # From an EP to an outside address: IN2OUT
         #
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
@@ -1295,7 +1324,7 @@ class TestGBP(VppTestCase):
 
         # no policy yet
         self.send_and_assert_no_replies(eps[0].itf,
-                                        pkt_inter_epg_220_to_global * 65)
+                                        pkt_inter_epg_220_to_global * NUM_PKTS)
 
         acl2 = VppGbpAcl(self)
         rule = acl2.create_rule(permit_deny=1, proto=17, sport_from=1234,
@@ -1306,18 +1335,18 @@ class TestGBP(VppTestCase):
 
         acl_index2 = acl2.add_vpp_config([rule, rule2])
         c4 = VppGbpContract(
-            self, epgs[0], epgs[3], acl_index2,
+            self, epgs[0].sclass, epgs[3].sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c4.add_vpp_config()
 
         self.send_and_expect_natted(eps[0].itf,
-                                    pkt_inter_epg_220_to_global * 65,
+                                    pkt_inter_epg_220_to_global * NUM_PKTS,
                                     self.pg7,
                                     eps[0].fip4.address)
 
@@ -1329,7 +1358,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_natted6(self.pg0,
-                                     pkt_inter_epg_220_to_global * 65,
+                                     pkt_inter_epg_220_to_global * NUM_PKTS,
                                      self.pg7,
                                      eps[0].fip6.address)
 
@@ -1343,22 +1372,22 @@ class TestGBP(VppTestCase):
                                          UDP(sport=1234, dport=1234) /
                                          Raw('\xa5' * 100))
 
-        self.send_and_assert_no_replies(self.pg7,
-                                        pkt_inter_epg_220_from_global * 65)
+        self.send_and_assert_no_replies(
+            self.pg7, pkt_inter_epg_220_from_global * NUM_PKTS)
 
         c5 = VppGbpContract(
-            self, epgs[3], epgs[0], acl_index2,
+            self, epgs[3].sclass, epgs[0].sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c5.add_vpp_config()
 
         self.send_and_expect_unnatted(self.pg7,
-                                      pkt_inter_epg_220_from_global * 65,
+                                      pkt_inter_epg_220_from_global * NUM_PKTS,
                                       eps[0].itf,
                                       eps[0].ip4.address)
 
@@ -1369,10 +1398,11 @@ class TestGBP(VppTestCase):
                                          UDP(sport=1234, dport=1234) /
                                          Raw('\xa5' * 100))
 
-        self.send_and_expect_unnatted6(self.pg7,
-                                       pkt_inter_epg_220_from_global * 65,
-                                       eps[0].itf,
-                                       eps[0].ip6.address)
+        self.send_and_expect_unnatted6(
+            self.pg7,
+            pkt_inter_epg_220_from_global * NUM_PKTS,
+            eps[0].itf,
+            eps[0].ip6.address)
 
         #
         # From a local VM to another local VM using resp. public addresses:
@@ -1386,7 +1416,7 @@ class TestGBP(VppTestCase):
                                     Raw('\xa5' * 100))
 
         self.send_and_expect_double_natted(eps[0].itf,
-                                           pkt_intra_epg_220_global * 65,
+                                           pkt_intra_epg_220_global * NUM_PKTS,
                                            eps[1].itf,
                                            eps[0].fip4.address,
                                            eps[1].ip4.address)
@@ -1398,46 +1428,50 @@ class TestGBP(VppTestCase):
                                     UDP(sport=1234, dport=1234) /
                                     Raw('\xa5' * 100))
 
-        self.send_and_expect_double_natted6(eps[0].itf,
-                                            pkt_intra_epg_220_global * 65,
-                                            eps[1].itf,
-                                            eps[0].fip6.address,
-                                            eps[1].ip6.address)
+        self.send_and_expect_double_natted6(
+            eps[0].itf,
+            pkt_intra_epg_220_global * NUM_PKTS,
+            eps[1].itf,
+            eps[0].fip6.address,
+            eps[1].ip6.address)
 
         #
         # cleanup
         #
         for ep in eps:
             # del static mappings for each EP from the 10/8 to 11/8 network
-            self.vapi.nat44_add_del_static_mapping(ep.ip4.bytes,
-                                                   ep.fip4.bytes,
-                                                   vrf_id=0,
-                                                   addr_only=1,
-                                                   is_add=0)
-            self.vapi.nat66_add_del_static_mapping(ep.ip6.bytes,
-                                                   ep.fip6.bytes,
-                                                   vrf_id=0,
-                                                   is_add=0)
+            flags = self.config_flags.NAT_IS_ADDR_ONLY
+            self.vapi.nat44_add_del_static_mapping(
+                is_add=0,
+                local_ip_address=ep.ip4.bytes,
+                external_ip_address=ep.fip4.bytes,
+                external_sw_if_index=0xFFFFFFFF,
+                vrf_id=0,
+                flags=flags)
+            self.vapi.nat66_add_del_static_mapping(
+                local_ip_address=ep.ip6.bytes,
+                external_ip_address=ep.fip6.bytes,
+                vrf_id=0, is_add=0)
 
         for epg in epgs:
             # IP config on the BVI interfaces
             if epg != epgs[0] and epg != epgs[3]:
-                self.vapi.nat44_interface_add_del_feature(epg.bvi.sw_if_index,
-                                                          is_inside=1,
-                                                          is_add=0)
-                self.vapi.nat66_add_del_interface(epg.bvi.sw_if_index,
-                                                  is_inside=1,
-                                                  is_add=0)
+                flags = self.config_flags.NAT_IS_INSIDE
+                self.vapi.nat44_interface_add_del_feature(
+                    sw_if_index=epg.bvi.sw_if_index,
+                    flags=flags,
+                    is_add=0)
+                self.vapi.nat66_add_del_interface(
+                    is_add=0, flags=flags,
+                    sw_if_index=epg.bvi.sw_if_index)
 
         for recirc in recircs:
             self.vapi.nat44_interface_add_del_feature(
-                recirc.recirc.sw_if_index,
-                is_inside=0,
+                sw_if_index=recirc.recirc.sw_if_index,
                 is_add=0)
             self.vapi.nat66_add_del_interface(
-                recirc.recirc.sw_if_index,
-                is_inside=0,
-                is_add=0)
+                is_add=0,
+                sw_if_index=recirc.recirc.sw_if_index)
 
     def wait_for_ep_timeout(self, sw_if_index=None, ip=None, mac=None,
                             n_tries=100, s_time=1):
@@ -1452,6 +1486,8 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_l2(self):
         """ GBP L2 Endpoint Learning """
 
+        self.vapi.cli("clear errors")
+
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
                    'ip': '10.0.0.1',
@@ -1526,7 +1562,7 @@ class TestGBP(VppTestCase):
 
         #
         # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # leanring enabled
+        # learning enabled
         #
         vx_tun_l2_1 = VppGbpVxlanTunnel(
             self, 99, bd1.bd_id,
@@ -1546,7 +1582,7 @@ class TestGBP(VppTestCase):
 
         self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
 
-        # a packet with an sclass from an unknwon EPG
+        # a packet with an sclass from an unknown EPG
         p = (Ether(src=self.pg2.remote_mac,
                    dst=self.pg2.local_mac) /
              IP(src=self.pg2.remote_hosts[0].ip4,
@@ -1560,6 +1596,10 @@ class TestGBP(VppTestCase):
 
         self.send_and_assert_no_replies(self.pg2, p)
 
+        self.logger.info(self.vapi.cli("sh error"))
+        # self.assert_packet_counter_equal(
+        #    '/err/gbp-policy-port/drop-no-contract', 1)
+
         #
         # we should not have learnt a new tunnel endpoint, since
         # the EPG was not learnt.
@@ -1570,14 +1610,14 @@ class TestGBP(VppTestCase):
                                                self.pg2.remote_hosts[0].ip4,
                                                99))
 
-        # epg is not learnt, becasue the EPG is unknwon
+        # epg is not learnt, because the EPG is unknown
         self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
 
         #
         # Learn new EPs from IP packets
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -1611,6 +1651,9 @@ class TestGBP(VppTestCase):
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
+        # self.assert_packet_counter_equal(
+        #    '/err/gbp-policy-port/allow-intra-sclass', 2)
+
         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 ip mfib"))
@@ -1627,7 +1670,7 @@ class TestGBP(VppTestCase):
         # Learn new EPs from GARP packets received on the BD's mcast tunnel
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -1672,7 +1715,7 @@ class TestGBP(VppTestCase):
         # Learn new EPs from L2 packets
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -1716,7 +1759,7 @@ class TestGBP(VppTestCase):
         # repeat. the do not learn bit is set so the EPs are not learnt
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1728,7 +1771,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, p*65, self.pg0)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
         for l in learnt:
             self.assertFalse(find_gbp_endpoint(self,
@@ -1739,7 +1782,7 @@ class TestGBP(VppTestCase):
         # repeat
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1751,7 +1794,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, p*65, self.pg0)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1791,7 +1834,7 @@ class TestGBP(VppTestCase):
         # so the packet is cleared for delivery
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1803,7 +1846,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, p*65, self.pg0)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1824,7 +1867,7 @@ class TestGBP(VppTestCase):
         # refresh the entries after the check for no replies above
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1836,7 +1879,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, p*65, self.pg0)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1850,13 +1893,13 @@ class TestGBP(VppTestCase):
         rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
         acl_index = acl.add_vpp_config([rule, rule2])
         c1 = VppGbpContract(
-            self, epg_220, epg_330, acl_index,
+            self, epg_220.sclass, epg_330.sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
@@ -1903,7 +1946,7 @@ class TestGBP(VppTestCase):
         # Check v6 Endpoints
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1915,7 +1958,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, p*65, self.pg0)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1932,7 +1975,6 @@ class TestGBP(VppTestCase):
         for l in learnt:
             self.wait_for_ep_timeout(vx_tun_l2_1.sw_if_index,
                                      mac=l['mac'])
-
         self.pg2.unconfig_ip4()
         self.pg3.unconfig_ip4()
         self.pg4.unconfig_ip4()
@@ -1940,139 +1982,430 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh int"))
         self.logger.info(self.vapi.cli("sh gbp vxlan"))
 
-    def test_gbp_learn_vlan_l2(self):
-        """ GBP L2 Endpoint w/ VLANs"""
+    def test_gbp_contract(self):
+        """ GBP CONTRACTS """
 
-        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'},
-                  {'mac': '00:00:11:11:11:02',
-                   'ip': '10.0.0.2',
-                   'ip6': '2001:10::3'}]
+        #
+        # Bridge Domains
+        #
+        bd1 = VppBridgeDomain(self, 1, arp_term=0)
+        bd2 = VppBridgeDomain(self, 2, arp_term=0)
+
+        bd1.add_vpp_config()
+        bd2.add_vpp_config()
+
+        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
+        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
+
+        gbd1.add_vpp_config()
+        gbd2.add_vpp_config()
 
         #
-        # IP tables
+        # Route Domains
         #
-        gt4 = VppIpTable(self, 1)
+        gt4 = VppIpTable(self, 0)
         gt4.add_vpp_config()
-        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6 = VppIpTable(self, 0, is_ip6=True)
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
-        rd1.add_vpp_config()
+        rd0 = VppGbpRouteDomain(self, 0, gt4, gt6, None, None)
+
+        rd0.add_vpp_config()
 
         #
-        # Pg2 hosts the vxlan tunnel, hosts on pg2 to act as TEPs
+        # 3 EPGs, 2 of which share a BD.
         #
-        self.pg2.config_ip4()
-        self.pg2.resolve_arp()
-        self.pg2.generate_remote_hosts(4)
-        self.pg2.configure_ipv4_neighbors()
-        self.pg3.config_ip4()
-        self.pg3.resolve_arp()
-
+        epgs = [VppGbpEndpointGroup(self, 220, 1220, rd0, gbd1,
+                                    None, self.loop0,
+                                    "10.0.0.128", "2001:10::128"),
+                VppGbpEndpointGroup(self, 221, 1221, rd0, gbd1,
+                                    None, self.loop0,
+                                    "10.0.1.128", "2001:10:1::128"),
+                VppGbpEndpointGroup(self, 222, 1222, rd0, gbd2,
+                                    None, self.loop1,
+                                    "10.0.2.128", "2001:10:2::128")]
         #
-        # The EP will be on a vlan sub-interface
+        # 4 end-points, 2 in the same subnet, 3 in the same BD
         #
-        vlan_11 = VppDot1QSubint(self, self.pg0, 11)
-        vlan_11.admin_up()
-        self.vapi.l2_interface_vlan_tag_rewrite(vlan_11.sw_if_index,
-                                                L2_VTR_OP.L2_POP_1,
-                                                11)
-
-        bd_uu_fwd = VppVxlanGbpTunnel(self, self.pg3.local_ip4,
-                                      self.pg3.remote_ip4, 116)
-        bd_uu_fwd.add_vpp_config()
+        eps = [VppGbpEndpoint(self, self.pg0,
+                              epgs[0], None,
+                              "10.0.0.1", "11.0.0.1",
+                              "2001:10::1", "3001::1"),
+               VppGbpEndpoint(self, self.pg1,
+                              epgs[0], None,
+                              "10.0.0.2", "11.0.0.2",
+                              "2001:10::2", "3001::2"),
+               VppGbpEndpoint(self, self.pg2,
+                              epgs[1], None,
+                              "10.0.1.1", "11.0.0.3",
+                              "2001:10:1::1", "3001::3"),
+               VppGbpEndpoint(self, self.pg3,
+                              epgs[2], None,
+                              "10.0.2.1", "11.0.0.4",
+                              "2001:10:2::1", "3001::4")]
 
         #
-        # a GBP bridge domain with a BVI and a UU-flood interface
-        # The BD is marked as do not learn, so no endpoints are ever
-        # learnt in this BD.
+        # Config related to each of the EPGs
         #
-        bd1 = VppBridgeDomain(self, 1)
-        bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, bd_uu_fwd,
-                                  learn=False)
-        gbd1.add_vpp_config()
+        for epg in epgs:
+            # IP config on the BVI interfaces
+            if epg != epgs[1]:
+                VppIpInterfaceBind(self, epg.bvi, epg.rd.t4).add_vpp_config()
+                VppIpInterfaceBind(self, epg.bvi, epg.rd.t6).add_vpp_config()
+                self.vapi.sw_interface_set_mac_address(
+                    epg.bvi.sw_if_index,
+                    self.router_mac.packed)
 
-        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
-        self.logger.info(self.vapi.cli("sh gbp bridge"))
+            if_ip4 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip4, 32)
+            if_ip6 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip6, 128)
+            if_ip4.add_vpp_config()
+            if_ip6.add_vpp_config()
 
-        # ... and has a /32 applied
-        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
-        ip_addr.add_vpp_config()
+            # add the BD ARP termination entry for BVI IP
+            epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
+                                                     str(self.router_mac),
+                                                     epg.bvi_ip4.address)
+            epg.bd_arp_ip4.add_vpp_config()
+
+            # EPG in VPP
+            epg.add_vpp_config()
 
         #
-        # The Endpoint-group in which we are learning endpoints
+        # config ep
         #
-        epg_220 = VppGbpEndpointGroup(self, 220, 441, rd1, gbd1,
-                                      None, self.loop0,
-                                      "10.0.0.128",
-                                      "2001:10::128",
-                                      VppGbpEndpointRetention(2))
-        epg_220.add_vpp_config()
+        for ep in eps:
+            ep.add_vpp_config()
+
+        self.logger.info(self.vapi.cli("show gbp endpoint"))
+        self.logger.info(self.vapi.cli("show interface"))
+        self.logger.info(self.vapi.cli("show br"))
 
         #
-        # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # leanring enabled
+        # Intra epg allowed without contract
         #
-        vx_tun_l2_1 = VppGbpVxlanTunnel(
-            self, 99, bd1.bd_id,
-            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L2,
-            self.pg2.local_ip4)
-        vx_tun_l2_1.add_vpp_config()
+        pkt_intra_epg_220_to_220 = (Ether(src=self.pg0.remote_mac,
+                                          dst=self.pg1.remote_mac) /
+                                    IP(src=eps[0].ip4.address,
+                                       dst=eps[1].ip4.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
+
+        self.send_and_expect_bridged(self.pg0,
+                                     pkt_intra_epg_220_to_220 * 65,
+                                     self.pg1)
 
         #
-        # A static endpoint that the learnt endpoints are trying to
-        # talk to
+        # Inter epg denied without contract
         #
-        ep = VppGbpEndpoint(self, vlan_11,
-                            epg_220, None,
-                            "10.0.0.127", "11.0.0.127",
-                            "2001:10::1", "3001::1")
-        ep.add_vpp_config()
+        pkt_inter_epg_220_to_221 = (Ether(src=self.pg0.remote_mac,
+                                          dst=self.pg2.remote_mac) /
+                                    IP(src=eps[0].ip4.address,
+                                       dst=eps[2].ip4.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
 
-        self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
+        self.send_and_assert_no_replies(self.pg0, pkt_inter_epg_220_to_221)
 
         #
-        # Send to the static EP
+        # A uni-directional contract from EPG 220 -> 221
         #
-        for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
-            # arriving on an unknown TEP
-            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=99, gpid=441, flags=0x88) /
-                 Ether(src=l['mac'], dst=ep.mac) /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        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, epgs[0].sclass, epgs[1].sclass, 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,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
 
-            rxs = self.send_and_expect(self.pg2, [p], self.pg0)
+        self.send_and_expect_bridged(eps[0].itf,
+                                     pkt_inter_epg_220_to_221 * 65,
+                                     eps[2].itf)
 
-            #
-            # packet to EP has the EP's vlan tag
-            #
-            for rx in rxs:
-                self.assertEqual(rx[Dot1Q].vlan, 11)
+        pkt_inter_epg_220_to_222 = (Ether(src=self.pg0.remote_mac,
+                                          dst=str(self.router_mac)) /
+                                    IP(src=eps[0].ip4.address,
+                                       dst=eps[3].ip4.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
+        self.send_and_assert_no_replies(eps[0].itf,
+                                        pkt_inter_epg_220_to_222 * 65)
 
-            #
-            # the EP is not learnt since the BD setting prevents it
-            # also no TEP too
-            #
-            self.assertFalse(find_gbp_endpoint(self,
-                                               vx_tun_l2_1.sw_if_index,
-                                               mac=l['mac']))
-            self.assertEqual(INDEX_INVALID,
-                             find_vxlan_gbp_tunnel(
-                                 self,
-                                 self.pg2.local_ip4,
-                                 self.pg2.remote_hosts[1].ip4,
-                                 99))
+        #
+        # contract for the return direction
+        #
+        c2 = VppGbpContract(
+            self, epgs[1].sclass, epgs[0].sclass, 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,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+
+        self.send_and_expect_bridged(eps[0].itf,
+                                     pkt_inter_epg_220_to_221 * 65,
+                                     eps[2].itf)
+        pkt_inter_epg_221_to_220 = (Ether(src=self.pg2.remote_mac,
+                                          dst=self.pg0.remote_mac) /
+                                    IP(src=eps[2].ip4.address,
+                                       dst=eps[0].ip4.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
+        self.send_and_expect_bridged(eps[2].itf,
+                                     pkt_inter_epg_221_to_220 * 65,
+                                     eps[0].itf)
+
+        #
+        # contract between 220 and 222 uni-direction
+        #
+        c3 = VppGbpContract(
+            self, epgs[0].sclass, epgs[2].sclass, 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,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c3.add_vpp_config()
+
+        self.send_and_expect(eps[0].itf,
+                             pkt_inter_epg_220_to_222 * 65,
+                             eps[3].itf)
+
+        c3.remove_vpp_config()
+        c1.remove_vpp_config()
+        c2.remove_vpp_config()
+        acl.remove_vpp_config()
+
+    def test_gbp_bd_flags(self):
+        """ GBP BD FLAGS """
+
+        #
+        # IP tables
+        #
+        gt4 = VppIpTable(self, 1)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1.add_vpp_config()
+
+        #
+        # Pg3 hosts the IP4 UU-flood VXLAN tunnel
+        # Pg4 hosts the IP6 UU-flood VXLAN tunnel
+        #
+        self.pg3.config_ip4()
+        self.pg3.resolve_arp()
+        self.pg4.config_ip4()
+        self.pg4.resolve_arp()
+
+        #
+        # Add a mcast destination VXLAN-GBP tunnel for B&M traffic
+        #
+        tun_bm = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
+                                   "239.1.1.1", 88,
+                                   mcast_itf=self.pg4)
+        tun_bm.add_vpp_config()
+
+        #
+        # a GBP bridge domain with a BVI and a UU-flood interface
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+
+        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3, tun_bm,
+                                  uu_drop=True, bm_drop=True)
+        gbd1.add_vpp_config()
+
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
+
+        # ... and has a /32 applied
+        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip_addr.add_vpp_config()
+
+        #
+        # The Endpoint-group
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 112, rd1, gbd1,
+                                      None, self.loop0,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_220.add_vpp_config()
+
+        ep = VppGbpEndpoint(self, self.pg0,
+                            epg_220, None,
+                            "10.0.0.127", "11.0.0.127",
+                            "2001:10::1", "3001::1")
+        ep.add_vpp_config()
+        #
+        # send UU/BM packet from the local EP with UU drop and BM drop enabled
+        # in bd
+        #
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
+        p_uu = (Ether(src=ep.mac, dst="00:11:11:11:11:11") /
+                IP(dst="10.0.0.133", src=ep.ip4.address) /
+                UDP(sport=1234, dport=1234) /
+                Raw('\xa5' * 100))
+        self.send_and_assert_no_replies(ep.itf, [p_uu])
+
+        p_bm = (Ether(src=ep.mac, dst="ff:ff:ff:ff:ff:ff") /
+                IP(dst="10.0.0.133", src=ep.ip4.address) /
+                UDP(sport=1234, dport=1234) /
+                Raw('\xa5' * 100))
+        self.send_and_assert_no_replies(ep.itf, [p_bm])
+
+        self.pg3.unconfig_ip4()
+        self.pg4.unconfig_ip4()
+
+        self.logger.info(self.vapi.cli("sh int"))
+
+    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'},
+                  {'mac': '00:00:11:11:11:02',
+                   'ip': '10.0.0.2',
+                   'ip6': '2001:10::3'}]
+
+        #
+        # IP tables
+        #
+        gt4 = VppIpTable(self, 1)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1.add_vpp_config()
+
+        #
+        # Pg2 hosts the vxlan tunnel, hosts on pg2 to act as TEPs
+        #
+        self.pg2.config_ip4()
+        self.pg2.resolve_arp()
+        self.pg2.generate_remote_hosts(4)
+        self.pg2.configure_ipv4_neighbors()
+        self.pg3.config_ip4()
+        self.pg3.resolve_arp()
+
+        #
+        # The EP will be on a vlan sub-interface
+        #
+        vlan_11 = VppDot1QSubint(self, self.pg0, 11)
+        vlan_11.admin_up()
+        self.vapi.l2_interface_vlan_tag_rewrite(
+            sw_if_index=vlan_11.sw_if_index, vtr_op=L2_VTR_OP.L2_POP_1,
+            push_dot1q=11)
+
+        bd_uu_fwd = VppVxlanGbpTunnel(self, self.pg3.local_ip4,
+                                      self.pg3.remote_ip4, 116)
+        bd_uu_fwd.add_vpp_config()
+
+        #
+        # a GBP bridge domain with a BVI and a UU-flood interface
+        # The BD is marked as do not learn, so no endpoints are ever
+        # learnt in this BD.
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, bd_uu_fwd,
+                                  learn=False)
+        gbd1.add_vpp_config()
+
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
+
+        # ... and has a /32 applied
+        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip_addr.add_vpp_config()
+
+        #
+        # The Endpoint-group in which we are learning endpoints
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 441, rd1, gbd1,
+                                      None, self.loop0,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_220.add_vpp_config()
+
+        #
+        # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
+        # learning enabled
+        #
+        vx_tun_l2_1 = VppGbpVxlanTunnel(
+            self, 99, bd1.bd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L2,
+            self.pg2.local_ip4)
+        vx_tun_l2_1.add_vpp_config()
+
+        #
+        # A static endpoint that the learnt endpoints are trying to
+        # talk to
+        #
+        ep = VppGbpEndpoint(self, vlan_11,
+                            epg_220, None,
+                            "10.0.0.127", "11.0.0.127",
+                            "2001:10::1", "3001::1")
+        ep.add_vpp_config()
+
+        self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
+
+        #
+        # Send to the static EP
+        #
+        for ii, l in enumerate(learnt):
+            # a packet with an sclass from a known EPG
+            # arriving on an unknown TEP
+            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=99, gpid=441, flags=0x88) /
+                 Ether(src=l['mac'], dst=ep.mac) /
+                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rxs = self.send_and_expect(self.pg2, [p], self.pg0)
+
+            #
+            # packet to EP has the EP's vlan tag
+            #
+            for rx in rxs:
+                self.assertEqual(rx[Dot1Q].vlan, 11)
+
+            #
+            # the EP is not learnt since the BD setting prevents it
+            # also no TEP too
+            #
+            self.assertFalse(find_gbp_endpoint(self,
+                                               vx_tun_l2_1.sw_if_index,
+                                               mac=l['mac']))
+            self.assertEqual(INDEX_INVALID,
+                             find_vxlan_gbp_tunnel(
+                                 self,
+                                 self.pg2.local_ip4,
+                                 self.pg2.remote_hosts[1].ip4,
+                                 99))
 
         self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
 
@@ -2107,7 +2440,7 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_l3(self):
         """ GBP L3 Endpoint Learning """
 
-        self.vapi.cli("set logging class gbp debug")
+        self.vapi.cli("set logging class gbp level debug")
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
@@ -2191,7 +2524,7 @@ class TestGBP(VppTestCase):
 
         #
         # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # leanring enabled
+        # learning enabled
         #
         vx_tun_l3 = VppGbpVxlanTunnel(
             self, 101, rd1.rd_id,
@@ -2213,7 +2546,7 @@ class TestGBP(VppTestCase):
         # learn some remote IPv4 EPs
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -2250,7 +2583,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rxs = self.send_and_expect(self.pg0, p*1, self.pg2)
+            rxs = self.send_and_expect(self.pg0, p * 1, self.pg2)
 
             for rx in rxs:
                 self.assertEqual(rx[IP].src, self.pg2.local_ip4)
@@ -2280,7 +2613,7 @@ class TestGBP(VppTestCase):
         # learn some remote IPv6 EPs
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -2323,7 +2656,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rxs = self.send_and_expect(self.pg0, p*65, self.pg2)
+            rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
 
             for rx in rxs:
                 self.assertEqual(rx[IP].src, self.pg2.local_ip4)
@@ -2390,7 +2723,7 @@ class TestGBP(VppTestCase):
         # learn some remote IPv4 EPs
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -2445,7 +2778,7 @@ class TestGBP(VppTestCase):
         rep_2.add_vpp_config()
 
         #
-        # Add a route to the leanred EP's v4 subnet
+        # Add a route to the learned EP's v4 subnet
         #  packets should be send on the v4/v6 uu=fwd interface resp.
         #
         se_10_1_24 = VppGbpSubnet(
@@ -2462,7 +2795,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rxs = self.send_and_expect(self.pg0, p*65, self.pg2)
+            rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
 
             for rx in rxs:
                 self.assertEqual(rx[IP].src, self.pg2.local_ip4)
@@ -2507,7 +2840,7 @@ class TestGBP(VppTestCase):
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
 
         #
-        # to appease the testcase we cannot have the registered EP stll
+        # to appease the testcase we cannot have the registered EP still
         # present (because it's DP learnt) when the TC ends so wait until
         # it is removed
         #
@@ -2546,7 +2879,7 @@ class TestGBP(VppTestCase):
     def test_gbp_redirect(self):
         """ GBP Endpoint Redirect """
 
-        self.vapi.cli("set logging class gbp debug")
+        self.vapi.cli("set logging class gbp level debug")
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
@@ -2745,7 +3078,7 @@ class TestGBP(VppTestCase):
         # test the src-ip hash mode
         #
         c1 = VppGbpContract(
-            self, epg_220, epg_222, acl_index,
+            self, epg_220.sclass, epg_222.sclass, 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,
@@ -2753,18 +3086,18 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
-            self, epg_222, epg_220, acl_index,
+            self, epg_222.sclass, epg_220.sclass, 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,
@@ -2772,13 +3105,13 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
 
@@ -2877,7 +3210,7 @@ class TestGBP(VppTestCase):
         # test the symmetric hash mode
         #
         c1 = VppGbpContract(
-            self, epg_220, epg_222, acl_index,
+            self, epg_220.sclass, epg_222.sclass, 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,
@@ -2885,18 +3218,18 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
-            self, epg_222, epg_220, acl_index,
+            self, epg_222.sclass, epg_220.sclass, 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,
@@ -2904,13 +3237,13 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
 
@@ -2961,7 +3294,7 @@ class TestGBP(VppTestCase):
                Raw('\xa5' * 100))]
 
         c3 = VppGbpContract(
-            self, epg_220, epg_221, acl_index,
+            self, epg_220.sclass, epg_221.sclass, 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,
@@ -2969,13 +3302,13 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c3.add_vpp_config()
 
@@ -2997,13 +3330,13 @@ class TestGBP(VppTestCase):
         vx_tun_l3.add_vpp_config()
 
         c4 = VppGbpContract(
-            self, epg_221, epg_220, acl_index,
+            self, epg_221.sclass, epg_220.sclass, 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,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c4.add_vpp_config()
 
@@ -3075,7 +3408,7 @@ class TestGBP(VppTestCase):
         # test the dst-ip hash mode
         #
         c5 = VppGbpContract(
-            self, epg_220, epg_221, acl_index,
+            self, epg_220.sclass, epg_221.sclass, 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,
@@ -3083,13 +3416,13 @@ class TestGBP(VppTestCase):
                                        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)])],
+                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)])],
             [ETH_P_IP, ETH_P_IPV6])
         c5.add_vpp_config()
 
@@ -3118,7 +3451,7 @@ class TestGBP(VppTestCase):
         """ GBP L3 Out """
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
-        self.vapi.cli("set logging class gbp debug")
+        self.vapi.cli("set logging class gbp level debug")
 
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
         routed_src_mac = "00:22:bd:f8:19:ff"
@@ -3185,7 +3518,7 @@ class TestGBP(VppTestCase):
         l3o_1 = VppGbpSubnet(
             self, rd1, "10.0.0.0", 24,
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
-            epg=epg_220)
+            sclass=113)
         l3o_1.add_vpp_config()
 
         #
@@ -3194,7 +3527,15 @@ class TestGBP(VppTestCase):
         #
         vlan_100 = VppDot1QSubint(self, self.pg0, 100)
         vlan_100.admin_up()
-        ext_itf = VppGbpExtItf(self, vlan_100, bd1, rd1)
+        VppL2Vtr(self, vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        vlan_101 = VppDot1QSubint(self, self.pg0, 101)
+        vlan_101.admin_up()
+        VppL2Vtr(self, vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        # vlan_102 is not poped
+        vlan_102 = VppDot1QSubint(self, self.pg0, 102)
+        vlan_102.admin_up()
+
+        ext_itf = VppGbpExtItf(self, self.loop0, bd1, rd1)
         ext_itf.add_vpp_config()
 
         #
@@ -3207,15 +3548,70 @@ class TestGBP(VppTestCase):
         vx_tun_l3.add_vpp_config()
 
         #
-        # packets destined to unkown addresses in the BVI's subnet
+        # External Endpoints
+        #
+        eep1 = 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)
+        eep1.add_vpp_config()
+        eep2 = VppGbpEndpoint(self, vlan_101,
+                              epg_220, None,
+                              "10.0.0.2", "11.0.0.2",
+                              "2001:10::2", "3001::2",
+                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
+        eep2.add_vpp_config()
+        eep3 = VppGbpEndpoint(self, vlan_102,
+                              epg_220, None,
+                              "10.0.0.3", "11.0.0.3",
+                              "2001:10::3", "3001::3",
+                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
+        eep3.add_vpp_config()
+
+        #
+        # A remote external 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()
+
+        #
+        # ARP packet from External EPs are accepted and replied to
+        #
+        p_arp = (Ether(src=eep1.mac, dst="ff:ff:ff:ff:ff:ff") /
+                 Dot1Q(vlan=100) /
+                 ARP(op="who-has",
+                     psrc=eep1.ip4.address, pdst="10.0.0.128",
+                     hwsrc=eep1.mac, hwdst="ff:ff:ff:ff:ff:ff"))
+        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0)
+
+        #
+        # ARP packet from host in remote subnet are accepted and replied to
+        #
+        p_arp = (Ether(src=vlan_102.remote_mac, dst="ff:ff:ff:ff:ff:ff") /
+                 Dot1Q(vlan=102) /
+                 ARP(op="who-has",
+                     psrc="10.0.0.17", pdst="10.0.0.128",
+                     hwsrc=vlan_102.remote_mac, hwdst="ff:ff:ff:ff:ff:ff"))
+        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0)
+
+        #
+        # packets destined to unknown addresses in the BVI's subnet
         # are ARP'd for
         #
-        p4 = (Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+        p4 = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
               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=str(self.router_mac)) /
+        p6 = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
               Dot1Q(vlan=100) /
               IPv6(src="2001:10::1", dst="2001:10::88") /
               UDP(sport=1234, dport=1234) /
@@ -3240,29 +3636,6 @@ class TestGBP(VppTestCase):
 
             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
         #
@@ -3280,20 +3653,156 @@ class TestGBP(VppTestCase):
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
-        # A subnet reachable through the external EP
+        # local EP pings router
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src=eep1.ip4.address, dst="10.0.0.128") /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
+
+        #
+        # local EP pings other local EP
+        #
+        p = (Ether(src=eep1.mac, dst=eep2.mac) /
+             Dot1Q(vlan=100) /
+             IP(src=eep1.ip4.address, dst=eep2.ip4.address) /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, eep1.mac)
+            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 101)
+
+        #
+        # local EP pings router w/o vlan tag poped
+        #
+        p = (Ether(src=eep3.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=102) /
+             IP(src=eep3.ip4.address, dst="10.0.0.128") /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, vlan_102.remote_mac)
+
+        #
+        # A subnet reachable through the external EP1
         #
         ip_220 = VppIpRoute(self, "10.220.0.0", 24,
-                            [VppRoutePath(eep.ip4.address,
-                                          eep.epg.bvi.sw_if_index)],
+                            [VppRoutePath(eep1.ip4.address,
+                                          eep1.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=epg_220)
+            sclass=4220)
         l3o_220.add_vpp_config()
 
+        #
+        # A subnet reachable through the external EP2
+        #
+        ip_221 = VppIpRoute(self, "10.221.0.0", 24,
+                            [VppRoutePath(eep2.ip4.address,
+                                          eep2.epg.bvi.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_221.add_vpp_config()
+
+        l3o_221 = VppGbpSubnet(
+            self, rd1, "10.221.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4221)
+        l3o_221.add_vpp_config()
+
+        #
+        # ping between hosts in remote subnets
+        #  dropped without a contract
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.221.0.1") /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
+
+        #
+        # contract for the external nets to communicate
+        #
+        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])
+
+        c1 = VppGbpContract(
+            self, 4220, 4221, 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,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
+
+        #
+        # Contracts allowing ext-net 200 to talk with external EPs
+        #
+        c2 = VppGbpContract(
+            self, 4220, 113, 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,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+        c3 = VppGbpContract(
+            self, 113, 4220, 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,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c3.add_vpp_config()
+
+        #
+        # ping between hosts in remote subnets
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.221.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 101)
+
+        # we did not learn these external hosts
+        self.assertFalse(find_gbp_endpoint(self, ip="10.220.0.1"))
+        self.assertFalse(find_gbp_endpoint(self, ip="10.221.0.1"))
+
+        #
+        # from remote external EP to local external EP
+        #
         p = (Ether(src=self.pg7.remote_mac,
                    dst=self.pg7.local_mac) /
              IP(src=self.pg7.remote_ip4,
@@ -3307,10 +3816,168 @@ class TestGBP(VppTestCase):
 
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
+        #
+        # ping from an external host to the remote external EP
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             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, p * 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)
+            # the sclass of the ext-net the packet came from
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            # policy was applied to the original IP packet
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            inner = rx[VXLAN].payload
+            self.assertEqual(inner[IP].src, "10.220.0.1")
+            self.assertEqual(inner[IP].dst, rep.ip4.address)
+
+        #
+        # An external subnet reachable via the remote external EP
+        #
+
+        #
+        # first the VXLAN-GBP tunnel over which it is reached
+        #
+        vx_tun_r = VppVxlanGbpTunnel(
+            self, self.pg7.local_ip4,
+            self.pg7.remote_ip4, 445,
+            mode=(VppEnum.vl_api_vxlan_gbp_api_tunnel_mode_t.
+                  VXLAN_GBP_API_TUNNEL_MODE_L3))
+        vx_tun_r.add_vpp_config()
+        VppIpInterfaceBind(self, vx_tun_r, t4).add_vpp_config()
+
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
+
+        #
+        # then the special adj to resolve through on that tunnel
+        #
+        n1 = VppNeighbor(self,
+                         vx_tun_r.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip4)
+        n1.add_vpp_config()
+
+        #
+        # the route via the adj above
+        #
+        ip_222 = VppIpRoute(self, "10.222.0.0", 24,
+                            [VppRoutePath(self.pg7.remote_ip4,
+                                          vx_tun_r.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_222.add_vpp_config()
+
+        l3o_222 = VppGbpSubnet(
+            self, rd1, "10.222.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4222)
+        l3o_222.add_vpp_config()
+
+        #
+        # ping between hosts in local and remote external subnets
+        #  dropped without a contract
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.222.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
+
+        #
+        # Add contracts ext-nets for 220 -> 222
+        #
+        c4 = VppGbpContract(
+            self, 4220, 4222, 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,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c4.add_vpp_config()
+
+        #
+        # ping from host in local to remote external subnets
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.222.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 3, 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, 445)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # the sclass of the ext-net the packet came from
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            # policy was applied to the original IP packet
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            inner = rx[VXLAN].payload
+            self.assertEqual(inner[Ether].dst, "00:0c:0c:0c:0c:0c")
+            self.assertEqual(inner[IP].src, "10.220.0.1")
+            self.assertEqual(inner[IP].dst, "10.222.0.1")
+
+        #
+        # ping from host in remote to local external subnets
+        # there's no contract for this, but the A bit is set.
+        #
+        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=445, gpid=4222, flags=0x88, gpflags='A') /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IP(src="10.222.0.1", dst="10.220.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
+        self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
+
+        #
+        # ping from host in remote to remote external subnets
+        #   this is dropped by reflection check.
+        #
+        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=445, gpid=4222, flags=0x88, gpflags='A') /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IP(src="10.222.0.1", dst="10.222.0.2") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_assert_no_replies(self.pg7, p * 3)
+
         #
         # cleanup
         #
         self.pg7.unconfig_ip4()
+        vlan_100.set_vtr(L2_VTR_OP.L2_DISABLED)
 
 
 if __name__ == '__main__':