devices: Add queues params in create_if
[vpp.git] / test / test_gbp.py
index e386a31..c30a729 100644 (file)
@@ -1,58 +1,93 @@
-#!/usr/bin/env python
-
-from socket import AF_INET, AF_INET6
+#!/usr/bin/env python3
+import typing
+from socket import AF_INET6, inet_pton, inet_ntop
 import unittest
+from ipaddress import ip_address, IPv4Network, IPv6Network
 
 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.data import ETH_P_IP, ETH_P_IPV6, ETH_P_ARP
-from scapy.utils import inet_pton, inet_ntop
+from scapy.data import ETH_P_IP, ETH_P_IPV6
 
+from framework import tag_fixme_vpp_workers
 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
-from vpp_l2 import VppBridgeDomain, VppBridgeDomainPort, \
-    VppBridgeDomainArpEntry, VppL2FibEntry, find_bridge_domain_port, VppL2Vtr
+from vpp_ip_route import (
+    VppIpRoute,
+    VppRoutePath,
+    VppIpTable,
+    VppIpInterfaceAddress,
+    VppIpInterfaceBind,
+    find_route,
+    FibPathProto,
+    FibPathType,
+)
+from vpp_l2 import (
+    VppBridgeDomain,
+    VppBridgeDomainPort,
+    VppBridgeDomainArpEntry,
+    VppL2FibEntry,
+    find_bridge_domain_port,
+    VppL2Vtr,
+)
 from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint
-from vpp_ip import VppIpAddress, VppIpPrefix
+from vpp_ip import DpoProto, get_dpo_proto
 from vpp_papi import VppEnum, MACAddress
 from vpp_vxlan_gbp_tunnel import find_vxlan_gbp_tunnel, INDEX_INVALID, \
     VppVxlanGbpTunnel
 from vpp_neighbor import VppNeighbor
+from vpp_acl import AclRule, VppAcl
 
 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, flags=None):
     if ip:
-        vip = VppIpAddress(ip)
+        vip = ip
     if mac:
         vmac = MACAddress(mac)
 
     eps = test.vapi.gbp_endpoint_dump()
 
     for ep in eps:
+        if tep:
+            src = tep[0]
+            dst = tep[1]
+            if src != str(ep.endpoint.tun.src) or \
+               dst != str(ep.endpoint.tun.dst):
+                continue
         if sw_if_index:
             if ep.endpoint.sw_if_index != sw_if_index:
                 continue
+        if sclass:
+            if ep.endpoint.sclass != sclass:
+                continue
+        if flags:
+            if flags != (flags & ep.endpoint.flags):
+                continue
         if ip:
             for eip in ep.endpoint.ips:
-                if vip == eip:
+                if vip == str(eip):
                     return True
         if mac:
-            if vmac.packed == ep.endpoint.mac:
+            if vmac == ep.endpoint.mac:
                 return True
+
     return False
 
 
-def find_gbp_vxlan(test, vni):
+def find_gbp_vxlan(test: VppTestCase, vni):
     ts = test.vapi.gbp_vxlan_tunnel_dump()
     for t in ts:
         if t.tunnel.vni == vni:
@@ -100,13 +135,14 @@ class VppGbpEndpoint(VppObject):
                  mac=True):
         self._test = test
         self.itf = itf
+        self.handle = None
         self.epg = epg
         self.recirc = recirc
 
-        self._ip4 = VppIpAddress(ip4)
-        self._fip4 = VppIpAddress(fip4)
-        self._ip6 = VppIpAddress(ip6)
-        self._fip6 = VppIpAddress(fip6)
+        self._ip4 = ip4
+        self._fip4 = fip4
+        self._ip6 = ip6
+        self._fip6 = fip6
 
         if mac:
             self.vmac = MACAddress(self.itf.remote_mac)
@@ -114,34 +150,44 @@ class VppGbpEndpoint(VppObject):
             self.vmac = MACAddress("00:00:00:00:00:00")
 
         self.flags = flags
-        self.tun_src = VppIpAddress(tun_src)
-        self.tun_dst = VppIpAddress(tun_dst)
+        self.tun_src = tun_src
+        self.tun_dst = tun_dst
+
+    def encode(self):
+        ips = [self.ip4, self.ip6]
+        return {
+            "sw_if_index": self.itf.sw_if_index,
+            "ips": ips,
+            "n_ips": len(ips),
+            "mac": self.vmac.packed,
+            "sclass": self.epg.sclass,
+            "flags": self.flags,
+            "tun": {
+                "src": self.tun_src,
+                "dst": self.tun_dst,
+            },
+        }
 
     def add_vpp_config(self):
         res = self._test.vapi.gbp_endpoint_add(
-            self.itf.sw_if_index,
-            [self.ip4.encode(), self.ip6.encode()],
-            self.vmac.packed,
-            self.epg.sclass,
-            self.flags,
-            self.tun_src.encode(),
-            self.tun_dst.encode())
+            endpoint=self.encode(),
+        )
         self.handle = res.handle
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.gbp_endpoint_del(self.handle)
+        self._test.vapi.gbp_endpoint_del(handle=self.handle)
 
     def object_id(self):
         return "gbp-endpoint:[%d==%d:%s:%d]" % (self.handle,
                                                 self.itf.sw_if_index,
-                                                self.ip4.address,
+                                                self.ip4,
                                                 self.epg.sclass)
 
     def query_vpp_config(self):
         return find_gbp_endpoint(self._test,
                                  self.itf.sw_if_index,
-                                 self.ip4.address)
+                                 self.ip4)
 
 
 class VppGbpRecirc(VppObject):
@@ -155,20 +201,25 @@ class VppGbpRecirc(VppObject):
         self.epg = epg
         self.is_ext = is_ext
 
+    def encode(self):
+        return {
+            "is_ext": self.is_ext,
+            "sw_if_index": self.recirc.sw_if_index,
+            "sclass": self.epg.sclass,
+        }
+
     def add_vpp_config(self):
         self._test.vapi.gbp_recirc_add_del(
             1,
-            self.recirc.sw_if_index,
-            self.epg.sclass,
-            self.is_ext)
+            recirc=self.encode(),
+        )
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_recirc_add_del(
             0,
-            self.recirc.sw_if_index,
-            self.epg.sclass,
-            self.is_ext)
+            recirc=self.encode(),
+        )
 
     def object_id(self):
         return "gbp-recirc:[%d]" % (self.recirc.sw_if_index)
@@ -186,29 +237,37 @@ class VppGbpExtItf(VppObject):
     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.flags = 1 if anon else 0
+
+    def encode(self):
+        return {
+            "sw_if_index": self.itf.sw_if_index,
+            "bd_id": self.bd.bd_id,
+            "rd_id": self.rd.rd_id,
+            "flags": self.flags,
+        }
 
     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)
+            ext_itf=self.encode(),
+        )
         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)
+            ext_itf=self.encode(),
+        )
 
     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()
@@ -224,30 +283,43 @@ class VppGbpSubnet(VppObject):
     """
 
     def __init__(self, test, rd, address, address_len,
-                 type, sw_if_index=None, sclass=None):
+                 type, sw_if_index=0xffffffff, sclass=0xffff):
+        # TODO: replace hardcoded defaults when vpp_papi supports
+        #  defaults in typedefs
         self._test = test
         self.rd_id = rd.rd_id
-        self.prefix = VppIpPrefix(address, address_len)
+        a = ip_address(address)
+        if 4 == a.version:
+            self.prefix = IPv4Network("%s/%d" % (address, address_len),
+                                      strict=False)
+        else:
+            self.prefix = IPv6Network("%s/%d" % (address, address_len),
+                                      strict=False)
         self.type = type
         self.sw_if_index = sw_if_index
         self.sclass = sclass
 
+    def encode(self):
+        return {
+            "type": self.type,
+            "sw_if_index": self.sw_if_index,
+            "sclass": self.sclass,
+            "prefix": self.prefix,
+            "rd_id": self.rd_id,
+        }
+
     def add_vpp_config(self):
         self._test.vapi.gbp_subnet_add_del(
-            1,
-            self.rd_id,
-            self.prefix.encode(),
-            self.type,
-            sw_if_index=self.sw_if_index if self.sw_if_index else 0xffffffff,
-            sclass=self.sclass if self.sclass else 0xffff)
+            is_add=1,
+            subnet=self.encode(),
+        )
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_subnet_add_del(
-            0,
-            self.rd_id,
-            self.prefix.encode(),
-            self.type)
+            is_add=0,
+            subnet=self.encode()
+        )
 
     def object_id(self):
         return "gbp-subnet:[%d-%s]" % (self.rd_id, self.prefix)
@@ -281,33 +353,38 @@ class VppGbpEndpointGroup(VppObject):
         self._test = test
         self.uplink = uplink
         self.bvi = bvi
-        self.bvi_ip4 = VppIpAddress(bvi_ip4)
-        self.bvi_ip6 = VppIpAddress(bvi_ip6)
+        self.bvi_ip4 = bvi_ip4
+        self.bvi_ip6 = bvi_ip6
         self.vnid = vnid
-        self.bd = bd
+        self.bd = bd  # VppGbpBridgeDomain
         self.rd = rd
         self.sclass = sclass
         if 0 == self.sclass:
             self.sclass = 0xffff
         self.retention = retention
 
+    def encode(self) -> dict:
+        return {
+            "uplink_sw_if_index": self.uplink.sw_if_index
+            if self.uplink else INDEX_INVALID,
+            "bd_id": self.bd.bd.bd_id,
+            "rd_id": self.rd.rd_id,
+            "vnid": self.vnid,
+            "sclass": self.sclass,
+            "retention": self.retention.encode(),
+        }
+
     def add_vpp_config(self):
-        self._test.vapi.gbp_endpoint_group_add(
-            self.vnid,
-            self.sclass,
-            self.bd.bd.bd_id,
-            self.rd.rd_id,
-            self.uplink.sw_if_index if self.uplink else INDEX_INVALID,
-            self.retention.encode())
+        self._test.vapi.gbp_endpoint_group_add(epg=self.encode())
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.gbp_endpoint_group_del(self.sclass)
+        self._test.vapi.gbp_endpoint_group_del(sclass=self.sclass)
 
-    def object_id(self):
+    def object_id(self) -> str:
         return "gbp-endpoint-group:[%d]" % (self.vnid)
 
-    def query_vpp_config(self):
+    def query_vpp_config(self) -> bool:
         epgs = self._test.vapi.gbp_endpoint_group_dump()
         for epg in epgs:
             if epg.epg.vnid == self.vnid:
@@ -320,40 +397,55 @@ class VppGbpBridgeDomain(VppObject):
     GBP Bridge Domain
     """
 
-    def __init__(self, test, bd, bvi, uu_fwd=None,
-                 bm_flood=None, learn=True, uu_drop=False, bm_drop=False):
+    def __init__(self, test, bd, rd, bvi,
+                 uu_fwd: typing.Optional[VppVxlanGbpTunnel] = 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.rd = rd
 
         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
-        if (uu_drop):
-            self.learn |= e.GBP_BD_API_FLAG_UU_FWD_DROP
-        if (bm_drop):
-            self.learn |= e.GBP_BD_API_FLAG_MCAST_DROP
+
+        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 encode(self) -> dict:
+        return {
+            "flags": self.flags,
+            "bvi_sw_if_index": self.bvi.sw_if_index,
+            "uu_fwd_sw_if_index": self.uu_fwd.sw_if_index
+            if self.uu_fwd else INDEX_INVALID,
+            "bm_flood_sw_if_index": self.bm_flood.sw_if_index
+            if self.bm_flood else INDEX_INVALID,
+            "bd_id": self.bd.bd_id,
+            "rd_id": self.rd.rd_id,
+        }
 
     def add_vpp_config(self):
         self._test.vapi.gbp_bridge_domain_add(
-            self.bd.bd_id,
-            self.learn,
-            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)
+            bd=self.encode(),
+        )
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.gbp_bridge_domain_del(self.bd.bd_id)
+        self._test.vapi.gbp_bridge_domain_del(bd_id=self.bd.bd_id)
 
-    def object_id(self):
+    def object_id(self) -> str:
         return "gbp-bridge-domain:[%d]" % (self.bd.bd_id)
 
-    def query_vpp_config(self):
+    def query_vpp_config(self) -> bool:
         bds = self._test.vapi.gbp_bridge_domain_dump()
         for bd in bds:
             if bd.bd.bd_id == self.bd.bd_id:
@@ -366,25 +458,36 @@ class VppGbpRouteDomain(VppObject):
     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.scope = scope
         self.t4 = t4
         self.t6 = t6
         self.ip4_uu = ip4_uu
         self.ip6_uu = ip6_uu
 
+    def encode(self) -> dict:
+        return {
+            "rd_id": self.rd_id,
+            "scope": self.scope,
+            "ip4_table_id": self.t4.table_id,
+            "ip6_table_id": self.t6.table_id,
+            "ip4_uu_sw_if_index": self.ip4_uu.sw_if_index
+            if self.ip4_uu else INDEX_INVALID,
+            "ip6_uu_sw_if_index": self.ip6_uu.sw_if_index
+            if self.ip6_uu else INDEX_INVALID,
+
+        }
+
     def add_vpp_config(self):
         self._test.vapi.gbp_route_domain_add(
-            self.rd_id,
-            self.t4.table_id,
-            self.t6.table_id,
-            self.ip4_uu.sw_if_index if self.ip4_uu else INDEX_INVALID,
-            self.ip6_uu.sw_if_index if self.ip6_uu else INDEX_INVALID)
+            rd=self.encode(),
+        )
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.gbp_route_domain_del(self.rd_id)
+        self._test.vapi.gbp_route_domain_del(rd_id=self.rd_id)
 
     def object_id(self):
         return "gbp-route-domain:[%d]" % (self.rd_id)
