api acl: breakout acl_types.api for reuse by others
[vpp.git] / test / test_gbp.py
index 1eedf54..f6bded6 100644 (file)
@@ -6,30 +6,37 @@ import unittest
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
 from scapy.layers.inet import IP, UDP, ICMP
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
 from scapy.layers.inet import IP, UDP, ICMP
-from scapy.layers.inet6 import IPv6, ICMPv6ND_NS,  ICMPv6NDOptSrcLLAddr, \
-    ICMPv6ND_NA
+from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6NDOptSrcLLAddr, \
+    ICMPv6ND_NA, ICMPv6EchoRequest
 from scapy.utils6 import in6_getnsma, in6_getnsmac
 from scapy.layers.vxlan import VXLAN
 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, \
 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, VppL2Vtr
 from vpp_l2 import VppBridgeDomain, VppBridgeDomainPort, \
     VppBridgeDomainArpEntry, VppL2FibEntry, find_bridge_domain_port, VppL2Vtr
-from vpp_sub_interface import VppDot1QSubint
-from vpp_ip import VppIpAddress, VppIpPrefix
+from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint
+from vpp_ip import VppIpAddress, VppIpPrefix, DpoProto
 from vpp_papi import VppEnum, MACAddress
 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
 from vpp_vxlan_gbp_tunnel import find_vxlan_gbp_tunnel, INDEX_INVALID, \
     VppVxlanGbpTunnel
 from vpp_neighbor import VppNeighbor
+try:
+    text_type = unicode
+except NameError:
+    text_type = str
 
 
+NUM_PKTS = 67
 
 
-def find_gbp_endpoint(test, sw_if_index=None, ip=None, mac=None):
+
+def find_gbp_endpoint(test, sw_if_index=None, ip=None, mac=None,
+                      tep=None, sclass=None):
     if ip:
         vip = VppIpAddress(ip)
     if mac:
     if ip:
         vip = VppIpAddress(ip)
     if mac:
@@ -38,9 +45,17 @@ def find_gbp_endpoint(test, sw_if_index=None, ip=None, mac=None):
     eps = test.vapi.gbp_endpoint_dump()
 
     for ep in eps:
     eps = test.vapi.gbp_endpoint_dump()
 
     for ep in eps:
+        if tep:
+            src = VppIpAddress(tep[0])
+            dst = VppIpAddress(tep[1])
+            if src != ep.endpoint.tun.src or dst != ep.endpoint.tun.dst:
+                continue
         if sw_if_index:
             if ep.endpoint.sw_if_index != sw_if_index:
                 continue
         if sw_if_index:
             if ep.endpoint.sw_if_index != sw_if_index:
                 continue
+        if sclass:
+            if ep.endpoint.sclass != sclass:
+                continue
         if ip:
             for eip in ep.endpoint.ips:
                 if vip == eip:
         if ip:
             for eip in ep.endpoint.ips:
                 if vip == eip:
@@ -48,6 +63,7 @@ def find_gbp_endpoint(test, sw_if_index=None, ip=None, mac=None):
         if mac:
             if vmac.packed == ep.endpoint.mac:
                 return True
         if mac:
             if vmac.packed == ep.endpoint.mac:
                 return True
+
     return False
 
 
     return False
 
 
@@ -131,9 +147,6 @@ class VppGbpEndpoint(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_endpoint_del(self.handle)
 
     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,
     def object_id(self):
         return "gbp-endpoint:[%d==%d:%s:%d]" % (self.handle,
                                                 self.itf.sw_if_index,
@@ -172,9 +185,6 @@ class VppGbpRecirc(VppObject):
             self.epg.sclass,
             self.is_ext)
 
             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)
 
     def object_id(self):
         return "gbp-recirc:[%d]" % (self.recirc.sw_if_index)
 
@@ -191,32 +201,25 @@ class VppGbpExtItf(VppObject):
     GBP ExtItfulation Interface
     """
 
     GBP ExtItfulation Interface
     """
 
-    def __init__(self, test, itf, bd, rd):
+    def __init__(self, test, itf, bd, rd, anon=False):
         self._test = test
         self.itf = itf
         self.bd = bd
         self.rd = rd
         self._test = test
         self.itf = itf
         self.bd = bd
         self.rd = rd
+        self.flags = 1 if anon else 0
 
     def add_vpp_config(self):
         self._test.vapi.gbp_ext_itf_add_del(
 
     def add_vpp_config(self):
         self._test.vapi.gbp_ext_itf_add_del(
-            1,
-            self.itf.sw_if_index,
-            self.bd.bd_id,
-            self.rd.rd_id)
+            1, self.itf.sw_if_index, self.bd.bd_id, self.rd.rd_id, self.flags)
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_ext_itf_add_del(
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_ext_itf_add_del(
-            0,
-            self.itf.sw_if_index,
-            self.bd.bd_id,
-            self.rd.rd_id)
-
-    def __str__(self):
-        return self.object_id()
+            0, self.itf.sw_if_index, self.bd.bd_id, self.rd.rd_id, self.flags)
 
     def object_id(self):
 
     def object_id(self):
-        return "gbp-ext-itf:[%d]" % (self.itf.sw_if_index)
+        return "gbp-ext-itf:[%d]%s" % (self.itf.sw_if_index,
+                                       " [anon]" if self.flags else "")
 
     def query_vpp_config(self):
         rs = self._test.vapi.gbp_ext_itf_dump()
 
     def query_vpp_config(self):
         rs = self._test.vapi.gbp_ext_itf_dump()
@@ -230,6 +233,7 @@ class VppGbpSubnet(VppObject):
     """
     GBP Subnet
     """
     """
     GBP Subnet
     """
+
     def __init__(self, test, rd, address, address_len,
                  type, sw_if_index=None, sclass=None):
         self._test = test
     def __init__(self, test, rd, address, address_len,
                  type, sw_if_index=None, sclass=None):
         self._test = test
@@ -256,9 +260,6 @@ class VppGbpSubnet(VppObject):
             self.prefix.encode(),
             self.type)
 
             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)
 
     def object_id(self):
         return "gbp-subnet:[%d-%s]" % (self.rd_id, self.prefix)
 
@@ -266,8 +267,8 @@ class VppGbpSubnet(VppObject):
         ss = self._test.vapi.gbp_subnet_dump()
         for s in ss:
             if s.subnet.rd_id == self.rd_id and \
         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
 
                 return True
         return False
 
@@ -314,9 +315,6 @@ class VppGbpEndpointGroup(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_endpoint_group_del(self.sclass)
 
     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)
 
     def object_id(self):
         return "gbp-endpoint-group:[%d]" % (self.vnid)
 
@@ -333,24 +331,34 @@ class VppGbpBridgeDomain(VppObject):
     GBP Bridge Domain
     """
 
     GBP Bridge Domain
     """
 
-    def __init__(self, test, bd, bvi, uu_fwd=None,
-                 bm_flood=None, learn=True):
+    def __init__(self, test, bd, rd, bvi, uu_fwd=None,
+                 bm_flood=None, learn=True,
+                 uu_drop=False, bm_drop=False,
+                 ucast_arp=False):
         self._test = test
         self.bvi = bvi
         self.uu_fwd = uu_fwd
         self.bm_flood = bm_flood
         self.bd = bd
         self._test = test
         self.bvi = bvi
         self.uu_fwd = uu_fwd
         self.bm_flood = bm_flood
         self.bd = bd
+        self.rd = rd
 
         e = VppEnum.vl_api_gbp_bridge_domain_flags_t
 
         e = VppEnum.vl_api_gbp_bridge_domain_flags_t
-        if (learn):
-            self.learn = e.GBP_BD_API_FLAG_NONE
-        else:
-            self.learn = e.GBP_BD_API_FLAG_DO_NOT_LEARN
+
+        self.flags = e.GBP_BD_API_FLAG_NONE
+        if not learn:
+            self.flags |= e.GBP_BD_API_FLAG_DO_NOT_LEARN
+        if uu_drop:
+            self.flags |= e.GBP_BD_API_FLAG_UU_FWD_DROP
+        if bm_drop:
+            self.flags |= e.GBP_BD_API_FLAG_MCAST_DROP
+        if ucast_arp:
+            self.flags |= e.GBP_BD_API_FLAG_UCAST_ARP
 
     def add_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_add(
             self.bd.bd_id,
 
     def add_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_add(
             self.bd.bd_id,
-            self.learn,
+            self.rd.rd_id,
+            self.flags,
             self.bvi.sw_if_index,
             self.uu_fwd.sw_if_index if self.uu_fwd else INDEX_INVALID,
             self.bm_flood.sw_if_index if self.bm_flood else INDEX_INVALID)
             self.bvi.sw_if_index,
             self.uu_fwd.sw_if_index if self.uu_fwd else INDEX_INVALID,
             self.bm_flood.sw_if_index if self.bm_flood else INDEX_INVALID)
@@ -359,9 +367,6 @@ class VppGbpBridgeDomain(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_del(self.bd.bd_id)
 
     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)
 
     def object_id(self):
         return "gbp-bridge-domain:[%d]" % (self.bd.bd_id)
 
@@ -378,9 +383,10 @@ class VppGbpRouteDomain(VppObject):
     GBP Route Domain
     """
 
     GBP Route Domain
     """
 
-    def __init__(self, test, rd_id, t4, t6, ip4_uu=None, ip6_uu=None):
+    def __init__(self, test, rd_id, scope, t4, t6, ip4_uu=None, ip6_uu=None):
         self._test = test
         self.rd_id = rd_id
         self._test = test
         self.rd_id = rd_id
+        self.scope = scope
         self.t4 = t4
         self.t6 = t6
         self.ip4_uu = ip4_uu
         self.t4 = t4
         self.t6 = t6
         self.ip4_uu = ip4_uu
@@ -389,6 +395,7 @@ class VppGbpRouteDomain(VppObject):
     def add_vpp_config(self):
         self._test.vapi.gbp_route_domain_add(
             self.rd_id,
     def add_vpp_config(self):
         self._test.vapi.gbp_route_domain_add(
             self.rd_id,
+            self.scope,
             self.t4.table_id,
             self.t6.table_id,
             self.ip4_uu.sw_if_index if self.ip4_uu else INDEX_INVALID,
             self.t4.table_id,
             self.t6.table_id,
             self.ip4_uu.sw_if_index if self.ip4_uu else INDEX_INVALID,
@@ -398,9 +405,6 @@ class VppGbpRouteDomain(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.gbp_route_domain_del(self.rd_id)
 
     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)
 
     def object_id(self):
         return "gbp-route-domain:[%d]" % (self.rd_id)
 
@@ -427,10 +431,10 @@ class VppGbpContractNextHop():
 
 
 class VppGbpContractRule():
 
 
 class VppGbpContractRule():
-    def __init__(self, action, hash_mode, nhs=[]):
+    def __init__(self, action, hash_mode, nhs=None):
         self.action = action
         self.hash_mode = hash_mode
         self.action = action
         self.hash_mode = hash_mode
-        self.nhs = nhs
+        self.nhs = [] if nhs is None else nhs
 
     def encode(self):
         nhs = []
 
     def encode(self):
         nhs = []
@@ -444,15 +448,24 @@ class VppGbpContractRule():
                     'n_nhs': len(self.nhs),
                     'nhs': nhs}}
 
                     'n_nhs': len(self.nhs),
                     'nhs': nhs}}
 
+    def __repr__(self):
+        return '<VppGbpContractRule action=%s, hash_mode=%s>' % (
+            self.action, self.hash_mode)
+
 
 class VppGbpContract(VppObject):
     """
     GBP Contract
     """
 
 
 class VppGbpContract(VppObject):
     """
     GBP Contract
     """
 
-    def __init__(self, test, sclass, dclass, acl_index,
+    def __init__(self, test, scope, sclass, dclass, acl_index,
                  rules, allowed_ethertypes):
         self._test = test
                  rules, allowed_ethertypes):
         self._test = test
+        if not isinstance(rules, list):
+            raise ValueError("'rules' must be a list.")
+        if not isinstance(allowed_ethertypes, list):
+            raise ValueError("'allowed_ethertypes' must be a list.")
+        self.scope = scope
         self.acl_index = acl_index
         self.sclass = sclass
         self.dclass = dclass
         self.acl_index = acl_index
         self.sclass = sclass
         self.dclass = dclass
@@ -465,40 +478,56 @@ class VppGbpContract(VppObject):
         rules = []
         for r in self.rules:
             rules.append(r.encode())
         rules = []
         for r in self.rules:
             rules.append(r.encode())
-        self._test.vapi.gbp_contract_add_del(
-            1,
-            self.sclass,
-            self.dclass,
-            self.acl_index,
-            rules,
-            self.allowed_ethertypes)
+        r = self._test.vapi.gbp_contract_add_del(
+            is_add=1,
+            contract={
+                'acl_index': self.acl_index,
+                'scope': self.scope,
+                'sclass': self.sclass,
+                'dclass': self.dclass,
+                'n_rules': len(rules),
+                'rules': rules,
+                'n_ether_types': len(self.allowed_ethertypes),
+                'allowed_ethertypes': 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(
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_contract_add_del(
-            0,
-            self.sclass,
-            self.dclass,
-            self.acl_index,
-            [],
-            self.allowed_ethertypes)
-
-    def __str__(self):
-        return self.object_id()
+            is_add=0,
+            contract={
+                'acl_index': self.acl_index,
+                'scope': self.scope,
+                'sclass': self.sclass,
+                'dclass': self.dclass,
+                'n_rules': 0,
+                'rules': [],
+                'n_ether_types': len(self.allowed_ethertypes),
+                'allowed_ethertypes': self.allowed_ethertypes})
 
     def object_id(self):
 
     def object_id(self):
-        return "gbp-contract:[%d:%s:%d]" % (self.sclass,
-                                            self.dclass,
-                                            self.acl_index)
+        return "gbp-contract:[%d:%d:%d:%d]" % (self.scope,
+                                               self.sclass,
+                                               self.dclass,
+                                               self.acl_index)
 
     def query_vpp_config(self):
         cs = self._test.vapi.gbp_contract_dump()
         for c in cs:
 
     def query_vpp_config(self):
         cs = self._test.vapi.gbp_contract_dump()
         for c in cs:
-            if c.contract.sclass == self.sclass \
+            if c.contract.scope == self.scope \
+               and c.contract.sclass == self.sclass \
                and c.contract.dclass == self.dclass:
                 return True
         return False
 
                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):
     """
 
 class VppGbpVxlanTunnel(VppInterface):
     """
@@ -525,9 +554,6 @@ class VppGbpVxlanTunnel(VppInterface):
     def remove_vpp_config(self):
         self._test.vapi.gbp_vxlan_tunnel_del(self.vni)
 
     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)
 
     def object_id(self):
         return "gbp-vxlan:%d" % (self.sw_if_index)
 
@@ -567,7 +593,7 @@ class VppGbpAcl(VppObject):
 
     def add_vpp_config(self, rules):
 
 
     def add_vpp_config(self, rules):
 
-        reply = self._test.vapi.acl_add_replace(self.acl_index,
+        reply = self._test.vapi.acl_add_replace(acl_index=self.acl_index,
                                                 r=rules,
                                                 tag=b'GBPTest')
         self.acl_index = reply.acl_index
                                                 r=rules,
                                                 tag=b'GBPTest')
         self.acl_index = reply.acl_index
@@ -576,9 +602,6 @@ class VppGbpAcl(VppObject):
     def remove_vpp_config(self):
         self._test.vapi.acl_del(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)
 
     def object_id(self):
         return "gbp-acl:[%d]" % (self.acl_index)
 
@@ -593,6 +616,18 @@ class VppGbpAcl(VppObject):
 class TestGBP(VppTestCase):
     """ GBP Test Case """
 
 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()
 
     def setUp(self):
         super(TestGBP, self).setUp()
 
@@ -606,11 +641,20 @@ class TestGBP(VppTestCase):
         for i in self.lo_interfaces:
             i.admin_up()
 
         for i in self.lo_interfaces:
             i.admin_up()
 
+        self.vlan_100 = VppDot1QSubint(self, self.pg0, 100)
+        self.vlan_100.admin_up()
+        self.vlan_101 = VppDot1QSubint(self, self.pg0, 101)
+        self.vlan_101.admin_up()
+        self.vlan_102 = VppDot1QSubint(self, self.pg0, 102)
+        self.vlan_102.admin_up()
+
     def tearDown(self):
         for i in self.pg_interfaces:
             i.admin_down()
     def tearDown(self):
         for i in self.pg_interfaces:
             i.admin_down()
