nat: nat44-ed add session timing out indicator in api
[vpp.git] / test / test_gbp.py
index cc26238..c30a729 100644 (file)
@@ -1,59 +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
 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.packet import Raw
 from scapy.layers.l2 import Ether, ARP, Dot1Q
 from scapy.layers.inet import IP, UDP, ICMP
-from scapy.layers.inet6 import IPv6, ICMPv6ND_NS, ICMPv6NDOptSrcLLAddr, \
-    ICMPv6ND_NA
+from scapy.layers.inet6 import (
+    IPv6,
+    ICMPv6ND_NS,
+    ICMPv6NDOptSrcLLAddr,
+    ICMPv6ND_NA,
+    ICMPv6EchoRequest,
+)
 from scapy.utils6 import in6_getnsma, in6_getnsmac
 from scapy.layers.vxlan import VXLAN
 from scapy.utils6 import in6_getnsma, in6_getnsmac
 from scapy.layers.vxlan import VXLAN
-from scapy.data import ETH_P_IP, ETH_P_IPV6, 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 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, FibPathProto, \
-    FibPathType
-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_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_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
 
 
 
 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:
     if ip:
-        vip = VppIpAddress(ip)
+        vip = ip
     if mac:
         vmac = MACAddress(mac)
 
     eps = test.vapi.gbp_endpoint_dump()
 
     for ep in eps:
     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 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 ip:
             for eip in ep.endpoint.ips:
-                if vip == eip:
+                if vip == str(eip):
                     return True
         if mac:
                     return True
         if mac:
-            if vmac.packed == ep.endpoint.mac:
+            if vmac == ep.endpoint.mac:
                 return True
                 return True
+
     return False
 
 
     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:
     ts = test.vapi.gbp_vxlan_tunnel_dump()
     for t in ts:
         if t.tunnel.vni == vni:
@@ -101,13 +135,14 @@ class VppGbpEndpoint(VppObject):
                  mac=True):
         self._test = test
         self.itf = itf
                  mac=True):
         self._test = test
         self.itf = itf
+        self.handle = None
         self.epg = epg
         self.recirc = recirc
 
         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)
 
         if mac:
             self.vmac = MACAddress(self.itf.remote_mac)
@@ -115,34 +150,44 @@ class VppGbpEndpoint(VppObject):
             self.vmac = MACAddress("00:00:00:00:00:00")
 
         self.flags = flags
             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(
 
     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.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,
 
     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.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):
 
 
 class VppGbpRecirc(VppObject):
@@ -156,20 +201,25 @@ class VppGbpRecirc(VppObject):
         self.epg = epg
         self.is_ext = is_ext
 
         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,
     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._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)
 
     def object_id(self):
         return "gbp-recirc:[%d]" % (self.recirc.sw_if_index)
@@ -187,29 +237,37 @@ class VppGbpExtItf(VppObject):
     GBP ExtItfulation Interface
     """
 
     GBP ExtItfulation Interface
     """
 
-    def __init__(self, test, itf, bd, rd):
+    def __init__(self, test, itf, bd, rd, anon=False):
         self._test = test
         self.itf = itf
         self.bd = bd
         self.rd = rd
         self._test = test
         self.itf = itf
         self.bd = bd
         self.rd = rd
+        self.flags = 1 if anon else 0
+
+    def 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,
 
     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._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):
 
     def object_id(self):
-        return "gbp-ext-itf:[%d]" % (self.itf.sw_if_index)
+        return "gbp-ext-itf:[%d]%s" % (self.itf.sw_if_index,
+                                       " [anon]" if self.flags else "")
 
     def query_vpp_config(self):
         rs = self._test.vapi.gbp_ext_itf_dump()
 
     def query_vpp_config(self):
         rs = self._test.vapi.gbp_ext_itf_dump()
@@ -225,30 +283,43 @@ class VppGbpSubnet(VppObject):
     """
 
     def __init__(self, test, rd, address, address_len,
     """
 
     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._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
 
         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(
     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(
         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)
 
     def object_id(self):
         return "gbp-subnet:[%d-%s]" % (self.rd_id, self.prefix)
@@ -282,33 +353,38 @@ class VppGbpEndpointGroup(VppObject):
         self._test = test
         self.uplink = uplink
         self.bvi = bvi
         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.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
 
         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):
     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.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)
 
         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:
         epgs = self._test.vapi.gbp_endpoint_group_dump()
         for epg in epgs:
             if epg.epg.vnid == self.vnid:
@@ -321,40 +397,55 @@ class VppGbpBridgeDomain(VppObject):
     GBP Bridge Domain
     """
 
     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._test = test
         self.bvi = bvi
         self.uu_fwd = uu_fwd
         self.bm_flood = bm_flood
         self.bd = bd
+        self.rd = rd
 
         e = VppEnum.vl_api_gbp_bridge_domain_flags_t
 
         e = VppEnum.vl_api_gbp_bridge_domain_flags_t
-        if (learn):
-            self.learn = e.GBP_BD_API_FLAG_NONE
-        else:
-            self.learn = e.GBP_BD_API_FLAG_DO_NOT_LEARN
-        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(
 
     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.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)
 
         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:
         bds = self._test.vapi.gbp_bridge_domain_dump()
         for bd in bds:
             if bd.bd.bd_id == self.bd.bd_id:
@@ -367,25 +458,36 @@ class VppGbpRouteDomain(VppObject):
     GBP Route Domain
     """
 
     GBP Route Domain
     """
 
-    def __init__(self, test, rd_id, t4, t6, ip4_uu=None, ip6_uu=None):
+    def __init__(self, test, rd_id, scope, t4, t6, ip4_uu=None, ip6_uu=None):
         self._test = test
         self.rd_id = rd_id
         self._test = test
         self.rd_id = rd_id
+        self.scope = scope
         self.t4 = t4
         self.t6 = t6
         self.ip4_uu = ip4_uu
         self.ip6_uu = ip6_uu
 
         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(
     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.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)
 
     def object_id(self):
         return "gbp-route-domain:[%d]" % (self.rd_id)
@@ -398,27 +500,29 @@ class VppGbpRouteDomain(VppObject):
         return False
 
 
         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 __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():
+class VppGbpContractRule:
     def __init__(self, action, hash_mode, nhs=None):
         self.action = action
         self.hash_mode = hash_mode
         self.nhs = [] if nhs is None else nhs
 
     def __init__(self, action, hash_mode, nhs=None):
         self.action = action
         self.hash_mode = hash_mode
         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())
         nhs = []
         for nh in self.nhs:
             nhs.append(nh.encode())
@@ -440,13 +544,10 @@ class VppGbpContract(VppObject):
     GBP Contract
     """
 
     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._test = test
-        if not isinstance(rules, list):
-            raise ValueError("'rules' must be a list.")
-        if not isinstance(allowed_ethertypes, list):
-            raise ValueError("'allowed_ethertypes' must be a list.")
+        self.scope = scope
         self.acl_index = acl_index
         self.sclass = sclass
         self.dclass = dclass
         self.acl_index = acl_index
         self.sclass = sclass
         self.dclass = dclass