@@ -397,27 +500,29 @@ class VppGbpRouteDomain(VppObject):
         return False
 
 
-class VppGbpContractNextHop():
+class VppGbpContractNextHop:
     def __init__(self, mac, bd, ip, rd):
         self.mac = mac
         self.ip = ip
         self.bd = bd
         self.rd = rd
 
-    def encode(self):
-        return {'ip': self.ip.encode(),
-                'mac': self.mac.packed,
-                'bd_id': self.bd.bd.bd_id,
-                'rd_id': self.rd.rd_id}
+    def encode(self) -> dict:
+        return {
+            "ip": self.ip,
+            "mac": self.mac.packed,
+            "bd_id": self.bd.bd.bd_id,
+            "rd_id": self.rd.rd_id,
+        }
 
 
-class VppGbpContractRule():
-    def __init__(self, action, hash_mode, nhs=[]):
+class VppGbpContractRule:
+    def __init__(self, action, hash_mode, nhs=None):
         self.action = action
         self.hash_mode = hash_mode
-        self.nhs = nhs
+        self.nhs = [] if nhs is None else nhs
 
-    def encode(self):
+    def encode(self) -> dict:
         nhs = []
         for nh in self.nhs:
             nhs.append(nh.encode())
@@ -429,15 +534,20 @@ class VppGbpContractRule():
                     '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
     """
 
-    def __init__(self, test, sclass, dclass, acl_index,
-                 rules, allowed_ethertypes):
+    def __init__(self, test, scope, sclass, dclass, acl_index,
+                 rules: list, allowed_ethertypes: list):
         self._test = test
+        self.scope = scope
         self.acl_index = acl_index
         self.sclass = sclass
         self.dclass = dclass
@@ -446,39 +556,48 @@ class VppGbpContract(VppObject):
         while (len(self.allowed_ethertypes) < 16):
             self.allowed_ethertypes.append(0)
 
-    def add_vpp_config(self):
+    def encode(self) -> dict:
         rules = []
         for r in self.rules:
             rules.append(r.encode())
+        return {
+            '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,
+        }
+
+    def add_vpp_config(self):
         r = self._test.vapi.gbp_contract_add_del(
-            1,
-            self.sclass,
-            self.dclass,
-            self.acl_index,
-            rules,
-            self.allowed_ethertypes)
+            is_add=1,
+            contract=self.encode()
+        )
+
         self.stats_index = r.stats_index
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
         self._test.vapi.gbp_contract_add_del(
-            0,
-            self.sclass,
-            self.dclass,
-            self.acl_index,
-            [],
-            self.allowed_ethertypes)
+            is_add=0,
+            contract=self.encode(),
+        )
 
     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:
-            if c.contract.sclass == self.sclass \
-                    and c.contract.dclass == self.dclass:
+            if c.contract.scope == self.scope \
+               and c.contract.sclass == self.sclass \
+               and c.contract.dclass == self.dclass:
                 return True
         return False
 
@@ -504,17 +623,23 @@ class VppGbpVxlanTunnel(VppInterface):
         self.mode = mode
         self.src = src
 
+    def encode(self) -> dict:
+        return {
+            "vni": self.vni,
+            "mode": self.mode,
+            "bd_rd_id": self.bd_rd_id,
+            "src": self.src,
+        }
+
     def add_vpp_config(self):
         r = self._test.vapi.gbp_vxlan_tunnel_add(
-            self.vni,
-            self.bd_rd_id,
-            self.mode,
-            self.src)
+            tunnel=self.encode(),
+        )
         self.set_sw_if_index(r.sw_if_index)
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.gbp_vxlan_tunnel_del(self.vni)
+        self._test.vapi.gbp_vxlan_tunnel_del(vni=self.vni)
 
     def object_id(self):
         return "gbp-vxlan:%d" % (self.sw_if_index)
@@ -523,65 +648,18 @@ class VppGbpVxlanTunnel(VppInterface):
         return find_gbp_vxlan(self._test, self.vni)
 
 
-class VppGbpAcl(VppObject):
-    """
-    GBP Acl
-    """
-
-    def __init__(self, test):
-        self._test = test
-        self.acl_index = 4294967295
-
-    def create_rule(self, is_ipv6=0, permit_deny=0, proto=-1,
-                    s_prefix=0, s_ip=b'\x00\x00\x00\x00', sport_from=0,
-                    sport_to=65535, d_prefix=0, d_ip=b'\x00\x00\x00\x00',
-                    dport_from=0, dport_to=65535):
-        if proto == -1 or proto == 0:
-            sport_to = 0
-            dport_to = sport_to
-        elif proto == 1 or proto == 58:
-            sport_to = 255
-            dport_to = sport_to
-        rule = ({'is_permit': permit_deny, 'is_ipv6': is_ipv6, 'proto': proto,
-                 'srcport_or_icmptype_first': sport_from,
-                 'srcport_or_icmptype_last': sport_to,
-                 'src_ip_prefix_len': s_prefix,
-                 'src_ip_addr': s_ip,
-                 'dstport_or_icmpcode_first': dport_from,
-                 'dstport_or_icmpcode_last': dport_to,
-                 'dst_ip_prefix_len': d_prefix,
-                 'dst_ip_addr': d_ip})
-        return rule
-
-    def add_vpp_config(self, rules):
-
-        reply = self._test.vapi.acl_add_replace(self.acl_index,
-                                                r=rules,
-                                                tag=b'GBPTest')
-        self.acl_index = reply.acl_index
-        return self.acl_index
-
-    def remove_vpp_config(self):
-        self._test.vapi.acl_del(self.acl_index)
-
-    def object_id(self):
-        return "gbp-acl:[%d]" % (self.acl_index)
-
-    def query_vpp_config(self):
-        cs = self._test.vapi.acl_dump()
-        for c in cs:
-            if c.acl_index == self.acl_index:
-                return True
-        return False
-
-
+@tag_fixme_vpp_workers
 class TestGBP(VppTestCase):
     """ GBP Test Case """
 
     @property
-    def config_flags(self):
+    def nat_config_flags(self):
         return VppEnum.vl_api_nat_config_flags_t
 
+    @property
+    def nat44_config_flags(self):
+        return VppEnum.vl_api_nat44_config_flags_t
+
     @classmethod
     def setUpClass(cls):
         super(TestGBP, cls).setUpClass()
@@ -603,11 +681,23 @@ class TestGBP(VppTestCase):
         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()
-
         super(TestGBP, self).tearDown()
+        for i in self.lo_interfaces:
+            i.remove_vpp_config()
+        self.lo_interfaces = []
+        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)
@@ -639,6 +729,16 @@ class TestGBP(VppTestCase):
             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)
 
@@ -703,7 +803,6 @@ class TestGBP(VppTestCase):
         self.pg_send(src, tx)
         dst.get_capture(0, timeout=1)
         dst.assert_nothing_captured(remark="")
-        timeout = 0.1
 
     def send_and_expect_arp(self, src, tx, dst):
         rx = self.send_and_expect(src, tx, dst)
@@ -722,25 +821,6 @@ class TestGBP(VppTestCase):
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
 
-        #
-        # 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, self.loop0)
-        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
-        gbd20 = VppGbpBridgeDomain(self, bd20, self.loop2)
-
-        gbd1.add_vpp_config()
-        gbd2.add_vpp_config()
-        gbd20.add_vpp_config()
-
         #
         # Route Domains
         #
@@ -753,12 +833,31 @@ class TestGBP(VppTestCase):
         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()
 
+        #
+        # 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
@@ -807,31 +906,36 @@ class TestGBP(VppTestCase):
                               "10.0.2.1", "11.0.0.4",
                               "2001:10:2::1", "3001::4")]
 
+        self.vapi.nat44_ed_plugin_enable_disable(enable=1)
+        self.vapi.nat66_plugin_enable_disable(enable=1)
+
         #
         # Config related to each of the EPGs
         #
         for epg in epgs:
             # IP config on the BVI interfaces
             if epg != epgs[1] and epg != epgs[4]:
-                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)
+                b4 = VppIpInterfaceBind(self, epg.bvi,
+                                        epg.rd.t4).add_vpp_config()
+                b6 = VppIpInterfaceBind(self, epg.bvi,
+                                        epg.rd.t6).add_vpp_config()
+                epg.bvi.set_mac(self.router_mac)
 
                 # The BVIs are NAT inside interfaces
-                flags = self.config_flags.NAT_IS_INSIDE
+                flags = self.nat_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)
+                    sw_if_index=epg.bvi.sw_if_index,
+                    flags=flags, is_add=1)
 
-            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()
+            if_ip4 = VppIpInterfaceAddress(self, epg.bvi,
+                                           epg.bvi_ip4, 32,
+                                           bind=b4).add_vpp_config()
+            if_ip6 = VppIpInterfaceAddress(self, epg.bvi,
+                                           epg.bvi_ip6, 128,
+                                           bind=b6).add_vpp_config()
 
             # EPG uplink interfaces in the RD
             VppIpInterfaceBind(self, epg.uplink, epg.rd.t4).add_vpp_config()
@@ -860,8 +964,7 @@ class TestGBP(VppTestCase):
             self.vapi.nat44_interface_add_del_feature(
                 sw_if_index=recirc.recirc.sw_if_index, is_add=1)
             self.vapi.nat66_add_del_interface(
-                is_add=1,
-                sw_if_index=recirc.recirc.sw_if_index)
+                sw_if_index=recirc.recirc.sw_if_index, is_add=1)
 
             recirc.add_vpp_config()
 
@@ -880,19 +983,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:
-                    flags = self.config_flags.NAT_IS_ADDR_ONLY
+                if ip_address(ip).version == 4:
+                    flags = self.nat_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,
+                        local_ip_address=ip,
+                        external_ip_address=fip,
                         external_sw_if_index=0xFFFFFFFF,
                         vrf_id=0,
                         flags=flags)
                 else:
                     self.vapi.nat66_add_del_static_mapping(
-                        local_ip_address=ip.bytes,
-                        external_ip_address=fip.bytes,
+                        local_ip_address=ip,
+                        external_ip_address=fip,
                         vrf_id=0, is_add=1)
 
             # VPP EP create ...
@@ -901,32 +1004,33 @@ class TestGBP(VppTestCase):
             self.logger.info(self.vapi.cli("sh gbp endpoint"))
 
             # ... results in a Gratuitous ARP/ND on the EPG's uplink
-            rx = ep.epg.uplink.get_capture(len(ep.ips), timeout=0.2)
+            rx = ep.epg.uplink.get_capture(len(ep.ips) + 1, timeout=0.2)
 
             for ii, ip in enumerate(ep.ips):
                 p = rx[ii]
 
-                if ip.is_ip6:
+                if ip_address(ip).version == 6:
                     self.assertTrue(p.haslayer(ICMPv6ND_NA))
-                    self.assertEqual(p[ICMPv6ND_NA].tgt, ip.address)
+                    self.assertEqual(p[ICMPv6ND_NA].tgt, ip)
                 else:
                     self.assertTrue(p.haslayer(ARP))
-                    self.assertEqual(p[ARP].psrc, ip.address)
-                    self.assertEqual(p[ARP].pdst, ip.address)
+                    self.assertEqual(p[ARP].psrc, ip)
+                    self.assertEqual(p[ARP].pdst, ip)
 
             # 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)
                 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, ip_address(fip).max_prefixlen,
+                    [VppRoutePath(fip,
+                                  ep.recirc.recirc.sw_if_index,
+                                  type=FibPathType.FIB_PATH_TYPE_DVR,
+                                  proto=get_dpo_proto(fip))],
+                    table_id=20)
                 r.add_vpp_config()
 
             # L2 FIB entries in the NAT EPG BD to bridge the packets from
@@ -962,17 +1066,17 @@ class TestGBP(VppTestCase):
                    ARP(op="who-has",
                        hwdst="ff:ff:ff:ff:ff:ff",
                        hwsrc=self.pg0.remote_mac,
-                       pdst=epgs[0].bvi_ip4.address,
-                       psrc=eps[0].ip4.address))
+                       pdst=epgs[0].bvi_ip4,
+                       psrc=eps[0].ip4))
 
         self.send_and_expect(self.pg0, [pkt_arp], self.pg0)
 
-        nsma = in6_getnsma(inet_pton(AF_INET6, eps[0].ip6.address))
+        nsma = in6_getnsma(inet_pton(AF_INET6, eps[0].ip6))
         d = inet_ntop(AF_INET6, nsma)
         pkt_nd = (Ether(dst=in6_getnsmac(nsma),
                         src=self.pg0.remote_mac) /
-                  IPv6(dst=d, src=eps[0].ip6.address) /
-                  ICMPv6ND_NS(tgt=epgs[0].bvi_ip6.address) /
+                  IPv6(dst=d, src=eps[0].ip6) /
+                  ICMPv6ND_NS(tgt=epgs[0].bvi_ip6) /
                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
         self.send_and_expect(self.pg0, [pkt_nd], self.pg0)
 
@@ -981,9 +1085,9 @@ class TestGBP(VppTestCase):
         #
         pkt_bcast = (Ether(dst="ff:ff:ff:ff:ff:ff",
                            src=self.pg0.remote_mac) /
-                     IP(src=eps[0].ip4.address, dst="232.1.1.1") /
+                     IP(src=eps[0].ip4, dst="232.1.1.1") /
                      UDP(sport=1234, dport=1234) /
-                     Raw('\xa5' * 100))
+                     Raw(b'\xa5' * 100))
 
         self.vapi.cli("clear trace")
         self.pg0.add_stream(pkt_bcast)
@@ -1001,26 +1105,26 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_ip4 = (Ether(src=self.pg0.remote_mac,
                                        dst=str(self.router_mac)) /
-                                 IP(src=eps[0].ip4.address,
+                                 IP(src=eps[0].ip4,
                                     dst="10.0.0.99") /
                                  UDP(sport=1234, dport=1234) /
-                                 Raw('\xa5' * 100))
+                                 Raw(b'\xa5' * 100))
         pkt_inter_epg_222_ip4 = (Ether(src=self.pg0.remote_mac,
                                        dst=str(self.router_mac)) /
-                                 IP(src=eps[0].ip4.address,
+                                 IP(src=eps[0].ip4,
                                     dst="10.0.1.99") /
                                  UDP(sport=1234, dport=1234) /
-                                 Raw('\xa5' * 100))
+                                 Raw(b'\xa5' * 100))
 
         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)) /
-                                 IPv6(src=eps[0].ip6.address,
+                                 IPv6(src=eps[0].ip6,
                                       dst="2001:10::99") /
                                  UDP(sport=1234, dport=1234) /
-                                 Raw('\xa5' * 100))
+                                 Raw(b'\xa5' * 100))
         self.send_and_assert_no_replies(self.pg0,
                                         pkt_inter_epg_222_ip6 * NUM_PKTS)
 
@@ -1078,10 +1182,10 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_to_uplink = (Ether(src=self.pg0.remote_mac,
                                              dst="00:00:00:33:44:55") /
-                                       IP(src=eps[0].ip4.address,
+                                       IP(src=eps[0].ip4,
                                           dst="10.0.0.99") /
                                        UDP(sport=1234, dport=1234) /
-                                       Raw('\xa5' * 100))
+                                       Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(eps[0].itf,
                                      pkt_intra_epg_220_to_uplink * NUM_PKTS,
@@ -1092,10 +1196,10 @@ class TestGBP(VppTestCase):
 
         pkt_intra_epg_221_to_uplink = (Ether(src=self.pg2.remote_mac,
                                              dst="00:00:00:33:44:66") /
-                                       IP(src=eps[0].ip4.address,
+                                       IP(src=eps[0].ip4,
                                           dst="10.0.0.99") /
                                        UDP(sport=1234, dport=1234) /
-                                       Raw('\xa5' * 100))
+                                       Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(eps[2].itf,
                                      pkt_intra_epg_221_to_uplink * NUM_PKTS,
@@ -1106,10 +1210,10 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_from_uplink = (Ether(src="00:00:00:33:44:55",
                                                dst=self.pg0.remote_mac) /
-                                         IP(src=eps[0].ip4.address,
+                                         IP(src=eps[0].ip4,
                                             dst="10.0.0.99") /
                                          UDP(sport=1234, dport=1234) /
-                                         Raw('\xa5' * 100))
+                                         Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg4,
                                      pkt_intra_epg_220_from_uplink * NUM_PKTS,
@@ -1121,10 +1225,10 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg = (Ether(src=self.pg0.remote_mac,
                                dst=self.pg1.remote_mac) /
-                         IP(src=eps[0].ip4.address,
-                            dst=eps[1].ip4.address) /
+                         IP(src=eps[0].ip4,
+                            dst=eps[1].ip4) /
                          UDP(sport=1234, dport=1234) /
-                         Raw('\xa5' * 100))
+                         Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg0,
                                      pkt_intra_epg * NUM_PKTS,
@@ -1136,22 +1240,22 @@ class TestGBP(VppTestCase):
         #
         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) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[2].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
         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) /
+                                    IP(src=eps[2].ip4,
+                                       dst=eps[0].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
         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) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[3].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(eps[0].itf,
                                         pkt_inter_epg_220_to_221 * NUM_PKTS)
@@ -1161,17 +1265,21 @@ class TestGBP(VppTestCase):
         #
         # A uni-directional contract from EPG 220 -> 221
         #
-        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])
+        rule = AclRule(is_permit=1, proto=17)
+        rule2 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule, rule2])
+        acl.add_vpp_config()
+
         c1 = VppGbpContract(
-            self, epgs[0].sclass, epgs[1].sclass, acl_index,
+            self, 400, epgs[0].sclass, epgs[1].sclass, acl.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])
         c1.add_vpp_config()
@@ -1186,12 +1294,14 @@ class TestGBP(VppTestCase):
         # 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.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()
@@ -1228,12 +1338,14 @@ class TestGBP(VppTestCase):
         # 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.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()
@@ -1244,7 +1356,6 @@ class TestGBP(VppTestCase):
                                     pkt_inter_epg_220_to_222 * NUM_PKTS,
                                     eps[3].itf,
                                     str(self.router_mac))
-
         #
         # remove both contracts, traffic stops in both directions
         #
@@ -1315,30 +1426,30 @@ class TestGBP(VppTestCase):
         #
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
-                                       IP(src=eps[0].ip4.address,
+                                       IP(src=eps[0].ip4,
                                           dst="1.1.1.1") /
                                        UDP(sport=1234, dport=1234) /
-                                       Raw('\xa5' * 100))
+                                       Raw(b'\xa5' * 100))
 
         # no policy yet
         self.send_and_assert_no_replies(eps[0].itf,
                                         pkt_inter_epg_220_to_global * NUM_PKTS)
+        rule = AclRule(is_permit=1, proto=17, ports=1234)
+        rule2 = AclRule(is_permit=1, proto=17, ports=1234,
+                        src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)))
+        acl2 = VppAcl(self, rules=[rule, rule2])
+        acl2.add_vpp_config()
 
-        acl2 = VppGbpAcl(self)
-        rule = acl2.create_rule(permit_deny=1, proto=17, sport_from=1234,
-                                sport_to=1234, dport_from=1234, dport_to=1234)
-        rule2 = acl2.create_rule(is_ipv6=1, permit_deny=1, proto=17,
-                                 sport_from=1234, sport_to=1234,
-                                 dport_from=1234, dport_to=1234)
-
-        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, acl2.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()
@@ -1346,40 +1457,41 @@ class TestGBP(VppTestCase):
         self.send_and_expect_natted(eps[0].itf,
                                     pkt_inter_epg_220_to_global * NUM_PKTS,
                                     self.pg7,
-                                    eps[0].fip4.address)
+                                    eps[0].fip4)
 
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
-                                       IPv6(src=eps[0].ip6.address,
+                                       IPv6(src=eps[0].ip6,
                                             dst="6001::1") /
                                        UDP(sport=1234, dport=1234) /
-                                       Raw('\xa5' * 100))
+                                       Raw(b'\xa5' * 100))
 
         self.send_and_expect_natted6(self.pg0,
                                      pkt_inter_epg_220_to_global * NUM_PKTS,
                                      self.pg7,
-                                     eps[0].fip6.address)
-
+                                     eps[0].fip6)
         #
         # From a global address to an EP: OUT2IN
         #
         pkt_inter_epg_220_from_global = (Ether(src=str(self.router_mac),
                                                dst=self.pg0.remote_mac) /
-                                         IP(dst=eps[0].fip4.address,
+                                         IP(dst=eps[0].fip4,
                                             src="1.1.1.1") /
                                          UDP(sport=1234, dport=1234) /
-                                         Raw('\xa5' * 100))
+                                         Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(
             self.pg7, pkt_inter_epg_220_from_global * NUM_PKTS)
 
         c5 = VppGbpContract(
-            self, epgs[3].sclass, epgs[0].sclass, acl_index2,
+            self, 400, epgs[3].sclass, epgs[0].sclass, acl2.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])
         c5.add_vpp_config()
@@ -1387,20 +1499,20 @@ class TestGBP(VppTestCase):
         self.send_and_expect_unnatted(self.pg7,
                                       pkt_inter_epg_220_from_global * NUM_PKTS,
                                       eps[0].itf,
-                                      eps[0].ip4.address)
+                                      eps[0].ip4)
 
         pkt_inter_epg_220_from_global = (Ether(src=str(self.router_mac),
                                                dst=self.pg0.remote_mac) /
-                                         IPv6(dst=eps[0].fip6.address,
+                                         IPv6(dst=eps[0].fip6,
                                               src="6001::1") /
                                          UDP(sport=1234, dport=1234) /
-                                         Raw('\xa5' * 100))
+                                         Raw(b'\xa5' * 100))
 
         self.send_and_expect_unnatted6(
             self.pg7,
             pkt_inter_epg_220_from_global * NUM_PKTS,
             eps[0].itf,
-            eps[0].ip6.address)
+            eps[0].ip6)
 
         #
         # From a local VM to another local VM using resp. public addresses:
@@ -1408,83 +1520,59 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_global = (Ether(src=self.pg0.remote_mac,
                                           dst=str(self.router_mac)) /
-                                    IP(src=eps[0].ip4.address,
-                                       dst=eps[1].fip4.address) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[1].fip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
 
         self.send_and_expect_double_natted(eps[0].itf,
                                            pkt_intra_epg_220_global * NUM_PKTS,
                                            eps[1].itf,
-                                           eps[0].fip4.address,
-                                           eps[1].ip4.address)
+                                           eps[0].fip4,
+                                           eps[1].ip4)
 
         pkt_intra_epg_220_global = (Ether(src=self.pg0.remote_mac,
                                           dst=str(self.router_mac)) /
-                                    IPv6(src=eps[0].ip6.address,
-                                         dst=eps[1].fip6.address) /
+                                    IPv6(src=eps[0].ip6,
+                                         dst=eps[1].fip6) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
 
         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)
+            eps[0].fip6,
+            eps[1].ip6)
 
         #
         # cleanup
         #
-        for ep in eps:
-            # del static mappings for each EP from the 10/8 to 11/8 network
-            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]:
-                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(
-                sw_if_index=recirc.recirc.sw_if_index,
-                is_add=0)
-            self.vapi.nat66_add_del_interface(
-                is_add=0,
-                sw_if_index=recirc.recirc.sw_if_index)
+        self.vapi.nat44_ed_plugin_enable_disable(enable=0)
+        self.vapi.nat66_plugin_enable_disable(enable=0)
 
     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):
+        # only learnt EP can timeout
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        flags = ep_flags.GBP_API_ENDPOINT_FLAG_LEARNT
         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,
+                                     flags=flags):
                 return True
             n_tries = n_tries - 1
             self.sleep(s_time)
-        self.assertFalse(find_gbp_endpoint(self, sw_if_index, ip, mac))
+        self.assertFalse(find_gbp_endpoint(self, sw_if_index, ip, mac, tep=tep,
+                                           flags=flags))
         return False
 
     def test_gbp_learn_l2(self):
         """ GBP L2 Endpoint Learning """
 
-        self.vapi.cli("clear errors")
+        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',
@@ -1502,7 +1590,7 @@ class TestGBP(VppTestCase):
         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()
 
         #
@@ -1532,7 +1620,8 @@ class TestGBP(VppTestCase):
         #
         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"))
@@ -1549,13 +1638,13 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
         epg_330 = VppGbpEndpointGroup(self, 330, 113, rd1, gbd1,
                                       None, self.loop1,
                                       "10.0.1.128",
                                       "2001:11::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_330.add_vpp_config()
 
         #
@@ -1578,7 +1667,7 @@ class TestGBP(VppTestCase):
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
 
-        self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
+        self.assertTrue(find_route(self, ep.ip4, 32, table_id=1))
 
         # a packet with an sclass from an unknown EPG
         p = (Ether(src=self.pg2.remote_mac,
@@ -1588,15 +1677,16 @@ class TestGBP(VppTestCase):
              UDP(sport=1234, dport=48879) /
              VXLAN(vni=99, gpid=88, flags=0x88) /
              Ether(src=learnt[0]["mac"], dst=ep.mac) /
-             IP(src=learnt[0]["ip"], dst=ep.ip4.address) /
+             IP(src=learnt[0]["ip"], dst=ep.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(self.pg2, p)
 
         self.logger.info(self.vapi.cli("sh error"))
-        # self.assert_packet_counter_equal(
-        #    '/err/gbp-policy-port/drop-no-contract', 1)
+        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
@@ -1608,7 +1698,7 @@ class TestGBP(VppTestCase):
                                                self.pg2.remote_hosts[0].ip4,
                                                99))
 
-        # epg is not learnt, because the EPG is unknown
+        # ep is not learnt, because the EPG is unknown
         self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
 
         #
@@ -1624,9 +1714,9 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=48879) /
                  VXLAN(vni=99, gpid=112, flags=0x88) /
                  Ether(src=l['mac'], dst=ep.mac) /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -1649,8 +1739,9 @@ class TestGBP(VppTestCase):
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
-        # self.assert_packet_counter_equal(
-        #    '/err/gbp-policy-port/allow-intra-sclass', 2)
+        self.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"))
@@ -1668,14 +1759,15 @@ class TestGBP(VppTestCase):
         # Learn new EPs from GARP packets received on the BD's mcast tunnel
         #
         for ii, l in enumerate(learnt):
-            # a packet with an sclass from a known 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) /
-                 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'],
@@ -1722,7 +1814,7 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=48879) /
                  VXLAN(vni=99, gpid=112, flags=0x88) /
                  Ether(src=l['mac'], dst=ep.mac) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -1765,9 +1857,9 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=48879) /
                  VXLAN(vni=99, gpid=112, flags=0x88, gpflags="D") /
                  Ether(src=l['mac'], dst=ep.mac) /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
@@ -1781,16 +1873,18 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             # 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) /
-                 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) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
@@ -1804,9 +1898,9 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh l2fib bd_id 1"))
         for l in learnt:
             p = (Ether(src=ep.mac, dst=l['mac']) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
+                 IP(dst=l['ip'], src=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg2)
 
@@ -1840,9 +1934,9 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=48879) /
                  VXLAN(vni=99, gpid=113, flags=0x88, gpflags='A') /
                  Ether(src=l['mac'], dst=ep.mac) /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
@@ -1855,9 +1949,9 @@ class TestGBP(VppTestCase):
         # only test 1 EP as the others could timeout
         #
         p = (Ether(src=ep.mac, dst=l['mac']) /
-             IP(dst=learnt[0]['ip'], src=ep.ip4.address) /
+             IP(dst=learnt[0]['ip'], src=ep.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(self.pg0, [p])
 
@@ -1873,9 +1967,9 @@ class TestGBP(VppTestCase):
                  UDP(sport=1234, dport=48879) /
                  VXLAN(vni=99, gpid=113, flags=0x88, gpflags='A') /
                  Ether(src=l['mac'], dst=ep.mac) /
-                 IP(src=l['ip'], dst=ep.ip4.address) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
@@ -1886,46 +1980,50 @@ class TestGBP(VppTestCase):
         #
         # Add the contract so they can talk
         #
-        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])
+        rule = AclRule(is_permit=1, proto=17)
+        rule2 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule, rule2])
+        acl.add_vpp_config()
+
         c1 = VppGbpContract(
-            self, epg_220.sclass, epg_330.sclass, acl_index,
+            self, 401, epg_220.sclass, epg_330.sclass, acl.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,
-                    [])],
+             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()
 
         for l in learnt:
             p = (Ether(src=ep.mac, dst=l['mac']) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
+                 IP(dst=l['ip'], src=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             self.send_and_expect(self.pg0, [p], self.pg2)
 
         #
         # 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 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) /
+                IP(dst="10.0.0.133", src=ep.ip4) /
                 UDP(sport=1234, dport=1234) /
-                Raw('\xa5' * 100))
+                Raw(b'\xa5' * 100))
         rxs = self.send_and_expect(ep.itf, [p_uu], gbd1.uu_fwd)
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
 
         p_bm = (Ether(src=ep.mac, dst="ff:ff:ff:ff:ff:ff") /
-                IP(dst="10.0.0.133", src=ep.ip4.address) /
+                IP(dst="10.0.0.133", src=ep.ip4) /
                 UDP(sport=1234, dport=1234) /
-                Raw('\xa5' * 100))
+                Raw(b'\xa5' * 100))
         rxs = self.send_and_expect_only(ep.itf, [p_bm], tun_bm.mcast_itf)
 
         for rx in rxs:
@@ -1940,8 +2038,30 @@ class TestGBP(VppTestCase):
             self.assertFalse(rx[VXLAN].gpflags.A)
             self.assertFalse(rx[VXLAN].gpflags.D)
 
+        rule = AclRule(is_permit=1, proto=17)
+        rule2 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule, rule2])
+        acl.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 401, epg_330.sclass, epg_220.sclass, acl.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:
             # a packet with an sclass from a known EPG
@@ -1950,22 +2070,137 @@ class TestGBP(VppTestCase):
                  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) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\xa5' * 100))
+
+            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,
+                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) /
+                 IPv6(src=l['ip6'], dst=ep.ip6) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\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']))
+            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) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\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) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\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) /
+                 UDP(sport=1234, dport=1234) /
+                 Raw(b'\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
@@ -1977,11 +2212,20 @@ class TestGBP(VppTestCase):
         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_contract(self):
-        """ GBP CONTRACTS """
+        """ GBP Contracts """
+
+        #
+        # Route Domains
+        #
+        gt4 = VppIpTable(self, 0)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 0, is_ip6=True)
+        gt6.add_vpp_config()
+
+        rd0 = VppGbpRouteDomain(self, 0, 400, gt4, gt6, None, None)
+
+        rd0.add_vpp_config()
 
         #
         # Bridge Domains
@@ -1992,26 +2236,14 @@ class TestGBP(VppTestCase):
         bd1.add_vpp_config()
         bd2.add_vpp_config()
 
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
-        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd0, self.loop0)
+        gbd2 = VppGbpBridgeDomain(self, bd2, rd0, self.loop1)
 
         gbd1.add_vpp_config()
         gbd2.add_vpp_config()
 
         #
-        # Route Domains
-        #
-        gt4 = VppIpTable(self, 0)
-        gt4.add_vpp_config()
-        gt6 = VppIpTable(self, 0, is_ip6=True)
-        gt6.add_vpp_config()
-
-        rd0 = VppGbpRouteDomain(self, 0, gt4, gt6, None, None)
-
-        rd0.add_vpp_config()
-
-        #
-        # 3 EPGs, 2 of which share a BD.
+        # 3 EPGs, 2 of which share a BD.
         #
         epgs = [VppGbpEndpointGroup(self, 220, 1220, rd0, gbd1,
                                     None, self.loop0,
@@ -2048,16 +2280,18 @@ class TestGBP(VppTestCase):
         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)
-
-            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()
+                b4 = VppIpInterfaceBind(self, epg.bvi,
+                                        epg.rd.t4).add_vpp_config()
+                b6 = VppIpInterfaceBind(self, epg.bvi,
+                                        epg.rd.t6).add_vpp_config()
+                epg.bvi.set_mac(self.router_mac)
+
+            if_ip4 = VppIpInterfaceAddress(self, epg.bvi,
+                                           epg.bvi_ip4, 32,
+                                           bind=b4).add_vpp_config()
+            if_ip6 = VppIpInterfaceAddress(self, epg.bvi,
+                                           epg.bvi_ip6, 128,
+                                           bind=b6).add_vpp_config()
 
             # add the BD ARP termination entry for BVI IP
             epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
@@ -2083,41 +2317,61 @@ class TestGBP(VppTestCase):
         #
         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) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[1].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg0,
                                      pkt_intra_epg_220_to_220 * 65,
                                      self.pg1)
 
+        pkt_intra_epg_220_to_220 = (Ether(src=self.pg0.remote_mac,
+                                          dst=self.pg1.remote_mac) /
+                                    IPv6(src=eps[0].ip6,
+                                         dst=eps[1].ip6) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw(b'\xa5' * 100))
+
+        self.send_and_expect_bridged6(self.pg0,
+                                      pkt_intra_epg_220_to_220 * 65,
+                                      self.pg1)
+
         #
         # Inter epg denied without contract
         #
         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) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[2].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(self.pg0, pkt_inter_epg_220_to_221)
 
         #
         # A uni-directional contract from EPG 220 -> 221
         #
-        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])
+        rule = AclRule(is_permit=1, proto=17)
+        rule2 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        rule3 = AclRule(is_permit=1, proto=1)
+        acl = VppAcl(self, rules=[rule, rule2, rule3])
+        acl.add_vpp_config()
+
         c1 = VppGbpContract(
-            self, epgs[0].sclass, epgs[1].sclass, acl_index,
+            self, 400, epgs[0].sclass, epgs[1].sclass, acl.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()
@@ -2128,23 +2382,44 @@ class TestGBP(VppTestCase):
 
         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) /
+                                    IP(src=eps[0].ip4,
+                                       dst=eps[3].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\xa5' * 100))
         self.send_and_assert_no_replies(eps[0].itf,
                                         pkt_inter_epg_220_to_222 * 65)
 
+        #
+        # ping router IP in different BD
+        #
+        pkt_router_ping_220_to_221 = (Ether(src=self.pg0.remote_mac,
+                                            dst=str(self.router_mac)) /
+                                      IP(src=eps[0].ip4,
+                                         dst=epgs[1].bvi_ip4) /
+                                      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,
+                                           dst=epgs[1].bvi_ip6) /
+                                      ICMPv6EchoRequest())
+
+        self.send_and_expect(self.pg0, [pkt_router_ping_220_to_221], self.pg0)
+
         #
         # 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.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()
@@ -2154,24 +2429,46 @@ class TestGBP(VppTestCase):
                                      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) /
+                                    IP(src=eps[2].ip4,
+                                       dst=eps[0].ip4) /
                                     UDP(sport=1234, dport=1234) /
-                                    Raw('\xa5' * 100))
+                                    Raw(b'\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,
+                                       dst=eps[0].ip4) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw(b'\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,
+                                         dst=eps[0].ip6) /
+                                    UDP(sport=1234, dport=1234) /
+                                    Raw(b'\xa5' * 100))
+        self.send_and_expect_routed6(eps[2].itf,
+                                     pkt_inter_epg_221_to_220 * 65,
+                                     eps[0].itf,
+                                     str(self.router_mac))
 
         #
         # contract between 220 and 222 uni-direction
         #
         c3 = VppGbpContract(
-            self, epgs[0].sclass, epgs[2].sclass, acl_index,
+            self, 400, epgs[0].sclass, epgs[2].sclass, acl.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()
@@ -2185,8 +2482,8 @@ class TestGBP(VppTestCase):
         c2.remove_vpp_config()
         acl.remove_vpp_config()
 
-    def test_gbp_bd_flags(self):
-        """ GBP BD FLAGS """
+    def test_gbp_bd_drop_flags(self):
+        """ GBP BD drop flags """
 
         #
         # IP tables
@@ -2196,33 +2493,17 @@ class TestGBP(VppTestCase):
         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()
 
         #
-        # Pg3 hosts the IP4 UU-flood VXLAN tunnel
-        # Pg4 hosts the IP6 UU-flood VXLAN tunnel
-        #
-        self.pg3.config_ip4()
-        self.pg3.resolve_arp()
-        self.pg4.config_ip4()
-        self.pg4.resolve_arp()
-
-        #
-        # Add a mcast destination VXLAN-GBP tunnel for B&M traffic
-        #
-        tun_bm = VppVxlanGbpTunnel(self, self.pg4.local_ip4,
-                                   "239.1.1.1", 88,
-                                   mcast_itf=self.pg4)
-        tun_bm.add_vpp_config()
-
-        #
-        # a GBP bridge domain with a BVI and a UU-flood interface
+        # a GBP bridge domain with a BVI only
         #
         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,
+                                  None, None,
                                   uu_drop=True, bm_drop=True)
         gbd1.add_vpp_config()
 
@@ -2230,8 +2511,8 @@ class TestGBP(VppTestCase):
         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()
+        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                        "10.0.0.128", 32).add_vpp_config()
 
         #
         # The Endpoint-group
@@ -2240,7 +2521,7 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(3))
         epg_220.add_vpp_config()
 
         ep = VppGbpEndpoint(self, self.pg0,
@@ -2248,6 +2529,7 @@ class TestGBP(VppTestCase):
                             "10.0.0.127", "11.0.0.127",
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
+
         #
         # send UU/BM packet from the local EP with UU drop and BM drop enabled
         # in bd
@@ -2255,22 +2537,94 @@ class TestGBP(VppTestCase):
         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) /
+                IP(dst="10.0.0.133", src=ep.ip4) /
                 UDP(sport=1234, dport=1234) /
-                Raw('\xa5' * 100))
+                Raw(b'\xa5' * 100))
         self.send_and_assert_no_replies(ep.itf, [p_uu])
 
         p_bm = (Ether(src=ep.mac, dst="ff:ff:ff:ff:ff:ff") /
-                IP(dst="10.0.0.133", src=ep.ip4.address) /
+                IP(dst="10.0.0.133", src=ep.ip4) /
                 UDP(sport=1234, dport=1234) /
-                Raw('\xa5' * 100))
+                Raw(b'\xa5' * 100))
         self.send_and_assert_no_replies(ep.itf, [p_bm])
 
         self.pg3.unconfig_ip4()
-        self.pg4.unconfig_ip4()
 
         self.logger.info(self.vapi.cli("sh int"))
 
+    def test_gbp_bd_arp_flags(self):
+        """ GBP BD arp flags """
+
+        #
+        # IP tables
+        #
+        gt4 = VppIpTable(self, 1)
+        gt4.add_vpp_config()
+        gt6 = VppIpTable(self, 1, is_ip6=True)
+        gt6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
+        rd1.add_vpp_config()
+
+        #
+        # Pg4 hosts the IP6 UU-flood VXLAN tunnel
+        #
+        self.pg4.config_ip4()
+        self.pg4.resolve_arp()
+
+        #
+        # 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()
+
+        #
+        # 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,
+                                  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).add_vpp_config()
+
+        #
+        # The Endpoint-group
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 112, rd1, gbd1,
+                                      None, self.loop0,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(2))
+        epg_220.add_vpp_config()
+
+        ep = VppGbpEndpoint(self, self.pg0,
+                            epg_220, None,
+                            "10.0.0.127", "11.0.0.127",
+                            "2001:10::1", "3001::1")
+        ep.add_vpp_config()
+
+        #
+        # send 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, 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"""
 