-
         super(TestGBP, self).tearDown()
         super(TestGBP, self).tearDown()
+        self.vlan_102.remove_vpp_config()
+        self.vlan_101.remove_vpp_config()
+        self.vlan_100.remove_vpp_config()
 
     def send_and_expect_bridged(self, src, tx, dst):
         rx = self.send_and_expect(src, tx, dst)
 
     def send_and_expect_bridged(self, src, tx, dst):
         rx = self.send_and_expect(src, tx, dst)
@@ -642,6 +686,16 @@ class TestGBP(VppTestCase):
             self.assertEqual(r[IP].dst, tx[0][IP].dst)
         return rx
 
             self.assertEqual(r[IP].dst, tx[0][IP].dst)
         return rx
 
+    def send_and_expect_routed6(self, src, tx, dst, src_mac):
+        rx = self.send_and_expect(src, tx, dst)
+
+        for r in rx:
+            self.assertEqual(r[Ether].src, src_mac)
+            self.assertEqual(r[Ether].dst, dst.remote_mac)
+            self.assertEqual(r[IPv6].src, tx[0][IPv6].src)
+            self.assertEqual(r[IPv6].dst, tx[0][IPv6].dst)
+        return rx
+
     def send_and_expect_natted(self, src, tx, dst, src_ip):
         rx = self.send_and_expect(src, tx, dst)
 
     def send_and_expect_natted(self, src, tx, dst, src_ip):
         rx = self.send_and_expect(src, tx, dst)
 
@@ -702,29 +756,28 @@ class TestGBP(VppTestCase):
             self.assertEqual(r[IPv6].src, src_ip)
         return rx
 
             self.assertEqual(r[IPv6].src, src_ip)
         return rx
 
-    def test_gbp(self):
-        """ Group Based Policy """
-
-        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+    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
 
 
-        #
-        # Bridge Domains
-        #
-        bd1 = VppBridgeDomain(self, 1)
-        bd2 = VppBridgeDomain(self, 2)
-        bd20 = VppBridgeDomain(self, 20)
+    def send_and_expect_arp(self, src, tx, dst):
+        rx = self.send_and_expect(src, tx, dst)
 
 
-        bd1.add_vpp_config()
-        bd2.add_vpp_config()
-        bd20.add_vpp_config()
+        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
 
 
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
-        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
-        gbd20 = VppGbpBridgeDomain(self, bd20, self.loop2)
+    def test_gbp(self):
+        """ Group Based Policy """
 
 
-        gbd1.add_vpp_config()
-        gbd2.add_vpp_config()
-        gbd20.add_vpp_config()
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
 
         #
         # Route Domains
 
         #
         # Route Domains
@@ -738,12 +791,31 @@ class TestGBP(VppTestCase):
         nt6 = VppIpTable(self, 20, is_ip6=True)
         nt6.add_vpp_config()
 
         nt6 = VppIpTable(self, 20, is_ip6=True)
         nt6.add_vpp_config()
 
-        rd0 = VppGbpRouteDomain(self, 0, gt4, gt6, None, None)
-        rd20 = VppGbpRouteDomain(self, 20, nt4, nt6, None, None)
+        rd0 = VppGbpRouteDomain(self, 0, 400, gt4, gt6, None, None)
+        rd20 = VppGbpRouteDomain(self, 20, 420, nt4, nt6, None, None)
 
         rd0.add_vpp_config()
         rd20.add_vpp_config()
 
 
         rd0.add_vpp_config()
         rd20.add_vpp_config()
 
+        #
+        # Bridge Domains
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd2 = VppBridgeDomain(self, 2)
+        bd20 = VppBridgeDomain(self, 20)
+
+        bd1.add_vpp_config()
+        bd2.add_vpp_config()
+        bd20.add_vpp_config()
+
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd0, self.loop0)
+        gbd2 = VppGbpBridgeDomain(self, bd2, rd0, self.loop1)
+        gbd20 = VppGbpBridgeDomain(self, bd20, rd20, self.loop2)
+
+        gbd1.add_vpp_config()
+        gbd2.add_vpp_config()
+        gbd20.add_vpp_config()
+
         #
         # 3 EPGs, 2 of which share a BD.
         # 2 NAT EPGs, one for floating-IP subnets, the other for internet
         #
         # 3 EPGs, 2 of which share a BD.
         # 2 NAT EPGs, one for floating-IP subnets, the other for internet
@@ -805,12 +877,13 @@ class TestGBP(VppTestCase):
                     self.router_mac.packed)
 
                 # The BVIs are NAT inside interfaces
                     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)
 
             if_ip4 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip4, 32)
             if_ip6 = VppIpInterfaceAddress(self, epg.bvi, epg.bvi_ip6, 128)
@@ -824,10 +897,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),
             # 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.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()
 
             epg.bd_arp_ip4.add_vpp_config()
             epg.bd_arp_ip6.add_vpp_config()
 
@@ -842,13 +915,10 @@ class TestGBP(VppTestCase):
                                recirc.epg.rd.t6).add_vpp_config()
 
             self.vapi.nat44_interface_add_del_feature(
                                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(
             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()
 
 
             recirc.add_vpp_config()
 
@@ -868,14 +938,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:
             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:
                 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()
 
             # VPP EP create ...
             ep.add_vpp_config()
@@ -898,17 +973,18 @@ class TestGBP(VppTestCase):
 
             # add the BD ARP termination entry for floating IP
             for fip in ep.fips:
 
             # 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
                 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
                 r.add_vpp_config()
 
             # L2 FIB entries in the NAT EPG BD to bridge the packets from
@@ -994,7 +1070,8 @@ class TestGBP(VppTestCase):
                                  UDP(sport=1234, dport=1234) /
                                  Raw('\xa5' * 100))
 
                                  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)) /
 
         pkt_inter_epg_222_ip6 = (Ether(src=self.pg0.remote_mac,
                                        dst=str(self.router_mac)) /
@@ -1002,7 +1079,8 @@ class TestGBP(VppTestCase):
                                       dst="2001:10::99") /
                                  UDP(sport=1234, dport=1234) /
                                  Raw('\xa5' * 100))
                                       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
 
         #
         # Add the subnet routes
@@ -1033,13 +1111,13 @@ class TestGBP(VppTestCase):
         s63.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
         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,
                                      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,
                                      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"))
                                       eps[0].epg.uplink)
 
         self.logger.info(self.vapi.cli("sh ip fib 11.0.0.2"))
@@ -1064,7 +1142,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(eps[0].itf,
                                        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)
                                      eps[0].epg.uplink)
         # ... and nowhere else
         self.pg1.get_capture(0, timeout=0.1)
@@ -1078,7 +1156,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(eps[2].itf,
                                        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)
 
         #
                                      eps[2].epg.uplink)
 
         #
@@ -1092,7 +1170,7 @@ class TestGBP(VppTestCase):
                                          Raw('\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg4,
                                          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)
 
         #
                                      self.pg0)
 
         #
@@ -1106,10 +1184,12 @@ class TestGBP(VppTestCase):
                          UDP(sport=1234, dport=1234) /
                          Raw('\xa5' * 100))
 
                          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,
         # cannot communicate
         #
         pkt_inter_epg_220_to_221 = (Ether(src=self.pg0.remote_mac,
@@ -1132,9 +1212,9 @@ class TestGBP(VppTestCase):
                                     Raw('\xa5' * 100))
 
         self.send_and_assert_no_replies(eps[0].itf,
                                     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,
         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
 
         #
         # A uni-directional contract from EPG 220 -> 221
@@ -1144,43 +1224,52 @@ 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(
         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,
+            self, 400, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
             [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,
                                      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(
 
         #
         # contract for the return direction
         #
         c2 = VppGbpContract(
-            self, epgs[1].sclass, epgs[0].sclass, acl_index,
+            self, 400, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
 
         self.send_and_expect_bridged(eps[0].itf,
             [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,
                                      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)
 
                                      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
         #
         #
         # the contract does not allow non-IP
         #
@@ -1195,26 +1284,28 @@ class TestGBP(VppTestCase):
         # not in the contract.
         #
         self.send_and_assert_no_replies(eps[0].itf,
         # 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(
 
         #
         # A uni-directional contract from EPG 220 -> 222 'L3 routed'
         #
         c3 = VppGbpContract(
-            self, epgs[0].sclass, epgs[2].sclass, acl_index,
+            self, 400, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [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,
             [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))
 
                                     eps[3].itf,
                                     str(self.router_mac))
 
@@ -1227,11 +1318,11 @@ class TestGBP(VppTestCase):
         acl.remove_vpp_config()
 
         self.send_and_assert_no_replies(eps[2].itf,
         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,
         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,
         self.send_and_expect_bridged(eps[0].itf,
-                                     pkt_intra_epg * 65,
+                                     pkt_intra_epg * NUM_PKTS,
                                      eps[1].itf)
 
         #
                                      eps[1].itf)
 
         #
@@ -1284,7 +1375,7 @@ class TestGBP(VppTestCase):
                                        eps[0].fip6))
 
         #
                                        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)) /
         #
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
@@ -1295,7 +1386,7 @@ class TestGBP(VppTestCase):
 
         # no policy yet
         self.send_and_assert_no_replies(eps[0].itf,
 
         # 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,
 
         acl2 = VppGbpAcl(self)
         rule = acl2.create_rule(permit_deny=1, proto=17, sport_from=1234,
@@ -1306,18 +1397,20 @@ class TestGBP(VppTestCase):
 
         acl_index2 = acl2.add_vpp_config([rule, rule2])
         c4 = VppGbpContract(
 
         acl_index2 = acl2.add_vpp_config([rule, rule2])
         c4 = VppGbpContract(
-            self, epgs[0].sclass, epgs[3].sclass, acl_index2,
+            self, 400, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c4.add_vpp_config()
 
         self.send_and_expect_natted(eps[0].itf,
             [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)
 
                                     self.pg7,
                                     eps[0].fip4.address)
 
@@ -1329,7 +1422,7 @@ class TestGBP(VppTestCase):
                                        Raw('\xa5' * 100))
 
         self.send_and_expect_natted6(self.pg0,
                                        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)
 
                                      self.pg7,
                                      eps[0].fip6.address)
 
@@ -1343,22 +1436,24 @@ class TestGBP(VppTestCase):
                                          UDP(sport=1234, dport=1234) /
                                          Raw('\xa5' * 100))
 
                                          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(
 
         c5 = VppGbpContract(
-            self, epgs[3].sclass, epgs[0].sclass, acl_index2,
+            self, 400, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c5.add_vpp_config()
 
         self.send_and_expect_unnatted(self.pg7,
             [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)
 
                                       eps[0].itf,
                                       eps[0].ip4.address)
 
@@ -1369,10 +1464,11 @@ class TestGBP(VppTestCase):
                                          UDP(sport=1234, dport=1234) /
                                          Raw('\xa5' * 100))
 
                                          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:
 
         #
         # From a local VM to another local VM using resp. public addresses:
@@ -1386,7 +1482,7 @@ class TestGBP(VppTestCase):
                                     Raw('\xa5' * 100))
 
         self.send_and_expect_double_natted(eps[0].itf,
                                     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)
                                            eps[1].itf,
                                            eps[0].fip4.address,
                                            eps[1].ip4.address)
@@ -1398,51 +1494,55 @@ class TestGBP(VppTestCase):
                                     UDP(sport=1234, dport=1234) /
                                     Raw('\xa5' * 100))
 
                                     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
 
         #
         # 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]:
 
         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(
 
         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(
                 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,
 
     def wait_for_ep_timeout(self, sw_if_index=None, ip=None, mac=None,
-                            n_tries=100, s_time=1):
+                            tep=None, n_tries=100, s_time=1):
         while (n_tries):
         while (n_tries):
-            if not find_gbp_endpoint(self, sw_if_index, ip, mac):
+            if not find_gbp_endpoint(self, sw_if_index, ip, mac, tep=tep):
                 return True
             n_tries = n_tries - 1
             self.sleep(s_time)
                 return True
             n_tries = n_tries - 1
             self.sleep(s_time)
@@ -1452,6 +1552,11 @@ class TestGBP(VppTestCase):
     def test_gbp_learn_l2(self):
         """ GBP L2 Endpoint Learning """
 
     def test_gbp_learn_l2(self):
         """ GBP L2 Endpoint Learning """
 
+        drop_no_contract = self.statistics.get_err_counter(
+            '/err/gbp-policy-port/drop-no-contract')
+        allow_intra_class = self.statistics.get_err_counter(
+            '/err/gbp-policy-port/allow-intra-sclass')
+
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
                    'ip': '10.0.0.1',
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
                    'ip': '10.0.0.1',
@@ -1468,7 +1573,7 @@ class TestGBP(VppTestCase):
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
         rd1.add_vpp_config()
 
         #
         rd1.add_vpp_config()
 
         #
@@ -1498,7 +1603,8 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3, tun_bm)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0,
+                                  self.pg3, tun_bm)
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
@@ -1526,7 +1632,7 @@ class TestGBP(VppTestCase):
 
         #
         # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
 
         #
         # 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,
         #
         vx_tun_l2_1 = VppGbpVxlanTunnel(
             self, 99, bd1.bd_id,
@@ -1546,7 +1652,7 @@ class TestGBP(VppTestCase):
 
         self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
 
 
         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,
         p = (Ether(src=self.pg2.remote_mac,
                    dst=self.pg2.local_mac) /
              IP(src=self.pg2.remote_hosts[0].ip4,
@@ -1560,6 +1666,11 @@ class TestGBP(VppTestCase):
 
         self.send_and_assert_no_replies(self.pg2, p)
 
 
         self.send_and_assert_no_replies(self.pg2, p)
 
+        self.logger.info(self.vapi.cli("sh error"))
+        self.assert_error_counter_equal(
+            '/err/gbp-policy-port/drop-no-contract',
+            drop_no_contract + 1)
+
         #
         # we should not have learnt a new tunnel endpoint, since
         # the EPG was not learnt.
         #
         # we should not have learnt a new tunnel endpoint, since
         # the EPG was not learnt.
@@ -1570,14 +1681,14 @@ class TestGBP(VppTestCase):
                                                self.pg2.remote_hosts[0].ip4,
                                                99))
 
                                                self.pg2.remote_hosts[0].ip4,
                                                99))
 
-        # epg is not learnt, becasue the EPG is unknwon
+        # ep 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):
         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) /
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -1611,6 +1722,10 @@ class TestGBP(VppTestCase):
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
+        self.assert_error_counter_equal(
+            '/err/gbp-policy-port/allow-intra-sclass',
+            allow_intra_class + 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"))
         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,14 +1742,15 @@ class TestGBP(VppTestCase):
         # Learn new EPs from GARP packets received on the BD's mcast tunnel
         #
         for ii, l in enumerate(learnt):
         # 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
-            # arriving on an unknown TEP
+            # add some junk in the reserved field of the vxlan-header
+            # next to the VNI. we should accept since reserved bits are
+            # ignored on rx.
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
                     dst="239.1.1.1") /
                  UDP(sport=1234, dport=48879) /
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
                     dst="239.1.1.1") /
                  UDP(sport=1234, dport=48879) /