@@ -455,46 +556,48 @@ class VppGbpContract(VppObject):
         while (len(self.allowed_ethertypes) < 16):
             self.allowed_ethertypes.append(0)
 
         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())
         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(
             is_add=1,
         r = self._test.vapi.gbp_contract_add_del(
             is_add=1,
-            contract={
-                'acl_index': self.acl_index,
-                'sclass': self.sclass,
-                'dclass': self.dclass,
-                'n_rules': len(rules),
-                'rules': rules,
-                'n_ether_types': len(self.allowed_ethertypes),
-                'allowed_ethertypes': self.allowed_ethertypes})
+            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(
             is_add=0,
         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(
             is_add=0,
-            contract={
-                'acl_index': self.acl_index,
-                'sclass': self.sclass,
-                'dclass': self.dclass,
-                'n_rules': 0,
-                'rules': [],
-                'n_ether_types': len(self.allowed_ethertypes),
-                'allowed_ethertypes': self.allowed_ethertypes}
+            contract=self.encode(),
         )
 
     def object_id(self):
         )
 
     def object_id(self):
-        return "gbp-contract:[%d:%s:%d]" % (self.sclass,
-                                            self.dclass,
-                                            self.acl_index)
+        return "gbp-contract:[%d:%d:%d:%d]" % (self.scope,
+                                               self.sclass,
+                                               self.dclass,
+                                               self.acl_index)
 
     def query_vpp_config(self):
         cs = self._test.vapi.gbp_contract_dump()
         for c in cs:
 
     def query_vpp_config(self):
         cs = self._test.vapi.gbp_contract_dump()
         for c in cs:
-            if c.contract.sclass == self.sclass \
-                    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
 
                 return True
         return False
 
@@ -520,17 +623,23 @@ class VppGbpVxlanTunnel(VppInterface):
         self.mode = mode
         self.src = src
 
         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(
     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.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)
 
     def object_id(self):
         return "gbp-vxlan:%d" % (self.sw_if_index)
@@ -539,65 +648,18 @@ class VppGbpVxlanTunnel(VppInterface):
         return find_gbp_vxlan(self._test, self.vni)
 
 
         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(acl_index=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
 class TestGBP(VppTestCase):
     """ GBP Test Case """
 
     @property
-    def config_flags(self):
+    def nat_config_flags(self):
         return VppEnum.vl_api_nat_config_flags_t
 
         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()
     @classmethod
     def setUpClass(cls):
         super(TestGBP, cls).setUpClass()
@@ -619,11 +681,23 @@ class TestGBP(VppTestCase):
         for i in self.lo_interfaces:
             i.admin_up()
 
         for i in self.lo_interfaces:
             i.admin_up()
 
+        self.vlan_100 = VppDot1QSubint(self, self.pg0, 100)
+        self.vlan_100.admin_up()
+        self.vlan_101 = VppDot1QSubint(self, self.pg0, 101)
+        self.vlan_101.admin_up()
+        self.vlan_102 = VppDot1QSubint(self, self.pg0, 102)
+        self.vlan_102.admin_up()
+
     def tearDown(self):
         for i in self.pg_interfaces:
             i.admin_down()
     def tearDown(self):
         for i in self.pg_interfaces:
             i.admin_down()
-
         super(TestGBP, self).tearDown()
         super(TestGBP, self).tearDown()
+        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)
 
     def send_and_expect_bridged(self, src, tx, dst):
         rx = self.send_and_expect(src, tx, dst)
@@ -655,6 +729,16 @@ class TestGBP(VppTestCase):
             self.assertEqual(r[IP].dst, tx[0][IP].dst)
         return rx
 
             self.assertEqual(r[IP].dst, tx[0][IP].dst)
         return rx
 
+    def send_and_expect_routed6(self, src, tx, dst, src_mac):
+        rx = self.send_and_expect(src, tx, dst)
+
+        for r in rx:
+            self.assertEqual(r[Ether].src, src_mac)
+            self.assertEqual(r[Ether].dst, dst.remote_mac)
+            self.assertEqual(r[IPv6].src, tx[0][IPv6].src)
+            self.assertEqual(r[IPv6].dst, tx[0][IPv6].dst)
+        return rx
+
     def send_and_expect_natted(self, src, tx, dst, src_ip):
         rx = self.send_and_expect(src, tx, dst)
 
     def send_and_expect_natted(self, src, tx, dst, src_ip):
         rx = self.send_and_expect(src, tx, dst)
 
@@ -719,7 +803,6 @@ class TestGBP(VppTestCase):
         self.pg_send(src, tx)
         dst.get_capture(0, timeout=1)
         dst.assert_nothing_captured(remark="")
         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)
 
     def send_and_expect_arp(self, src, tx, dst):
         rx = self.send_and_expect(src, tx, dst)
@@ -738,25 +821,6 @@ class TestGBP(VppTestCase):
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
 
 
         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
         #
         #
         # Route Domains
         #
@@ -769,12 +833,31 @@ class TestGBP(VppTestCase):
         nt6 = VppIpTable(self, 20, is_ip6=True)
         nt6.add_vpp_config()
 
         nt6 = VppIpTable(self, 20, is_ip6=True)
         nt6.add_vpp_config()
 
-        rd0 = VppGbpRouteDomain(self, 0, gt4, gt6, None, None)
-        rd20 = VppGbpRouteDomain(self, 20, nt4, nt6, None, None)
+        rd0 = VppGbpRouteDomain(self, 0, 400, gt4, gt6, None, None)
+        rd20 = VppGbpRouteDomain(self, 20, 420, nt4, nt6, None, None)
 
         rd0.add_vpp_config()
         rd20.add_vpp_config()
 
 
         rd0.add_vpp_config()
         rd20.add_vpp_config()
 
+        #
+        # Bridge Domains
+        #
+        bd1 = VppBridgeDomain(self, 1)
+        bd2 = VppBridgeDomain(self, 2)
+        bd20 = VppBridgeDomain(self, 20)
+
+        bd1.add_vpp_config()
+        bd2.add_vpp_config()
+        bd20.add_vpp_config()
+
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd0, self.loop0)
+        gbd2 = VppGbpBridgeDomain(self, bd2, rd0, self.loop1)
+        gbd20 = VppGbpBridgeDomain(self, bd20, rd20, self.loop2)
+
+        gbd1.add_vpp_config()
+        gbd2.add_vpp_config()
+        gbd20.add_vpp_config()
+
         #
         # 3 EPGs, 2 of which share a BD.
         # 2 NAT EPGs, one for floating-IP subnets, the other for internet
         #
         # 3 EPGs, 2 of which share a BD.
         # 2 NAT EPGs, one for floating-IP subnets, the other for internet
@@ -823,31 +906,36 @@ class TestGBP(VppTestCase):
                               "10.0.2.1", "11.0.0.4",
                               "2001:10:2::1", "3001::4")]
 
                               "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]:
         #
         # 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
 
                 # 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(
                 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()
 
             # EPG uplink interfaces in the RD
             VppIpInterfaceBind(self, epg.uplink, epg.rd.t4).add_vpp_config()
@@ -856,10 +944,10 @@ class TestGBP(VppTestCase):
             # add the BD ARP termination entry for BVI IP
             epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
             # add the BD ARP termination entry for BVI IP
             epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
-                                                     epg.bvi_ip4.address)
+                                                     epg.bvi_ip4)
             epg.bd_arp_ip6 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
             epg.bd_arp_ip6 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
-                                                     epg.bvi_ip6.address)
+                                                     epg.bvi_ip6)
             epg.bd_arp_ip4.add_vpp_config()
             epg.bd_arp_ip6.add_vpp_config()
 
             epg.bd_arp_ip4.add_vpp_config()
             epg.bd_arp_ip6.add_vpp_config()
 