@@ -2290,7 +2644,7 @@ class TestGBP(VppTestCase):
         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()
 
         #
@@ -2323,7 +2677,7 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, bd_uu_fwd,
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, bd_uu_fwd,
                                   learn=False)
         gbd1.add_vpp_config()
 
@@ -2331,8 +2685,8 @@ class TestGBP(VppTestCase):
         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()
+        ip_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                        "10.0.0.128", 32).add_vpp_config()
 
         #
         # The Endpoint-group in which we are learning endpoints
@@ -2341,7 +2695,7 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
 
         #
@@ -2364,7 +2718,7 @@ class TestGBP(VppTestCase):
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
 
-        self.assertTrue(find_route(self, ep.ip4.address, 32, table_id=1))
+        self.assertTrue(find_route(self, ep.ip4, 32, table_id=1))
 
         #
         # Send to the static EP
@@ -2379,9 +2733,9 @@ class TestGBP(VppTestCase):
                  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) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2414,9 +2768,9 @@ class TestGBP(VppTestCase):
         for l in learnt:
             p = (Ether(src=ep.mac, dst=l['mac']) /
                  Dot1Q(vlan=11) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
+                 IP(dst=l['ip'], src=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg3)
 
@@ -2466,7 +2820,7 @@ class TestGBP(VppTestCase):
         tun_ip4_uu.add_vpp_config()
         tun_ip6_uu.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6, tun_ip4_uu, tun_ip6_uu)