-                 VXLAN(vni=88, gpid=112, flags=0x88) /
+                 VXLAN(vni=88, reserved2=0x80, gpid=112, flags=0x88) /
                  Ether(src=l['mac'], dst="ff:ff:ff:ff:ff:ff") /
                  ARP(op="who-has",
                      psrc=l['ip'], pdst=l['ip'],
                  Ether(src=l['mac'], dst="ff:ff:ff:ff:ff:ff") /
                  ARP(op="who-has",
                      psrc=l['ip'], pdst=l['ip'],
@@ -1672,7 +1788,7 @@ class TestGBP(VppTestCase):
         # Learn new EPs from L2 packets
         #
         for ii, l in enumerate(learnt):
         # 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) /
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
@@ -1716,7 +1832,7 @@ class TestGBP(VppTestCase):
         # repeat. the do not learn bit is set so the EPs are not learnt
         #
         for l in learnt:
         # 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,
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1728,7 +1844,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  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,
 
         for l in learnt:
             self.assertFalse(find_gbp_endpoint(self,
@@ -1739,19 +1855,21 @@ class TestGBP(VppTestCase):
         # repeat
         #
         for l in learnt:
         # repeat
         #
         for l in learnt:
-            # a packet with an sclass from a knwon EPG
+            # a packet with an sclass from a known EPG
+            # set a reserved bit in addition to the G and I
+            # reserved bits should not be checked on rx.
             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) /
             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=112, flags=0x88) /
+                 VXLAN(vni=99, gpid=112, flags=0xc8) /
                  Ether(src=l['mac'], dst=ep.mac) /
                  IP(src=l['ip'], dst=ep.ip4.address) /
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  Ether(src=l['mac'], dst=ep.mac) /
                  IP(src=l['ip'], dst=ep.ip4.address) /
                  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,
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1791,7 +1909,7 @@ class TestGBP(VppTestCase):
         # so the packet is cleared for delivery
         #
         for l in learnt:
         # 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,
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1803,7 +1921,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  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,
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1824,7 +1942,7 @@ class TestGBP(VppTestCase):
         # refresh the entries after the check for no replies above
         #
         for l in learnt:
         # 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,
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
@@ -1836,7 +1954,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  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,
 
             self.assertTrue(find_gbp_endpoint(self,
                                               vx_tun_l2_1.sw_if_index,
@@ -1850,12 +1968,14 @@ 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(
         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.sclass, epg_330.sclass, acl_index,
+            self, 401, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
              VppGbpContractRule(
                  VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
                 []),
              VppGbpContractRule(
                  VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                  [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
                  [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
@@ -1871,8 +1991,8 @@ class TestGBP(VppTestCase):
         #
         # send UU packets from the local EP
         #
         #
         # send UU packets from the local EP
         #
-        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
         self.logger.info(self.vapi.cli("sh gbp bridge"))
         self.logger.info(self.vapi.cli("sh gbp bridge"))
+        self.logger.info(self.vapi.cli("sh bridge-domain 1 detail"))
         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) /
         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) /
@@ -1899,32 +2019,167 @@ class TestGBP(VppTestCase):
             self.assertFalse(rx[VXLAN].gpflags.A)
             self.assertFalse(rx[VXLAN].gpflags.D)
 
             self.assertFalse(rx[VXLAN].gpflags.A)
             self.assertFalse(rx[VXLAN].gpflags.D)
 
+        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])
+        c2 = VppGbpContract(
+            self, 401, epg_330.sclass, epg_220.sclass, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+
+        for l in learnt:
+            self.wait_for_ep_timeout(vx_tun_l2_1.sw_if_index,
+                                     mac=l['mac'])
         #
         #
-        # Check v6 Endpoints
+        # Check v6 Endpoints learning
         #
         for l in 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,
                     dst=self.pg2.local_ip4) /
                  UDP(sport=1234, dport=48879) /
             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=113, flags=0x88, gpflags='A') /
+                 VXLAN(vni=99, gpid=113, flags=0x88) /
                  Ether(src=l['mac'], dst=ep.mac) /
                  IPv6(src=l['ip6'], dst=ep.ip6.address) /
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  Ether(src=l['mac'], dst=ep.mac) /
                  IPv6(src=l['ip6'], dst=ep.ip6.address) /
                  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)
+            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,
-                                              mac=l['mac']))
+            self.assertTrue(find_gbp_endpoint(
+                self,
+                vx_tun_l2_1.sw_if_index,
+                ip=l['ip6'],
+                tep=[self.pg2.local_ip4,
+                     self.pg2.remote_hosts[1].ip4]))
+
+        self.logger.info(self.vapi.cli("sh int"))
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
+        self.logger.info(self.vapi.cli("sh gbp vxlan"))
+        self.logger.info(self.vapi.cli("sh gbp endpoint"))
+        self.logger.info(self.vapi.cli("sh gbp interface"))
+
+        #
+        # EP moves to a different TEP
+        #
+        for l in learnt:
+            # 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[2].ip4,
+                    dst=self.pg2.local_ip4) /
+                 UDP(sport=1234, dport=48879) /
+                 VXLAN(vni=99, gpid=113, flags=0x88) /
+                 Ether(src=l['mac'], dst=ep.mac) /
+                 IPv6(src=l['ip6'], dst=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rx = self.send_and_expect(self.pg2, p * 1, 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,
+                sclass=113,
+                mac=l['mac'],
+                tep=[self.pg2.local_ip4,
+                     self.pg2.remote_hosts[2].ip4]))
+
+        #
+        # v6 remote EP reachability
+        #
+        for l in learnt:
+            p = (Ether(src=ep.mac, dst=l['mac']) /
+                 IPv6(dst=l['ip6'], src=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            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)
+                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[2].ip4)
+                self.assertEqual(rx[UDP].dport, 48879)
+                # the UDP source port is a random value for hashing
+                self.assertEqual(rx[VXLAN].gpid, 112)
+                self.assertEqual(rx[VXLAN].vni, 99)
+                self.assertTrue(rx[VXLAN].flags.G)
+                self.assertTrue(rx[VXLAN].flags.Instance)
+                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.D)
+                self.assertEqual(rx[IPv6].dst, l['ip6'])
+
+        #
+        # EP changes sclass
+        #
+        for l in learnt:
+            # 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[2].ip4,
+                    dst=self.pg2.local_ip4) /
+                 UDP(sport=1234, dport=48879) /
+                 VXLAN(vni=99, gpid=112, flags=0x88) /
+                 Ether(src=l['mac'], dst=ep.mac) /
+                 IPv6(src=l['ip6'], dst=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rx = self.send_and_expect(self.pg2, p * 1, 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,
+                mac=l['mac'],
+                sclass=112,
+                tep=[self.pg2.local_ip4,
+                     self.pg2.remote_hosts[2].ip4]))
 
         #
 
         #
-        # L3 Endpoint Learning
-        #  - configured on the bridge's BVI
+        # check reachability and contract intra-epg
         #
         #
+        allow_intra_class = self.statistics.get_err_counter(
+            '/err/gbp-policy-mac/allow-intra-sclass')
+
+        for l in learnt:
+            p = (Ether(src=ep.mac, dst=l['mac']) /
+                 IPv6(dst=l['ip6'], src=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            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)
+                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[2].ip4)
+                self.assertEqual(rx[UDP].dport, 48879)
+                self.assertEqual(rx[VXLAN].gpid, 112)
+                self.assertEqual(rx[VXLAN].vni, 99)
+                self.assertTrue(rx[VXLAN].flags.G)
+                self.assertTrue(rx[VXLAN].flags.Instance)
+                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.D)
+                self.assertEqual(rx[IPv6].dst, l['ip6'])
+
+            allow_intra_class += NUM_PKTS
+
+        self.assert_error_counter_equal(
+            '/err/gbp-policy-mac/allow-intra-sclass',
+            allow_intra_class)
 
         #
         # clean up
 
         #
         # clean up
@@ -1932,633 +2187,1680 @@ class TestGBP(VppTestCase):
         for l in learnt:
             self.wait_for_ep_timeout(vx_tun_l2_1.sw_if_index,
                                      mac=l['mac'])
         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()
 
         self.pg2.unconfig_ip4()
         self.pg3.unconfig_ip4()
         self.pg4.unconfig_ip4()
 