@@ -876,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(
             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()
 
 
             recirc.add_vpp_config()
 
@@ -896,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
             #
             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,
                     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(
                         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 ...
                         vrf_id=0, is_add=1)
 
             # VPP EP create ...
@@ -917,32 +1004,32 @@ class TestGBP(VppTestCase):
             self.logger.info(self.vapi.cli("sh gbp endpoint"))
 
             # ... results in a Gratuitous ARP/ND on the EPG's uplink
             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]
 
 
             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.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))
                 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,
 
             # add the BD ARP termination entry for floating IP
             for fip in ep.fips:
                 ba = VppBridgeDomainArpEntry(self, epg_nat.bd.bd, ep.mac,
-                                             fip.address)
+                                             fip)
                 ba.add_vpp_config()
 
                 # floating IPs route via EPG recirc
                 r = VppIpRoute(
                 ba.add_vpp_config()
 
                 # floating IPs route via EPG recirc
                 r = VppIpRoute(
-                    self, fip.address, fip.length,
-                    [VppRoutePath(fip.address,
+                    self, fip, ip_address(fip).max_prefixlen,
+                    [VppRoutePath(fip,
                                   ep.recirc.recirc.sw_if_index,
                                   type=FibPathType.FIB_PATH_TYPE_DVR,
                                   ep.recirc.recirc.sw_if_index,
                                   type=FibPathType.FIB_PATH_TYPE_DVR,
-                                  proto=fip.dpo_proto)],
+                                  proto=get_dpo_proto(fip))],
                     table_id=20)
                 r.add_vpp_config()
 
                     table_id=20)
                 r.add_vpp_config()
 
@@ -979,17 +1066,17 @@ class TestGBP(VppTestCase):
                    ARP(op="who-has",
                        hwdst="ff:ff:ff:ff:ff:ff",
                        hwsrc=self.pg0.remote_mac,
                    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)
 
 
         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) /
         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)
 
                   ICMPv6NDOptSrcLLAddr(lladdr=self.pg0.remote_mac))
         self.send_and_expect(self.pg0, [pkt_nd], self.pg0)
 
@@ -998,9 +1085,9 @@ class TestGBP(VppTestCase):
         #
         pkt_bcast = (Ether(dst="ff:ff:ff:ff:ff:ff",
                            src=self.pg0.remote_mac) /
         #
         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) /
                      UDP(sport=1234, dport=1234) /
-                     Raw('\xa5' * 100))
+                     Raw(b'\xa5' * 100))
 
         self.vapi.cli("clear trace")
         self.pg0.add_stream(pkt_bcast)
 
         self.vapi.cli("clear trace")
         self.pg0.add_stream(pkt_bcast)
@@ -1018,26 +1105,26 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_ip4 = (Ether(src=self.pg0.remote_mac,
                                        dst=str(self.router_mac)) /
         #
         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) /
                                     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)) /
         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) /
                                     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)) /
 
         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) /
                                       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)
 
         self.send_and_assert_no_replies(self.pg0,
                                         pkt_inter_epg_222_ip6 * NUM_PKTS)
 
@@ -1095,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") /
         #
         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) /
                                           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,
 
         self.send_and_expect_bridged(eps[0].itf,
                                      pkt_intra_epg_220_to_uplink * NUM_PKTS,
@@ -1109,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") /
 
         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) /
                                           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,
 
         self.send_and_expect_bridged(eps[2].itf,
                                      pkt_intra_epg_221_to_uplink * NUM_PKTS,
@@ -1123,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) /
         #
         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) /
                                             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,
 
         self.send_and_expect_bridged(self.pg4,
                                      pkt_intra_epg_220_from_uplink * NUM_PKTS,
@@ -1138,10 +1225,10 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg = (Ether(src=self.pg0.remote_mac,
                                dst=self.pg1.remote_mac) /
         #
         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) /
                          UDP(sport=1234, dport=1234) /
-                         Raw('\xa5' * 100))
+                         Raw(b'\xa5' * 100))
 
         self.send_and_expect_bridged(self.pg0,
                                      pkt_intra_epg * NUM_PKTS,
 
         self.send_and_expect_bridged(self.pg0,
                                      pkt_intra_epg * NUM_PKTS,
@@ -1153,22 +1240,22 @@ class TestGBP(VppTestCase):
         #
         pkt_inter_epg_220_to_221 = (Ether(src=self.pg0.remote_mac,
                                           dst=self.pg2.remote_mac) /
         #
         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) /
                                     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) /
         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) /
                                     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)) /
         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) /
                                     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)
 
         self.send_and_assert_no_replies(eps[0].itf,
                                         pkt_inter_epg_220_to_221 * NUM_PKTS)
@@ -1178,12 +1265,14 @@ class TestGBP(VppTestCase):
         #
         # A uni-directional contract from EPG 220 -> 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)
+        acl = VppAcl(self, rules=[rule, rule2])
+        acl.add_vpp_config()
+
         c1 = VppGbpContract(
         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,
@@ -1205,7 +1294,7 @@ class TestGBP(VppTestCase):
         # contract for the return direction
         #
         c2 = VppGbpContract(
         # contract for the return direction
         #
         c2 = VppGbpContract(
-            self, epgs[1].sclass, epgs[0].sclass, acl_index,
+            self, 400, epgs[1].sclass, epgs[0].sclass, acl.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,
@@ -1249,7 +1338,7 @@ class TestGBP(VppTestCase):
         # A uni-directional contract from EPG 220 -> 222 'L3 routed'
         #
         c3 = VppGbpContract(
         # A uni-directional contract from EPG 220 -> 222 'L3 routed'
         #
         c3 = VppGbpContract(
-            self, epgs[0].sclass, epgs[2].sclass, acl_index,
+            self, 400, epgs[0].sclass, epgs[2].sclass, acl.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,
@@ -1267,7 +1356,6 @@ class TestGBP(VppTestCase):
                                     pkt_inter_epg_220_to_222 * NUM_PKTS,
                                     eps[3].itf,
                                     str(self.router_mac))
                                     pkt_inter_epg_220_to_222 * NUM_PKTS,
                                     eps[3].itf,
                                     str(self.router_mac))
-
         #
         # remove both contracts, traffic stops in both directions
         #
         #
         # remove both contracts, traffic stops in both directions
         #
@@ -1338,25 +1426,23 @@ class TestGBP(VppTestCase):
         #
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
         #
         pkt_inter_epg_220_to_global = (Ether(src=self.pg0.remote_mac,
                                              dst=str(self.router_mac)) /
-                                       IP(src=eps[0].ip4.address,
+                                       IP(src=eps[0].ip4,
                                           dst="1.1.1.1") /
                                        UDP(sport=1234, dport=1234) /
                                           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)
 
         # 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(
         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,
@@ -1371,35 +1457,34 @@ class TestGBP(VppTestCase):
         self.send_and_expect_natted(eps[0].itf,
                                     pkt_inter_epg_220_to_global * NUM_PKTS,
                                     self.pg7,
         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)) /
 
         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) /
                                             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,
 
         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) /
         #
         # 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) /
                                             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.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,
@@ -1414,20 +1499,20 @@ class TestGBP(VppTestCase):
         self.send_and_expect_unnatted(self.pg7,
                                       pkt_inter_epg_220_from_global * NUM_PKTS,
                                       eps[0].itf,
         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) /
 
         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) /
                                               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,
 
         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:
 
         #
         # From a local VM to another local VM using resp. public addresses:
@@ -1435,83 +1520,59 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_global = (Ether(src=self.pg0.remote_mac,
                                           dst=str(self.router_mac)) /
         #
         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) /
                                     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,
 
         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)) /
 
         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) /
                                     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,
 
         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
         #
 
         #
         # 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,
 
     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):
         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)
                 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 """
 
         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',
 
         ep_flags = VppEnum.vl_api_gbp_endpoint_flags_t
         learnt = [{'mac': '00:00:11:11:11:01',
@@ -1529,7 +1590,7 @@ class TestGBP(VppTestCase):
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
         rd1.add_vpp_config()
 
         #
         rd1.add_vpp_config()
 
         #
@@ -1559,7 +1620,8 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3, tun_bm)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0,
+                                  self.pg3, tun_bm)
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
@@ -1576,13 +1638,13 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       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",
         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()
 
         #
         epg_330.add_vpp_config()
 
         #
@@ -1605,7 +1667,7 @@ class TestGBP(VppTestCase):
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
 
                             "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,
 
         # a packet with an sclass from an unknown EPG
         p = (Ether(src=self.pg2.remote_mac,
@@ -1615,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) /
              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) /
              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.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
 
         #
         # we should not have learnt a new tunnel endpoint, since
@@ -1635,7 +1698,7 @@ class TestGBP(VppTestCase):
                                                self.pg2.remote_hosts[0].ip4,
                                                99))
 
                                                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)
 
         #
         self.assertEqual(len(self.vapi.gbp_endpoint_dump()), 1)
 
         #
@@ -1651,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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -1676,8 +1739,9 @@ class TestGBP(VppTestCase):
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
                                               vx_tun_l2_1.sw_if_index,
                                               ip=l['ip']))
 
-        # self.assert_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"))
 
         self.logger.info(self.vapi.cli("show gbp endpoint"))
         self.logger.info(self.vapi.cli("show gbp vxlan"))
@@ -1695,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):
         # 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) /
             p = (Ether(src=self.pg2.remote_mac,
                        dst=self.pg2.local_mac) /
                  IP(src=self.pg2.remote_hosts[1].ip4,
                     dst="239.1.1.1") /
                  UDP(sport=1234, dport=48879) /
-                 VXLAN(vni=88, gpid=112, flags=0x88) /
+                 VXLAN(vni=88, reserved2=0x80, gpid=112, flags=0x88) /
                  Ether(src=l['mac'], dst="ff:ff:ff:ff:ff:ff") /
                  ARP(op="who-has",
                      psrc=l['ip'], pdst=l['ip'],
                  Ether(src=l['mac'], dst="ff:ff:ff:ff:ff:ff") /
                  ARP(op="who-has",
                      psrc=l['ip'], pdst=l['ip'],
@@ -1749,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) /
                  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)
 
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -1792,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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 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)
 
@@ -1808,16 +1873,18 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             # a packet with an sclass from a known EPG
         #
         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) /
             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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 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)
 
@@ -1831,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']) /
         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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg2)
 
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg2)
 
@@ -1867,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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 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)
 
@@ -1882,9 +1949,9 @@ class TestGBP(VppTestCase):
         # only test 1 EP as the others could timeout
         #
         p = (Ether(src=ep.mac, dst=l['mac']) /
         # 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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         self.send_and_assert_no_replies(self.pg0, [p])
 
 
         self.send_and_assert_no_replies(self.pg0, [p])
 
@@ -1900,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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 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)
 
@@ -1913,48 +1980,50 @@ class TestGBP(VppTestCase):
         #
         # Add the contract so they can talk
         #
         #
         # 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(
         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,
                 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()
 
         for l in learnt:
             p = (Ether(src=ep.mac, dst=l['mac']) /
             [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) /
                  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.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 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") /
         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) /
                 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") /
         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) /
                 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:
         rxs = self.send_and_expect_only(ep.itf, [p_bm], tun_bm.mcast_itf)
 
         for rx in rxs:
@@ -1969,8 +2038,30 @@ class TestGBP(VppTestCase):
             self.assertFalse(rx[VXLAN].gpflags.A)
             self.assertFalse(rx[VXLAN].gpflags.D)
 
             self.assertFalse(rx[VXLAN].gpflags.A)
             self.assertFalse(rx[VXLAN].gpflags.D)
 
+        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
         #
         for l in learnt:
             # a packet with an sclass from a known EPG
@@ -1979,22 +2070,137 @@ class TestGBP(VppTestCase):
                  IP(src=self.pg2.remote_hosts[1].ip4,
                     dst=self.pg2.local_ip4) /
                  UDP(sport=1234, dport=48879) /
                  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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 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)
+            rx = self.send_and_expect(self.pg2, p * NUM_PKTS, self.pg0)
 
 
-            self.assertTrue(find_gbp_endpoint(self,
-                                              vx_tun_l2_1.sw_if_index,
-                                              mac=l['mac']))
+            self.assertTrue(find_gbp_endpoint(
+                self,
+                vx_tun_l2_1.sw_if_index,
+                ip=l['ip6'],
+                tep=[self.pg2.local_ip4,
+                     self.pg2.remote_hosts[1].ip4]))
+
+        self.logger.info(self.vapi.cli("sh int"))
+        self.logger.info(self.vapi.cli("sh vxlan-gbp tunnel"))
+        self.logger.info(self.vapi.cli("sh gbp vxlan"))
+        self.logger.info(self.vapi.cli("sh gbp endpoint"))
+        self.logger.info(self.vapi.cli("sh gbp interface"))
+
+        #
+        # EP moves to a different TEP
+        #
+        for l in learnt:
+            # a packet with an sclass from a known EPG
+            p = (Ether(src=self.pg2.remote_mac,
+                       dst=self.pg2.local_mac) /
+                 IP(src=self.pg2.remote_hosts[2].ip4,
+                    dst=self.pg2.local_ip4) /
+                 UDP(sport=1234, dport=48879) /
+                 VXLAN(vni=99, gpid=113, flags=0x88) /
+                 Ether(src=l['mac'], dst=ep.mac) /
+                 IPv6(src=l['ip6'], dst=ep.ip6) /
+                 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,
+                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
 
         #
         # clean up
@@ -2006,11 +2212,20 @@ class TestGBP(VppTestCase):
         self.pg3.unconfig_ip4()
         self.pg4.unconfig_ip4()
 
         self.pg3.unconfig_ip4()
         self.pg4.unconfig_ip4()
 
-        self.logger.info(self.vapi.cli("sh int"))
-        self.logger.info(self.vapi.cli("sh gbp vxlan"))
-
     def test_gbp_contract(self):
     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
 
         #
         # Bridge Domains
@@ -2021,26 +2236,14 @@ class TestGBP(VppTestCase):
         bd1.add_vpp_config()
         bd2.add_vpp_config()
 
         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()
 
         #
 
         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,
         #
         epgs = [VppGbpEndpointGroup(self, 220, 1220, rd0, gbd1,
                                     None, self.loop0,
@@ -2077,21 +2280,23 @@ class TestGBP(VppTestCase):
         for epg in epgs:
             # IP config on the BVI interfaces
             if epg != epgs[1]:
         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,
                                                      str(self.router_mac),
 
             # add the BD ARP termination entry for BVI IP
             epg.bd_arp_ip4 = VppBridgeDomainArpEntry(self, epg.bd.bd,
                                                      str(self.router_mac),
-                                                     epg.bvi_ip4.address)
+                                                     epg.bvi_ip4)
             epg.bd_arp_ip4.add_vpp_config()
 
             # EPG in VPP
             epg.bd_arp_ip4.add_vpp_config()
 
             # EPG in VPP
@@ -2112,40 +2317,58 @@ class TestGBP(VppTestCase):
         #
         pkt_intra_epg_220_to_220 = (Ether(src=self.pg0.remote_mac,
                                           dst=self.pg1.remote_mac) /
         #
         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) /
                                     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)
 
 
         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) /
         #
         # 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) /
                                     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
         #
 
         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(
         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,
+                 []),
              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,
@@ -2159,18 +2382,37 @@ class TestGBP(VppTestCase):
 
         pkt_inter_epg_220_to_222 = (Ether(src=self.pg0.remote_mac,
                                           dst=str(self.router_mac)) /
 
         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) /
                                     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)
 
         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(
         #
         # 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,
@@ -2187,19 +2429,39 @@ class TestGBP(VppTestCase):
                                      eps[2].itf)
         pkt_inter_epg_221_to_220 = (Ether(src=self.pg2.remote_mac,
                                           dst=self.pg0.remote_mac) /
                                      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) /
                                     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)
         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(
 
         #
         # 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,
@@ -2220,8 +2482,8 @@ class TestGBP(VppTestCase):
         c2.remove_vpp_config()
         acl.remove_vpp_config()
 
         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
 
         #
         # IP tables
@@ -2231,33 +2493,17 @@ class TestGBP(VppTestCase):
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
         rd1.add_vpp_config()
 
         #
         rd1.add_vpp_config()
 
         #
-        # 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()
 
         #
         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()
 
                                   uu_drop=True, bm_drop=True)
         gbd1.add_vpp_config()
 
@@ -2265,8 +2511,8 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh gbp bridge"))
 
         # ... and has a /32 applied
         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
 
         #
         # The Endpoint-group
@@ -2275,7 +2521,7 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(3))
         epg_220.add_vpp_config()
 
         ep = VppGbpEndpoint(self, self.pg0,
         epg_220.add_vpp_config()
 
         ep = VppGbpEndpoint(self, self.pg0,
@@ -2283,6 +2529,7 @@ class TestGBP(VppTestCase):
                             "10.0.0.127", "11.0.0.127",
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
                             "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
         #
         # send UU/BM packet from the local EP with UU drop and BM drop enabled
         # in bd
@@ -2290,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") /
         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) /
                 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") /
         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) /
                 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.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"))
 
 
         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"""
 
     def test_gbp_learn_vlan_l2(self):
         """ GBP L2 Endpoint w/ VLANs"""
 