+        rd1 = VppGbpRouteDomain(self, 2, 401, t4, t6, tun_ip4_uu, tun_ip6_uu)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
@@ -2474,8 +2828,8 @@ class TestGBP(VppTestCase):
         #
         # Bind the BVI to the RD
         #
-        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
-        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+        b4 = VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        b6 = VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
 
         #
         # Pg2 hosts the vxlan tunnel
@@ -2497,7 +2851,7 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, self.pg3)
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
@@ -2505,10 +2859,12 @@ class TestGBP(VppTestCase):
         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()
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                         "10.0.0.128", 32,
+                                         bind=b4).add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                         "2001:10::128", 128,
+                                         bind=b6).add_vpp_config()
 
         #
         # The Endpoint-group in which we are learning endpoints
@@ -2517,12 +2873,11 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
 
         #
-        # The VXLAN GBP tunnel is a bridge-port and has L2 endpoint
-        # learning enabled
+        # The VXLAN GBP tunnel is in L3 mode with learning enabled
         #
         vx_tun_l3 = VppGbpVxlanTunnel(
             self, 101, rd1.rd_id,
@@ -2553,9 +2908,9 @@ class TestGBP(VppTestCase):
                  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) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2577,9 +2932,9 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IP(dst=l['ip'], src=ep.ip4.address) /