-        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"""
-
-        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'}]
+    def test_gbp_contract(self):
+        """ GBP Contracts """
 
         #
 
         #
-        # IP tables
+        # Route Domains
         #
         #
-        gt4 = VppIpTable(self, 1)
+        gt4 = VppIpTable(self, 0)
         gt4.add_vpp_config()
         gt4.add_vpp_config()
-        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6 = VppIpTable(self, 0, is_ip6=True)
         gt6.add_vpp_config()
 
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
-        rd1.add_vpp_config()
+        rd0 = VppGbpRouteDomain(self, 0, 400, gt4, gt6, None, None)
 
 
-        #
-        # 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()
+        rd0.add_vpp_config()
 
         #
 
         #
-        # The EP will be on a vlan sub-interface
+        # Bridge Domains
         #
         #
-        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()
+        bd1 = VppBridgeDomain(self, 1, arp_term=0)
+        bd2 = VppBridgeDomain(self, 2, arp_term=0)
 
 
-        #
-        # 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()
         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"))
+        bd2.add_vpp_config()
 
 
-        # ... and has a /32 applied
-        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
-        ip_addr.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd0, self.loop0)
+        gbd2 = VppGbpBridgeDomain(self, bd2, rd0, self.loop1)
 
 
-        #
-        # 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()
+        gbd1.add_vpp_config()
+        gbd2.add_vpp_config()
 
         #
 
         #
-        # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # leanring enabled
+        # 3 EPGs, 2 of which share a BD.
         #
         #
-        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()
-
+        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")]
         #
         #
-        # A static endpoint that the learnt endpoints are trying to
-        # talk to
+        # 4 end-points, 2 in the same subnet, 3 in the same BD
         #
         #
-        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))
+        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")]
 
         #
 
         #
-        # Send to the static EP
+        # Config related to each of the EPGs
         #
         #
-        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))
-
-            rxs = self.send_and_expect(self.pg2, [p], self.pg0)
+        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)
 
 
-            #
-            # packet to EP has the EP's vlan tag
-            #
-            for rx in rxs:
-                self.assertEqual(rx[Dot1Q].vlan, 11)
+            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()
 
 
-            #
-            # 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))
+            # 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()
 
 
-        self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
+            # EPG in VPP
+            epg.add_vpp_config()
 
         #
 
         #
-        # static to remotes
-        # we didn't learn the remotes so they are sent to the UU-fwd
+        # config ep
         #
         #
-        for l in learnt:
-            p = (Ether(src=ep.mac, dst=l['mac']) /
-                 Dot1Q(vlan=11) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
-
-            rxs = self.send_and_expect(self.pg0, p * 17, self.pg3)
-
-            for rx in rxs:
-                self.assertEqual(rx[IP].src, self.pg3.local_ip4)
-                self.assertEqual(rx[IP].dst, self.pg3.remote_ip4)
-                self.assertEqual(rx[UDP].dport, 48879)
-                # the UDP source port is a random value for hashing
-                self.assertEqual(rx[VXLAN].gpid, 441)
-                self.assertEqual(rx[VXLAN].vni, 116)
-                self.assertTrue(rx[VXLAN].flags.G)
-                self.assertTrue(rx[VXLAN].flags.Instance)
-                self.assertFalse(rx[VXLAN].gpflags.A)
-                self.assertFalse(rx[VXLAN].gpflags.D)
-
-        self.pg2.unconfig_ip4()
-        self.pg3.unconfig_ip4()
-
-    def test_gbp_learn_l3(self):
-        """ GBP L3 Endpoint Learning """
-
-        self.vapi.cli("set logging class gbp debug")
-
-        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
-        routed_dst_mac = "00:0c:0c:0c:0c:0c"
-        routed_src_mac = "00:22:bd:f8:19:ff"
+        for ep in eps:
+            ep.add_vpp_config()
 
 
-        learnt = [{'mac': '00:00:11:11:11:02',
-                   'ip': '10.0.1.2',
-                   'ip6': '2001:10::2'},
-                  {'mac': '00:00:11:11:11:03',
-                   'ip': '10.0.1.3',
-                   'ip6': '2001:10::3'}]
+        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"))
 
         #
 
         #
-        # IP tables
+        # Intra epg allowed without contract
         #
         #
-        t4 = VppIpTable(self, 1)
-        t4.add_vpp_config()
-        t6 = VppIpTable(self, 1, True)
-        t6.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))
 
 
-        tun_ip4_uu = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
-                                       self.pg4.remote_ip4, 114)
-        tun_ip6_uu = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
-                                       self.pg4.remote_ip4, 116)
-        tun_ip4_uu.add_vpp_config()
-        tun_ip6_uu.add_vpp_config()
+        self.send_and_expect_bridged(self.pg0,
+                                     pkt_intra_epg_220_to_220 * 65,
+                                     self.pg1)
 
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6, tun_ip4_uu, tun_ip6_uu)
-        rd1.add_vpp_config()
+        pkt_intra_epg_220_to_220 = (Ether(src=self.pg0.remote_mac,
+                                          dst=self.pg1.remote_mac) /
+                                    IPv6(src=eps[0].ip6.address,
+                                         dst=eps[1].ip6.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
 
 
-        self.loop0.set_mac(self.router_mac)
+        self.send_and_expect_bridged6(self.pg0,
+                                      pkt_intra_epg_220_to_220 * 65,
+                                      self.pg1)
 
         #
 
         #
-        # Bind the BVI to the RD
+        # Inter epg denied without contract
         #
         #
-        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
-        VppIpInterfaceBind(self, self.loop0, t6).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))
 
 
-        #
-        # Pg2 hosts the vxlan tunnel
-        # hosts on pg2 to act as TEPs
-        # pg3 is BD uu-fwd
-        # pg4 is RD uu-fwd
-        #
-        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()
-        self.pg4.config_ip4()
-        self.pg4.resolve_arp()
+        self.send_and_assert_no_replies(self.pg0, pkt_inter_epg_220_to_221)
 
         #
 
         #
-        # a GBP bridge domain with a BVI and a UU-flood interface
+        # A uni-directional contract from EPG 220 -> 221
         #
         #
-        bd1 = VppBridgeDomain(self, 1)
-        bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3)
-        gbd1.add_vpp_config()
+        acl = VppGbpAcl(self)
+        rule = acl.create_rule(permit_deny=1, proto=17)
+        rule2 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
+        rule3 = acl.create_rule(permit_deny=1, proto=1)
+        acl_index = acl.add_vpp_config([rule, rule2, rule3])
+        c1 = VppGbpContract(
+            self, 400, epgs[0].sclass, epgs[1].sclass, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
 
 
-        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
-        self.logger.info(self.vapi.cli("sh gbp bridge"))
-        self.logger.info(self.vapi.cli("sh gbp route"))
+        self.send_and_expect_bridged(eps[0].itf,
+                                     pkt_inter_epg_220_to_221 * 65,
+                                     eps[2].itf)
 
 
-        # ... and has a /32 and /128 applied
-        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
-        ip4_addr.add_vpp_config()
-        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 128)
-        ip6_addr.add_vpp_config()
+        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 Endpoint-group in which we are learning endpoints
+        # ping router IP in different BD
         #
         #
-        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()
+        pkt_router_ping_220_to_221 = (Ether(src=self.pg0.remote_mac,
+                                            dst=str(self.router_mac)) /
+                                      IP(src=eps[0].ip4.address,
+                                         dst=epgs[1].bvi_ip4.address) /
+                                      ICMP(type='echo-request'))
+
+        self.send_and_expect(self.pg0, [pkt_router_ping_220_to_221], self.pg0)
+
+        pkt_router_ping_220_to_221 = (Ether(src=self.pg0.remote_mac,
+                                            dst=str(self.router_mac)) /
+                                      IPv6(src=eps[0].ip6.address,
+                                           dst=epgs[1].bvi_ip6.address) /
+                                      ICMPv6EchoRequest())
+
+        self.send_and_expect(self.pg0, [pkt_router_ping_220_to_221], self.pg0)
 
         #
 
         #
-        # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # leanring enabled
+        # contract for the return direction
         #
         #
-        vx_tun_l3 = VppGbpVxlanTunnel(
-            self, 101, rd1.rd_id,
-            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
-            self.pg2.local_ip4)
-        vx_tun_l3.add_vpp_config()
+        c2 = VppGbpContract(
+            self, 400, epgs[1].sclass, epgs[0].sclass, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
 
 
-        #
-        # A static endpoint that the learnt endpoints are trying to
-        # talk to
-        #
-        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()
+        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)
+        pkt_inter_epg_221_to_220 = (Ether(src=self.pg2.remote_mac,
+                                          dst=str(self.router_mac)) /
+                                    IP(src=eps[2].ip4.address,
+                                       dst=eps[0].ip4.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
+        self.send_and_expect_routed(eps[2].itf,
+                                    pkt_inter_epg_221_to_220 * 65,
+                                    eps[0].itf,
+                                    str(self.router_mac))
+        pkt_inter_epg_221_to_220 = (Ether(src=self.pg2.remote_mac,
+                                          dst=str(self.router_mac)) /
+                                    IPv6(src=eps[2].ip6.address,
+                                         dst=eps[0].ip6.address) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw('\xa5' * 100))
+        self.send_and_expect_routed6(eps[2].itf,
+                                     pkt_inter_epg_221_to_220 * 65,
+                                     eps[0].itf,
+                                     str(self.router_mac))
 
         #
 
         #
-        # learn some remote IPv4 EPs
+        # contract between 220 and 222 uni-direction
         #
         #
-        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=101, gpid=441, flags=0x88) /
-                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        c3 = VppGbpContract(
+            self, 400, epgs[0].sclass, epgs[2].sclass, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+             VppGbpContractRule(
+                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                 [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c3.add_vpp_config()
 
 
-            rx = self.send_and_expect(self.pg2, [p], self.pg0)
+        self.send_and_expect(eps[0].itf,
+                             pkt_inter_epg_220_to_222 * 65,
+                             eps[3].itf)
 
 
-            # the new TEP
-            tep1_sw_if_index = find_vxlan_gbp_tunnel(
-                self,
-                self.pg2.local_ip4,
-                self.pg2.remote_hosts[1].ip4,
-                vx_tun_l3.vni)
-            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+        c3.remove_vpp_config()
+        c1.remove_vpp_config()
+        c2.remove_vpp_config()
+        acl.remove_vpp_config()
 
 
-            # endpoint learnt via the parent GBP-vxlan interface
-            self.assertTrue(find_gbp_endpoint(self,
-                                              vx_tun_l3._sw_if_index,
-                                              ip=l['ip']))
+    def test_gbp_bd_drop_flags(self):
+        """ GBP BD drop flags """
 
         #
 
         #
-        # Static IPv4 EP replies to learnt
+        # IP tables
         #
         #
-        for l in learnt:
-            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        gt4 = VppIpTable(self, 1)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6.add_vpp_config()
 
 
-            rxs = self.send_and_expect(self.pg0, p*1, self.pg2)
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
+        rd1.add_vpp_config()
 
 
-            for rx in rxs:
-                self.assertEqual(rx[IP].src, self.pg2.local_ip4)
-                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
-                self.assertEqual(rx[UDP].dport, 48879)
-                # the UDP source port is a random value for hashing
-                self.assertEqual(rx[VXLAN].gpid, 441)
-                self.assertEqual(rx[VXLAN].vni, 101)
-                self.assertTrue(rx[VXLAN].flags.G)
-                self.assertTrue(rx[VXLAN].flags.Instance)
-                self.assertTrue(rx[VXLAN].gpflags.A)
-                self.assertFalse(rx[VXLAN].gpflags.D)
+        #
+        # a GBP bridge domain with a BVI only
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
 
 
-                inner = rx[VXLAN].payload
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0,
+                                  None, None,
+                                  uu_drop=True, bm_drop=True)
+        gbd1.add_vpp_config()
 
 
-                self.assertEqual(inner[Ether].src, routed_src_mac)
-                self.assertEqual(inner[Ether].dst, routed_dst_mac)
-                self.assertEqual(inner[IP].src, ep.ip4.address)
-                self.assertEqual(inner[IP].dst, l['ip'])
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
 
 
-        for l in learnt:
-            self.assertFalse(find_gbp_endpoint(self,
-                                               tep1_sw_if_index,
-                                               ip=l['ip']))
+        # ... and has a /32 applied
+        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip_addr.add_vpp_config()
 
         #
 
         #
-        # learn some remote IPv6 EPs
+        # The Endpoint-group
         #
         #
-        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=101, gpid=441, flags=0x88) /
-                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
-                 IPv6(src=l['ip6'], dst=ep.ip6.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+        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()
 
 
-            rx = self.send_and_expect(self.pg2, [p], self.pg0)
+        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()
 
 
-            # the new TEP
-            tep1_sw_if_index = find_vxlan_gbp_tunnel(
-                self,
-                self.pg2.local_ip4,
-                self.pg2.remote_hosts[1].ip4,
-                vx_tun_l3.vni)
-            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+        #
+        # 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])
 
 
-            self.logger.info(self.vapi.cli("show gbp bridge"))
-            self.logger.info(self.vapi.cli("show vxlan-gbp tunnel"))
-            self.logger.info(self.vapi.cli("show gbp vxlan"))
-            self.logger.info(self.vapi.cli("show int addr"))
+        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])
 
 
-            # endpoint learnt via the TEP
-            self.assertTrue(find_gbp_endpoint(self, ip=l['ip6']))
+        self.pg3.unconfig_ip4()
 
 
-        self.logger.info(self.vapi.cli("show gbp endpoint"))
-        self.logger.info(self.vapi.cli("show ip fib index 1 %s" % l['ip']))
+        self.logger.info(self.vapi.cli("sh int"))
+
+    def test_gbp_bd_arp_flags(self):
+        """ GBP BD arp flags """
 
         #
 
         #
-        # Static EP replies to learnt
+        # IP tables
         #
         #
-        for l in learnt:
-            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IPv6(dst=l['ip6'], src=ep.ip6.address) /
-                 UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
-
-            rxs = self.send_and_expect(self.pg0, p*65, self.pg2)
-
-            for rx in rxs:
-                self.assertEqual(rx[IP].src, self.pg2.local_ip4)
-                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
-                self.assertEqual(rx[UDP].dport, 48879)
-                # the UDP source port is a random value for hashing
-                self.assertEqual(rx[VXLAN].gpid, 441)
-                self.assertEqual(rx[VXLAN].vni, 101)
-                self.assertTrue(rx[VXLAN].flags.G)
-                self.assertTrue(rx[VXLAN].flags.Instance)
-                self.assertTrue(rx[VXLAN].gpflags.A)
-                self.assertFalse(rx[VXLAN].gpflags.D)
+        gt4 = VppIpTable(self, 1)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6.add_vpp_config()
 
 
-                inner = rx[VXLAN].payload
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
+        rd1.add_vpp_config()
 
 
-                self.assertEqual(inner[Ether].src, routed_src_mac)
-                self.assertEqual(inner[Ether].dst, routed_dst_mac)
-                self.assertEqual(inner[IPv6].src, ep.ip6.address)
-                self.assertEqual(inner[IPv6].dst, l['ip6'])
+        #
+        # Pg4 hosts the IP6 UU-flood VXLAN tunnel
+        #
+        self.pg4.config_ip4()
+        self.pg4.resolve_arp()
 
 
-        self.logger.info(self.vapi.cli("sh gbp endpoint"))
-        for l in learnt:
-            self.wait_for_ep_timeout(ip=l['ip'])
+        #
+        # Add a mcast destination VXLAN-GBP tunnel for B&M traffic
+        #
+        tun_uu = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
+                                   "239.1.1.1", 88,
+                                   mcast_itf=self.pg4)
+        tun_uu.add_vpp_config()
 
         #
 
         #
-        # Static sends to unknown EP with no route
+        # a GBP bridge domain with a BVI and a UU-flood interface
         #
         #
-        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(dst="10.0.0.99", src=ep.ip4.address) /
-             UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
 
 
-        self.send_and_assert_no_replies(self.pg0, [p])
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0,
+                                  tun_uu, None,
+                                  ucast_arp=True)
+        gbd1.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 a route to static EP's v4 and v6 subnet
-        #  packets should be sent on the v4/v6 uu=fwd interface resp.
+        # The Endpoint-group
         #
         #
-        se_10_24 = VppGbpSubnet(
-            self, rd1, "10.0.0.0", 24,
-            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT)
-        se_10_24.add_vpp_config()
+        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()
 
 
-        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(dst="10.0.0.99", src=ep.ip4.address) /
-             UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+        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()
 
 
-        rxs = self.send_and_expect(self.pg0, [p], self.pg4)
-        for rx in rxs:
-            self.assertEqual(rx[IP].src, self.pg4.local_ip4)
-            self.assertEqual(rx[IP].dst, self.pg4.remote_ip4)
-            self.assertEqual(rx[UDP].dport, 48879)
-            # the UDP source port is a random value for hashing
-            self.assertEqual(rx[VXLAN].gpid, 441)
-            self.assertEqual(rx[VXLAN].vni, 114)
-            self.assertTrue(rx[VXLAN].flags.G)
-            self.assertTrue(rx[VXLAN].flags.Instance)
-            # policy is not applied to packets sent to the uu-fwd interfaces
-            self.assertFalse(rx[VXLAN].gpflags.A)
-            self.assertFalse(rx[VXLAN].gpflags.D)
+        #
+        # send ARP packet from the local EP expect it on the uu interface
+        #
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
+        p_arp = (Ether(src=ep.mac, dst="ff:ff:ff:ff:ff:ff") /
+                 ARP(op="who-has",
+                     psrc=ep.ip4.address, pdst="10.0.0.99",
+                     hwsrc=ep.mac,
+                     hwdst="ff:ff:ff:ff:ff:ff"))
+        self.send_and_expect(ep.itf, [p_arp], self.pg4)
+
+        self.pg4.unconfig_ip4()
+
+    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'}]
 
         #
 
         #
-        # learn some remote IPv4 EPs
+        # 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, 401, 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, rd1, 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):
         #
         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) /
             # arriving on an unknown TEP
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
-                 IP(src=self.pg2.remote_hosts[2].ip4,
+                 IP(src=self.pg2.remote_hosts[1].ip4,
                     dst=self.pg2.local_ip4) /
                  UDP(sport=1234, dport=48879) /
                     dst=self.pg2.local_ip4) /
                  UDP(sport=1234, dport=48879) /
-                 VXLAN(vni=101, gpid=441, flags=0x88) /
-                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+                 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))
 
                  IP(src=l['ip'], dst=ep.ip4.address) /
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
-            rx = self.send_and_expect(self.pg2, [p], self.pg0)
-
-            # the new TEP
-            tep1_sw_if_index = find_vxlan_gbp_tunnel(
-                self,
-                self.pg2.local_ip4,
-                self.pg2.remote_hosts[2].ip4,
-                vx_tun_l3.vni)
-            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+            rxs = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
-            # endpoint learnt via the parent GBP-vxlan interface
-            self.assertTrue(find_gbp_endpoint(self,
-                                              vx_tun_l3._sw_if_index,
-                                              ip=l['ip']))
+            #
+            # packet to EP has the EP's vlan tag
+            #
+            for rx in rxs:
+                self.assertEqual(rx[Dot1Q].vlan, 11)
 
 
-        #
-        # Add a remote endpoint from the API
-        #
-        rep_88 = VppGbpEndpoint(self, vx_tun_l3,
-                                epg_220, None,
-                                "10.0.0.88", "11.0.0.88",
-                                "2001:10::88", "3001::88",
-                                ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
-                                self.pg2.local_ip4,
-                                self.pg2.remote_hosts[1].ip4,
-                                mac=None)
-        rep_88.add_vpp_config()
+            #
+            # 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))
 
 
-        #
-        # Add a remote endpoint from the API that matches an existing one
-        #
-        rep_2 = VppGbpEndpoint(self, vx_tun_l3,
-                               epg_220, None,
-                               learnt[0]['ip'], "11.0.0.101",
-                               learnt[0]['ip6'], "3001::101",
-                               ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
-                               self.pg2.local_ip4,
-                               self.pg2.remote_hosts[1].ip4,
-                               mac=None)
-        rep_2.add_vpp_config()
+        self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
 
         #
 
         #
-        # Add a route to the leanred EP's v4 subnet
-        #  packets should be send on the v4/v6 uu=fwd interface resp.
+        # static to remotes
+        # we didn't learn the remotes so they are sent to the UU-fwd
         #
         #
-        se_10_1_24 = VppGbpSubnet(
-            self, rd1, "10.0.1.0", 24,
-            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT)
-        se_10_1_24.add_vpp_config()
-
-        self.logger.info(self.vapi.cli("show gbp endpoint"))
-
-        ips = ["10.0.0.88", learnt[0]['ip']]
-        for ip in ips:
-            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IP(dst=ip, src=ep.ip4.address) /
+        for l in learnt:
+            p = (Ether(src=ep.mac, dst=l['mac']) /
+                 Dot1Q(vlan=11) /
+                 IP(dst=l['ip'], src=ep.ip4.address) /
                  UDP(sport=1234, dport=1234) /
                  Raw('\xa5' * 100))
 
                  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 * 17, self.pg3)
 
             for rx in rxs:
 
             for rx in rxs:
-                self.assertEqual(rx[IP].src, self.pg2.local_ip4)
-                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
+                self.assertEqual(rx[IP].src, self.pg3.local_ip4)
+                self.assertEqual(rx[IP].dst, self.pg3.remote_ip4)
                 self.assertEqual(rx[UDP].dport, 48879)
                 # the UDP source port is a random value for hashing
                 self.assertEqual(rx[VXLAN].gpid, 441)
                 self.assertEqual(rx[UDP].dport, 48879)
                 # the UDP source port is a random value for hashing
                 self.assertEqual(rx[VXLAN].gpid, 441)
-                self.assertEqual(rx[VXLAN].vni, 101)
+                self.assertEqual(rx[VXLAN].vni, 116)
                 self.assertTrue(rx[VXLAN].flags.G)
                 self.assertTrue(rx[VXLAN].flags.Instance)
                 self.assertTrue(rx[VXLAN].flags.G)
                 self.assertTrue(rx[VXLAN].flags.Instance)
-                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.A)
                 self.assertFalse(rx[VXLAN].gpflags.D)
 
                 self.assertFalse(rx[VXLAN].gpflags.D)
 
-                inner = rx[VXLAN].payload
-
-                self.assertEqual(inner[Ether].src, routed_src_mac)
-                self.assertEqual(inner[Ether].dst, routed_dst_mac)
-                self.assertEqual(inner[IP].src, ep.ip4.address)
-                self.assertEqual(inner[IP].dst, ip)
-
-        #
-        # remove the API remote EPs, only API sourced is gone, the DP
-        # learnt one remains
-        #
-        rep_88.remove_vpp_config()
-        rep_2.remove_vpp_config()
+        self.pg2.unconfig_ip4()
+        self.pg3.unconfig_ip4()
 
 
-        self.assertTrue(find_gbp_endpoint(self, ip=rep_2.ip4.address))
+    def test_gbp_learn_l3(self):
+        """ GBP L3 Endpoint Learning """
 
 
-        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(src=ep.ip4.address, dst=rep_2.ip4.address) /
-             UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
-        rxs = self.send_and_expect(self.pg0, [p], self.pg2)
+        self.vapi.cli("set logging class gbp level debug")
 
 
-        self.assertFalse(find_gbp_endpoint(self, ip=rep_88.ip4.address))
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        routed_dst_mac = "00:0c:0c:0c:0c:0c"
+        routed_src_mac = "00:22:bd:f8:19:ff"
 
 
-        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(src=ep.ip4.address, dst=rep_88.ip4.address) /
-             UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
-        rxs = self.send_and_expect(self.pg0, [p], self.pg4)
+        learnt = [{'mac': '00:00:11:11:11:02',
+                   'ip': '10.0.1.2',
+                   'ip6': '2001:10::2'},
+                  {'mac': '00:00:11:11:11:03',
+                   'ip': '10.0.1.3',
+                   'ip6': '2001:10::3'}]
 
         #
 
         #
-        # to appease the testcase we cannot have the registered EP stll
-        # present (because it's DP learnt) when the TC ends so wait until
-        # it is removed
+        # IP tables
         #
         #
-        self.wait_for_ep_timeout(ip=rep_88.ip4.address)
-        self.wait_for_ep_timeout(ip=rep_2.ip4.address)
-
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.add_vpp_config()
+
+        tun_ip4_uu = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
+                                       self.pg4.remote_ip4, 114)
+        tun_ip6_uu = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
+                                       self.pg4.remote_ip4, 116)
+        tun_ip4_uu.add_vpp_config()
+        tun_ip6_uu.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, 401, t4, t6, tun_ip4_uu, tun_ip6_uu)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac)
+
+        #
+        # Bind the BVI to the RD
+        #
+        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg2 hosts the vxlan tunnel
+        # hosts on pg2 to act as TEPs
+        # pg3 is BD uu-fwd
+        # pg4 is RD uu-fwd
+        #
+        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()
+        self.pg4.config_ip4()
+        self.pg4.resolve_arp()
+
+        #
+        # a GBP bridge domain with a BVI and a UU-flood interface
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, self.pg3)
+        gbd1.add_vpp_config()
+
+        self.logger.info(self.vapi.cli("sh bridge 1 detail"))
+        self.logger.info(self.vapi.cli("sh gbp bridge"))
+        self.logger.info(self.vapi.cli("sh gbp route"))
+
+        # ... and has a /32 and /128 applied
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 128)
+        ip6_addr.add_vpp_config()
+
+        #
+        # 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 in L3 mode with learning enabled
+        #
+        vx_tun_l3 = VppGbpVxlanTunnel(
+            self, 101, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
+            self.pg2.local_ip4)
+        vx_tun_l3.add_vpp_config()
+
+        #
+        # A static endpoint that the learnt endpoints are trying to
+        # talk to
+        #
+        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()
+
+        #
+        # learn some remote IPv4 EPs
+        #
+        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=101, gpid=441, flags=0x88) /
+                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rx = self.send_and_expect(self.pg2, [p], self.pg0)
+
+            # the new TEP
+            tep1_sw_if_index = find_vxlan_gbp_tunnel(
+                self,
+                self.pg2.local_ip4,
+                self.pg2.remote_hosts[1].ip4,
+                vx_tun_l3.vni)
+            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+
+            # endpoint learnt via the parent GBP-vxlan interface
+            self.assertTrue(find_gbp_endpoint(self,
+                                              vx_tun_l3._sw_if_index,
+                                              ip=l['ip']))
+
+        #
+        # Static IPv4 EP replies to learnt
+        #
+        for l in learnt:
+            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+                 IP(dst=l['ip'], src=ep.ip4.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rxs = self.send_and_expect(self.pg0, p * 1, self.pg2)
+
+            for rx in rxs:
+                self.assertEqual(rx[IP].src, self.pg2.local_ip4)
+                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
+                self.assertEqual(rx[UDP].dport, 48879)
+                # the UDP source port is a random value for hashing
+                self.assertEqual(rx[VXLAN].gpid, 441)
+                self.assertEqual(rx[VXLAN].vni, 101)
+                self.assertTrue(rx[VXLAN].flags.G)
+                self.assertTrue(rx[VXLAN].flags.Instance)
+                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.D)
+
+                inner = rx[VXLAN].payload
+
+                self.assertEqual(inner[Ether].src, routed_src_mac)
+                self.assertEqual(inner[Ether].dst, routed_dst_mac)
+                self.assertEqual(inner[IP].src, ep.ip4.address)
+                self.assertEqual(inner[IP].dst, l['ip'])
+
+        for l in learnt:
+            self.assertFalse(find_gbp_endpoint(self,
+                                               tep1_sw_if_index,
+                                               ip=l['ip']))
+
+        #
+        # learn some remote IPv6 EPs
+        #
+        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=101, gpid=441, flags=0x88) /
+                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+                 IPv6(src=l['ip6'], dst=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rx = self.send_and_expect(self.pg2, [p], self.pg0)
+
+            # the new TEP
+            tep1_sw_if_index = find_vxlan_gbp_tunnel(
+                self,
+                self.pg2.local_ip4,
+                self.pg2.remote_hosts[1].ip4,
+                vx_tun_l3.vni)
+            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+
+            self.logger.info(self.vapi.cli("show gbp bridge"))
+            self.logger.info(self.vapi.cli("show vxlan-gbp tunnel"))
+            self.logger.info(self.vapi.cli("show gbp vxlan"))
+            self.logger.info(self.vapi.cli("show int addr"))
+
+            # endpoint learnt via the TEP
+            self.assertTrue(find_gbp_endpoint(self, ip=l['ip6']))
+
+        self.logger.info(self.vapi.cli("show gbp endpoint"))
+        self.logger.info(self.vapi.cli("show ip fib index 1 %s" % l['ip']))
+
+        #
+        # Static EP replies to learnt
+        #
+        for l in learnt:
+            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+                 IPv6(dst=l['ip6'], src=ep.ip6.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            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)
+                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
+                self.assertEqual(rx[UDP].dport, 48879)
+                # the UDP source port is a random value for hashing
+                self.assertEqual(rx[VXLAN].gpid, 441)
+                self.assertEqual(rx[VXLAN].vni, 101)
+                self.assertTrue(rx[VXLAN].flags.G)
+                self.assertTrue(rx[VXLAN].flags.Instance)
+                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.D)
+
+                inner = rx[VXLAN].payload
+
+                self.assertEqual(inner[Ether].src, routed_src_mac)
+                self.assertEqual(inner[Ether].dst, routed_dst_mac)
+                self.assertEqual(inner[IPv6].src, ep.ip6.address)
+                self.assertEqual(inner[IPv6].dst, l['ip6'])
+
+        self.logger.info(self.vapi.cli("sh gbp endpoint"))
+        for l in learnt:
+            self.wait_for_ep_timeout(ip=l['ip'])
+
+        #
+        # Static sends to unknown EP with no route
+        #
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(dst="10.0.0.99", src=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        self.send_and_assert_no_replies(self.pg0, [p])
+
+        #
+        # Add a route to static EP's v4 and v6 subnet
+        #
+        se_10_24 = VppGbpSubnet(
+            self, rd1, "10.0.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT)
+        se_10_24.add_vpp_config()
+
+        #
+        # static pings router
+        #
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(dst=epg_220.bvi_ip4.address, src=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg0)
+
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IPv6(dst=epg_220.bvi_ip6.address, src=ep.ip6.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg0)
+
+        #
+        # packets to address in the subnet are sent on the uu-fwd
+        #
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(dst="10.0.0.99", src=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, [p], self.pg4)
+        for rx in rxs:
+            self.assertEqual(rx[IP].src, self.pg4.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg4.remote_ip4)
+            self.assertEqual(rx[UDP].dport, 48879)
+            # the UDP source port is a random value for hashing
+            self.assertEqual(rx[VXLAN].gpid, 441)
+            self.assertEqual(rx[VXLAN].vni, 114)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # policy is not applied to packets sent to the uu-fwd interfaces
+            self.assertFalse(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+        #
+        # learn some remote IPv4 EPs
+        #
+        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[2].ip4,
+                    dst=self.pg2.local_ip4) /
+                 UDP(sport=1234, dport=48879) /
+                 VXLAN(vni=101, gpid=441, flags=0x88) /
+                 Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            rx = self.send_and_expect(self.pg2, [p], self.pg0)
+
+            # the new TEP
+            tep1_sw_if_index = find_vxlan_gbp_tunnel(
+                self,
+                self.pg2.local_ip4,
+                self.pg2.remote_hosts[2].ip4,
+                vx_tun_l3.vni)
+            self.assertNotEqual(INDEX_INVALID, tep1_sw_if_index)
+
+            # endpoint learnt via the parent GBP-vxlan interface
+            self.assertTrue(find_gbp_endpoint(self,
+                                              vx_tun_l3._sw_if_index,
+                                              ip=l['ip']))
+
+        #
+        # Add a remote endpoint from the API
+        #
+        rep_88 = VppGbpEndpoint(self, vx_tun_l3,
+                                epg_220, None,
+                                "10.0.0.88", "11.0.0.88",
+                                "2001:10::88", "3001::88",
+                                ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
+                                self.pg2.local_ip4,
+                                self.pg2.remote_hosts[2].ip4,
+                                mac=None)
+        rep_88.add_vpp_config()
+
+        #
+        # Add a remote endpoint from the API that matches an existing one
+        # this is a lower priority, hence the packet is sent to the DP leanrt
+        # TEP
+        #
+        rep_2 = VppGbpEndpoint(self, vx_tun_l3,
+                               epg_220, None,
+                               learnt[0]['ip'], "11.0.0.101",
+                               learnt[0]['ip6'], "3001::101",
+                               ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
+                               self.pg2.local_ip4,
+                               self.pg2.remote_hosts[1].ip4,
+                               mac=None)
+        rep_2.add_vpp_config()
+
+        #
+        # 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(
+            self, rd1, "10.0.1.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT)
+        se_10_1_24.add_vpp_config()
+
+        self.logger.info(self.vapi.cli("show gbp endpoint"))
+
+        ips = ["10.0.0.88", learnt[0]['ip']]
+        for ip in ips:
+            p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+                 IP(dst=ip, src=ep.ip4.address) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw('\xa5' * 100))
+
+            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)
+                self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[2].ip4)
+                self.assertEqual(rx[UDP].dport, 48879)
+                # the UDP source port is a random value for hashing
+                self.assertEqual(rx[VXLAN].gpid, 441)
+                self.assertEqual(rx[VXLAN].vni, 101)
+                self.assertTrue(rx[VXLAN].flags.G)
+                self.assertTrue(rx[VXLAN].flags.Instance)
+                self.assertTrue(rx[VXLAN].gpflags.A)
+                self.assertFalse(rx[VXLAN].gpflags.D)
+
+                inner = rx[VXLAN].payload
+
+                self.assertEqual(inner[Ether].src, routed_src_mac)
+                self.assertEqual(inner[Ether].dst, routed_dst_mac)
+                self.assertEqual(inner[IP].src, ep.ip4.address)
+                self.assertEqual(inner[IP].dst, ip)
+
+        #
+        # remove the API remote EPs, only API sourced is gone, the DP
+        # learnt one remains
+        #
+        rep_88.remove_vpp_config()
+        rep_2.remove_vpp_config()
+
+        self.assertTrue(find_gbp_endpoint(self, ip=rep_2.ip4.address))
+
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(src=ep.ip4.address, dst=rep_2.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg0, [p], self.pg2)
+
+        self.assertFalse(find_gbp_endpoint(self, ip=rep_88.ip4.address))
+
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(src=ep.ip4.address, dst=rep_88.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg0, [p], self.pg4)
+
+        #
+        # 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
+        #
+        self.wait_for_ep_timeout(ip=rep_88.ip4.address)
+        self.wait_for_ep_timeout(ip=rep_2.ip4.address)
+
+        #
+        # Same as above, learn a remote EP via CP and DP
+        # this time remove the DP one first. expect the CP data to remain
+        #
+        rep_3 = VppGbpEndpoint(self, vx_tun_l3,
+                               epg_220, None,
+                               "10.0.1.4", "11.0.0.103",
+                               "2001::10:3", "3001::103",
+                               ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
+                               self.pg2.local_ip4,
+                               self.pg2.remote_hosts[1].ip4,
+                               mac=None)
+        rep_3.add_vpp_config()
+
+        p = (Ether(src=self.pg2.remote_mac,
+                   dst=self.pg2.local_mac) /
+             IP(src=self.pg2.remote_hosts[2].ip4,
+                dst=self.pg2.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=101, gpid=441, flags=0x88) /
+             Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+             IP(src="10.0.1.4", dst=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
+
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip=rep_3.ip4.address,
+                                          tep=[self.pg2.local_ip4,
+                                               self.pg2.remote_hosts[2].ip4]))
+
+        p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
+             IP(dst="10.0.1.4", src=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+        rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
+
+        # host 2 is the DP learned TEP
+        for rx in rxs:
+            self.assertEqual(rx[IP].src, self.pg2.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[2].ip4)
+
+        self.wait_for_ep_timeout(ip=rep_3.ip4.address,
+                                 tep=[self.pg2.local_ip4,
+                                      self.pg2.remote_hosts[2].ip4])
+
+        rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
+
+        # host 1 is the CP learned TEP
+        for rx in rxs:
+            self.assertEqual(rx[IP].src, self.pg2.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg2.remote_hosts[1].ip4)
+
+        #
+        # shutdown with learnt endpoint present
+        #
+        p = (Ether(src=self.pg2.remote_mac,
+                   dst=self.pg2.local_mac) /
+             IP(src=self.pg2.remote_hosts[1].ip4,
+                dst=self.pg2.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=101, gpid=441, flags=0x88) /
+             Ether(src=l['mac'], dst="00:00:00:11:11:11") /
+             IP(src=learnt[1]['ip'], dst=ep.ip4.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rx = self.send_and_expect(self.pg2, [p], self.pg0)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip=l['ip']))
+
+        #
+        # TODO
+        # remote endpoint becomes local
+        #
+        self.pg2.unconfig_ip4()
+        self.pg3.unconfig_ip4()
+        self.pg4.unconfig_ip4()
+
+    def test_gbp_redirect(self):
+        """ GBP Endpoint Redirect """
+
+        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"
+        routed_src_mac = "00:22:bd:f8:19:ff"
+
+        learnt = [{'mac': '00:00:11:11:11:02',
+                   'ip': '10.0.1.2',
+                   'ip6': '2001:10::2'},
+                  {'mac': '00:00:11:11:11:03',
+                   'ip': '10.0.1.3',
+                   'ip6': '2001:10::3'}]
+
+        #
+        # IP tables
+        #
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, 402, t4, t6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac)
+
+        #
+        # Bind the BVI to the RD
+        #
+        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg7 hosts a BD's UU-fwd
+        #
+        self.pg7.config_ip4()
+        self.pg7.resolve_arp()
+
+        #
+        # a GBP bridge domains for the EPs
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0)
+        gbd1.add_vpp_config()
+
+        bd2 = VppBridgeDomain(self, 2)
+        bd2.add_vpp_config()
+        gbd2 = VppGbpBridgeDomain(self, bd2, rd1, self.loop1)
+        gbd2.add_vpp_config()
+
+        # ... and has a /32 and /128 applied
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 128)
+        ip6_addr.add_vpp_config()
+        ip4_addr = VppIpInterfaceAddress(self, gbd2.bvi, "10.0.1.128", 32)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd2.bvi, "2001:11::128", 128)
+        ip6_addr.add_vpp_config()
+
+        #
+        # The Endpoint-groups in which we are learning endpoints
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 440, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_220.add_vpp_config()
+        epg_221 = VppGbpEndpointGroup(self, 221, 441, rd1, gbd2,
+                                      None, gbd2.bvi,
+                                      "10.0.1.128",
+                                      "2001:11::128",
+                                      VppGbpEndpointRetention(2))
+        epg_221.add_vpp_config()
+        epg_222 = VppGbpEndpointGroup(self, 222, 442, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.2.128",
+                                      "2001:12::128",
+                                      VppGbpEndpointRetention(2))
+        epg_222.add_vpp_config()
+
+        #
+        # a GBP bridge domains for the SEPs
+        #
+        bd_uu1 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   self.pg7.remote_ip4, 116)
+        bd_uu1.add_vpp_config()
+        bd_uu2 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   self.pg7.remote_ip4, 117)
+        bd_uu2.add_vpp_config()
+
+        bd3 = VppBridgeDomain(self, 3)
+        bd3.add_vpp_config()
+        gbd3 = VppGbpBridgeDomain(self, bd3, rd1, self.loop2,
+                                  bd_uu1, learn=False)
+        gbd3.add_vpp_config()
+        bd4 = VppBridgeDomain(self, 4)
+        bd4.add_vpp_config()
+        gbd4 = VppGbpBridgeDomain(self, bd4, rd1, self.loop3,
+                                  bd_uu2, learn=False)
+        gbd4.add_vpp_config()
+
+        #
+        # EPGs in which the service endpoints exist
+        #
+        epg_320 = VppGbpEndpointGroup(self, 320, 550, rd1, gbd3,
+                                      None, gbd1.bvi,
+                                      "12.0.0.128",
+                                      "4001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_320.add_vpp_config()
+        epg_321 = VppGbpEndpointGroup(self, 321, 551, rd1, gbd4,
+                                      None, gbd2.bvi,
+                                      "12.0.1.128",
+                                      "4001:11::128",
+                                      VppGbpEndpointRetention(2))
+        epg_321.add_vpp_config()
+
+        #
+        # three local endpoints
+        #
+        ep1 = VppGbpEndpoint(self, self.pg0,
+                             epg_220, None,
+                             "10.0.0.1", "11.0.0.1",
+                             "2001:10::1", "3001:10::1")
+        ep1.add_vpp_config()
+        ep2 = VppGbpEndpoint(self, self.pg1,
+                             epg_221, None,
+                             "10.0.1.1", "11.0.1.1",
+                             "2001:11::1", "3001:11::1")
+        ep2.add_vpp_config()
+        ep3 = VppGbpEndpoint(self, self.pg2,
+                             epg_222, None,
+                             "10.0.2.2", "11.0.2.2",
+                             "2001:12::1", "3001:12::1")
+        ep3.add_vpp_config()
+
+        #
+        # service endpoints
+        #
+        sep1 = VppGbpEndpoint(self, self.pg3,
+                              epg_320, None,
+                              "12.0.0.1", "13.0.0.1",
+                              "4001:10::1", "5001:10::1")
+        sep1.add_vpp_config()
+        sep2 = VppGbpEndpoint(self, self.pg4,
+                              epg_320, None,
+                              "12.0.0.2", "13.0.0.2",
+                              "4001:10::2", "5001:10::2")
+        sep2.add_vpp_config()
+        sep3 = VppGbpEndpoint(self, self.pg5,
+                              epg_321, None,
+                              "12.0.1.1", "13.0.1.1",
+                              "4001:11::1", "5001:11::1")
+        sep3.add_vpp_config()
+        # this EP is not installed immediately
+        sep4 = VppGbpEndpoint(self, self.pg6,
+                              epg_321, None,
+                              "12.0.1.2", "13.0.1.2",
+                              "4001:11::2", "5001:11::2")
+
+        #
+        # an L2 switch packet between local EPs in different EPGs
+        #  different dest ports on each so the are LB hashed differently
         #
         #
-        # shutdown with learnt endpoint present
+        p4 = [(Ether(src=ep1.mac, dst=ep3.mac) /
+               IP(src=ep1.ip4.address, dst=ep3.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep3.mac, dst=ep1.mac) /
+               IP(src=ep3.ip4.address, dst=ep1.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=ep3.mac) /
+               IPv6(src=ep1.ip6.address, dst=ep3.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep3.mac, dst=ep1.mac) /
+               IPv6(src=ep3.ip6.address, dst=ep1.ip6.address) /
+               UDP(sport=1234, dport=1230) /
+               Raw('\xa5' * 100))]
+
+        # should be dropped since no contract yet
+        self.send_and_assert_no_replies(self.pg0, [p4[0]])
+        self.send_and_assert_no_replies(self.pg0, [p6[0]])
+
         #
         #
-        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) /
+        # Add a contract with a rule to load-balance redirect via SEP1 and SEP2
+        # one of the next-hops is via an EP that is not known
+        #
+        acl = VppGbpAcl(self)
+        rule4 = acl.create_rule(permit_deny=1, proto=17)
+        rule6 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
+        acl_index = acl.add_vpp_config([rule4, rule6])
+
+        #
+        # test the src-ip hash mode
+        #
+        c1 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+
+        #
+        # send again with the contract preset, now packets arrive
+        # at SEP1 or SEP2 depending on the hashing
+        #
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+
+        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep2.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep2.mac)
+            self.assertEqual(rx[IP].src, ep3.ip4.address)
+            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
+
+            self.assertEqual(inner[Ether].src, routed_src_mac)
+            self.assertEqual(inner[Ether].dst, sep4.mac)
+            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
+
+        rxs = self.send_and_expect(self.pg2, p6[1] * 17, sep3.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep3.mac)
+            self.assertEqual(rx[IPv6].src, ep3.ip6.address)
+            self.assertEqual(rx[IPv6].dst, ep1.ip6.address)
+
+        #
+        # programme the unknown EP
+        #
+        sep4.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep4.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, ep3.ip6.address)
+
+        #
+        # and revert back to unprogrammed
+        #
+        sep4.remove_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
+
+            self.assertEqual(inner[Ether].src, routed_src_mac)
+            self.assertEqual(inner[Ether].dst, sep4.mac)
+            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
+
+        c1.remove_vpp_config()
+        c2.remove_vpp_config()
+
+        #
+        # test the symmetric hash mode
+        #
+        c1 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+
+        #
+        # send again with the contract preset, now packets arrive
+        # at SEP1 for both directions
+        #
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+
+        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep3.ip4.address)
+            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+
+        #
+        # programme the unknown EP for the L3 tests
+        #
+        sep4.add_vpp_config()
+
+        #
+        # an L3 switch packet between local EPs in different EPGs
+        #  different dest ports on each so the are LB hashed differently
+        #
+        p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IP(src=ep1.ip4.address, dst=ep2.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
+               IP(src=ep2.ip4.address, dst=ep1.ip4.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IPv6(src=ep1.ip6.address, dst=ep2.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
+               IPv6(src=ep2.ip6.address, dst=ep1.ip6.address) /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+
+        c3 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c3.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, ep2.ip4.address)
+
+        #
+        # learn a remote EP in EPG 221
+        #
+        vx_tun_l3 = VppGbpVxlanTunnel(
+            self, 444, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
+            self.pg2.local_ip4)
+        vx_tun_l3.add_vpp_config()
+
+        c4 = VppGbpContract(
+            self, 402, epg_221.sclass, epg_220.sclass, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c4.add_vpp_config()
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
              UDP(sport=1234, dport=48879) /
              UDP(sport=1234, dport=48879) /
-             VXLAN(vni=101, gpid=441, flags=0x88) /
-             Ether(src=l['mac'], dst="00:00:00:11:11:11") /
-             IP(src=learnt[1]['ip'], dst=ep.ip4.address) /
+             VXLAN(vni=444, gpid=441, flags=0x88) /
+             Ether(src="00:22:22:22:22:33", dst=str(self.router_mac)) /
+             IP(src="10.0.0.88", dst=ep1.ip4.address) /
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg2, [p], self.pg0)
+        rx = self.send_and_expect(self.pg7, [p], self.pg0)
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
                                           vx_tun_l3._sw_if_index,
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
                                           vx_tun_l3._sw_if_index,
-                                          ip=l['ip']))
+                                          ip="10.0.0.88"))
+
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=441, flags=0x88) /
+             Ether(src="00:22:22:22:22:33", dst=str(self.router_mac)) /
+             IPv6(src="2001:10::88", dst=ep1.ip6.address) /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip="2001:10::88"))
 
         #
 
         #
-        # TODO
-        # remote endpoint becomes local
+        # L3 switch from local to remote EP
         #
         #
-        self.pg2.unconfig_ip4()
-        self.pg3.unconfig_ip4()
-        self.pg4.unconfig_ip4()
+        p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IP(src=ep1.ip4.address, dst="10.0.0.88") /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IPv6(src=ep1.ip6.address, dst="2001:10::88") /
+               UDP(sport=1234, dport=1234) /
+               Raw('\xa5' * 100))]
 
 
-    def test_gbp_redirect(self):
-        """ GBP Endpoint Redirect """
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, "10.0.0.88")
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep4.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+
+        #
+        # test the dst-ip hash mode
+        #
+        c5 = VppGbpContract(
+            self, 402, 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,
+                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
+                                       sep1.ip4, sep1.epg.rd),
+                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
+                                       sep2.ip4, sep2.epg.rd)]),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_DST_IP,
+                    [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
+                                           sep3.ip6, sep3.epg.rd),
+                     VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
+                                           sep4.ip6, sep4.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c5.add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].dst, "10.0.0.88")
+
+        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep3.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep3.mac)
+            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
+            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+
+        #
+        # cleanup
+        #
+        self.pg7.unconfig_ip4()
 
 
-        self.vapi.cli("set logging class gbp debug")
+    def test_gbp_l3_out(self):
+        """ GBP L3 Out """
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        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"
 
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
         routed_src_mac = "00:22:bd:f8:19:ff"
 
-        learnt = [{'mac': '00:00:11:11:11:02',
-                   'ip': '10.0.1.2',
-                   'ip6': '2001:10::2'},
-                  {'mac': '00:00:11:11:11:03',
-                   'ip': '10.0.1.3',
-                   'ip6': '2001:10::3'}]
-
         #
         # IP tables
         #
         #
         # IP tables
         #
@@ -2567,174 +3869,299 @@ class TestGBP(VppTestCase):
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
-        rd1.add_vpp_config()
+        rd1 = VppGbpRouteDomain(self, 2, 55, t4, t6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac)
+
+        #
+        # Bind the BVI to the RD
+        #
+        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg7 hosts a BD's BUM
+        # Pg1 some other l3 interface
+        #
+        self.pg7.config_ip4()
+        self.pg7.resolve_arp()
+
+        #
+        # a multicast vxlan-gbp tunnel for broadcast in the BD
+        #
+        tun_bm = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   "239.1.1.1", 88,
+                                   mcast_itf=self.pg7)
+        tun_bm.add_vpp_config()
+
+        #
+        # a GBP external bridge domains for the EPs
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd1.add_vpp_config()
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, None, tun_bm)
+        gbd1.add_vpp_config()
+
+        #
+        # The Endpoint-groups in which the external endpoints exist
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 113, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_220.add_vpp_config()
+
+        # the BVIs have the subnets applied ...
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 24)
+        ip4_addr.add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 64)
+        ip6_addr.add_vpp_config()
+
+        # ... which are L3-out subnets
+        l3o_1 = VppGbpSubnet(
+            self, rd1, "10.0.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=113)
+        l3o_1.add_vpp_config()
+
+        #
+        # an external interface attached to the outside world and the
+        # external BD
+        #
+        VppL2Vtr(self, self.vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        VppL2Vtr(self, self.vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        vlan_144 = VppDot1QSubint(self, self.pg0, 144)
+        vlan_144.admin_up()
+        # vlan_102 is not poped
+
+        #
+        # an unicast vxlan-gbp for inter-RD traffic
+        #
+        vx_tun_l3 = VppGbpVxlanTunnel(
+            self, 444, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
+            self.pg2.local_ip4)
+        vx_tun_l3.add_vpp_config()
+
+        #
+        # External Endpoints
+        #
+        eep1 = VppGbpEndpoint(self, 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, 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, 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()
+
+        #
+        # EP1 impersonating EP3 is dropped
+        #
+        p = (Ether(src=eep1.mac, dst="ff:ff:ff:ff:ff:ff") /
+             Dot1Q(vlan=100) /
+             ARP(op="who-has",
+                 psrc="10.0.0.3", pdst="10.0.0.128",
+                 hwsrc=eep1.mac, hwdst="ff:ff:ff:ff:ff:ff"))
+        self.send_and_assert_no_replies(self.pg0, p)
+
+        #
+        # 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=eep3.mac, dst="ff:ff:ff:ff:ff:ff") /
+                 Dot1Q(vlan=102) /
+                 ARP(op="who-has",
+                     psrc=eep3.ip4.address, pdst="10.0.0.128",
+                     hwsrc=eep3.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=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=eep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=100) /
+              IPv6(src="2001:10::1", dst="2001:10::88") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p4 * 1, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            # self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, "239.1.1.1")
+            self.assertEqual(rx[VXLAN].vni, 88)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # policy was applied to the original IP packet
+            self.assertEqual(rx[VXLAN].gpid, 113)
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertFalse(rx[VXLAN].gpflags.D)
+
+            inner = rx[VXLAN].payload
 
 
-        self.loop0.set_mac(self.router_mac)
+            self.assertTrue(inner.haslayer(ARP))
 
         #
 
         #
-        # Bind the BVI to the RD
+        # remote to external
         #
         #
-        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
-        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=113, flags=0x88) /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IP(src="10.0.0.101", dst="10.0.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
 
 
-        #
-        # Pg7 hosts a BD's UU-fwd
-        #
-        self.pg7.config_ip4()
-        self.pg7.resolve_arp()
+        rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
 
         #
-        # a GBP bridge domains for the EPs
+        # local EP pings router
         #
         #
-        bd1 = VppBridgeDomain(self, 1)
-        bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
-        gbd1.add_vpp_config()
+        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'))
 
 
-        bd2 = VppBridgeDomain(self, 2)
-        bd2.add_vpp_config()
-        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
-        gbd2.add_vpp_config()
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
 
-        # ... and has a /32 and /128 applied
-        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 32)
-        ip4_addr.add_vpp_config()
-        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 128)
-        ip6_addr.add_vpp_config()
-        ip4_addr = VppIpInterfaceAddress(self, gbd2.bvi, "10.0.1.128", 32)
-        ip4_addr.add_vpp_config()
-        ip6_addr = VppIpInterfaceAddress(self, gbd2.bvi, "2001:11::128", 128)
-        ip6_addr.add_vpp_config()
+        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)
 
         #
 
         #
-        # The Endpoint-groups in which we are learning endpoints
+        # local EP pings other local EP
         #
         #
-        epg_220 = VppGbpEndpointGroup(self, 220, 440, rd1, gbd1,
-                                      None, gbd1.bvi,
-                                      "10.0.0.128",
-                                      "2001:10::128",
-                                      VppGbpEndpointRetention(2))
-        epg_220.add_vpp_config()
-        epg_221 = VppGbpEndpointGroup(self, 221, 441, rd1, gbd2,
-                                      None, gbd2.bvi,
-                                      "10.0.1.128",
-                                      "2001:11::128",
-                                      VppGbpEndpointRetention(2))
-        epg_221.add_vpp_config()
-        epg_222 = VppGbpEndpointGroup(self, 222, 442, rd1, gbd1,
-                                      None, gbd1.bvi,
-                                      "10.0.2.128",
-                                      "2001:12::128",
-                                      VppGbpEndpointRetention(2))
-        epg_222.add_vpp_config()
+        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)
 
         #
 
         #
-        # a GBP bridge domains for the SEPs
+        # local EP pings router w/o vlan tag poped
         #
         #
-        bd_uu1 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
-                                   self.pg7.remote_ip4, 116)
-        bd_uu1.add_vpp_config()
-        bd_uu2 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
-                                   self.pg7.remote_ip4, 117)
-        bd_uu2.add_vpp_config()
+        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'))
 
 
-        bd3 = VppBridgeDomain(self, 3)
-        bd3.add_vpp_config()
-        gbd3 = VppGbpBridgeDomain(self, bd3, self.loop2, bd_uu1, learn=False)
-        gbd3.add_vpp_config()
-        bd4 = VppBridgeDomain(self, 4)
-        bd4.add_vpp_config()
-        gbd4 = VppGbpBridgeDomain(self, bd4, self.loop3, bd_uu2, learn=False)
-        gbd4.add_vpp_config()
+        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, self.vlan_102.remote_mac)
 
         #
 
         #
-        # EPGs in which the service endpoints exist
+        # A ip4 subnet reachable through the external EP1
         #
         #
-        epg_320 = VppGbpEndpointGroup(self, 320, 550, rd1, gbd3,
-                                      None, gbd1.bvi,
-                                      "12.0.0.128",
-                                      "4001:10::128",
-                                      VppGbpEndpointRetention(2))
-        epg_320.add_vpp_config()
-        epg_321 = VppGbpEndpointGroup(self, 321, 551, rd1, gbd4,
-                                      None, gbd2.bvi,
-                                      "12.0.1.128",
-                                      "4001:11::128",
-                                      VppGbpEndpointRetention(2))
-        epg_321.add_vpp_config()
+        ip_220 = VppIpRoute(self, "10.220.0.0", 24,
+                            [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,
+            sclass=4220)
+        l3o_220.add_vpp_config()
 
         #
 
         #
-        # three local endpoints
+        # An ip6 subnet reachable through the external EP1
         #
         #
-        ep1 = VppGbpEndpoint(self, self.pg0,
-                             epg_220, None,
-                             "10.0.0.1", "11.0.0.1",
-                             "2001:10::1", "3001:10::1")
-        ep1.add_vpp_config()
-        ep2 = VppGbpEndpoint(self, self.pg1,
-                             epg_221, None,
-                             "10.0.1.1", "11.0.1.1",
-                             "2001:11::1", "3001:11::1")
-        ep2.add_vpp_config()
-        ep3 = VppGbpEndpoint(self, self.pg2,
-                             epg_222, None,
-                             "10.0.2.2", "11.0.2.2",
-                             "2001:12::1", "3001:12::1")
-        ep3.add_vpp_config()
+        ip6_220 = VppIpRoute(self, "10:220::", 64,
+                             [VppRoutePath(eep1.ip6.address,
+                                           eep1.epg.bvi.sw_if_index)],
+                             table_id=t6.table_id)
+        ip6_220.add_vpp_config()
+
+        l3o6_220 = VppGbpSubnet(
+            self, rd1, "10:220::", 64,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4220)
+        l3o6_220.add_vpp_config()
 
         #
 
         #
-        # service endpoints
+        # A subnet reachable through the external EP2
         #
         #
-        sep1 = VppGbpEndpoint(self, self.pg3,
-                              epg_320, None,
-                              "12.0.0.1", "13.0.0.1",
-                              "4001:10::1", "5001:10::1")
-        sep1.add_vpp_config()
-        sep2 = VppGbpEndpoint(self, self.pg4,
-                              epg_320, None,
-                              "12.0.0.2", "13.0.0.2",
-                              "4001:10::2", "5001:10::2")
-        sep2.add_vpp_config()
-        sep3 = VppGbpEndpoint(self, self.pg5,
-                              epg_321, None,
-                              "12.0.1.1", "13.0.1.1",
-                              "4001:11::1", "5001:11::1")
-        sep3.add_vpp_config()
-        # this EP is not installed immediately
-        sep4 = VppGbpEndpoint(self, self.pg6,
-                              epg_321, None,
-                              "12.0.1.2", "13.0.1.2",
-                              "4001:11::2", "5001:11::2")
+        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()
 
         #
 
         #
-        # an L2 switch packet between local EPs in different EPGs
-        #  different dest ports on each so the are LB hashed differently
+        # ping between hosts in remote subnets
+        #  dropped without a contract
         #
         #
-        p4 = [(Ether(src=ep1.mac, dst=ep3.mac) /
-               IP(src=ep1.ip4.address, dst=ep3.ip4.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
-              (Ether(src=ep3.mac, dst=ep1.mac) /
-               IP(src=ep3.ip4.address, dst=ep1.ip4.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
-        p6 = [(Ether(src=ep1.mac, dst=ep3.mac) /
-               IPv6(src=ep1.ip6.address, dst=ep3.ip6.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
-              (Ether(src=ep3.mac, dst=ep1.mac) /
-               IPv6(src=ep3.ip6.address, dst=ep1.ip6.address) /
-               UDP(sport=1234, dport=1230) /
-               Raw('\xa5' * 100))]
+        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'))
 
 
-        # should be dropped since no contract yet
-        self.send_and_assert_no_replies(self.pg0, [p4[0]])
-        self.send_and_assert_no_replies(self.pg0, [p6[0]])
+        self.send_and_assert_no_replies(self.pg0, p * 1)
 
         #
 
         #
-        # Add a contract with a rule to load-balance redirect via SEP1 and SEP2
-        # one of the next-hops is via an EP that is not known
+        # contract for the external nets to communicate
         #
         acl = VppGbpAcl(self)
         rule4 = acl.create_rule(permit_deny=1, proto=17)
         #
         acl = VppGbpAcl(self)
         rule4 = acl.create_rule(permit_deny=1, proto=17)
@@ -2742,383 +4169,477 @@ class TestGBP(VppTestCase):
         acl_index = acl.add_vpp_config([rule4, rule6])
 
         #
         acl_index = acl.add_vpp_config([rule4, rule6])
 
         #
-        # test the src-ip hash mode
+        # A contract with the wrong scope is not matched
         #
         #
+        c_44 = VppGbpContract(
+            self, 44, 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])
+        c_44.add_vpp_config()
+        self.send_and_assert_no_replies(self.pg0, p * 1)
+
         c1 = VppGbpContract(
         c1 = VppGbpContract(
-            self, epg_220.sclass, epg_222.sclass, acl_index,
+            self, 55, 4220, 4221, acl_index,
             [VppGbpContractRule(
             [VppGbpContractRule(
-                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
+        #
+        # Contracts allowing ext-net 200 to talk with external EPs
+        #
         c2 = VppGbpContract(
         c2 = VppGbpContract(
-            self, epg_222.sclass, epg_220.sclass, acl_index,
+            self, 55, 4220, 113, acl_index,
             [VppGbpContractRule(
             [VppGbpContractRule(
-                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
+        c3 = VppGbpContract(
+            self, 55, 113, 4220, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
+            [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"))
 
         #
 
         #
-        # send again with the contract preset, now packets arrive
-        # at SEP1 or SEP2 depending on the hashing
+        # from remote external EP to local external EP
         #
         #
-        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
-
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
-            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=113, flags=0x88) /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IP(src="10.0.0.101", dst="10.220.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
 
 
-        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep2.itf)
+        rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep2.mac)
-            self.assertEqual(rx[IP].src, ep3.ip4.address)
-            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+        #
+        # 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, p6[0] * 17, self.pg7)
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg7)
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, self.pg7.local_mac)
 
         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[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[IP].src, self.pg7.local_ip4)
             self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
-            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertEqual(rx[VXLAN].vni, 444)
             self.assertTrue(rx[VXLAN].flags.G)
             self.assertTrue(rx[VXLAN].flags.Instance)
             self.assertTrue(rx[VXLAN].flags.G)
             self.assertTrue(rx[VXLAN].flags.Instance)
-            # redirect policy has been applied
+            # 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)
             self.assertTrue(rx[VXLAN].gpflags.A)
-            self.assertFalse(rx[VXLAN].gpflags.D)
-
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
             inner = rx[VXLAN].payload
             inner = rx[VXLAN].payload
+            self.assertEqual(inner[IP].src, "10.220.0.1")
+            self.assertEqual(inner[IP].dst, rep.ip4.address)
 
 
-            self.assertEqual(inner[Ether].src, routed_src_mac)
-            self.assertEqual(inner[Ether].dst, sep4.mac)
-            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
-            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
+        #
+        # An external subnet reachable via the remote external EP
+        #
 
 
-        rxs = self.send_and_expect(self.pg2, p6[1] * 17, sep3.itf)
+        #
+        # first the VXLAN-GBP tunnel over which it is reached
+        #
+        vx_tun_r1 = 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_r1.add_vpp_config()
+        VppIpInterfaceBind(self, vx_tun_r1, t4).add_vpp_config()
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep3.mac)
-            self.assertEqual(rx[IPv6].src, ep3.ip6.address)
-            self.assertEqual(rx[IPv6].dst, ep1.ip6.address)
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
 
         #
 
         #
-        # programme the unknown EP
+        # then the special adj to resolve through on that tunnel
         #
         #
-        sep4.add_vpp_config()
+        n1 = VppNeighbor(self,
+                         vx_tun_r1.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip4)
+        n1.add_vpp_config()
 
 
-        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+        #
+        # the route via the adj above
+        #
+        ip_222 = VppIpRoute(self, "10.222.0.0", 24,
+                            [VppRoutePath(self.pg7.remote_ip4,
+                                          vx_tun_r1.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_222.add_vpp_config()
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep4.mac)
-            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
-            self.assertEqual(rx[IPv6].dst, ep3.ip6.address)
+        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()
 
         #
 
         #
-        # and revert back to unprogrammed
+        # ping between hosts in local and remote external subnets
+        #  dropped without a contract
         #
         #
-        sep4.remove_vpp_config()
+        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, p6[0] * 17, self.pg7)
+        rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
+
+        #
+        # Add contracts ext-nets for 220 -> 222
+        #
+        c4 = VppGbpContract(
+            self, 55, 4220, 4222, acl_index,
+            [VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                []),
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
+            [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)
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, self.pg7.local_mac)
             self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
             self.assertEqual(rx[IP].src, self.pg7.local_ip4)
             self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
-            self.assertEqual(rx[VXLAN].vni, 117)
+            self.assertEqual(rx[VXLAN].vni, 445)
             self.assertTrue(rx[VXLAN].flags.G)
             self.assertTrue(rx[VXLAN].flags.Instance)
             self.assertTrue(rx[VXLAN].flags.G)
             self.assertTrue(rx[VXLAN].flags.Instance)
-            # redirect policy has been applied
+            # 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)
             self.assertTrue(rx[VXLAN].gpflags.A)
-            self.assertFalse(rx[VXLAN].gpflags.D)
-
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
             inner = rx[VXLAN].payload
             inner = rx[VXLAN].payload
-
-            self.assertEqual(inner[Ether].src, routed_src_mac)
-            self.assertEqual(inner[Ether].dst, sep4.mac)
-            self.assertEqual(inner[IPv6].src, ep1.ip6.address)
-            self.assertEqual(inner[IPv6].dst, ep3.ip6.address)
-
-        c1.remove_vpp_config()
-        c2.remove_vpp_config()
+            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")
 
         #
 
         #
-        # test the symmetric hash mode
+        # make the external subnet ECMP
         #
         #
-        c1 = VppGbpContract(
-            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,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
-            [ETH_P_IP, ETH_P_IPV6])
-        c1.add_vpp_config()
+        vx_tun_r2 = VppVxlanGbpTunnel(
+            self, self.pg7.local_ip4,
+            self.pg7.remote_ip4, 446,
+            mode=(VppEnum.vl_api_vxlan_gbp_api_tunnel_mode_t.
+                  VXLAN_GBP_API_TUNNEL_MODE_L3))
+        vx_tun_r2.add_vpp_config()
+        VppIpInterfaceBind(self, vx_tun_r2, t4).add_vpp_config()
 
 
-        c2 = VppGbpContract(
-            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,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
-            [ETH_P_IP, ETH_P_IPV6])
-        c2.add_vpp_config()
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
+
+        n2 = VppNeighbor(self,
+                         vx_tun_r2.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip4)
+        n2.add_vpp_config()
+
+        ip_222.modify([VppRoutePath(self.pg7.remote_ip4,
+                                    vx_tun_r1.sw_if_index),
+                       VppRoutePath(self.pg7.remote_ip4,
+                                    vx_tun_r2.sw_if_index)])
 
         #
 
         #
-        # send again with the contract preset, now packets arrive
-        # at SEP1 for both directions
+        # now expect load-balance
         #
         #
-        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
-
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
-            self.assertEqual(rx[IP].dst, ep3.ip4.address)
+        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)),
+             (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=1222, dport=1235) /
+             Raw('\xa5' * 100))]
 
 
-        rxs = self.send_and_expect(self.pg2, p4[1] * 17, sep1.itf)
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep3.ip4.address)
-            self.assertEqual(rx[IP].dst, ep1.ip4.address)
+        self.assertEqual(rxs[0][VXLAN].vni, 445)
+        self.assertEqual(rxs[1][VXLAN].vni, 446)
 
         #
 
         #
-        # programme the unknown EP for the L3 tests
+        # Same LB test for v6
         #
         #
-        sep4.add_vpp_config()
+        n3 = VppNeighbor(self,
+                         vx_tun_r1.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip6)
+        n3.add_vpp_config()
+        n4 = VppNeighbor(self,
+                         vx_tun_r2.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip6)
+        n4.add_vpp_config()
+
+        ip_222_6 = VppIpRoute(self, "10:222::", 64,
+                              [VppRoutePath(self.pg7.remote_ip6,
+                                            vx_tun_r1.sw_if_index),
+                               VppRoutePath(self.pg7.remote_ip6,
+                                            vx_tun_r2.sw_if_index)],
+                              table_id=t6.table_id)
+        ip_222_6.add_vpp_config()
+
+        l3o_222_6 = VppGbpSubnet(
+            self, rd1, "10:222::", 64,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4222)
+        l3o_222_6.add_vpp_config()
 
 
-        #
-        # an L3 switch packet between local EPs in different EPGs
-        #  different dest ports on each so the are LB hashed differently
-        #
-        p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IP(src=ep1.ip4.address, dst=ep2.ip4.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
-              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
-               IP(src=ep2.ip4.address, dst=ep1.ip4.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
-        p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep1.ip6.address, dst=ep2.ip6.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
-              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep2.ip6.address, dst=ep1.ip6.address) /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+        p = [(Ether(src=eep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=100) /
+              IPv6(src="10:220::1", dst="10:222::1") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100)),
+             (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=100) /
+              IPv6(src="10:220::1", dst="10:222::1") /
+              UDP(sport=7777, dport=8881) /
+             Raw('\xa5' * 100))]
 
 
-        c3 = VppGbpContract(
-            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,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
-            [ETH_P_IP, ETH_P_IPV6])
-        c3.add_vpp_config()
+        self.logger.info(self.vapi.cli("sh ip6 fib 10:222::1"))
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
 
 
-        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+        self.assertEqual(rxs[0][VXLAN].vni, 445)
+        self.assertEqual(rxs[1][VXLAN].vni, 446)
+
+        #
+        # 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))
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
-            self.assertEqual(rx[IP].dst, ep2.ip4.address)
+        rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
+        self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
 
         #
 
         #
-        # learn a remote EP in EPG 221
+        # ping from host in remote to remote external subnets
+        #   this is dropped by reflection check.
         #
         #
-        vx_tun_l3 = VppGbpVxlanTunnel(
-            self, 444, rd1.rd_id,
-            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
-            self.pg2.local_ip4)
-        vx_tun_l3.add_vpp_config()
-
-        c4 = VppGbpContract(
-            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,
-                 [])],
-            [ETH_P_IP, ETH_P_IPV6])
-        c4.add_vpp_config()
-
-        p = (Ether(src=self.pg7.remote_mac,
-                   dst=self.pg7.local_mac) /
-             IP(src=self.pg7.remote_ip4,
-                dst=self.pg7.local_ip4) /
+        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) /
              UDP(sport=1234, dport=48879) /
-             VXLAN(vni=444, gpid=441, flags=0x88) /
-             Ether(src="00:22:22:22:22:33", dst=str(self.router_mac)) /
-             IP(src="10.0.0.88", dst=ep1.ip4.address) /
+             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))
 
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
-
-        # endpoint learnt via the parent GBP-vxlan interface
-        self.assertTrue(find_gbp_endpoint(self,
-                                          vx_tun_l3._sw_if_index,
-                                          ip="10.0.0.88"))
+        rxs = self.send_and_assert_no_replies(self.pg7, p * 3)
 
 
-        p = (Ether(src=self.pg7.remote_mac,
-                   dst=self.pg7.local_mac) /
-             IP(src=self.pg7.remote_ip4,
-                dst=self.pg7.local_ip4) /
+        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) /
              UDP(sport=1234, dport=48879) /
-             VXLAN(vni=444, gpid=441, flags=0x88) /
-             Ether(src="00:22:22:22:22:33", dst=str(self.router_mac)) /
-             IPv6(src="2001:10::88", dst=ep1.ip6.address) /
+             VXLAN(vni=445, gpid=4222, flags=0x88, gpflags='A') /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IPv6(src="10:222::1", dst="10:222::2") /
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+        rxs = self.send_and_assert_no_replies(self.pg7, p * 3)
 
 
-        # endpoint learnt via the parent GBP-vxlan interface
-        self.assertTrue(find_gbp_endpoint(self,
-                                          vx_tun_l3._sw_if_index,
-                                          ip="2001:10::88"))
+        #
+        # local EP
+        #
+        lep1 = VppGbpEndpoint(self, vlan_144,
+                              epg_220, None,
+                              "10.0.0.44", "11.0.0.44",
+                              "2001:10::44", "3001::44")
+        lep1.add_vpp_config()
 
         #
 
         #
-        # L3 switch from local to remote EP
+        # local EP to local ip4 external subnet
         #
         #
-        p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IP(src=ep1.ip4.address, dst="10.0.0.88") /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
-        p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep1.ip6.address, dst="2001:10::88") /
-               UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+        p = (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=144) /
+             IP(src=lep1.ip4.address, dst="10.220.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
 
 
-        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
 
         for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
-            self.assertEqual(rx[IP].dst, "10.0.0.88")
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
 
 
-        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
+        #
+        # local EP to local ip6 external subnet
+        #
+        p = (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=144) /
+             IPv6(src=lep1.ip6.address, dst="10:220::1") /
+             UDP(sport=1234, dport=1234) /
+             Raw('\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
 
         for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep4.mac)
-            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
-            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
 
         #
 
         #
-        # test the dst-ip hash mode
+        # ip4 and ip6 subnets that load-balance
         #
         #
-        c5 = VppGbpContract(
-            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,
-                [VppGbpContractNextHop(sep1.vmac, sep1.epg.bd,
-                                       sep1.ip4, sep1.epg.rd),
-                 VppGbpContractNextHop(sep2.vmac, sep2.epg.bd,
-                                       sep2.ip4, sep2.epg.rd)]),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
-                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_DST_IP,
-                 [VppGbpContractNextHop(sep3.vmac, sep3.epg.bd,
-                                        sep3.ip6, sep3.epg.rd),
-                  VppGbpContractNextHop(sep4.vmac, sep4.epg.bd,
-                                        sep4.ip6, sep4.epg.rd)])],
-            [ETH_P_IP, ETH_P_IPV6])
-        c5.add_vpp_config()
+        ip_20 = VppIpRoute(self, "10.20.0.0", 24,
+                           [VppRoutePath(eep1.ip4.address,
+                                         eep1.epg.bvi.sw_if_index),
+                            VppRoutePath(eep2.ip4.address,
+                                         eep2.epg.bvi.sw_if_index)],
+                           table_id=t4.table_id)
+        ip_20.add_vpp_config()
 
 
-        rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
+        l3o_20 = VppGbpSubnet(
+            self, rd1, "10.20.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4220)
+        l3o_20.add_vpp_config()
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
-            self.assertEqual(rx[IP].dst, "10.0.0.88")
+        ip6_20 = VppIpRoute(self, "10:20::", 64,
+                            [VppRoutePath(eep1.ip6.address,
+                                          eep1.epg.bvi.sw_if_index),
+                             VppRoutePath(eep2.ip6.address,
+                                          eep2.epg.bvi.sw_if_index)],
+                            table_id=t6.table_id)
+        ip6_20.add_vpp_config()
 
 
-        rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep3.itf)
+        l3o6_20 = VppGbpSubnet(
+            self, rd1, "10:20::", 64,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4220)
+        l3o6_20.add_vpp_config()
 
 
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, routed_src_mac)
-            self.assertEqual(rx[Ether].dst, sep3.mac)
-            self.assertEqual(rx[IPv6].src, ep1.ip6.address)
-            self.assertEqual(rx[IPv6].dst, "2001:10::88")
+        self.logger.info(self.vapi.cli("sh ip fib 10.20.0.1"))
+        self.logger.info(self.vapi.cli("sh ip6 fib 10:20::1"))
+
+        # two ip6 packets whose port are chosen so they load-balance
+        p = [(Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IPv6(src=lep1.ip6.address, dst="10:20::1") /
+              UDP(sport=1234, dport=1234) /
+              Raw('\xa5' * 100)),
+             (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IPv6(src=lep1.ip6.address, dst="10:20::1") /
+              UDP(sport=124, dport=1230) /
+              Raw('\xa5' * 100))]
+
+        rxs = self.send_and_expect(self.pg0, p, self.pg0, 2)
+
+        self.assertEqual(rxs[0][Dot1Q].vlan, 101)
+        self.assertEqual(rxs[1][Dot1Q].vlan, 100)
+
+        # two ip4 packets whose port are chosen so they load-balance
+        p = [(Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IP(src=lep1.ip4.address, dst="10.20.0.1") /
+              UDP(sport=1235, dport=1235) /
+              Raw('\xa5' * 100)),
+             (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IP(src=lep1.ip4.address, dst="10.20.0.1") /
+              UDP(sport=124, dport=1230) /
+              Raw('\xa5' * 100))]
+
+        rxs = self.send_and_expect(self.pg0, p, self.pg0, 2)
+
+        self.assertEqual(rxs[0][Dot1Q].vlan, 101)
+        self.assertEqual(rxs[1][Dot1Q].vlan, 100)
 
         #
         # cleanup
         #
 
         #
         # cleanup
         #
+        ip_222.remove_vpp_config()
         self.pg7.unconfig_ip4()
         self.pg7.unconfig_ip4()
+        self.vlan_101.set_vtr(L2_VTR_OP.L2_DISABLED)
+        self.vlan_100.set_vtr(L2_VTR_OP.L2_DISABLED)
 
 
-    def test_gbp_l3_out(self):
-        """ GBP L3 Out """
+    def test_gbp_anon_l3_out(self):
+        """ GBP Anonymous L3 Out """
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
 
         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"
 
         routed_dst_mac = "00:0c:0c:0c:0c:0c"
         routed_src_mac = "00:22:bd:f8:19:ff"
@@ -3131,7 +4652,7 @@ class TestGBP(VppTestCase):
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
+        rd1 = VppGbpRouteDomain(self, 2, 55, t4, t6)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
@@ -3149,20 +4670,12 @@ class TestGBP(VppTestCase):
         self.pg7.config_ip4()
         self.pg7.resolve_arp()
 
         self.pg7.config_ip4()
         self.pg7.resolve_arp()
 
-        #
-        # a multicast vxlan-gbp tunnel for broadcast in the BD
-        #
-        tun_bm = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
-                                   "239.1.1.1", 88,
-                                   mcast_itf=self.pg7)
-        tun_bm.add_vpp_config()
-
         #
         # a GBP external bridge domains for the EPs
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         # a GBP external bridge domains for the EPs
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, None, tun_bm)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, None, None)
         gbd1.add_vpp_config()
 
         #
         gbd1.add_vpp_config()
 
         #
@@ -3175,16 +4688,14 @@ class TestGBP(VppTestCase):
                                       VppGbpEndpointRetention(2))
         epg_220.add_vpp_config()
 
                                       VppGbpEndpointRetention(2))
         epg_220.add_vpp_config()
 
-        # the BVIs have the subnets applied ...
+        # the BVIs have the subnet applied ...
         ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 24)
         ip4_addr.add_vpp_config()
         ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128", 24)
         ip4_addr.add_vpp_config()
-        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128", 64)
-        ip6_addr.add_vpp_config()
 
 
-        # ... which are L3-out subnets
+        # ... which is an Anonymous L3-out subnets
         l3o_1 = VppGbpSubnet(
             self, rd1, "10.0.0.0", 24,
         l3o_1 = VppGbpSubnet(
             self, rd1, "10.0.0.0", 24,
-            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_ANON_L3_OUT,
             sclass=113)
         l3o_1.add_vpp_config()
 
             sclass=113)
         l3o_1.add_vpp_config()
 
@@ -3192,14 +4703,15 @@ class TestGBP(VppTestCase):
         # an external interface attached to the outside world and the
         # external BD
         #
         # an external interface attached to the outside world and the
         # external BD
         #
-        vlan_100 = VppDot1QSubint(self, self.pg0, 100)
-        vlan_100.admin_up()
-        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()
+        VppL2Vtr(self, self.vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        VppL2Vtr(self, self.vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
 
 
-        ext_itf = VppGbpExtItf(self, self.loop0, bd1, rd1)
+        #
+        # vlan_100 and vlan_101 are anonymous l3-out interfaces
+        #
+        ext_itf = VppGbpExtItf(self, self.vlan_100, bd1, rd1, anon=True)
+        ext_itf.add_vpp_config()
+        ext_itf = VppGbpExtItf(self, self.vlan_101, bd1, rd1, anon=True)
         ext_itf.add_vpp_config()
 
         #
         ext_itf.add_vpp_config()
 
         #
@@ -3211,29 +4723,13 @@ class TestGBP(VppTestCase):
             self.pg2.local_ip4)
         vx_tun_l3.add_vpp_config()
 
             self.pg2.local_ip4)
         vx_tun_l3.add_vpp_config()
 
-        #
-        # 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()
-
         #
         # A remote external endpoint
         #
         rep = VppGbpEndpoint(self, vx_tun_l3,
                              epg_220, None,
         #
         # 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",
+                             "10.0.0.201", "11.0.0.201",
+                             "2001:10::201", "3001::101",
                              ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                              self.pg7.local_ip4,
                              self.pg7.remote_ip4,
                              ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                              self.pg7.local_ip4,
                              self.pg7.remote_ip4,
@@ -3241,48 +4737,30 @@ class TestGBP(VppTestCase):
         rep.add_vpp_config()
 
         #
         rep.add_vpp_config()
 
         #
-        # ARP packet from External EPs are accpeted and replied to
+        # ARP packet from host in external subnet are accepted, flooded and
+        # replied to. We expect 2 packets:
+        #   - APR request flooded over the other vlan subif
+        #   - ARP reply from BVI
         #
         #
-        p_arp = (Ether(src=eep1.mac, dst="ff:ff:ff:ff:ff:ff") /
+        p_arp = (Ether(src=self.vlan_100.remote_mac,
+                       dst="ff:ff:ff:ff:ff:ff") /
                  Dot1Q(vlan=100) /
                  ARP(op="who-has",
                  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)
-
-        #
-        # packets destined to unknown addresses in the BVI's subnet
-        # are ARP'd for
-        #
-        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=eep1.mac, dst=str(self.router_mac)) /
-              Dot1Q(vlan=100) /
-              IPv6(src="2001:10::1", dst="2001:10::88") /
-              UDP(sport=1234, dport=1234) /
-              Raw('\xa5' * 100))
-
-        rxs = self.send_and_expect(self.pg0, p4 * 1, self.pg7)
-
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
-            # self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
-            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
-            self.assertEqual(rx[IP].dst, "239.1.1.1")
-            self.assertEqual(rx[VXLAN].vni, 88)
-            self.assertTrue(rx[VXLAN].flags.G)
-            self.assertTrue(rx[VXLAN].flags.Instance)
-            # policy was applied to the original IP packet
-            self.assertEqual(rx[VXLAN].gpid, 113)
-            self.assertTrue(rx[VXLAN].gpflags.A)
-            self.assertFalse(rx[VXLAN].gpflags.D)
-
-            inner = rx[VXLAN].payload
-
-            self.assertTrue(inner.haslayer(ARP))
+                     psrc="10.0.0.100",
+                     pdst="10.0.0.128",
+                     hwsrc=self.vlan_100.remote_mac,
+                     hwdst="ff:ff:ff:ff:ff:ff"))
+        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0, n_rx=2)
+
+        p_arp = (Ether(src=self.vlan_101.remote_mac,
+                       dst="ff:ff:ff:ff:ff:ff") /
+                 Dot1Q(vlan=101) /
+                 ARP(op="who-has",
+                     psrc='10.0.0.101',
+                     pdst="10.0.0.128",
+                     hwsrc=self.vlan_101.remote_mac,
+                     hwdst="ff:ff:ff:ff:ff:ff"))
+        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0, n_rx=2)
 
         #
         # remote to external
 
         #
         # remote to external
@@ -3292,70 +4770,70 @@ class TestGBP(VppTestCase):
              IP(src=self.pg7.remote_ip4,
                 dst=self.pg7.local_ip4) /
              UDP(sport=1234, dport=48879) /
              IP(src=self.pg7.remote_ip4,
                 dst=self.pg7.local_ip4) /
              UDP(sport=1234, dport=48879) /
-             VXLAN(vni=444, gpid=113, flags=0x88) /
+             VXLAN(vni=vx_tun_l3.vni, gpid=epg_220.sclass, flags=0x88) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
-             IP(src="10.0.0.101", dst="10.0.0.1") /
+             IP(src=str(rep.ip4), dst="10.0.0.100") /
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
-
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
         # local EP pings router
         #
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
         # local EP pings router
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              Dot1Q(vlan=100) /
-             IP(src=eep1.ip4.address, dst="10.0.0.128") /
+             IP(src="10.0.0.100", dst="10.0.0.128") /
              ICMP(type='echo-request'))
              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))
         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[Ether].dst, self.vlan_100.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 100)
 
         #
         # local EP pings other local EP
         #
             self.assertEqual(rx[Dot1Q].vlan, 100)
 
         #
         # local EP pings other local EP
         #
-        p = (Ether(src=eep1.mac, dst=eep2.mac) /
+        p = (Ether(src=self.vlan_100.remote_mac,
+                   dst=self.vlan_101.remote_mac) /
              Dot1Q(vlan=100) /
              Dot1Q(vlan=100) /
-             IP(src=eep1.ip4.address, dst=eep2.ip4.address) /
+             IP(src="10.0.0.100", dst="10.0.0.101") /
              ICMP(type='echo-request'))
              ICMP(type='echo-request'))
-
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
         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[Ether].src, self.vlan_100.remote_mac)
+            self.assertEqual(rx[Ether].dst, self.vlan_101.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         #
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         #
-        # A subnet reachable through the external EP1
+        # A subnet reachable through an external router on vlan 100
         #
         ip_220 = VppIpRoute(self, "10.220.0.0", 24,
         #
         ip_220 = VppIpRoute(self, "10.220.0.0", 24,
-                            [VppRoutePath(eep1.ip4.address,
-                                          eep1.epg.bvi.sw_if_index)],
+                            [VppRoutePath("10.0.0.100",
+                                          epg_220.bvi.sw_if_index)],
                             table_id=t4.table_id)
         ip_220.add_vpp_config()
 
         l3o_220 = VppGbpSubnet(
             self, rd1, "10.220.0.0", 24,
                             table_id=t4.table_id)
         ip_220.add_vpp_config()
 
         l3o_220 = VppGbpSubnet(
             self, rd1, "10.220.0.0", 24,
+            # note: this a "regular" L3 out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4220)
         l3o_220.add_vpp_config()
 
         #
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4220)
         l3o_220.add_vpp_config()
 
         #
-        # A subnet reachable through the external EP2
+        # A subnet reachable through an external router on vlan 101
         #
         ip_221 = VppIpRoute(self, "10.221.0.0", 24,
         #
         ip_221 = VppIpRoute(self, "10.221.0.0", 24,
-                            [VppRoutePath(eep2.ip4.address,
-                                          eep2.epg.bvi.sw_if_index)],
+                            [VppRoutePath("10.0.0.101",
+                                          epg_220.bvi.sw_if_index)],
                             table_id=t4.table_id)
         ip_221.add_vpp_config()
 
         l3o_221 = VppGbpSubnet(
             self, rd1, "10.221.0.0", 24,
                             table_id=t4.table_id)
         ip_221.add_vpp_config()
 
         l3o_221 = VppGbpSubnet(
             self, rd1, "10.221.0.0", 24,
+            # note: this a "regular" L3 out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4221)
         l3o_221.add_vpp_config()
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4221)
         l3o_221.add_vpp_config()
@@ -3364,7 +4842,7 @@ class TestGBP(VppTestCase):
         # ping between hosts in remote subnets
         #  dropped without a contract
         #
         # ping between hosts in remote subnets
         #  dropped without a contract
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              ICMP(type='echo-request'))
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              ICMP(type='echo-request'))
@@ -3380,13 +4858,15 @@ class TestGBP(VppTestCase):
         acl_index = acl.add_vpp_config([rule4, rule6])
 
         c1 = VppGbpContract(
         acl_index = acl.add_vpp_config([rule4, rule6])
 
         c1 = VppGbpContract(
-            self, 4220, 4221, acl_index,
+            self, 55, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
             [ETH_P_IP, ETH_P_IPV6])
         c1.add_vpp_config()
 
@@ -3394,30 +4874,34 @@ class TestGBP(VppTestCase):
         # Contracts allowing ext-net 200 to talk with external EPs
         #
         c2 = VppGbpContract(
         # Contracts allowing ext-net 200 to talk with external EPs
         #
         c2 = VppGbpContract(
-            self, 4220, 113, acl_index,
+            self, 55, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
         c3 = VppGbpContract(
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
         c3 = VppGbpContract(
-            self, 113, 4220, acl_index,
+            self, 55, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c3.add_vpp_config()
 
         #
         # ping between hosts in remote subnets
         #
             [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)) /
+        p = (Ether(src=self.vlan_100.remote_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) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              UDP(sport=1234, dport=1234) /
@@ -3427,7 +4911,7 @@ class TestGBP(VppTestCase):
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, str(self.router_mac))
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, str(self.router_mac))
-            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Ether].dst, self.vlan_101.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         # we did not learn these external hosts
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         # we did not learn these external hosts
@@ -3444,7 +4928,7 @@ class TestGBP(VppTestCase):
              UDP(sport=1234, dport=48879) /
              VXLAN(vni=444, gpid=113, flags=0x88) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
              UDP(sport=1234, dport=48879) /
              VXLAN(vni=444, gpid=113, flags=0x88) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
-             IP(src="10.0.0.101", dst="10.220.0.1") /
+             IP(src=rep.ip4.address, dst="10.220.0.1") /
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
              UDP(sport=1234, dport=1234) /
              Raw('\xa5' * 100))
 
@@ -3453,7 +4937,7 @@ class TestGBP(VppTestCase):
         #
         # ping from an external host to the remote external EP
         #
         #
         # ping from an external host to the remote external EP
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst=rep.ip4.address) /
              UDP(sport=1234, dport=1234) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst=rep.ip4.address) /
              UDP(sport=1234, dport=1234) /
@@ -3516,6 +5000,7 @@ class TestGBP(VppTestCase):
 
         l3o_222 = VppGbpSubnet(
             self, rd1, "10.222.0.0", 24,
 
         l3o_222 = VppGbpSubnet(
             self, rd1, "10.222.0.0", 24,
+            # note: this a "regular" l3out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4222)
         l3o_222.add_vpp_config()
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4222)
         l3o_222.add_vpp_config()
@@ -3524,7 +5009,7 @@ class TestGBP(VppTestCase):
         # ping between hosts in local and remote external subnets
         #  dropped without a contract
         #
         # ping between hosts in local and remote external subnets
         #  dropped without a contract
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_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) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
@@ -3536,20 +5021,22 @@ class TestGBP(VppTestCase):
         # Add contracts ext-nets for 220 -> 222
         #
         c4 = VppGbpContract(
         # Add contracts ext-nets for 220 -> 222
         #
         c4 = VppGbpContract(
-            self, 4220, 4222, acl_index,
+            self, 55, 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,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
                 []),
                 []),
-             VppGbpContractRule(
-                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                 [])],
+                VppGbpContractRule(
+                    VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                    VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SRC_IP,
+                    [])],
             [ETH_P_IP, ETH_P_IPV6])
         c4.add_vpp_config()
 
         #
         # ping from host in local to remote external subnets
         #
             [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)) /
+        p = (Ether(src=self.vlan_100.remote_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) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
@@ -3592,11 +5079,27 @@ class TestGBP(VppTestCase):
         rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
         self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
 
         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
         #
         #
         # cleanup
         #
+        self.vlan_101.set_vtr(L2_VTR_OP.L2_DISABLED)
+        self.vlan_100.set_vtr(L2_VTR_OP.L2_DISABLED)
         self.pg7.unconfig_ip4()
         self.pg7.unconfig_ip4()
-        vlan_100.set_vtr(L2_VTR_OP.L2_DISABLED)
 
 
 if __name__ == '__main__':
 
 
 if __name__ == '__main__':