@@ -2325,7 +2644,7 @@ class TestGBP(VppTestCase):
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
         gt6 = VppIpTable(self, 1, is_ip6=True)
         gt6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 1, gt4, gt6)
+        rd1 = VppGbpRouteDomain(self, 1, 401, gt4, gt6)
         rd1.add_vpp_config()
 
         #
         rd1.add_vpp_config()
 
         #
@@ -2358,7 +2677,7 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, bd_uu_fwd,
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, bd_uu_fwd,
                                   learn=False)
         gbd1.add_vpp_config()
 
                                   learn=False)
         gbd1.add_vpp_config()
 
@@ -2366,8 +2685,8 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh gbp bridge"))
 
         # ... and has a /32 applied
         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
 
         #
         # The Endpoint-group in which we are learning endpoints
@@ -2376,7 +2695,7 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
 
         #
         epg_220.add_vpp_config()
 
         #
@@ -2399,7 +2718,7 @@ class TestGBP(VppTestCase):
                             "2001:10::1", "3001::1")
         ep.add_vpp_config()
 
                             "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
 
         #
         # Send to the static EP
@@ -2414,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) /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
             rxs = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2449,9 +2768,9 @@ class TestGBP(VppTestCase):
         for l in learnt:
             p = (Ether(src=ep.mac, dst=l['mac']) /
                  Dot1Q(vlan=11) /
         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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg3)
 
 
             rxs = self.send_and_expect(self.pg0, p * 17, self.pg3)
 
@@ -2501,7 +2820,7 @@ class TestGBP(VppTestCase):
         tun_ip4_uu.add_vpp_config()
         tun_ip6_uu.add_vpp_config()
 
         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)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
@@ -2509,8 +2828,8 @@ class TestGBP(VppTestCase):
         #
         # Bind the BVI to the RD
         #
         #
         # 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
 
         #
         # Pg2 hosts the vxlan tunnel
@@ -2532,7 +2851,7 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, self.pg3)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0, self.pg3)
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
         gbd1.add_vpp_config()
 
         self.logger.info(self.vapi.cli("sh bridge 1 detail"))
@@ -2540,10 +2859,12 @@ class TestGBP(VppTestCase):
         self.logger.info(self.vapi.cli("sh gbp route"))
 
         # ... and has a /32 and /128 applied
         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
 
         #
         # The Endpoint-group in which we are learning endpoints
@@ -2552,12 +2873,11 @@ class TestGBP(VppTestCase):
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       None, self.loop0,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
 
         #
         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,
         #
         vx_tun_l3 = VppGbpVxlanTunnel(
             self, 101, rd1.rd_id,
@@ -2588,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") /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2612,9 +2932,9 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
         #
         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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * 1, self.pg2)
 
 
             rxs = self.send_and_expect(self.pg0, p * 1, self.pg2)
 
@@ -2634,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[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:
                 self.assertEqual(inner[IP].dst, l['ip'])
 
         for l in learnt:
@@ -2655,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") /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2685,9 +3005,9 @@ class TestGBP(VppTestCase):
         #
         for l in learnt:
             p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
         #
         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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
 
 
             rxs = self.send_and_expect(self.pg0, p * NUM_PKTS, self.pg2)
 
@@ -2707,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[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"))
                 self.assertEqual(inner[IPv6].dst, l['ip6'])
 
         self.logger.info(self.vapi.cli("sh gbp endpoint"))
@@ -2718,25 +3038,44 @@ class TestGBP(VppTestCase):
         # Static sends to unknown EP with no route
         #
         p = (Ether(src=ep.mac, dst=self.loop0.local_mac) /
         # 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) /
              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
 
         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()
 
         #
         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) /
+             IPv6(dst=epg_220.bvi_ip6, src=ep.ip6) /
+             UDP(sport=1234, dport=1234) /
+             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) /
         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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
         for rx in rxs:
 
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
         for rx in rxs:
@@ -2765,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") /
                  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) /
                  UDP(sport=1234, dport=1234) /
-                 Raw('\xa5' * 100))
+                 Raw(b'\xa5' * 100))
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
             rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2793,12 +3132,14 @@ class TestGBP(VppTestCase):
                                 "2001:10::88", "3001::88",
                                 ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                                 self.pg2.local_ip4,
                                 "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
                                 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,
         #
         rep_2 = VppGbpEndpoint(self, vx_tun_l3,
                                epg_220, None,
@@ -2824,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) /
         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) /
                  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)
 
             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)
                 self.assertEqual(rx[UDP].dport, 48879)
                 # the UDP source port is a random value for hashing
                 self.assertEqual(rx[VXLAN].gpid, 441)
@@ -2846,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[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)
 
         #
                 self.assertEqual(inner[IP].dst, ip)
 
         #