+                 IP(dst=l['ip'], src=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 1, self.pg2)
 
@@ -2599,7 +2954,7 @@ class TestGBP(VppTestCase):
 
                 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].src, ep.ip4)
                 self.assertEqual(inner[IP].dst, l['ip'])
 
         for l in learnt:
@@ -2620,9 +2975,9 @@ class TestGBP(VppTestCase):
                  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) /
+                 IPv6(src=l['ip6'], dst=ep.ip6) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2650,9 +3005,9 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-                 IPv6(dst=l['ip6'], src=ep.ip6.address) /
+                 IPv6(dst=l['ip6'], src=ep.ip6) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
 
@@ -2672,7 +3027,7 @@ class TestGBP(VppTestCase):
 
                 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].src, ep.ip6)
                 self.assertEqual(inner[IPv6].dst, l['ip6'])
 
         self.logger.info(self.vapi.cli("sh gbp endpoint"))
@@ -2683,25 +3038,44 @@ class TestGBP(VppTestCase):
         # 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) /
+             IP(dst="10.0.0.99", src=ep.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(self.pg0, [p])
 
         #
         # Add a route to static EP's v4 and v6 subnet
-        #  packets should be sent on the v4/v6 uu=fwd interface resp.
         #
         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, src=ep.ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg0)
+
         p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(dst="10.0.0.99", src=ep.ip4.address) /
+             IPv6(dst=epg_220.bvi_ip6, src=ep.ip6) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\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) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
         for rx in rxs:
@@ -2730,9 +3104,9 @@ class TestGBP(VppTestCase):
                  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) /
+                 IP(src=l['ip'], dst=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2758,12 +3132,14 @@ class TestGBP(VppTestCase):
                                 "2001:10::88", "3001::88",
                                 ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                                 self.pg2.local_ip4,
-                                self.pg2.remote_hosts[1].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,
@@ -2789,15 +3165,15 @@ class TestGBP(VppTestCase):
         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) /
+                 IP(dst=ip, src=ep.ip4) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\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[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)
@@ -2811,7 +3187,7 @@ class TestGBP(VppTestCase):
 
                 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].src, ep.ip4)
                 self.assertEqual(inner[IP].dst, ip)
 
         #
@@ -2821,20 +3197,20 @@ class TestGBP(VppTestCase):
         rep_88.remove_vpp_config()
         rep_2.remove_vpp_config()
 
-        self.assertTrue(find_gbp_endpoint(self, ip=rep_2.ip4.address))
+        self.assertTrue(find_gbp_endpoint(self, ip=rep_2.ip4))
 
         p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(src=ep.ip4.address, dst=rep_2.ip4.address) /
+             IP(src=ep.ip4, dst=rep_2.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
         rxs = self.send_and_expect(self.pg0, [p], self.pg2)
 
-        self.assertFalse(find_gbp_endpoint(self, ip=rep_88.ip4.address))
+        self.assertFalse(find_gbp_endpoint(self, ip=rep_88.ip4))
 
         p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
-             IP(src=ep.ip4.address, dst=rep_88.ip4.address) /
+             IP(src=ep.ip4, dst=rep_88.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
 
         #
@@ -2842,8 +3218,62 @@ class TestGBP(VppTestCase):
         # 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)
+        self.wait_for_ep_timeout(ip=rep_88.ip4)
+        self.wait_for_ep_timeout(ip=rep_2.ip4)
+
+        #
+        # 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) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\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,
+                                          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) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\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,
+                                 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
@@ -2855,9 +3285,9 @@ class TestGBP(VppTestCase):
              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) /
+             IP(src=learnt[1]['ip'], dst=ep.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2898,7 +3328,7 @@ class TestGBP(VppTestCase):
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
+        rd1 = VppGbpRouteDomain(self, 2, 402, t4, t6)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
@@ -2906,8 +3336,8 @@ class TestGBP(VppTestCase):
         #
         # Bind the BVI to the RD
         #
-        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
-        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+        b_ip4 = VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        b_ip6 = VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
 
         #
         # Pg7 hosts a BD's UU-fwd
@@ -2920,23 +3350,25 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0)
         gbd1.add_vpp_config()
 
         bd2 = VppBridgeDomain(self, 2)
         bd2.add_vpp_config()
-        gbd2 = VppGbpBridgeDomain(self, bd2, self.loop1)
+        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()
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                         "10.0.0.128", 32,
+                                         bind=b_ip4).add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                         "2001:10::128", 128,
+                                         bind=b_ip6).add_vpp_config()
+        ip4_addr = VppIpInterfaceAddress(self, gbd2.bvi,
+                                         "10.0.1.128", 32).add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd2.bvi,
+                                         "2001:11::128", 128).add_vpp_config()
 
         #
         # The Endpoint-groups in which we are learning endpoints
@@ -2945,19 +3377,19 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(60))
         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))
+                                      VppGbpEndpointRetention(60))
         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))
+                                      VppGbpEndpointRetention(60))
         epg_222.add_vpp_config()
 
         #
@@ -2972,11 +3404,13 @@ class TestGBP(VppTestCase):
 
         bd3 = VppBridgeDomain(self, 3)
         bd3.add_vpp_config()
-        gbd3 = VppGbpBridgeDomain(self, bd3, self.loop2, bd_uu1, learn=False)
+        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, self.loop3, bd_uu2, learn=False)
+        gbd4 = VppGbpBridgeDomain(self, bd4, rd1, self.loop3,
+                                  bd_uu2, learn=False)
         gbd4.add_vpp_config()
 
         #
@@ -2986,13 +3420,13 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "12.0.0.128",
                                       "4001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(60))
         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))
+                                      VppGbpEndpointRetention(60))
         epg_321.add_vpp_config()
 
         #
@@ -3043,21 +3477,21 @@ class TestGBP(VppTestCase):
         #  different dest ports on each so the are LB hashed differently
         #
         p4 = [(Ether(src=ep1.mac, dst=ep3.mac) /
-               IP(src=ep1.ip4.address, dst=ep3.ip4.address) /
+               IP(src=ep1.ip4, dst=ep3.ip4) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep3.mac, dst=ep1.mac) /
-               IP(src=ep3.ip4.address, dst=ep1.ip4.address) /
+               IP(src=ep3.ip4, dst=ep1.ip4) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=ep3.mac) /
-               IPv6(src=ep1.ip6.address, dst=ep3.ip6.address) /
+               IPv6(src=ep1.ip6, dst=ep3.ip6) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep3.mac, dst=ep1.mac) /
-               IPv6(src=ep3.ip6.address, dst=ep1.ip6.address) /
+               IPv6(src=ep3.ip6, dst=ep1.ip6) /
                UDP(sport=1234, dport=1230) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
 
         # should be dropped since no contract yet
         self.send_and_assert_no_replies(self.pg0, [p4[0]])
@@ -3067,16 +3501,17 @@ class TestGBP(VppTestCase):
         # 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])
+        rule4 = AclRule(is_permit=1, proto=17)
+        rule6 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule4, rule6])
+        acl.add_vpp_config()
 
         #
         # test the src-ip hash mode
         #
         c1 = VppGbpContract(
-            self, epg_220.sclass, epg_222.sclass, acl_index,
+            self, 402, epg_220.sclass, epg_222.sclass, acl.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,
@@ -3095,7 +3530,7 @@ class TestGBP(VppTestCase):
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
-            self, epg_222.sclass, epg_220.sclass, acl_index,
+            self, 402, epg_222.sclass, epg_220.sclass, acl.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,
@@ -3122,16 +3557,16 @@ class TestGBP(VppTestCase):
         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)
+            self.assertEqual(rx[IP].src, ep1.ip4)
+            self.assertEqual(rx[IP].dst, ep3.ip4)
 
         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)
+            self.assertEqual(rx[IP].src, ep3.ip4)
+            self.assertEqual(rx[IP].dst, ep1.ip4)
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
 
@@ -3151,16 +3586,16 @@ class TestGBP(VppTestCase):
 
             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)
+            self.assertEqual(inner[IPv6].src, ep1.ip6)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6)
 
         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)
+            self.assertEqual(rx[IPv6].src, ep3.ip6)
+            self.assertEqual(rx[IPv6].dst, ep1.ip6)
 
         #
         # programme the unknown EP
@@ -3172,8 +3607,8 @@ class TestGBP(VppTestCase):
         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)
+            self.assertEqual(rx[IPv6].src, ep1.ip6)
+            self.assertEqual(rx[IPv6].dst, ep3.ip6)
 
         #
         # and revert back to unprogrammed
@@ -3198,8 +3633,8 @@ class TestGBP(VppTestCase):
 
             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)
+            self.assertEqual(inner[IPv6].src, ep1.ip6)
+            self.assertEqual(inner[IPv6].dst, ep3.ip6)
 
         c1.remove_vpp_config()
         c2.remove_vpp_config()
@@ -3208,7 +3643,7 @@ class TestGBP(VppTestCase):
         # test the symmetric hash mode
         #
         c1 = VppGbpContract(
-            self, epg_220.sclass, epg_222.sclass, acl_index,
+            self, 402, epg_220.sclass, epg_222.sclass, acl.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,
@@ -3227,7 +3662,7 @@ class TestGBP(VppTestCase):
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
-            self, epg_222.sclass, epg_220.sclass, acl_index,
+            self, 402, epg_222.sclass, epg_220.sclass, acl.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,
@@ -3254,16 +3689,16 @@ class TestGBP(VppTestCase):
         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)
+            self.assertEqual(rx[IP].src, ep1.ip4)
+            self.assertEqual(rx[IP].dst, ep3.ip4)
 
         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)
+            self.assertEqual(rx[IP].src, ep3.ip4)
+            self.assertEqual(rx[IP].dst, ep1.ip4)
 
         #
         # programme the unknown EP for the L3 tests
@@ -3275,24 +3710,24 @@ class TestGBP(VppTestCase):
         #  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) /
+               IP(src=ep1.ip4, dst=ep2.ip4) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep2.mac, dst=str(self.router_mac)) /
-               IP(src=ep2.ip4.address, dst=ep1.ip4.address) /
+               IP(src=ep2.ip4, dst=ep1.ip4) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep1.ip6.address, dst=ep2.ip6.address) /
+               IPv6(src=ep1.ip6, dst=ep2.ip6) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep2.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep2.ip6.address, dst=ep1.ip6.address) /
+               IPv6(src=ep2.ip6, dst=ep1.ip6) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
 
         c3 = VppGbpContract(
-            self, epg_220.sclass, epg_221.sclass, acl_index,
+            self, 402, epg_220.sclass, epg_221.sclass, acl.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,
@@ -3315,11 +3750,12 @@ class TestGBP(VppTestCase):
         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)
+            self.assertEqual(rx[IP].src, ep1.ip4)
+            self.assertEqual(rx[IP].dst, ep2.ip4)
 
         #
         # learn a remote EP in EPG 221
+        #   packets coming from unknown remote EPs will be leant & redirected
         #
         vx_tun_l3 = VppGbpVxlanTunnel(
             self, 444, rd1.rd_id,
@@ -3328,13 +3764,21 @@ class TestGBP(VppTestCase):
         vx_tun_l3.add_vpp_config()
 
         c4 = VppGbpContract(
-            self, epg_221.sclass, epg_220.sclass, acl_index,
+            self, 402, epg_221.sclass, epg_220.sclass, acl.acl_index,
             [VppGbpContractRule(
-                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
-                []),
+                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_PERMIT,
-                    [])],
+                    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])
         c4.add_vpp_config()
 
@@ -3345,11 +3789,18 @@ class TestGBP(VppTestCase):
              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) /
+             IP(src="10.0.0.88", dst=ep1.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+        # unknown remote EP to local EP redirected
+        rxs = self.send_and_expect(self.pg7, [p], 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, "10.0.0.88")
+            self.assertEqual(rx[IP].dst, ep1.ip4)
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
@@ -3363,11 +3814,18 @@ class TestGBP(VppTestCase):
              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) /
+             IPv6(src="2001:10::88", dst=ep1.ip6) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+        # unknown remote EP to local EP redirected (ipv6)
+        rxs = self.send_and_expect(self.pg7, [p], 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, "2001:10::88")
+            self.assertEqual(rx[IPv6].dst, ep1.ip6)
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
@@ -3378,20 +3836,20 @@ class TestGBP(VppTestCase):
         # L3 switch from local to remote EP
         #
         p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IP(src=ep1.ip4.address, dst="10.0.0.88") /
+               IP(src=ep1.ip4, dst="10.0.0.88") /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
-               IPv6(src=ep1.ip6.address, dst="2001:10::88") /
+               IPv6(src=ep1.ip6, dst="2001:10::88") /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
 
         rxs = self.send_and_expect(self.pg0, p4[0] * 17, sep1.itf)
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, routed_src_mac)
             self.assertEqual(rx[Ether].dst, sep1.mac)
-            self.assertEqual(rx[IP].src, ep1.ip4.address)
+            self.assertEqual(rx[IP].src, ep1.ip4)
             self.assertEqual(rx[IP].dst, "10.0.0.88")
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
@@ -3399,14 +3857,14 @@ class TestGBP(VppTestCase):
         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].src, ep1.ip6)
             self.assertEqual(rx[IPv6].dst, "2001:10::88")
 
         #
         # test the dst-ip hash mode
         #
         c5 = VppGbpContract(
-            self, epg_220.sclass, epg_221.sclass, acl_index,
+            self, 402, epg_220.sclass, epg_221.sclass, acl.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,
@@ -3429,7 +3887,7 @@ class TestGBP(VppTestCase):
         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].src, ep1.ip4)
             self.assertEqual(rx[IP].dst, "10.0.0.88")
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep3.itf)
@@ -3437,63 +3895,1603 @@ class TestGBP(VppTestCase):
         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].src, ep1.ip6)
             self.assertEqual(rx[IPv6].dst, "2001:10::88")
 
         #
-        # cleanup
+        # a programmed remote SEP in EPG 320
         #
-        self.pg7.unconfig_ip4()
 
-    def test_gbp_l3_out(self):
-        """ GBP L3 Out """
+        # gbp vxlan tunnel for the remote SEP
+        vx_tun_l3_sep = VppGbpVxlanTunnel(
+            self, 555, rd1.rd_id,
+            VppEnum.vl_api_gbp_vxlan_tunnel_mode_t.GBP_VXLAN_TUNNEL_MODE_L3,
+            self.pg2.local_ip4)
+        vx_tun_l3_sep.add_vpp_config()
 