@@ -2856,20 +3197,20 @@ class TestGBP(VppTestCase):
         rep_88.remove_vpp_config()
         rep_2.remove_vpp_config()
 
         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) /
 
         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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
         rxs = self.send_and_expect(self.pg0, [p], self.pg2)
 
         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) /
 
         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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
 
         #
         rxs = self.send_and_expect(self.pg0, [p], self.pg4)
 
         #
@@ -2877,8 +3218,62 @@ class TestGBP(VppTestCase):
         # present (because it's DP learnt) when the TC ends so wait until
         # it is removed
         #
         # 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
 
         #
         # shutdown with learnt endpoint present
@@ -2890,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") /
              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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
 
         rx = self.send_and_expect(self.pg2, [p], self.pg0)
 
@@ -2933,7 +3328,7 @@ class TestGBP(VppTestCase):
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
         t6 = VppIpTable(self, 1, True)
         t6.add_vpp_config()
 
-        rd1 = VppGbpRouteDomain(self, 2, t4, t6)
+        rd1 = VppGbpRouteDomain(self, 2, 402, t4, t6)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
         rd1.add_vpp_config()
 
         self.loop0.set_mac(self.router_mac)
@@ -2941,8 +3336,8 @@ class TestGBP(VppTestCase):
         #
         # Bind the BVI to the RD
         #
         #
         # 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
 
         #
         # Pg7 hosts a BD's UU-fwd
@@ -2955,23 +3350,25 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0)
+        gbd1 = VppGbpBridgeDomain(self, bd1, rd1, self.loop0)
         gbd1.add_vpp_config()
 
         bd2 = VppBridgeDomain(self, 2)
         bd2.add_vpp_config()
         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
         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
 
         #
         # The Endpoint-groups in which we are learning endpoints
@@ -2980,19 +3377,19 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       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",
         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",
         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()
 
         #
         epg_222.add_vpp_config()
 
         #
@@ -3007,11 +3404,13 @@ class TestGBP(VppTestCase):
 
         bd3 = VppBridgeDomain(self, 3)
         bd3.add_vpp_config()
 
         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()
         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()
 
         #
         gbd4.add_vpp_config()
 
         #
@@ -3021,13 +3420,13 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "12.0.0.128",
                                       "4001:10::128",
                                       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",
         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()
 
         #
         epg_321.add_vpp_config()
 
         #
@@ -3078,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) /
         #  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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep3.mac, dst=ep1.mac) /
               (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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=ep3.mac) /
         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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep3.mac, dst=ep1.mac) /
               (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) /
                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]])
 
         # should be dropped since no contract yet
         self.send_and_assert_no_replies(self.pg0, [p4[0]])
@@ -3102,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
         #
         # 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(
 
         #
         # 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,
             [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,
@@ -3130,7 +3530,7 @@ class TestGBP(VppTestCase):
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
         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,
             [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,
@@ -3157,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)
         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)
 
         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)
 
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, self.pg7)
 
@@ -3186,16 +3586,16 @@ class TestGBP(VppTestCase):
 
             self.assertEqual(inner[Ether].src, routed_src_mac)
             self.assertEqual(inner[Ether].dst, sep4.mac)
 
             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)
 
         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
 
         #
         # programme the unknown EP
@@ -3207,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)
         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
 
         #
         # and revert back to unprogrammed
@@ -3233,8 +3633,8 @@ class TestGBP(VppTestCase):
 
             self.assertEqual(inner[Ether].src, routed_src_mac)
             self.assertEqual(inner[Ether].dst, sep4.mac)
 
             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()
 
         c1.remove_vpp_config()
         c2.remove_vpp_config()
@@ -3243,7 +3643,7 @@ class TestGBP(VppTestCase):
         # test the symmetric hash mode
         #
         c1 = VppGbpContract(
         # 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,
             [VppGbpContractRule(
                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
@@ -3262,7 +3662,7 @@ class TestGBP(VppTestCase):
         c1.add_vpp_config()
 
         c2 = VppGbpContract(
         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,
             [VppGbpContractRule(
                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
@@ -3289,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)
         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)
 
         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
 
         #
         # programme the unknown EP for the L3 tests
@@ -3310,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)) /
         #  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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep2.mac, dst=str(self.router_mac)) /
               (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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
         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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100)),
+               Raw(b'\xa5' * 100)),
               (Ether(src=ep2.mac, dst=str(self.router_mac)) /
               (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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
 
         c3 = VppGbpContract(
 
         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,
             [VppGbpContractRule(
                 VppEnum.vl_api_gbp_rule_action_t.GBP_API_RULE_REDIRECT,
                 VppEnum.vl_api_gbp_hash_mode_t.GBP_API_HASH_MODE_SYMMETRIC,
@@ -3350,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)
         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
 
         #
         # 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,
         #
         vx_tun_l3 = VppGbpVxlanTunnel(
             self, 444, rd1.rd_id,
@@ -3363,15 +3764,21 @@ class TestGBP(VppTestCase):
         vx_tun_l3.add_vpp_config()
 
         c4 = VppGbpContract(
         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(
             [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,
                 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(
                 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,
                     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()
 
             [ETH_P_IP, ETH_P_IPV6])
         c4.add_vpp_config()
 
@@ -3382,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)) /
              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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
+
+        # unknown remote EP to local EP redirected
+        rxs = self.send_and_expect(self.pg7, [p], sep1.itf)
 
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+        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,
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
@@ -3400,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)) /
              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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
+
+        # unknown remote EP to local EP redirected (ipv6)
+        rxs = self.send_and_expect(self.pg7, [p], sep3.itf)
 
 
-        rx = self.send_and_expect(self.pg7, [p], self.pg0)
+        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,
 
         # endpoint learnt via the parent GBP-vxlan interface
         self.assertTrue(find_gbp_endpoint(self,
@@ -3415,20 +3836,20 @@ class TestGBP(VppTestCase):
         # L3 switch from local to remote EP
         #
         p4 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
         # 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) /
                UDP(sport=1234, dport=1234) /
-               Raw('\xa5' * 100))]
+               Raw(b'\xa5' * 100))]
         p6 = [(Ether(src=ep1.mac, dst=str(self.router_mac)) /
         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) /
                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)
 
         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)
             self.assertEqual(rx[IP].dst, "10.0.0.88")
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep4.itf)
@@ -3436,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)
         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.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,
             [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,
@@ -3466,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)
         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)
             self.assertEqual(rx[IP].dst, "10.0.0.88")
 
         rxs = self.send_and_expect(self.pg0, p6[0] * 17, sep3.itf)
@@ -3474,51 +3895,813 @@ class TestGBP(VppTestCase):
         for rx in rxs:
             self.assertEqual(rx[Ether].src, routed_src_mac)
             self.assertEqual(rx[Ether].dst, sep3.mac)
         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")
 
         #
             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()
+
+        rxs = self.send_and_expect(self.pg0, 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, 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)
 
         #
 
         #
-        # a multicast vxlan-gbp tunnel for broadcast in the BD
+        # 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,
         #
         tun_bm = VppVxlanGbpTunnel(self, self.pg7.local_ip4,
                                    "239.1.1.1", 88,
@@ -3530,7 +4713,785 @@ class TestGBP(VppTestCase):
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
         #
         bd1 = VppBridgeDomain(self, 1)
         bd1.add_vpp_config()
-        gbd1 = VppGbpBridgeDomain(self, bd1, self.loop0, None, tun_bm)
+        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, rd1, self.loop0, None, None)
         gbd1.add_vpp_config()
 
         #
         gbd1.add_vpp_config()
 
         #