-        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
-        self.vapi.cli("set logging class gbp level debug")
+        # remote SEP
+        sep5 = VppGbpEndpoint(self, vx_tun_l3_sep,
+                              epg_320, None,
+                              "12.0.0.10", "13.0.0.10",
+                              "4001:10::10", "5001:10::10",
+                              ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
+                              self.pg7.local_ip4,
+                              self.pg7.remote_ip4,
+                              mac=None)
+        sep5.add_vpp_config()
+
+        #
+        # local l3out redirect tests
+        #
+
+        # add local l3out
+        # the external bd
+        self.loop4.set_mac(self.router_mac)
+        b_lo4_ip4 = VppIpInterfaceBind(self, self.loop4, t4).add_vpp_config()
+        b_lo4_ip6 = VppIpInterfaceBind(self, self.loop4, t6).add_vpp_config()
+        ebd = VppBridgeDomain(self, 100)
+        ebd.add_vpp_config()
+        gebd = VppGbpBridgeDomain(self, ebd, rd1, self.loop4, None, None)
+        gebd.add_vpp_config()
+        # the external epg
+        eepg = VppGbpEndpointGroup(self, 888, 765, rd1, gebd,
+                                   None, gebd.bvi,
+                                   "10.1.0.128",
+                                   "2001:10:1::128",
+                                   VppGbpEndpointRetention(60))
+        eepg.add_vpp_config()
+        # add subnets to BVI
+        VppIpInterfaceAddress(
+            self,
+            gebd.bvi,
+            "10.1.0.128",
+            24, bind=b_lo4_ip4).add_vpp_config()
+        VppIpInterfaceAddress(
+            self,
+            gebd.bvi,
+            "2001:10:1::128",
+            64, bind=b_lo4_ip6).add_vpp_config()
+        # ... which are L3-out subnets
+        VppGbpSubnet(self, rd1, "10.1.0.0", 24,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=765).add_vpp_config()
+        VppGbpSubnet(self, rd1, "2001:10:1::128", 64,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=765).add_vpp_config()
+        # external endpoints
+        VppL2Vtr(self, self.vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        eep1 = VppGbpEndpoint(self, self.vlan_100, eepg, None, "10.1.0.1",
+                              "11.1.0.1", "2001:10:1::1", "3001:10:1::1",
+                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
+        eep1.add_vpp_config()
+        VppL2Vtr(self, self.vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        eep2 = VppGbpEndpoint(self, self.vlan_101, eepg, None, "10.1.0.2",
+                              "11.1.0.2", "2001:10:1::2", "3001:10:1::2",
+                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
+        eep2.add_vpp_config()
 
-        routed_dst_mac = "00:0c:0c:0c:0c:0c"
-        routed_src_mac = "00:22:bd:f8:19:ff"
+        # external subnets reachable though eep1 and eep2 respectively
+        VppIpRoute(self, "10.220.0.0", 24,
+                   [VppRoutePath(eep1.ip4, eep1.epg.bvi.sw_if_index)],
+                   table_id=t4.table_id).add_vpp_config()
+        VppGbpSubnet(self, rd1, "10.220.0.0", 24,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=4220).add_vpp_config()
+        VppIpRoute(self, "10:220::", 64,
+                   [VppRoutePath(eep1.ip6, eep1.epg.bvi.sw_if_index)],
+                   table_id=t6.table_id).add_vpp_config()
+        VppGbpSubnet(self, rd1, "10:220::", 64,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=4220).add_vpp_config()
+        VppIpRoute(self, "10.221.0.0", 24,
+                   [VppRoutePath(eep2.ip4, eep2.epg.bvi.sw_if_index)],
+                   table_id=t4.table_id).add_vpp_config()
+        VppGbpSubnet(self, rd1, "10.221.0.0", 24,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=4221).add_vpp_config()
+        VppIpRoute(self, "10:221::", 64,
+                   [VppRoutePath(eep2.ip6, eep2.epg.bvi.sw_if_index)],
+                   table_id=t6.table_id).add_vpp_config()
+        VppGbpSubnet(self, rd1, "10:221::", 64,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+                     sclass=4221).add_vpp_config()
+
+        #
+        # l3out redirect to remote (known, then unknown) SEP
+        #
+
+        # packets from 1 external subnet to the other
+        p = [(Ether(src=eep1.mac, dst=self.router_mac) /
+              Dot1Q(vlan=100) /
+              IP(src="10.220.0.17", dst="10.221.0.65") /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100)),
+             (Ether(src=eep1.mac, dst=self.router_mac) /
+              Dot1Q(vlan=100) /
+              IPv6(src="10:220::17", dst="10:221::65") /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100))]
 
-        #
-        # IP tables
-        #
-        t4 = VppIpTable(self, 1)
-        t4.add_vpp_config()
-        t6 = VppIpTable(self, 1, True)
-        t6.add_vpp_config()
+        # packets should be dropped in absence of contract
+        self.send_and_assert_no_replies(self.pg0, p)
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
-        rd1.add_vpp_config()
+        # contract redirecting to sep5
+        VppGbpContract(
+            self, 402, 4220, 4221, acl.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(sep5.vmac, sep5.epg.bd,
+                                       sep5.ip4, sep5.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(sep5.vmac, sep5.epg.bd,
+                                           sep5.ip6, sep5.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6]).add_vpp_config()
 
-        self.loop0.set_mac(self.router_mac)
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
 
-        #
-        # Bind the BVI to the RD
-        #
-        VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
-        VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+        for rx, tx in zip(rxs, p):
+            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)
+            # this should use the programmed remote leaf TEP
+            self.assertEqual(rx[VXLAN].vni, 555)
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            rxip = rx[VXLAN][Ether].payload
+            txip = tx[Dot1Q].payload
+            self.assertEqual(rxip.src, txip.src)
+            self.assertEqual(rxip.dst, txip.dst)
 
-        #
-        # Pg7 hosts a BD's BUM
-        # Pg1 some other l3 interface
-        #
-        self.pg7.config_ip4()
-        self.pg7.resolve_arp()
+        # remote SEP: it is now an unknown remote SEP and should go
+        # to spine proxy
+        sep5.remove_vpp_config()
 
-        #
-        # a multicast vxlan-gbp tunnel for broadcast in the BD
-        #
-        tun_bm = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
-                                   "239.1.1.1", 88,
-                                   mcast_itf=self.pg7)
-        tun_bm.add_vpp_config()
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
 
-        #
-        # a GBP external bridge domains for the EPs
-        #
+        for rx, tx in zip(rxs, p):
+            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)
+            # this should use the spine proxy TEP
+            self.assertEqual(rx[VXLAN].vni, epg_320.bd.uu_fwd.vni)
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            rxip = rx[VXLAN][Ether].payload
+            txip = tx[Dot1Q].payload
+            self.assertEqual(rxip.src, txip.src)
+            self.assertEqual(rxip.dst, txip.dst)
+
+        #
+        # l3out redirect to local SEP
+        #
+
+        # change the contract between l3out to redirect to local SEPs
+        # instead of remote SEP
+        VppGbpContract(
+            self, 402, 4220, 4221, acl.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)]),
+                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.ip6, sep1.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6]).add_vpp_config()
+
+        rxs = self.send_and_expect(self.pg0, p, sep1.itf)
+        for rx, tx in zip(rxs, p):
+            self.assertEqual(rx[Ether].src, routed_src_mac)
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            rxip = rx[Ether].payload
+            txip = tx[Ether].payload
+            self.assertEqual(rxip.src, txip.src)
+            self.assertEqual(rxip.dst, txip.dst)
+
+        #
+        # redirect remote EP to remote (known then unknown) SEP
+        #
+
+        # remote SEP known again
+        sep5.add_vpp_config()
+
+        # contract to redirect to learnt SEP
+        VppGbpContract(
+            self, 402, epg_221.sclass, epg_222.sclass, acl.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(sep5.vmac, sep5.epg.bd,
+                                       sep5.ip4, sep5.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(sep5.vmac, sep5.epg.bd,
+                                           sep5.ip6, sep5.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6]).add_vpp_config()
+
+        # packets from unknown EP 221 to known EP in EPG 222
+        # should be redirected to known remote SEP
+        base = (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:44", dst=str(self.router_mac)))
+        p = [(base /
+              IP(src="10.0.1.100", dst=ep3.ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100)),
+             (base /
+              IPv6(src="2001:10::100", dst=ep3.ip6) /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100))]
+
+        # unknown remote EP to local EP redirected to known remote SEP
+        rxs = self.send_and_expect(self.pg7, p, self.pg7)
+
+        for rx, tx in zip(rxs, p):
+            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)
+            # this should use the programmed remote leaf TEP
+            self.assertEqual(rx[VXLAN].vni, 555)
+            self.assertEqual(rx[VXLAN].gpid, epg_221.sclass)
+            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)
+            rxip = rx[VXLAN][Ether].payload
+            txip = tx[VXLAN][Ether].payload
+            self.assertEqual(rxip.src, txip.src)
+            self.assertEqual(rxip.dst, txip.dst)
+
+        # endpoint learnt via the parent GBP-vxlan interface
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip="10.0.1.100"))
+        self.assertTrue(find_gbp_endpoint(self,
+                                          vx_tun_l3._sw_if_index,
+                                          ip="2001:10::100"))
+
+        # remote SEP: it is now an unknown remote SEP and should go
+        # to spine proxy
+        sep5.remove_vpp_config()
+
+        # remote EP (coming from spine proxy) to local EP redirected to
+        # known remote SEP
+        rxs = self.send_and_expect(self.pg7, p, self.pg7)
+
+        for rx, tx in zip(rxs, p):
+            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)
+            # this should use the spine proxy TEP
+            self.assertEqual(rx[VXLAN].vni, epg_320.bd.uu_fwd.vni)
+            self.assertEqual(rx[VXLAN].gpid, epg_221.sclass)
+            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)
+            rxip = rx[VXLAN][Ether].payload
+            txip = tx[VXLAN][Ether].payload
+            self.assertEqual(rxip.src, txip.src)
+            self.assertEqual(rxip.dst, txip.dst)
+
+        #
+        # cleanup
+        #
+        self.pg7.unconfig_ip4()
+
+    def test_gbp_redirect_extended(self):
+        """ GBP Endpoint Redirect Extended """
+
+        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()
+
+        # create IPv4 and IPv6 RD UU VxLAN-GBP TEP and bind them to the right
+        # VRF
+        rd_uu4 = VppVxlanGbpTunnel(
+            self,
+            self.pg7.local_ip4,
+            self.pg7.remote_ip4,
+            114,
+            mode=(VppEnum.vl_api_vxlan_gbp_api_tunnel_mode_t.
+                  VXLAN_GBP_API_TUNNEL_MODE_L3))
+        rd_uu4.add_vpp_config()
+        VppIpInterfaceBind(self, rd_uu4, t4).add_vpp_config()
+
+        rd_uu6 = VppVxlanGbpTunnel(
+            self,
+            self.pg7.local_ip4,
+            self.pg7.remote_ip4,
+            115,
+            mode=(VppEnum.vl_api_vxlan_gbp_api_tunnel_mode_t.
+                  VXLAN_GBP_API_TUNNEL_MODE_L3))
+        rd_uu6.add_vpp_config()
+        VppIpInterfaceBind(self, rd_uu6, t4).add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, 402, t4, t6, rd_uu4, rd_uu6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac)
+        self.loop1.set_mac(self.router_mac)
+        self.loop2.set_mac(self.router_mac)
+
+        #
+        # Bind the BVI to the RD
+        #
+        b_lo0_ip4 = VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        b_lo0_ip6 = VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+        b_lo1_ip4 = VppIpInterfaceBind(self, self.loop1, t4).add_vpp_config()
+        b_lo1_ip6 = VppIpInterfaceBind(self, self.loop1, t6).add_vpp_config()
+        b_lo2_ip4 = VppIpInterfaceBind(self, self.loop2, t4).add_vpp_config()
+        b_lo2_ip6 = VppIpInterfaceBind(self, self.loop2, 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_addr1 = VppIpInterfaceAddress(self, gbd1.bvi,
+                                          "10.0.0.128", 32,
+                                          bind=b_lo0_ip4).add_vpp_config()
+        ip6_addr1 = VppIpInterfaceAddress(self, gbd1.bvi,
+                                          "2001:10::128", 128,
+                                          bind=b_lo0_ip6).add_vpp_config()
+        ip4_addr2 = VppIpInterfaceAddress(self, gbd2.bvi,
+                                          "10.0.1.128", 32,
+                                          bind=b_lo1_ip4).add_vpp_config()
+        ip6_addr2 = VppIpInterfaceAddress(self, gbd2.bvi,
+                                          "2001:11::128", 128,
+                                          bind=b_lo1_ip6).add_vpp_config()
+
+        #
+        # The Endpoint-groups
+        #
+        epg_220 = VppGbpEndpointGroup(self, 220, 440, rd1, gbd1,
+                                      None, gbd1.bvi,
+                                      "10.0.0.128",
+                                      "2001:10::128",
+                                      VppGbpEndpointRetention(60))
+        epg_220.add_vpp_config()
+        epg_221 = VppGbpEndpointGroup(self, 221, 441, rd1, gbd2,
+                                      None, gbd2.bvi,
+                                      "10.0.1.128",
+                                      "2001:11::128",
+                                      VppGbpEndpointRetention(60))
+        epg_221.add_vpp_config()
+
+        #
+        # a GBP bridge domains for the SEPs
+        #
+        bd_uu3 = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
+                                   self.pg7.remote_ip4, 116)
+        bd_uu3.add_vpp_config()
+
+        bd3 = VppBridgeDomain(self, 3)
+        bd3.add_vpp_config()
+        gbd3 = VppGbpBridgeDomain(self, bd3, rd1, self.loop2,
+                                  bd_uu3, learn=False)
+        gbd3.add_vpp_config()
+
+        ip4_addr3 = VppIpInterfaceAddress(self, gbd3.bvi,
+                                          "12.0.0.128", 32,
+                                          bind=b_lo2_ip4).add_vpp_config()
+        ip6_addr3 = VppIpInterfaceAddress(self, gbd3.bvi,
+                                          "4001:10::128", 128,
+                                          bind=b_lo2_ip6).add_vpp_config()
+
+        #
+        # 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"))
+        #
+
+        #
+        # EPGs in which the service endpoints exist
+        #
+        epg_320 = VppGbpEndpointGroup(self, 320, 550, rd1, gbd3,
+                                      None, gbd3.bvi,
+                                      "12.0.0.128",
+                                      "4001:10::128",
+                                      VppGbpEndpointRetention(60))
+        epg_320.add_vpp_config()
+
+        #
+        # 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()
+
+        #
+        # service endpoints
+        #
+        sep1 = VppGbpEndpoint(self, self.pg3,
+                              epg_320, None,
+                              "12.0.0.1", "13.0.0.1",
+                              "4001:10::1", "5001:10::1")
+        sep2 = VppGbpEndpoint(self, self.pg4,
+                              epg_320, None,
+                              "12.0.0.2", "13.0.0.2",
+                              "4001:10::2", "5001:10::2")
+
+        # sep1 and sep2 are not added to config yet
+        # they are unknown for now
+
+        #
+        # add routes to EPG subnets
+        #
+        VppGbpSubnet(self, rd1, "10.0.0.0", 24,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT
+                     ).add_vpp_config()
+        VppGbpSubnet(self, rd1, "10.0.1.0", 24,
+                     VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_TRANSPORT
+                     ).add_vpp_config()
+
+        #
+        # Local host to known local host in different BD
+        # with SFC contract (source and destination are in
+        # one node and service endpoint in another node)
+        #
+        p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IP(src=ep1.ip4, dst=ep2.ip4) /
+               UDP(sport=1234, dport=1234) /
+               Raw(b'\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
+               IP(src=ep2.ip4, dst=ep1.ip4) /
+               UDP(sport=1234, dport=1234) /
+               Raw(b'\xa5' * 100))]
+        p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
+               IPv6(src=ep1.ip6, dst=ep2.ip6) /
+               UDP(sport=1234, dport=1234) /
+               Raw(b'\xa5' * 100)),
+              (Ether(src=ep2.mac, dst=str(self.router_mac)) /
+               IPv6(src=ep2.ip6, dst=ep1.ip6) /
+               UDP(sport=1234, dport=1230) /
+               Raw(b'\xa5' * 100))]
+
+        # should be dropped since no contract yet
+        self.send_and_assert_no_replies(self.pg0, [p4[0]])
+        self.send_and_assert_no_replies(self.pg0, [p6[0]])
+
+        #
+        # Add a contract with a rule to load-balance redirect via SEP1 and SEP2
+        # one of the next-hops is via an EP that is not known
+        #
+        rule4 = AclRule(is_permit=1, proto=17)
+        rule6 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule4, rule6])
+        acl.add_vpp_config()
+
+        #
+        # test the src-ip hash mode
+        #
+        c1 = VppGbpContract(
+            self, 402, epg_220.sclass, epg_221.sclass, acl.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)]),
+             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.ip6, sep1.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c1.add_vpp_config()
+
+        c2 = VppGbpContract(
+            self, 402, epg_221.sclass, epg_220.sclass, acl.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)]),
+             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.ip6, sep1.epg.rd)])],
+            [ETH_P_IP, ETH_P_IPV6])
+        c2.add_vpp_config()
+
+        # ep1 <--> ep2 redirected through sep1
+        # sep1 is unknown
+        # packet is redirected to sep bd and then go through sep bd UU
+
+        rxs = self.send_and_expect(self.pg0, p4[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, 116)
+            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, sep1.mac)
+            self.assertEqual(inner[IP].src, ep1.ip4)
+            self.assertEqual(inner[IP].dst, ep2.ip4)
+
+        rxs = self.send_and_expect(self.pg1, p4[1] * 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, 116)
+            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, sep1.mac)
+            self.assertEqual(inner[IP].src, ep2.ip4)
+            self.assertEqual(inner[IP].dst, ep1.ip4)
+
+        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, 116)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            inner = rx[VXLAN].payload
+
+            self.assertEqual(inner[Ether].src, routed_src_mac)
+            self.assertEqual(inner[Ether].dst, sep1.mac)
+            self.assertEqual(inner[IPv6].src, ep1.ip6)
+            self.assertEqual(inner[IPv6].dst, ep2.ip6)
+
+        rxs = self.send_and_expect(self.pg1, p6[1] * 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, 116)
+            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, sep1.mac)
+            self.assertEqual(inner[IPv6].src, ep2.ip6)
+            self.assertEqual(inner[IPv6].dst, ep1.ip6)
+
+        # configure sep1: it is now local
+        # packets between ep1 and ep2 are redirected locally
+        sep1.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)
+            self.assertEqual(rx[IP].dst, ep2.ip4)
+
+        rxs = self.send_and_expect(self.pg1, p6[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[IPv6].src, ep2.ip6)
+            self.assertEqual(rx[IPv6].dst, ep1.ip6)
+
+        # packet coming from the l2 spine-proxy to sep1
+        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=116, gpid=440, gpflags=0x08, flags=0x88) /
+             Ether(src=str(self.router_mac), dst=sep1.mac) /
+             IP(src=ep1.ip4, dst=ep2.ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, [p] * 17, sep1.itf)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, sep1.mac)
+            self.assertEqual(rx[IP].src, ep1.ip4)
+            self.assertEqual(rx[IP].dst, ep2.ip4)
+
+        # contract for SEP to communicate with dst EP
+        c3 = VppGbpContract(
+            self, 402, epg_320.sclass, epg_221.sclass, acl.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_SYMMETRIC),
+             VppGbpContractRule(
+                VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_PERMIT,
+                VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC)],
+            [ETH_P_IP, ETH_P_IPV6])
+        c3.add_vpp_config()
+
+        # temporarily remove ep2, so that ep2 is remote & unknown
+        ep2.remove_vpp_config()
+
+        # packet going back from sep1 to its original dest (ep2)
+        # as ep2 is now unknown (see above), it must go through
+        # the rd UU (packet is routed)
+
+        p1 = (Ether(src=sep1.mac, dst=self.router_mac) /
+              IP(src=ep1.ip4, dst=ep2.ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg3, [p1] * 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, 114)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # redirect policy has been applied
+            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, ep1.ip4)
+            self.assertEqual(inner[IP].dst, ep2.ip4)
+
+        self.logger.info(self.vapi.cli("show bridge 3 detail"))
+        sep1.remove_vpp_config()
+
+        self.logger.info(self.vapi.cli("show bridge 1 detail"))
+        self.logger.info(self.vapi.cli("show bridge 2 detail"))
+
+        # re-add ep2: it is local again :)
+        ep2.add_vpp_config()
+
+        # packet coming back from the remote sep through rd UU
+        p2 = (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=114, gpid=441, gpflags=0x09, flags=0x88) /
+              Ether(src=str(self.router_mac), dst=self.router_mac) /
+              IP(src=ep1.ip4, dst=ep2.ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, [p2], self.pg1)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, self.pg1.remote_mac)
+            self.assertEqual(rx[IP].src, ep1.ip4)
+            self.assertEqual(rx[IP].dst, ep2.ip4)
+
+        #
+        # bd_uu2.add_vpp_config()
+        #
+
+        #
+        # cleanup
+        #
+        c1.remove_vpp_config()
+        c2.remove_vpp_config()
+        c3.remove_vpp_config()
+        self.pg7.unconfig_ip4()
+
+    def test_gbp_l3_out(self):
+        """ GBP L3 Out """
+
+        ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
+        self.vapi.cli("set logging class gbp level debug")
+
+        routed_dst_mac = "00:0c:0c:0c:0c:0c"
+        routed_src_mac = "00:22:bd:f8:19:ff"
+
+        #
+        # IP tables
+        #
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.add_vpp_config()
+
+        rd1 = VppGbpRouteDomain(self, 2, 55, t4, t6)
+        rd1.add_vpp_config()
+
+        self.loop0.set_mac(self.router_mac)
+
+        #
+        # Bind the BVI to the RD
+        #
+        b_ip4 = VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        b_ip6 = 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(4))
+        epg_220.add_vpp_config()
+
+        # the BVIs have the subnets applied ...
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi, "10.0.0.128",
+                                         24, bind=b_ip4).add_vpp_config()
+        ip6_addr = VppIpInterfaceAddress(self, gbd1.bvi, "2001:10::128",
+                                         64, bind=b_ip6).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, 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, 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(b'\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(b'\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))
+
+        #
+        # remote to external
+        #
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=444, gpid=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(b'\xa5' * 100))
+
+        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)) /
+             Dot1Q(vlan=100) /
+             IP(src=eep1.ip4, dst="10.0.0.128") /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
+
+        #
+        # local EP pings other local EP
+        #
+        p = (Ether(src=eep1.mac, dst=eep2.mac) /
+             Dot1Q(vlan=100) /
+             IP(src=eep1.ip4, dst=eep2.ip4) /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, eep1.mac)
+            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 101)
+
+        #
+        # local EP pings router w/o vlan tag poped
+        #
+        p = (Ether(src=eep3.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=102) /
+             IP(src=eep3.ip4, dst="10.0.0.128") /
+             ICMP(type='echo-request'))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, self.vlan_102.remote_mac)
+
+        #
+        # A ip4 subnet reachable through the external EP1
+        #
+        ip_220 = VppIpRoute(self, "10.220.0.0", 24,
+                            [VppRoutePath(eep1.ip4,
+                                          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()
+
+        #
+        # An ip6 subnet reachable through the external EP1
+        #
+        ip6_220 = VppIpRoute(self, "10:220::", 64,
+                             [VppRoutePath(eep1.ip6,
+                                           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()
+
+        #
+        # A subnet reachable through the external EP2
+        #
+        ip_221 = VppIpRoute(self, "10.221.0.0", 24,
+                            [VppRoutePath(eep2.ip4,
+                                          eep2.epg.bvi.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_221.add_vpp_config()
+
+        l3o_221 = VppGbpSubnet(
+            self, rd1, "10.221.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4221)
+        l3o_221.add_vpp_config()
+
+        #
+        # ping between hosts in remote subnets
+        #  dropped without a contract
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.221.0.1") /
+             ICMP(type='echo-request'))
+
+        self.send_and_assert_no_replies(self.pg0, p * 1)
+
+        #
+        # contract for the external nets to communicate
+        #
+        rule4 = AclRule(is_permit=1, proto=17)
+        rule6 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule4, rule6])
+        acl.add_vpp_config()
+
+        #
+        # A contract with the wrong scope is not matched
+        #
+        c_44 = VppGbpContract(
+            self, 44, 4220, 4221, acl.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])
+        c_44.add_vpp_config()
+        self.send_and_assert_no_replies(self.pg0, p * 1)
+
+        c1 = VppGbpContract(
+            self, 55, 4220, 4221, acl.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])
+        c1.add_vpp_config()
+
+        #
+        # Contracts allowing ext-net 200 to talk with external EPs
+        #
+        c2 = VppGbpContract(
+            self, 55, 4220, 113, acl.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()
+        c3 = VppGbpContract(
+            self, 55, 113, 4220, acl.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(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, str(self.router_mac))
+            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 101)
+
+        # we did not learn these external hosts
+        self.assertFalse(find_gbp_endpoint(self, ip="10.220.0.1"))
+        self.assertFalse(find_gbp_endpoint(self, ip="10.221.0.1"))
+
+        #
+        # from remote external EP to local external EP
+        #
+        p = (Ether(src=self.pg7.remote_mac,
+                   dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4,
+                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(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
+
+        #
+        # ping from an external host to the remote external EP
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst=rep.ip4) /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 1, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            # self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 444)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # the sclass of the ext-net the packet came from
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            # policy was applied to the original IP packet
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            inner = rx[VXLAN].payload
+            self.assertEqual(inner[IP].src, "10.220.0.1")
+            self.assertEqual(inner[IP].dst, rep.ip4)
+
+        #
+        # An external subnet reachable via the remote external EP
+        #
+
+        #
+        # 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()
+
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
+
+        #
+        # then the special adj to resolve through on that tunnel
+        #
+        n1 = VppNeighbor(self,
+                         vx_tun_r1.sw_if_index,
+                         "00:0c:0c:0c:0c:0c",
+                         self.pg7.remote_ip4)
+        n1.add_vpp_config()
+
+        #
+        # the route via the adj above
+        #
+        ip_222 = VppIpRoute(self, "10.222.0.0", 24,
+                            [VppRoutePath(self.pg7.remote_ip4,
+                                          vx_tun_r1.sw_if_index)],
+                            table_id=t4.table_id)
+        ip_222.add_vpp_config()
+
+        l3o_222 = VppGbpSubnet(
+            self, rd1, "10.222.0.0", 24,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            sclass=4222)
+        l3o_222.add_vpp_config()
+
+        #
+        # ping between hosts in local and remote external subnets
+        #  dropped without a contract
+        #
+        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=100) /
+             IP(src="10.220.0.1", dst="10.222.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        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.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(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg0, p * 3, self.pg7)
+
+        for rx in rxs:
+            self.assertEqual(rx[Ether].src, self.pg7.local_mac)
+            self.assertEqual(rx[Ether].dst, self.pg7.remote_mac)
+            self.assertEqual(rx[IP].src, self.pg7.local_ip4)
+            self.assertEqual(rx[IP].dst, self.pg7.remote_ip4)
+            self.assertEqual(rx[VXLAN].vni, 445)
+            self.assertTrue(rx[VXLAN].flags.G)
+            self.assertTrue(rx[VXLAN].flags.Instance)
+            # the sclass of the ext-net the packet came from
+            self.assertEqual(rx[VXLAN].gpid, 4220)
+            # policy was applied to the original IP packet
+            self.assertTrue(rx[VXLAN].gpflags.A)
+            # since it's an external host the reciever should not learn it
+            self.assertTrue(rx[VXLAN].gpflags.D)
+            inner = rx[VXLAN].payload
+            self.assertEqual(inner[Ether].dst, "00:0c:0c:0c:0c:0c")
+            self.assertEqual(inner[IP].src, "10.220.0.1")
+            self.assertEqual(inner[IP].dst, "10.222.0.1")
+
+        #
+        # make the external subnet ECMP
+        #
+        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()
+
+        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)])
+
+        #
+        # now expect load-balance
+        #
+        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(b'\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(b'\xa5' * 100))]
+
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
+
+        self.assertEqual(rxs[0][VXLAN].vni, 445)
+        self.assertEqual(rxs[1][VXLAN].vni, 446)
+
+        #
+        # Same LB test for v6
+        #
+        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()
+
+        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(b'\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(b'\xa5' * 100))]
+
+        self.logger.info(self.vapi.cli("sh ip6 fib 10:222::1"))
+        rxs = self.send_and_expect(self.pg0, p, self.pg7)
+
+        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(b'\xa5' * 100))
+
+        rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
+        self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
+
+        #
+        # ping from host in remote to remote external subnets
+        #   this is dropped by reflection check.
+        #
+        p = (Ether(src=self.pg7.remote_mac, dst=self.pg7.local_mac) /
+             IP(src=self.pg7.remote_ip4, dst=self.pg7.local_ip4) /
+             UDP(sport=1234, dport=48879) /
+             VXLAN(vni=445, gpid=4222, flags=0x88, gpflags='A') /
+             Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
+             IP(src="10.222.0.1", dst="10.222.0.2") /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        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) /
+             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)) /
+             IPv6(src="10:222::1", dst="10:222::2") /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\xa5' * 100))
+
+        rxs = self.send_and_assert_no_replies(self.pg7, p * 3)
+
+        #
+        # 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()
+
+        #
+        # local EP to local ip4 external subnet
+        #
+        p = (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=144) /
+             IP(src=lep1.ip4, dst="10.220.0.1") /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\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, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
+
+        #
+        # local EP to local ip6 external subnet
+        #
+        p = (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+             Dot1Q(vlan=144) /
+             IPv6(src=lep1.ip6, dst="10:220::1") /
+             UDP(sport=1234, dport=1234) /
+             Raw(b'\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, eep1.mac)
+            self.assertEqual(rx[Dot1Q].vlan, 100)
+
+        #
+        # ip4 and ip6 subnets that load-balance
+        #
+        ip_20 = VppIpRoute(self, "10.20.0.0", 24,
+                           [VppRoutePath(eep1.ip4,
+                                         eep1.epg.bvi.sw_if_index),
+                            VppRoutePath(eep2.ip4,
+                                         eep2.epg.bvi.sw_if_index)],
+                           table_id=t4.table_id)
+        ip_20.add_vpp_config()
+
+        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()
+
+        ip6_20 = VppIpRoute(self, "10:20::", 64,
+                            [VppRoutePath(eep1.ip6,
+                                          eep1.epg.bvi.sw_if_index),
+                             VppRoutePath(eep2.ip6,
+                                          eep2.epg.bvi.sw_if_index)],
+                            table_id=t6.table_id)
+        ip6_20.add_vpp_config()
+
+        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()
+
+        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, dst="10:20::1") /
+              UDP(sport=1234, dport=1234) /
+              Raw(b'\xa5' * 100)),
+             (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IPv6(src=lep1.ip6, dst="10:20::1") /
+              UDP(sport=124, dport=1230) /
+              Raw(b'\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, dst="10.20.0.1") /
+              UDP(sport=1235, dport=1235) /
+              Raw(b'\xa5' * 100)),
+             (Ether(src=lep1.mac, dst=str(self.router_mac)) /
+              Dot1Q(vlan=144) /
+              IP(src=lep1.ip4, dst="10.20.0.1") /
+              UDP(sport=124, dport=1230) /
+              Raw(b'\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
+        #
+        ip_222.remove_vpp_config()
+        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_anon_l3_out(self):
+        """ GBP Anonymous L3 Out """
+
+        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"
+
+        #
+        # IP tables
+        #
+        t4 = VppIpTable(self, 1)
+        t4.add_vpp_config()
+        t6 = VppIpTable(self, 1, True)
+        t6.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
+        #
+        bind_l0_ip4 = VppIpInterfaceBind(self, self.loop0, t4).add_vpp_config()
+        bind_l0_ip6 = VppIpInterfaceBind(self, self.loop0, t6).add_vpp_config()
+
+        #
+        # Pg7 hosts a BD's BUM
+        # Pg1 some other l3 interface
+        #
+        self.pg7.config_ip4()
+        self.pg7.resolve_arp()
+
+        #
+        # a GBP external bridge domains for the EPs
+        #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, None, tun_bm)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, None, None)
         gbd1.add_vpp_config()
 
         #
@@ -3503,19 +5501,18 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         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()
+        # the BVIs have the subnet applied ...
+        ip4_addr = VppIpInterfaceAddress(self, gbd1.bvi,
+                                         "10.0.0.128", 24,
+                                         bind=bind_l0_ip4).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,
-            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()
 
@@ -3523,14 +5520,15 @@ class TestGBP(VppTestCase):
         # 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()
 
         #
@@ -3542,29 +5540,13 @@ class TestGBP(VppTestCase):
             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,
-                             "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,
@@ -3572,48 +5554,30 @@ class TestGBP(VppTestCase):
         rep.add_vpp_config()
 
         #
-        # ARP packet from External EPs are accepted 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",
-                     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
@@ -3623,70 +5587,70 @@ class TestGBP(VppTestCase):
              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)) /
-             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))
-
+             Raw(b'\xa5' * 100))
         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) /
-             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'))
-
         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
         #
-        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) /
-             IP(src=eep1.ip4.address, dst=eep2.ip4.address) /
+             IP(src="10.0.0.100", dst="10.0.0.101") /
              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[Ether].src, self.vlan_100.remote_mac)