@@ -3540,19 +5501,18 @@ class TestGBP(VppTestCase):
                                       None, gbd1.bvi,
                                       "10.0.0.128",
                                       "2001:10::128",
                                       None, gbd1.bvi,
                                       "10.0.0.128",
                                       "2001:10::128",
-                                      VppGbpEndpointRetention(2))
+                                      VppGbpEndpointRetention(4))
         epg_220.add_vpp_config()
 
         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,
         l3o_1 = VppGbpSubnet(
             self, rd1, "10.0.0.0", 24,
-            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
+            VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_ANON_L3_OUT,
             sclass=113)
         l3o_1.add_vpp_config()
 
             sclass=113)
         l3o_1.add_vpp_config()
 
@@ -3560,17 +5520,15 @@ class TestGBP(VppTestCase):
         # an external interface attached to the outside world and the
         # external BD
         #
         # an external interface attached to the outside world and the
         # external BD
         #
-        vlan_100 = VppDot1QSubint(self, self.pg0, 100)
-        vlan_100.admin_up()
-        VppL2Vtr(self, vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
-        vlan_101 = VppDot1QSubint(self, self.pg0, 101)
-        vlan_101.admin_up()
-        VppL2Vtr(self, vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
-        # vlan_102 is not poped
-        vlan_102 = VppDot1QSubint(self, self.pg0, 102)
-        vlan_102.admin_up()
+        VppL2Vtr(self, self.vlan_100, L2_VTR_OP.L2_POP_1).add_vpp_config()
+        VppL2Vtr(self, self.vlan_101, L2_VTR_OP.L2_POP_1).add_vpp_config()
 
 
-        ext_itf = VppGbpExtItf(self, self.loop0, bd1, rd1)
+        #
+        # vlan_100 and vlan_101 are anonymous l3-out interfaces
+        #
+        ext_itf = VppGbpExtItf(self, self.vlan_100, bd1, rd1, anon=True)
+        ext_itf.add_vpp_config()
+        ext_itf = VppGbpExtItf(self, self.vlan_101, bd1, rd1, anon=True)
         ext_itf.add_vpp_config()
 
         #
         ext_itf.add_vpp_config()
 
         #
@@ -3582,35 +5540,13 @@ class TestGBP(VppTestCase):
             self.pg2.local_ip4)
         vx_tun_l3.add_vpp_config()
 
             self.pg2.local_ip4)
         vx_tun_l3.add_vpp_config()
 
-        #
-        # External Endpoints
-        #
-        eep1 = VppGbpEndpoint(self, vlan_100,
-                              epg_220, None,
-                              "10.0.0.1", "11.0.0.1",
-                              "2001:10::1", "3001::1",
-                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
-        eep1.add_vpp_config()
-        eep2 = VppGbpEndpoint(self, vlan_101,
-                              epg_220, None,
-                              "10.0.0.2", "11.0.0.2",
-                              "2001:10::2", "3001::2",
-                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
-        eep2.add_vpp_config()
-        eep3 = VppGbpEndpoint(self, vlan_102,
-                              epg_220, None,
-                              "10.0.0.3", "11.0.0.3",
-                              "2001:10::3", "3001::3",
-                              ep_flags.GBP_API_ENDPOINT_FLAG_EXTERNAL)
-        eep3.add_vpp_config()
-
         #
         # A remote external endpoint
         #
         rep = VppGbpEndpoint(self, vx_tun_l3,
                              epg_220, None,
         #
         # A remote external endpoint
         #
         rep = VppGbpEndpoint(self, vx_tun_l3,
                              epg_220, None,
-                             "10.0.0.101", "11.0.0.101",
-                             "2001:10::101", "3001::101",
+                             "10.0.0.201", "11.0.0.201",
+                             "2001:10::201", "3001::101",
                              ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                              self.pg7.local_ip4,
                              self.pg7.remote_ip4,
                              ep_flags.GBP_API_ENDPOINT_FLAG_REMOTE,
                              self.pg7.local_ip4,
                              self.pg7.remote_ip4,
@@ -3618,58 +5554,30 @@ class TestGBP(VppTestCase):
         rep.add_vpp_config()
 
         #
         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",
                  Dot1Q(vlan=100) /
                  ARP(op="who-has",
-                     psrc=eep1.ip4.address, pdst="10.0.0.128",
-                     hwsrc=eep1.mac, hwdst="ff:ff:ff:ff:ff:ff"))
-        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0)
-
-        #
-        # ARP packet from host in remote subnet are accepted and replied to
-        #
-        p_arp = (Ether(src=vlan_102.remote_mac, dst="ff:ff:ff:ff:ff:ff") /
-                 Dot1Q(vlan=102) /
+                     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",
                  ARP(op="who-has",
-                     psrc="10.0.0.17", pdst="10.0.0.128",
-                     hwsrc=vlan_102.remote_mac, hwdst="ff:ff:ff:ff:ff:ff"))
-        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0)
-
-        #
-        # packets destined to unknown addresses in the BVI's subnet
-        # are ARP'd for
-        #
-        p4 = (Ether(src=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.101',
+                     pdst="10.0.0.128",
+                     hwsrc=self.vlan_101.remote_mac,
+                     hwdst="ff:ff:ff:ff:ff:ff"))
+        rxs = self.send_and_expect(self.pg0, p_arp * 1, self.pg0, n_rx=2)
 
         #
         # remote to external
 
         #
         # remote to external
@@ -3679,84 +5587,70 @@ class TestGBP(VppTestCase):
              IP(src=self.pg7.remote_ip4,
                 dst=self.pg7.local_ip4) /
              UDP(sport=1234, dport=48879) /
              IP(src=self.pg7.remote_ip4,
                 dst=self.pg7.local_ip4) /
              UDP(sport=1234, dport=48879) /
-             VXLAN(vni=444, gpid=113, flags=0x88) /
+             VXLAN(vni=vx_tun_l3.vni, gpid=epg_220.sclass, flags=0x88) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
              Ether(src=self.pg0.remote_mac, dst=str(self.router_mac)) /
-             IP(src="10.0.0.101", dst="10.0.0.1") /
+             IP(src=str(rep.ip4), dst="10.0.0.100") /
              UDP(sport=1234, dport=1234) /
              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
         #
         rxs = self.send_and_expect(self.pg7, p * 1, self.pg0)
 
         #
         # local EP pings router
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              Dot1Q(vlan=100) /
-             IP(src=eep1.ip4.address, dst="10.0.0.128") /
+             IP(src="10.0.0.100", dst="10.0.0.128") /
              ICMP(type='echo-request'))
              ICMP(type='echo-request'))
-
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, str(self.router_mac))
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
             self.assertEqual(rx[Ether].src, str(self.router_mac))
-            self.assertEqual(rx[Ether].dst, eep1.mac)
+            self.assertEqual(rx[Ether].dst, self.vlan_100.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 100)
 
         #
         # local EP pings other local EP
         #
             self.assertEqual(rx[Dot1Q].vlan, 100)
 
         #
         # local EP pings other local EP
         #
-        p = (Ether(src=eep1.mac, dst=eep2.mac) /
+        p = (Ether(src=self.vlan_100.remote_mac,
+                   dst=self.vlan_101.remote_mac) /
              Dot1Q(vlan=100) /
              Dot1Q(vlan=100) /
-             IP(src=eep1.ip4.address, dst=eep2.ip4.address) /
+             IP(src="10.0.0.100", dst="10.0.0.101") /
              ICMP(type='echo-request'))
              ICMP(type='echo-request'))