+            self.assertEqual(rx[Ether].dst, self.vlan_101.remote_mac)
             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,
-                            [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,
+            # 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()
 
         #
-        # 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,
-                            [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,
+            # 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()
@@ -3695,7 +5659,7 @@ class TestGBP(VppTestCase):
         # 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'))
@@ -3705,18 +5669,21 @@ class TestGBP(VppTestCase):
         #
         # contract for the external nets to communicate
         #
-        acl = VppGbpAcl(self)
-        rule4 = acl.create_rule(permit_deny=1, proto=17)
-        rule6 = acl.create_rule(is_ipv6=1, permit_deny=1, proto=17)
-        acl_index = acl.add_vpp_config([rule4, rule6])
+        rule4 = AclRule(is_permit=1, proto=17)
+        rule6 = AclRule(src_prefix=IPv6Network((0, 0)),
+                        dst_prefix=IPv6Network((0, 0)), is_permit=1, proto=17)
+        acl = VppAcl(self, rules=[rule4, rule6])
+        acl.add_vpp_config()
 
         c1 = VppGbpContract(
-            self, 4220, 4221, acl_index,
+            self, 55, 4220, 4221, acl.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])
         c1.add_vpp_config()
@@ -3725,22 +5692,26 @@ class TestGBP(VppTestCase):
         # Contracts allowing ext-net 200 to talk with external EPs
         #
         c2 = VppGbpContract(
-            self, 4220, 113, acl_index,
+            self, 55, 4220, 113, acl.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()
         c3 = VppGbpContract(
-            self, 113, 4220, acl_index,
+            self, 55, 113, 4220, acl.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()
@@ -3748,17 +5719,17 @@ class TestGBP(VppTestCase):
         #
         # 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) /
-             Raw('\xa5' * 100))
+             Raw(b'\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[Ether].dst, self.vlan_101.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         # we did not learn these external hosts
@@ -3775,20 +5746,20 @@ 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)) /
-             IP(src="10.0.0.101", dst="10.220.0.1") /
+             IP(src=rep.ip4, dst="10.220.0.1") /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
         # ping from an external host to the remote external EP
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        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) /
+             IP(src="10.220.0.1", dst=rep.ip4) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg7)
 
@@ -3808,7 +5779,7 @@ class TestGBP(VppTestCase):
             self.assertTrue(rx[VXLAN].gpflags.D)
             inner = rx[VXLAN].payload
             self.assertEqual(inner[IP].src, "10.220.0.1")
-            self.assertEqual(inner[IP].dst, rep.ip4.address)
+            self.assertEqual(inner[IP].dst, rep.ip4)
 
         #
         # An external subnet reachable via the remote external EP
@@ -3847,6 +5818,7 @@ class TestGBP(VppTestCase):
 
         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()
@@ -3855,11 +5827,11 @@ class TestGBP(VppTestCase):
         # 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) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
 
@@ -3867,12 +5839,14 @@ class TestGBP(VppTestCase):
         # Add contracts ext-nets for 220 -> 222
         #
         c4 = VppGbpContract(
-            self, 4220, 4222, acl_index,
+            self, 55, 4220, 4222, acl.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()
@@ -3880,11 +5854,11 @@ class TestGBP(VppTestCase):
         #
         # 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) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, p * 3, self.pg7)
 
@@ -3918,7 +5892,7 @@ class TestGBP(VppTestCase):
              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))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
         self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
@@ -3934,15 +5908,18 @@ class TestGBP(VppTestCase):
              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))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_assert_no_replies(self.pg7, p * 3)
 
         #
         # 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()
-        vlan_100.set_vtr(L2_VTR_OP.L2_DISABLED)
+        # make sure the programmed EP is no longer learnt from DP
+        self.wait_for_ep_timeout(sw_if_index=rep.itf.sw_if_index, ip=rep.ip4)
 
 
 if __name__ == '__main__':