-
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
 
         for rx in rxs:
-            self.assertEqual(rx[Ether].src, eep1.mac)
-            self.assertEqual(rx[Ether].dst, eep2.mac)
+            self.assertEqual(rx[Ether].src, self.vlan_100.remote_mac)
+            self.assertEqual(rx[Ether].dst, self.vlan_101.remote_mac)
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         #
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         #
-        # local EP pings router w/o vlan tag poped
-        #
-        p = (Ether(src=eep3.mac, dst=str(self.router_mac)) /
-             Dot1Q(vlan=102) /
-             IP(src=eep3.ip4.address, dst="10.0.0.128") /
-             ICMP(type='echo-request'))
-
-        rxs = self.send_and_expect(self.pg0, p * 1, self.pg0)
-
-        for rx in rxs:
-            self.assertEqual(rx[Ether].src, str(self.router_mac))
-            self.assertEqual(rx[Ether].dst, vlan_102.remote_mac)
-
-        #
-        # A subnet reachable through the external EP1
+        # A subnet reachable through an external router on vlan 100
         #
         ip_220 = VppIpRoute(self, "10.220.0.0", 24,
         #
         ip_220 = VppIpRoute(self, "10.220.0.0", 24,
-                            [VppRoutePath(eep1.ip4.address,
-                                          eep1.epg.bvi.sw_if_index)],
+                            [VppRoutePath("10.0.0.100",
+                                          epg_220.bvi.sw_if_index)],
                             table_id=t4.table_id)
         ip_220.add_vpp_config()
 
         l3o_220 = VppGbpSubnet(
             self, rd1, "10.220.0.0", 24,
                             table_id=t4.table_id)
         ip_220.add_vpp_config()
 
         l3o_220 = VppGbpSubnet(
             self, rd1, "10.220.0.0", 24,
+            # note: this a "regular" L3 out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4220)
         l3o_220.add_vpp_config()
 
         #
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4220)
         l3o_220.add_vpp_config()
 
         #
-        # A subnet reachable through the external EP2
+        # A subnet reachable through an external router on vlan 101
         #
         ip_221 = VppIpRoute(self, "10.221.0.0", 24,
         #
         ip_221 = VppIpRoute(self, "10.221.0.0", 24,
-                            [VppRoutePath(eep2.ip4.address,
-                                          eep2.epg.bvi.sw_if_index)],
+                            [VppRoutePath("10.0.0.101",
+                                          epg_220.bvi.sw_if_index)],
                             table_id=t4.table_id)
         ip_221.add_vpp_config()
 
         l3o_221 = VppGbpSubnet(
             self, rd1, "10.221.0.0", 24,
                             table_id=t4.table_id)
         ip_221.add_vpp_config()
 
         l3o_221 = VppGbpSubnet(
             self, rd1, "10.221.0.0", 24,
+            # note: this a "regular" L3 out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4221)
         l3o_221.add_vpp_config()
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4221)
         l3o_221.add_vpp_config()
@@ -3765,7 +5659,7 @@ class TestGBP(VppTestCase):
         # ping between hosts in remote subnets
         #  dropped without a contract
         #
         # ping between hosts in remote subnets
         #  dropped without a contract
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              ICMP(type='echo-request'))
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              ICMP(type='echo-request'))
@@ -3775,13 +5669,14 @@ class TestGBP(VppTestCase):
         #
         # contract for the external nets to communicate
         #
         #
         # 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(
 
         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,
@@ -3797,7 +5692,7 @@ class TestGBP(VppTestCase):
         # Contracts allowing ext-net 200 to talk with external EPs
         #
         c2 = VppGbpContract(
         # Contracts allowing ext-net 200 to talk with external EPs
         #
         c2 = VppGbpContract(
-            self, 4220, 113, acl_index,
+            self, 55, 4220, 113, acl.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,
@@ -3809,7 +5704,7 @@ class TestGBP(VppTestCase):
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
         c3 = VppGbpContract(
             [ETH_P_IP, ETH_P_IPV6])
         c2.add_vpp_config()
         c3 = VppGbpContract(
-            self, 113, 4220, acl_index,
+            self, 55, 113, 4220, acl.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,
@@ -3824,17 +5719,17 @@ class TestGBP(VppTestCase):
         #
         # ping between hosts in remote subnets
         #
         #
         # ping between hosts in remote subnets
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              UDP(sport=1234, dport=1234) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.221.0.1") /
              UDP(sport=1234, dport=1234) /
-             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))
 
         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
             self.assertEqual(rx[Dot1Q].vlan, 101)
 
         # we did not learn these external hosts
@@ -3851,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)) /
              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) /
              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
         #
 
         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) /
              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) /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg7)
 
 
         rxs = self.send_and_expect(self.pg0, p * 1, self.pg7)
 
@@ -3884,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.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
 
         #
         # An external subnet reachable via the remote external EP
@@ -3923,6 +5818,7 @@ class TestGBP(VppTestCase):
 
         l3o_222 = VppGbpSubnet(
             self, rd1, "10.222.0.0", 24,
 
         l3o_222 = VppGbpSubnet(
             self, rd1, "10.222.0.0", 24,
+            # note: this a "regular" l3out subnet (not connected)
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4222)
         l3o_222.add_vpp_config()
             VppEnum.vl_api_gbp_subnet_type_t.GBP_API_SUBNET_L3_OUT,
             sclass=4222)
         l3o_222.add_vpp_config()
@@ -3931,11 +5827,11 @@ class TestGBP(VppTestCase):
         # ping between hosts in local and remote external subnets
         #  dropped without a contract
         #
         # ping between hosts in local and remote external subnets
         #  dropped without a contract
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
 
 
         rxs = self.send_and_assert_no_replies(self.pg0, p * 1)
 
@@ -3943,7 +5839,7 @@ class TestGBP(VppTestCase):
         # Add contracts ext-nets for 220 -> 222
         #
         c4 = VppGbpContract(
         # Add contracts ext-nets for 220 -> 222
         #
         c4 = VppGbpContract(
-            self, 4220, 4222, acl_index,
+            self, 55, 4220, 4222, acl.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,
@@ -3958,11 +5854,11 @@ class TestGBP(VppTestCase):
         #
         # ping from host in local to remote external subnets
         #
         #
         # ping from host in local to remote external subnets
         #
-        p = (Ether(src=eep1.mac, dst=str(self.router_mac)) /
+        p = (Ether(src=self.vlan_100.remote_mac, dst=str(self.router_mac)) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
              Dot1Q(vlan=100) /
              IP(src="10.220.0.1", dst="10.222.0.1") /
              UDP(sport=1234, dport=1234) /
-             Raw('\xa5' * 100))
+             Raw(b'\xa5' * 100))
 
         rxs = self.send_and_expect(self.pg0, p * 3, self.pg7)
 
 
         rxs = self.send_and_expect(self.pg0, p * 3, self.pg7)
 
@@ -3996,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) /
              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"))
 
         rxs = self.send_and_expect(self.pg7, p * 3, self.pg0)
         self.assertFalse(find_gbp_endpoint(self, ip="10.222.0.1"))
@@ -4012,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) /
              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
         #
 
         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()
         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__':
 
 
 if __name__ == '__main__':