diff mbox series

[ovs-dev,v4,ovn] Add VXLAN support for non-VTEP datapath bindings

Message ID 20200826171253.1010528-1-ihrachys@redhat.com
State Superseded, archived
Headers show
Series [ovs-dev,v4,ovn] Add VXLAN support for non-VTEP datapath bindings | expand

Commit Message

Ihar Hrachyshka Aug. 26, 2020, 5:12 p.m. UTC
Because of limited space in VXLAN VNI to pass over all three of -
datapath id, ingress port, egress port - the implementation ignores
ingress; and splits the remaining 24 bits of VNI into two chunks, 12
bits each - one for datapath and one for egress port.

Limitations: because ingress port is not passed, ACLs that rely on it
won't work with VXLAN; reduced number of networks and ports per
network (max 4096 for both).

NB consumers may use NB_Global options:max_tunid to determine maximum
capacity for logical switches supported by the setup.

Renamed MLF_RCV_FROM_VXLAN_BIT into MLF_RCV_FROM_VTEP_BIT to reflect
the new use case.

Added test scenarios that ping through VXLAN tunnel between two
hypervisors added. Also max_tunid is validated.

Changes:
- v2: run several dvr connectivity tests with vxlan tunnels.
- v2: update ovn-architecture.7 documentation.
- v3: added is_vxlan helper.
- v4: reduce max tunid when vxlan is enabled in cluster.
- v4: added options:max_tunid key for NB_Global.

Signed-off-by: Ihar Hrachyshka <ihrachys@redhat.com>
---
 controller/physical.c        |   84 ++-
 include/ovn/logical-fields.h |   12 +-
 lib/ovn-util.h               |    5 +
 northd/ovn-northd.c          |   78 ++-
 ovn-architecture.7.xml       |  103 +++-
 ovn-nb.xml                   |    5 +
 tests/ovn-macros.at          |    4 +-
 tests/ovn.at                 | 1083 +++++++++++++++++-----------------
 8 files changed, 728 insertions(+), 646 deletions(-)
diff mbox series

Patch

diff --git a/controller/physical.c b/controller/physical.c
index 535c77730..1eacafbfb 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -180,7 +180,8 @@  static void
 put_encapsulation(enum mf_field_id mff_ovn_geneve,
                   const struct chassis_tunnel *tun,
                   const struct sbrec_datapath_binding *datapath,
-                  uint16_t outport, struct ofpbuf *ofpacts)
+                  uint16_t outport, bool is_ramp_switch,
+                  struct ofpbuf *ofpacts)
 {
     if (tun->type == GENEVE) {
         put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts);
@@ -191,7 +192,10 @@  put_encapsulation(enum mf_field_id mff_ovn_geneve,
                  MFF_TUN_ID, 0, 64, ofpacts);
         put_move(MFF_LOG_INPORT, 0, MFF_TUN_ID, 40, 15, ofpacts);
     } else if (tun->type == VXLAN) {
-        put_load(datapath->tunnel_key, MFF_TUN_ID, 0, 24, ofpacts);
+        uint64_t vni = (is_ramp_switch?
+                        datapath->tunnel_key :
+                        datapath->tunnel_key | ((uint64_t) outport << 12));
+        put_load(vni, MFF_TUN_ID, 0, 24, ofpacts);
     } else {
         OVS_NOT_REACHED();
     }
@@ -323,8 +327,9 @@  put_remote_port_redirect_overlay(const struct
         if (!rem_tun) {
             return;
         }
-        put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
-                          port_key, ofpacts_p);
+        put_encapsulation(mff_ovn_geneve, tun, binding->datapath, port_key,
+                          !strcmp(binding->type, "vtep"),
+                          ofpacts_p);
         /* Output to tunnel. */
         ofpact_put_OUTPUT(ofpacts_p)->port = rem_tun->ofport;
     } else {
@@ -360,8 +365,9 @@  put_remote_port_redirect_overlay(const struct
             return;
         }
 
-        put_encapsulation(mff_ovn_geneve, tun, binding->datapath,
-                          port_key, ofpacts_p);
+        put_encapsulation(mff_ovn_geneve, tun, binding->datapath, port_key,
+                          !strcmp(binding->type, "vtep"),
+                          ofpacts_p);
 
         /* Output to tunnels with active/backup */
         struct ofpact_bundle *bundle = ofpact_put_BUNDLE(ofpacts_p);
@@ -1370,7 +1376,7 @@  consider_mc_group(enum mf_field_id mff_ovn_geneve,
 
             if (!prev || tun->type != prev->type) {
                 put_encapsulation(mff_ovn_geneve, tun, mc->datapath,
-                                  mc->tunnel_key, &remote_ofpacts);
+                                  mc->tunnel_key, true, &remote_ofpacts);
                 prev = tun;
             }
             ofpact_put_OUTPUT(&remote_ofpacts)->port = tun->ofport;
@@ -1450,6 +1456,7 @@  void
 physical_run(struct physical_ctx *p_ctx,
              struct ovn_desired_flow_table *flow_table)
 {
+
     if (!hc_uuid) {
         hc_uuid = xmalloc(sizeof(struct uuid));
         uuid_generate(hc_uuid);
@@ -1615,11 +1622,12 @@  physical_run(struct physical_ctx *p_ctx,
      * Process packets that arrive from a remote hypervisor (by matching
      * on tunnel in_port). */
 
-    /* Add flows for Geneve and STT encapsulations.  These
-     * encapsulations have metadata about the ingress and egress logical
-     * ports.  We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and
-     * MFF_LOG_OUTPORT from the tunnel key data, then resubmit to table
-     * 33 to handle packets to the local hypervisor. */
+    /* Add flows for Geneve, STT and VXLAN encapsulations.  Geneve and STT
+     * encapsulations have metadata about the ingress and egress logical ports.
+     * VXLAN encapsulations have metadata about the egress logical port only.
+     * We set MFF_LOG_DATAPATH, MFF_LOG_INPORT, and MFF_LOG_OUTPORT from the
+     * tunnel key data where possible, then resubmit to table 33 to handle
+     * packets to the local hypervisor. */
     HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
         struct match match = MATCH_CATCHALL_INITIALIZER;
         match_set_in_port(&match, tun->ofport);
@@ -1648,11 +1656,7 @@  physical_run(struct physical_ctx *p_ctx,
                         &ofpacts, hc_uuid);
     }
 
-    /* Add flows for VXLAN encapsulations.  Due to the limited amount of
-     * metadata, we only support VXLAN for connections to gateways.  The
-     * VNI is used to populate MFF_LOG_DATAPATH.  The gateway's logical
-     * port is set to MFF_LOG_INPORT.  Then the packet is resubmitted to
-     * table 16 to determine the logical egress port. */
+    /* Handle VXLAN encapsulations. */
     HMAP_FOR_EACH (tun, hmap_node, &tunnels) {
         if (tun->type != VXLAN) {
             continue;
@@ -1669,19 +1673,41 @@  physical_run(struct physical_ctx *p_ctx,
             }
 
             match_set_in_port(&match, tun->ofport);
-            match_set_tun_id(&match, htonll(binding->datapath->tunnel_key));
-
             ofpbuf_clear(&ofpacts);
-            put_move(MFF_TUN_ID, 0,  MFF_LOG_DATAPATH, 0, 24, &ofpacts);
-            put_load(binding->tunnel_key, MFF_LOG_INPORT, 0, 15, &ofpacts);
-            /* For packets received from a vxlan tunnel, set a flag to that
-             * effect. */
-            put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_VXLAN_BIT, 1, &ofpacts);
-            put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
-
-            ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100,
-                            binding->header_.uuid.parts[0],
-                            &match, &ofpacts, hc_uuid);
+
+            if (!strcmp(binding->type, "vtep")) {
+                /* Add flows for ramp switches.  The VNI is used to populate
+                 * MFF_LOG_DATAPATH.  The gateway's logical port is set to
+                 * MFF_LOG_INPORT.  Then the packet is resubmitted to table 8
+                 * to determine the logical egress port. */
+                match_set_tun_id(&match,
+                                 htonll(binding->datapath->tunnel_key));
+
+                put_move(MFF_TUN_ID, 0,  MFF_LOG_DATAPATH, 0, 24, &ofpacts);
+                put_load(binding->tunnel_key, MFF_LOG_INPORT, 0, 15, &ofpacts);
+                /* For packets received from a ramp tunnel, set a flag to that
+                 * effect. */
+                put_load(1, MFF_LOG_FLAGS, MLF_RCV_FROM_RAMP_BIT, 1, &ofpacts);
+                put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &ofpacts);
+
+                ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100,
+                                binding->header_.uuid.parts[0],
+                                &match, &ofpacts, hc_uuid);
+            } else {
+                /* Add flows for non-VTEP tunnels. Split VNI into two 12-bit
+                 * sections and use them for datapath and outport IDs. */
+                match_set_tun_id_masked(
+                    &match,
+                    htonll(binding->datapath->tunnel_key),
+                    (OVS_FORCE ovs_be64) 0xff0f000000000000ULL);
+
+                put_move(MFF_TUN_ID, 12, MFF_LOG_OUTPORT,  0, 12, &ofpacts);
+                put_move(MFF_TUN_ID, 0, MFF_LOG_DATAPATH, 0, 12, &ofpacts);
+
+                put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
+                ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, 100, 0,
+                                &match, &ofpacts, hc_uuid);
+            }
         }
     }
 
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index 61d17d14f..1dff44dd1 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -51,7 +51,7 @@  void ovn_init_symtab(struct shash *symtab);
 /* MFF_LOG_FLAGS_REG bit assignments */
 enum mff_log_flags_bits {
     MLF_ALLOW_LOOPBACK_BIT = 0,
-    MLF_RCV_FROM_VXLAN_BIT = 1,
+    MLF_RCV_FROM_RAMP_BIT = 1,
     MLF_FORCE_SNAT_FOR_DNAT_BIT = 2,
     MLF_FORCE_SNAT_FOR_LB_BIT = 3,
     MLF_LOCAL_ONLY_BIT = 4,
@@ -64,11 +64,11 @@  enum mff_log_flags {
     /* Allow outputting back to inport. */
     MLF_ALLOW_LOOPBACK = (1 << MLF_ALLOW_LOOPBACK_BIT),
 
-    /* Indicate that a packet was received from a VXLAN tunnel to
-     * compensate for the lack of egress port information available in
-     * VXLAN encapsulation.  Egress port information is available for
-     * Geneve and STT tunnel types. */
-    MLF_RCV_FROM_VXLAN = (1 << MLF_RCV_FROM_VXLAN_BIT),
+    /* Indicate that a packet was received from a ramp switch to compensate for
+     * the lack of egress port information available in ramp switch
+     * encapsulation.  Egress port information is available for Geneve, STT and
+     * regular VXLAN tunnel types. */
+    MLF_RCV_FROM_VXLAN = (1 << MLF_RCV_FROM_RAMP_BIT),
 
     /* Indicate that a packet needs a force SNAT in the gateway router when
      * DNAT has taken place. */
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 0f7b501f1..58d41a582 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -16,6 +16,7 @@ 
 #ifndef OVN_UTIL_H
 #define OVN_UTIL_H 1
 
+#include "lib/ovn-sb-idl.h"
 #include "lib/packets.h"
 #include "include/ovn/version.h"
 
@@ -107,6 +108,10 @@  void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
 #define OVN_MAX_DP_KEY_LOCAL (OVN_MAX_DP_KEY - OVN_MAX_DP_GLOBAL_NUM)
 #define OVN_MIN_DP_KEY_GLOBAL (OVN_MAX_DP_KEY_LOCAL + 1)
 #define OVN_MAX_DP_KEY_GLOBAL OVN_MAX_DP_KEY
+
+#define OVN_MAX_DP_VXLAN_KEY ((1u << 12) - 1)
+#define OVN_MAX_DP_VXLAN_KEY_LOCAL (OVN_MAX_DP_KEY - OVN_MAX_DP_GLOBAL_NUM)
+
 struct hmap;
 void ovn_destroy_tnlids(struct hmap *tnlids);
 void ovn_add_tnlid(struct hmap *set, uint32_t tnlid);
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 233125401..c7bd891d6 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -63,6 +63,7 @@  struct northd_context {
     struct ovsdb_idl *ovnsb_idl;
     struct ovsdb_idl_txn *ovnnb_txn;
     struct ovsdb_idl_txn *ovnsb_txn;
+    struct ovsdb_idl_index *sbrec_chassis_by_name;
     struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
     struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp;
     struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
@@ -1179,12 +1180,34 @@  join_datapaths(struct northd_context *ctx, struct hmap *datapaths,
     }
 }
 
+static int is_vxlan_mode(struct ovsdb_idl *ovnsb_idl)
+{
+    const struct sbrec_chassis *chassis, *chassis_next;
+    SBREC_CHASSIS_FOR_EACH_SAFE (chassis, chassis_next, ovnsb_idl) {
+        for (int i = 0; i < chassis->n_encaps; i++) {
+            if (!strcmp(chassis->encaps[i]->type, "vxlan")) {
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+static uint32_t
+get_ovn_max_dp_key_local(struct northd_context *ctx) {
+    if (is_vxlan_mode(ctx->ovnsb_idl)) {
+        /* TODO: what to do with global tunids? */
+        return OVN_MAX_DP_VXLAN_KEY;
+    }
+    return OVN_MAX_DP_KEY - OVN_MAX_DP_GLOBAL_NUM;
+}
+
 static uint32_t
-ovn_datapath_allocate_key(struct hmap *dp_tnlids)
+ovn_datapath_allocate_key(struct northd_context *ctx, struct hmap *dp_tnlids)
 {
     static uint32_t hint;
     return ovn_allocate_tnlid(dp_tnlids, "datapath", OVN_MIN_DP_KEY_LOCAL,
-                              OVN_MAX_DP_KEY_LOCAL, &hint);
+                              get_ovn_max_dp_key_local(ctx), &hint);
 }
 
 /* Updates the southbound Datapath_Binding table so that it contains the
@@ -1227,7 +1250,7 @@  build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
             }
         }
         if (!tunnel_key) {
-            tunnel_key = ovn_datapath_allocate_key(&dp_tnlids);
+            tunnel_key = ovn_datapath_allocate_key(ctx, &dp_tnlids);
             if (!tunnel_key) {
                 break;
             }
@@ -11689,32 +11712,34 @@  ovnnb_db_run(struct northd_context *ctx,
         }
     }
 
-    if (!mac_addr_prefix || !monitor_mac) {
-        struct smap options;
-        smap_clone(&options, &nb->options);
+    struct smap options;
+    smap_clone(&options, &nb->options);
 
-        if (!mac_addr_prefix) {
-            eth_addr_random(&mac_prefix);
-            memset(&mac_prefix.ea[3], 0, 3);
+    if (!mac_addr_prefix) {
+        eth_addr_random(&mac_prefix);
+        memset(&mac_prefix.ea[3], 0, 3);
 
-            smap_add_format(&options, "mac_prefix",
-                            "%02"PRIx8":%02"PRIx8":%02"PRIx8,
-                            mac_prefix.ea[0], mac_prefix.ea[1],
-                            mac_prefix.ea[2]);
-        }
+        smap_add_format(&options, "mac_prefix",
+                        "%02"PRIx8":%02"PRIx8":%02"PRIx8,
+                        mac_prefix.ea[0], mac_prefix.ea[1],
+                        mac_prefix.ea[2]);
+    }
 
-        if (!monitor_mac) {
-            eth_addr_random(&svc_monitor_mac_ea);
-            snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
-                     ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
-            smap_replace(&options, "svc_monitor_mac", svc_monitor_mac);
-        }
+    if (!monitor_mac) {
+        eth_addr_random(&svc_monitor_mac_ea);
+        snprintf(svc_monitor_mac, sizeof svc_monitor_mac,
+                 ETH_ADDR_FMT, ETH_ADDR_ARGS(svc_monitor_mac_ea));
+        smap_replace(&options, "svc_monitor_mac", svc_monitor_mac);
+    }
 
-        nbrec_nb_global_verify_options(nb);
-        nbrec_nb_global_set_options(nb, &options);
+    char *max_tunid = xasprintf("%d", get_ovn_max_dp_key_local(ctx));
+    smap_replace(&options, "max_tunid", max_tunid);
+    free(max_tunid);
 
-        smap_destroy(&options);
-    }
+    nbrec_nb_global_verify_options(nb);
+    nbrec_nb_global_set_options(nb, &options);
+
+    smap_destroy(&options);
 
     /* Update the probe interval. */
     northd_probe_interval_nb = get_probe_interval(ovnnb_db, nb);
@@ -12567,6 +12592,10 @@  main(int argc, char *argv[])
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_name);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_other_config);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_encaps);
+
+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_encap);
+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_encap_col_type);
 
     ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis_private);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl,
@@ -12678,6 +12707,7 @@  main(int argc, char *argv[])
                 .ovnnb_txn = ovsdb_idl_loop_run(&ovnnb_idl_loop),
                 .ovnsb_idl = ovnsb_idl_loop.idl,
                 .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
+                .sbrec_chassis_by_name = sbrec_chassis_by_name,
                 .sbrec_ha_chassis_grp_by_name = sbrec_ha_chassis_grp_by_name,
                 .sbrec_mcast_group_by_name_dp = sbrec_mcast_group_by_name_dp,
                 .sbrec_ip_mcast_by_dp = sbrec_ip_mcast_by_dp,
diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
index b1a462933..ac01fbc4c 100644
--- a/ovn-architecture.7.xml
+++ b/ovn-architecture.7.xml
@@ -1163,11 +1163,14 @@ 
 
       <p>
         Geneve and STT tunnels pass this field as part of the tunnel key.
-        Although VXLAN tunnels do not explicitly carry a logical input port,
-        OVN only uses VXLAN to communicate with gateways that from OVN's
+        Ramp switch VXLAN tunnels do not explicitly carry a logical input port,
+        but since they are used to communicate with gateways that from OVN's
         perspective consist of only a single logical port, so that OVN can set
         the logical input port field to this one on ingress to the OVN logical
-        pipeline.
+        pipeline. As for regular VXLAN tunnels, they don't carry input port
+        field at all. This puts additional limitations on cluster
+        capabilities that are described in
+        <code>Tunnel Encapsulations</code> section.
       </p>
     </dd>
 
@@ -1183,15 +1186,15 @@ 
       </p>
 
       <p>
-        Geneve and STT tunnels pass this field as part of the tunnel key.
-        VXLAN tunnels do not transmit the logical output port field.
-        Since VXLAN tunnels do not carry a logical output port field in
-        the tunnel key, when a packet is received from VXLAN tunnel by
-        an OVN hypervisor, the packet is resubmitted to table 8 to
-        determine the output port(s);  when the packet reaches table 32,
+        Geneve, STT and regular VXLAN tunnels pass this field as part of the
+        tunnel key. Ramp switch VXLAN tunnels do not transmit the logical
+        output port field, and since they do not carry a logical output port
+        field in the tunnel key, when a packet is received from ramp switch
+        VXLAN tunnel by an OVN hypervisor, the packet is resubmitted to table 8
+        to determine the output port(s); when the packet reaches table 32,
         these packets are resubmitted to table 33 for local delivery by
         checking a MLF_RCV_FROM_VXLAN flag, which is set when the packet
-        arrives from a VXLAN tunnel.
+        arrives from a ramp tunnel.
       </p>
     </dd>
 
@@ -1263,15 +1266,16 @@ 
       </p>
 
       <p>
-        Table 0 also processes packets that arrive from other chassis.  It
+        Table 0 also processes packets that arrive from other chassis. It
         distinguishes them from other packets by ingress port, which is a
-        tunnel.  As with packets just entering the OVN pipeline, the actions
-        annotate these packets with logical datapath and logical ingress port
-        metadata.  In addition, the actions set the logical output port field,
+        tunnel. As with packets just entering the OVN pipeline, the actions
+        annotate these packets with logical datapath metadata. For tunnel types
+        that support it, they are also annotated with logical ingress port
+        metadata. In addition, the actions set the logical output port field,
         which is available because in OVN tunneling occurs after the logical
-        output port is known.  These three pieces of information are obtained
+        output port is known. These pieces of information are obtained
         from the tunnel encapsulation metadata (see <code>Tunnel
-        Encapsulations</code> for encoding details).  Then the actions resubmit
+        Encapsulations</code> for encoding details). Then the actions resubmit
         to table 33 to enter the logical egress pipeline.
       </p>
     </li>
@@ -1430,12 +1434,12 @@ 
 
       <ul>
         <li>
-          A higher-priority rule to match packets received from VXLAN tunnels,
-          based on flag MLF_RCV_FROM_VXLAN, and resubmit these packets to table
-          33 for local delivery.  Packets received from VXLAN tunnels reach
-          here because of a lack of logical output port field in the tunnel key
-          and thus these packets needed to be submitted to table 8 to
-          determine the output port.
+          A higher-priority rule to match packets received from ramp switch
+          tunnels, based on flag MLF_RCV_FROM_VXLAN, and resubmit these packets
+          to table 33 for local delivery.  Packets received from ramp switch
+          tunnels reach here because of a lack of logical output port field in
+          the tunnel key and thus these packets needed to be submitted to table
+          8 to determine the output port.
         </li>
         <li>
           A higher-priority rule to match packets received from ports of type
@@ -2658,9 +2662,9 @@ 
   <h2>Tunnel Encapsulations</h2>
 
   <p>
-    OVN annotates logical network packets that it sends from one hypervisor to
-    another with the following three pieces of metadata, which are encoded in
-    an encapsulation-specific fashion:
+    In general, OVN annotates logical network packets that it sends from one
+    hypervisor to another with the following three pieces of metadata, which
+    are encoded in an encapsulation-specific fashion:
   </p>
 
   <ul>
@@ -2686,16 +2690,57 @@ 
   </ul>
 
   <p>
-    For hypervisor-to-hypervisor traffic, OVN supports only Geneve and STT
-    encapsulations, for the following reasons:
+      When VXLAN is enabled on any hypervisor in a cluster, datapath and egress
+      port identifier ranges are reduced to 12-bits. This is done because only
+      STT and Geneve provide the large space for metadata (over 32 bits per
+      packet). To accommodate for VXLAN, 24 bits available are split as
+      follows:
+  </p>
+
+  <ul>
+    <li>
+      12-bit logical datapath identifier, derived from the
+      <code>tunnel_key</code> column in the OVN Southbound
+      <code>Datapath_Binding</code> table.
+    </li>
+
+    <li>
+      12-bit logical egress port identifier.  IDs 0 through 32767 have the same
+      meaning as for logical ingress ports.  IDs 32768 through 65535,
+      inclusive, may be assigned to logical multicast groups (see the
+      <code>tunnel_key</code> column in the OVN Southbound
+      <code>Multicast_Group</code> table).
+    </li>
+
+    <li>
+      No logical ingress port identifier.
+    </li>
+  </ul>
+
+  <p>
+      The limited space available for metadata when VXLAN tunnels are enabled
+      in a cluster put the following functional limitations onto features
+      available to users:
   </p>
 
   <ul>
     <li>
-      Only STT and Geneve support the large amounts of metadata (over 32 bits
-      per packet) that OVN uses (as described above).
+      The maximum number of networks is reduced to 4096.
+    </li>
+    <li>
+      The maximum number of ports per network is reduced to 4096. (Including
+      multicast group ports.)
+    </li>
+    <li>
+      ACLs matching against logical ingress port identifiers are not supported.
     </li>
+  </ul>
 
+  <p>
+      In addition to functional limitations described above, the following
+      should be considered before enabling it in your cluster:
+  </p>
+  <ul>
     <li>
       STT and Geneve use randomized UDP or TCP source ports that allows
       efficient distribution among multiple paths in environments that use ECMP
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 9f3621dcd..66bd37ce1 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -147,6 +147,11 @@ 
         </p>
       </column>
 
+      <column name="options" key="max_tunid">
+        The maximum supported tunnel ID. Depends on types of encapsulation
+        enabled in the cluster.
+      </column>
+
       <group title="Options for configuring interconnection route advertisement">
         <p>
           These options control how routes are advertised between OVN
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index c639c0ceb..a6719be83 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -215,7 +215,7 @@  net_attach () {
 
 # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN]
 ovn_az_attach() {
-    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24}
+    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan}
     net_attach $net $bridge || return 1
 
     mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
@@ -232,7 +232,7 @@  ovn_az_attach() {
     ovs-vsctl \
         -- set Open_vSwitch . external-ids:system-id=$sandbox \
         -- set Open_vSwitch . external-ids:ovn-remote=$ovn_remote \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve,vxlan \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=$encap \
         -- set Open_vSwitch . external-ids:ovn-encap-ip=$ip \
         -- --may-exist add-br br-int \
         -- set bridge br-int fail-mode=secure other-config:disable-in-band=true \
diff --git a/tests/ovn.at b/tests/ovn.at
index 8aabdf307..388236b51 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -17753,612 +17753,583 @@  AT_CHECK([ovn-nbctl -u $sockfile show])
 AT_CLEANUP
 
 
-AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S ARP handling])
-ovn_start
-
-# In this test cases we create 3 switches, all connected to same
-# physical network (through br-phys on each HV). LS1 and LS2 have
-# 1 VIF each. Each HV has 1 VIF port. The first digit
-# of VIF port name indicates the hypervisor it is bound to, e.g.
-# lp23 means VIF 3 on hv2.
-#
-# All the switches are connected to a logical router "router".
-#
-# Each switch's VLAN tag and their logical switch ports are:
-#   - ls1:
-#       - tagged with VLAN 101
-#       - ports: lp11
-#   - ls2:
-#       - tagged with VLAN 201
-#       - ports: lp22
-#   - ls-underlay:
-#       - tagged with VLAN 1000
-# Note: a localnet port is created for each switch to connect to
-# physical network.
-
-for i in 1 2; do
-    ls_name=ls$i
-    ovn-nbctl ls-add $ls_name
-    ln_port_name=ln$i
-    if test $i -eq 1; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
-    elif test $i -eq 2; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 201
-    fi
-    ovn-nbctl lsp-set-addresses $ln_port_name unknown
-    ovn-nbctl lsp-set-type $ln_port_name localnet
-    ovn-nbctl lsp-set-options $ln_port_name network_name=phys
-done
-
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[11]]) echo ls1 ;; dnl (
-        lp?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_hv () {
-    case $1 in dnl (
-        vif[[1]]?) echo hv1 ;; dnl (
-        vif[[2]]?) echo hv2 ;; dnl (
-        vif?[[north]]?) echo hv4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-ip_to_hex() {
-       printf "%02x%02x%02x%02x" "$@"
-}
-
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-    ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"
-    ovn_attach n1 br-phys 192.168.0.$i
-
-    ovs-vsctl add-port br-int vif$i$i -- \
-        set Interface vif$i$i external-ids:iface-id=lp$i$i \
-                              options:tx_pcap=hv$i/vif$i$i-tx.pcap \
-                              options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
-                              ofport-request=$i$i
-
-    lsp_name=lp$i$i
-    ls_name=$(lsp_to_ls $lsp_name)
-
-    ovn-nbctl lsp-add $ls_name $lsp_name
-    ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i"
-    ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i
-
-    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
-
-done
-
-ovn-nbctl ls-add ls-underlay
-ovn-nbctl lsp-add ls-underlay ln3 "" 1000
-ovn-nbctl lsp-set-addresses ln3 unknown
-ovn-nbctl lsp-set-type ln3 localnet
-ovn-nbctl lsp-set-options ln3 network_name=phys
-
-ovn-nbctl ls-add ls-north
-ovn-nbctl lsp-add ls-north ln4 "" 1000
-ovn-nbctl lsp-set-addresses ln4 unknown
-ovn-nbctl lsp-set-type ln4 localnet
-ovn-nbctl lsp-set-options ln4 network_name=phys
-
-# Add a VM on ls-north
-ovn-nbctl lsp-add ls-north lp-north
-ovn-nbctl lsp-set-addresses lp-north "f0:f0:00:00:00:11 172.31.0.10"
-ovn-nbctl lsp-set-port-security lp-north f0:f0:00:00:00:11
-
-# Add 3rd hypervisor
-sim_add hv3
-as hv3 ovs-vsctl add-br br-phys
-as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as hv3 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:33"
-as hv3 ovn_attach n1 br-phys 192.168.0.3
-
-# Add 4th hypervisor
-sim_add hv4
-as hv4 ovs-vsctl add-br br-phys
-as hv4 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as hv4 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:44"
-as hv4 ovn_attach n1 br-phys 192.168.0.4
-
-as hv4 ovs-vsctl add-port br-int vif-north -- \
-        set Interface vif-north external-ids:iface-id=lp-north \
-                              options:tx_pcap=hv4/vif-north-tx.pcap \
-                              options:rxq_pcap=hv4/vif-north-rx.pcap \
-                              ofport-request=44
-
-ovn-nbctl lr-add router
-ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
-ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
-ovn-nbctl lrp-add router router-to-underlay 00:00:01:01:02:07 172.31.0.1/24
-
-ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router \
-          options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
-ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router \
-          options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
-ovn-nbctl lsp-add ls-underlay underlay-to-router -- set Logical_Switch_Port \
-                              underlay-to-router type=router \
-                              options:router-port=router-to-underlay \
-                              -- lsp-set-addresses underlay-to-router router
-
-
-OVN_POPULATE_ARP
-
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[11]]) echo ls1 ;; dnl (
-        lp?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_ls () {
-    case $1 in dnl (
-        vif?[[11]]) echo ls1 ;; dnl (
-        vif?[[12]]) echo ls2 ;; dnl (
-        vif-north) echo ls-north ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-hv_to_num () {
-    case $1 in dnl (
-        hv1) echo 1 ;; dnl (
-        hv2) echo 2 ;; dnl (
-        hv3) echo 3 ;; dnl (
-        hv4) echo 4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_num () {
-    case $1 in dnl (
-        vif22) echo 22 ;; dnl (
-        vif21) echo 21 ;; dnl (
-        vif11) echo 11 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_hv () {
-    case $1 in dnl (
-        vif[[1]]?) echo hv1 ;; dnl (
-        vif[[2]]?) echo hv2 ;; dnl (
-        vif-north) echo hv4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
-
-vif_to_lrp () {
-    echo router-to-`vif_to_ls $1`
-}
-
-ip_to_hex() {
-       printf "%02x%02x%02x%02x" "$@"
-}
-
-# test_arp INPORT SHA SPA TPA [REPLY_HA]
-#
-# Causes a packet to be received on INPORT.  The packet is an ARP
-# request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
-# it should be the hardware address of the target to expect to receive in an
-# ARP reply; otherwise no reply is expected.
-#
-# INPORT is an logical switch port number, e.g. 11 for vif11.
-# SHA and REPLY_HA are each 12 hex digits.
-# SPA and TPA are each 8 hex digits.
-test_arp() {
-    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
-    local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
-    hv=`vif_to_hv $inport`
-    as $hv ovs-appctl netdev-dummy/receive $inport $request
-
-    if test X$reply_ha = X; then
-        # Expect to receive the broadcast ARP on the other logical switch ports
-        # if no reply is expected.
-        local i j
-        for i in 1 2 3; do
-            for j in 1 2 3; do
-                if test $i$j != $inport; then
-                    echo $request >> $i$j.expected
-                fi
-            done
-        done
-    else
-        # Expect to receive the reply, if any.
-        local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
-        local reply_vid=${sha}${reply_ha}810003e808060001080006040002${reply_ha}${tpa}${sha}${spa}
-        echo $reply_vid >> ${inport}_vid.expected
-        echo $reply >> $inport.expected
-    fi
-}
-
-sip=`ip_to_hex 172 31 0 10`
-tip=`ip_to_hex 172 31 0 1`
-
-# Set a hypervisor as gateway chassis, for router port 172.31.0.1
-ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
-ovn-nbctl --wait=sb sync
-
-# Dump a bunch of info helpful for debugging if there's a failure.
-
-echo "------ OVN dump ------"
-ovn-nbctl show
-ovn-sbctl show
-ovn-sbctl list port_binding
-ovn-sbctl list mac_binding
-
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-vsctl list Open_Vswitch
-
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-vsctl list Open_Vswitch
-
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-as hv3 ovs-vsctl list Open_Vswitch
-
-echo "------ hv4 dump ------"
-as hv4 ovs-vsctl show
-as hv4 ovs-vsctl list Open_Vswitch
-
-OVS_WAIT_UNTIL([test x`ovn-sbctl --bare --columns chassis find port_binding  logical_port=cr-router-to-underlay | wc -l` = x1])
-
-test_arp vif-north f0f000000011 $sip $tip 000001010207
-
-# Confirm that vif-north gets a single ARP reply
-OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
-
-# Confirm that only redirect chassis allowed arp resolution.
-OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv3/br-phys_n1-tx.pcap], [vif-north_vid.expected])
-
-# Confirm that other OVN chassis did not generate ARP reply.
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap > hv1/br-phys_n1-tx.packets
-$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > hv2/br-phys_n1-tx.packets
-
-AT_CHECK([grep 000001010207 hv1/br-phys_n1-tx.packets | wc -l], [0], [[0
-]])
-AT_CHECK([grep 000001010207 hv2/br-phys_n1-tx.packets | wc -l], [0], [[0
-]])
-
-echo "----------- Post Traffic hv1 dump -----------"
-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv1 ovs-appctl fdb/show br-phys
-
-echo "----------- Post Traffic hv2 dump -----------"
-as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv2 ovs-appctl fdb/show br-phys
-
-echo "----------- Post Traffic hv3 dump -----------"
-as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv3 ovs-appctl fdb/show br-phys
-
-echo "----------- Post Traffic hv4 dump -----------"
-as hv4 ovs-ofctl -O OpenFlow13 dump-flows br-int
-as hv4 ovs-appctl fdb/show br-phys
-
-OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
-
-AT_CLEANUP
-
-AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S Ping])
-ovn_start
-
-# In this test cases we create 3 switches, all connected to same
-# physical network (through br-phys on each HV). LS1 and LS2 have
-# 1 VIF each. Each HV has 1 VIF port. The first digit
-# of VIF port name indicates the hypervisor it is bound to, e.g.
-# lp23 means VIF 3 on hv2.
-#
-# All the switches are connected to a logical router "router".
-#
-# Each switch's VLAN tag and their logical switch ports are:
-#   - ls1:
-#       - tagged with VLAN 101
-#       - ports: lp11
-#   - ls2:
-#       - tagged with VLAN 201
-#       - ports: lp22
-#   - ls-underlay:
-#       - tagged with VLAN 1000
-# Note: a localnet port is created for each switch to connect to
-# physical network.
-
-for i in 1 2; do
-    ls_name=ls$i
-    ovn-nbctl ls-add $ls_name
-    ln_port_name=ln$i
-    if test $i -eq 1; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
-    elif test $i -eq 2; then
-        ovn-nbctl lsp-add $ls_name $ln_port_name "" 201
-    fi
-    ovn-nbctl lsp-set-addresses $ln_port_name unknown
-    ovn-nbctl lsp-set-type $ln_port_name localnet
-    ovn-nbctl lsp-set-options $ln_port_name network_name=phys
-done
-
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[11]]) echo ls1 ;; dnl (
-        lp?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+m4_define([DVR_N_S_ARP_HANDLING],
+  [AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S ARP handling, encap $1])
+   set -x
+   encap=$1
+   ovn_start
+
+   # In this test cases we create 3 switches, all connected to same
+   # physical network (through br-phys on each HV). LS1 and LS2 have
+   # 1 VIF each. Each HV has 1 VIF port. The first digit
+   # of VIF port name indicates the hypervisor it is bound to, e.g.
+   # lp23 means VIF 3 on hv2.
+   #
+   # All the switches are connected to a logical router "router".
+   #
+   # Each switch's VLAN tag and their logical switch ports are:
+   #   - ls1:
+   #       - tagged with VLAN 101
+   #       - ports: lp11
+   #   - ls2:
+   #       - tagged with VLAN 201
+   #       - ports: lp22
+   #   - ls-underlay:
+   #       - tagged with VLAN 1000
+   # Note: a localnet port is created for each switch to connect to
+   # physical network.
+
+   for i in 1 2; do
+       ls_name=ls$i
+       ovn-nbctl ls-add $ls_name
+       ln_port_name=ln$i
+       if test $i -eq 1; then
+           ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
+       elif test $i -eq 2; then
+           ovn-nbctl lsp-add $ls_name $ln_port_name "" 201
+       fi
+       ovn-nbctl lsp-set-addresses $ln_port_name unknown
+       ovn-nbctl lsp-set-type $ln_port_name localnet
+       ovn-nbctl lsp-set-options $ln_port_name network_name=phys
+   done
+
+   # lsp_to_ls LSP
+   #
+   # Prints the name of the logical switch that contains LSP.
+   lsp_to_ls () {
+       case ${1} in dnl (
+           lp?[[11]]) echo ls1 ;; dnl (
+           lp?[[12]]) echo ls2 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-vif_to_hv () {
-    case $1 in dnl (
-        vif[[1]]?) echo hv1 ;; dnl (
-        vif[[2]]?) echo hv2 ;; dnl (
-        vif?[[north]]?) echo hv4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   vif_to_hv () {
+       case $1 in dnl (
+           vif[[1]]?) echo hv1 ;; dnl (
+           vif[[2]]?) echo hv2 ;; dnl (
+           vif?[[north]]?) echo hv4 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-ip_to_hex() {
-       printf "%02x%02x%02x%02x" "$@"
-}
+   net_add n1
+   for i in 1 2; do
+       sim_add hv$i
+       as hv$i
+       ovs-vsctl add-br br-phys
+       ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+       ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"
+       ovn_attach n1 br-phys 192.168.0.$i 24 $encap
+
+       ovs-vsctl add-port br-int vif$i$i -- \
+           set Interface vif$i$i external-ids:iface-id=lp$i$i \
+                                 options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+                                 options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+                                 ofport-request=$i$i
+
+       lsp_name=lp$i$i
+       ls_name=$(lsp_to_ls $lsp_name)
+
+       ovn-nbctl lsp-add $ls_name $lsp_name
+       ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i"
+       ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i
+
+       OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+
+   done
+
+   ovn-nbctl ls-add ls-underlay
+   ovn-nbctl lsp-add ls-underlay ln3 "" 1000
+   ovn-nbctl lsp-set-addresses ln3 unknown
+   ovn-nbctl lsp-set-type ln3 localnet
+   ovn-nbctl lsp-set-options ln3 network_name=phys
+
+   ovn-nbctl ls-add ls-north
+   ovn-nbctl lsp-add ls-north ln4 "" 1000
+   ovn-nbctl lsp-set-addresses ln4 unknown
+   ovn-nbctl lsp-set-type ln4 localnet
+   ovn-nbctl lsp-set-options ln4 network_name=phys
+
+   # Add a VM on ls-north
+   ovn-nbctl lsp-add ls-north lp-north
+   ovn-nbctl lsp-set-addresses lp-north "f0:f0:00:00:00:11 172.31.0.10"
+   ovn-nbctl lsp-set-port-security lp-north f0:f0:00:00:00:11
+
+   # Add 3rd hypervisor
+   sim_add hv3
+   as hv3 ovs-vsctl add-br br-phys
+   as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+   as hv3 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:33"
+   as hv3 ovn_attach n1 br-phys 192.168.0.3 24 $encap
+
+   # Add 4th hypervisor
+   sim_add hv4
+   as hv4 ovs-vsctl add-br br-phys
+   as hv4 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+   as hv4 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:44"
+   as hv4 ovn_attach n1 br-phys 192.168.0.4 24 $encap
+
+   as hv4 ovs-vsctl add-port br-int vif-north -- \
+           set Interface vif-north external-ids:iface-id=lp-north \
+                                 options:tx_pcap=hv4/vif-north-tx.pcap \
+                                 options:rxq_pcap=hv4/vif-north-rx.pcap \
+                                 ofport-request=44
+
+   ovn-nbctl lr-add router
+   ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
+   ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
+   ovn-nbctl lrp-add router router-to-underlay 00:00:01:01:02:07 172.31.0.1/24
+
+   ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router \
+             options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
+   ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router \
+             options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
+   ovn-nbctl lsp-add ls-underlay underlay-to-router -- set Logical_Switch_Port \
+                                 underlay-to-router type=router \
+                                 options:router-port=router-to-underlay \
+                                 -- lsp-set-addresses underlay-to-router router
+
+
+   OVN_POPULATE_ARP
+
+   vif_to_hv () {
+       case ${1} in dnl (
+           vif[[1]]?) echo hv1 ;; dnl (
+           vif[[2]]?) echo hv2 ;; dnl (
+           vif-north) echo hv4 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-net_add n1
-for i in 1 2; do
-    sim_add hv$i
-    as hv$i
-    ovs-vsctl add-br br-phys
-    ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-    ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"
-    ovn_attach n1 br-phys 192.168.0.$i
+   # test_arp INPORT SHA SPA TPA [REPLY_HA]
+   #
+   # Causes a packet to be received on INPORT.  The packet is an ARP
+   # request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
+   # it should be the hardware address of the target to expect to receive in an
+   # ARP reply; otherwise no reply is expected.
+   #
+   # INPORT is an logical switch port number, e.g. 11 for vif11.
+   # SHA and REPLY_HA are each 12 hex digits.
+   # SPA and TPA are each 8 hex digits.
+   test_arp() {
+       local inport=${1} sha=${2} spa=${3} tpa=${4} reply_ha=${5}
+       local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
+       hv=`vif_to_hv $inport`
+       as $hv ovs-appctl netdev-dummy/receive $inport $request
+
+       if test X$reply_ha = X; then
+           # Expect to receive the broadcast ARP on the other logical switch ports
+           # if no reply is expected.
+           local i j
+           for i in 1 2 3; do
+               for j in 1 2 3; do
+                   if test $i$j != $inport; then
+                       echo $request >> $i$j.expected
+                   fi
+               done
+           done
+       else
+           # Expect to receive the reply, if any.
+           local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa}
+           local reply_vid=${sha}${reply_ha}810003e808060001080006040002${reply_ha}${tpa}${sha}${spa}
+           echo $reply_vid >> ${inport}_vid.expected
+           echo $reply >> $inport.expected
+       fi
+   }
 
-    ovs-vsctl add-port br-int vif$i$i -- \
-        set Interface vif$i$i external-ids:iface-id=lp$i$i \
-                              options:tx_pcap=hv$i/vif$i$i-tx.pcap \
-                              options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
-                              ofport-request=$i$i
+   sip=`printf "%02x%02x%02x%02x" 172 31 0 10`
+   tip=`printf "%02x%02x%02x%02x" 172 31 0 1`
 
-    lsp_name=lp$i$i
-    ls_name=$(lsp_to_ls $lsp_name)
+   # Set a hypervisor as gateway chassis, for router port 172.31.0.1
+   ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
+   ovn-nbctl --wait=sb sync
 
-    ovn-nbctl lsp-add $ls_name $lsp_name
-    ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i"
-    ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i
+   # Dump a bunch of info helpful for debugging if there's a failure.
 
-    OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+   echo "------ OVN dump ------"
+   ovn-nbctl show
+   ovn-sbctl show
+   ovn-sbctl list port_binding
+   ovn-sbctl list mac_binding
 
-done
+   echo "------ hv1 dump ------"
+   as hv1 ovs-vsctl show
+   as hv1 ovs-vsctl list Open_Vswitch
 
-ovn-nbctl ls-add ls-underlay
-ovn-nbctl lsp-add ls-underlay ln3 "" 1000
-ovn-nbctl lsp-set-addresses ln3 unknown
-ovn-nbctl lsp-set-type ln3 localnet
-ovn-nbctl lsp-set-options ln3 network_name=phys
+   echo "------ hv2 dump ------"
+   as hv2 ovs-vsctl show
+   as hv2 ovs-vsctl list Open_Vswitch
 
-ovn-nbctl ls-add ls-north
-ovn-nbctl lsp-add ls-north ln4 "" 1000
-ovn-nbctl lsp-set-addresses ln4 unknown
-ovn-nbctl lsp-set-type ln4 localnet
-ovn-nbctl lsp-set-options ln4 network_name=phys
+   echo "------ hv3 dump ------"
+   as hv3 ovs-vsctl show
+   as hv3 ovs-vsctl list Open_Vswitch
 
-# Add a VM on ls-north
-ovn-nbctl lsp-add ls-north lp-north
-ovn-nbctl lsp-set-addresses lp-north "f0:f0:00:00:00:11 172.31.0.10"
-ovn-nbctl lsp-set-port-security lp-north f0:f0:00:00:00:11
+   echo "------ hv4 dump ------"
+   as hv4 ovs-vsctl show
+   as hv4 ovs-vsctl list Open_Vswitch
 
-# Add 3rd hypervisor
-sim_add hv3
-as hv3 ovs-vsctl add-br br-phys
-as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as hv3 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:33"
-as hv3 ovn_attach n1 br-phys 192.168.0.3
+   OVS_WAIT_UNTIL([test x`ovn-sbctl --bare --columns chassis find port_binding  logical_port=cr-router-to-underlay | wc -l` = x1])
 
-# Add 4th hypervisor
-sim_add hv4
-as hv4 ovs-vsctl add-br br-phys
-as hv4 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
-as hv4 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:44"
-as hv4 ovn_attach n1 br-phys 192.168.0.4
+   test_arp vif-north f0f000000011 $sip $tip 000001010207
 
-as hv4 ovs-vsctl add-port br-int vif-north -- \
-        set Interface vif-north external-ids:iface-id=lp-north \
-                              options:tx_pcap=hv4/vif-north-tx.pcap \
-                              options:rxq_pcap=hv4/vif-north-rx.pcap \
-                              ofport-request=44
+   # Confirm that vif-north gets a single ARP reply
+   OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
 
-ovn-nbctl lr-add router
-ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
-ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
-ovn-nbctl lrp-add router router-to-underlay 00:00:01:01:02:07 172.31.0.1/24
+   # Confirm that only redirect chassis allowed arp resolution.
+   OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv3/br-phys_n1-tx.pcap], [vif-north_vid.expected])
 
-ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router \
-          options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
-ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router \
-          options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
-ovn-nbctl lsp-add ls-underlay underlay-to-router -- set Logical_Switch_Port \
-                              underlay-to-router type=router \
-                              options:router-port=router-to-underlay \
-                              -- lsp-set-addresses underlay-to-router router
+   # Confirm that other OVN chassis did not generate ARP reply.
+   $PYTHON "$ovs_srcdir//utilities/ovs-pcap.in" hv1/br-phys_n1-tx.pcap > hv1/br-phys_n1-tx.packets
+   $PYTHON "$ovs_srcdir//utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap > hv2/br-phys_n1-tx.packets
 
-ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
-ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
+   AT_CHECK([grep 000001010207 hv1/br-phys_n1-tx.packets | wc -l], [0], [[0
+]])
+   AT_CHECK([grep 000001010207 hv2/br-phys_n1-tx.packets | wc -l], [0], [[0
+]])
 
-ovn-nbctl --wait=sb sync
+   # validate max_tunid reflects the type of encapsulation used
+   max_tunid=`ovn-nbctl get NB_Global . options:max_tunid | sed s/":"//g | sed s/\"//g`
+   echo $max_tunid
+   if [[ $encap = vxlan ]]; then
+       max_tunid_expected=4095
+   else
+       max_tunid_expected=16711680
+   fi
+   AT_CHECK([test $max_tunid -eq $max_tunid_expected])
+
+   echo "----------- Post Traffic hv1 dump -----------"
+   as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
+   as hv1 ovs-appctl fdb/show br-phys
+
+   echo "----------- Post Traffic hv2 dump -----------"
+   as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int
+   as hv2 ovs-appctl fdb/show br-phys
+
+   echo "----------- Post Traffic hv3 dump -----------"
+   as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int
+   as hv3 ovs-appctl fdb/show br-phys
+
+   echo "----------- Post Traffic hv4 dump -----------"
+   as hv4 ovs-ofctl -O OpenFlow13 dump-flows br-int
+   as hv4 ovs-appctl fdb/show br-phys
+
+   OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
+
+   AT_CLEANUP])
+
+DVR_N_S_ARP_HANDLING([geneve])
+DVR_N_S_ARP_HANDLING([vxlan])
+
+m4_define([DVR_N_S_PING],
+  [AT_SETUP([ovn -- 2 HVs, 2 lports/HV, localnet ports, DVR N-S Ping, encap $1])
+   AT_KEYWORDS([$1])
+   encap=$1
+   ovn_start
+
+   # In this test cases we create 3 switches, all connected to same
+   # physical network (through br-phys on each HV). LS1 and LS2 have
+   # 1 VIF each. Each HV has 1 VIF port. The first digit
+   # of VIF port name indicates the hypervisor it is bound to, e.g.
+   # lp23 means VIF 3 on hv2.
+   #
+   # All the switches are connected to a logical router "router".
+   #
+   # Each switch's VLAN tag and their logical switch ports are:
+   #   - ls1:
+   #       - tagged with VLAN 101
+   #       - ports: lp11
+   #   - ls2:
+   #       - tagged with VLAN 201
+   #       - ports: lp22
+   #   - ls-underlay:
+   #       - tagged with VLAN 1000
+   # Note: a localnet port is created for each switch to connect to
+   # physical network.
+
+   for i in 1 2; do
+       ls_name=ls$i
+       ovn-nbctl ls-add $ls_name
+       ln_port_name=ln$i
+       if test $i -eq 1; then
+           ovn-nbctl lsp-add $ls_name $ln_port_name "" 101
+       elif test $i -eq 2; then
+           ovn-nbctl lsp-add $ls_name $ln_port_name "" 201
+       fi
+       ovn-nbctl lsp-set-addresses $ln_port_name unknown
+       ovn-nbctl lsp-set-type $ln_port_name localnet
+       ovn-nbctl lsp-set-options $ln_port_name network_name=phys
+   done
+
+   # lsp_to_ls LSP
+   #
+   # Prints the name of the logical switch that contains LSP.
+   lsp_to_ls () {
+       case ${1} in dnl (
+           lp?[[11]]) echo ls1 ;; dnl (
+           lp?[[12]]) echo ls2 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
+   vif_to_hv () {
+       case ${1} in dnl (
+           vif[[1]]?) echo hv1 ;; dnl (
+           vif[[2]]?) echo hv2 ;; dnl (
+           vif?[[north]]?) echo hv4 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-OVN_POPULATE_ARP
+   ip_to_hex() {
+          printf "%02x%02x%02x%02x" "$@"
+   }
 
-# lsp_to_ls LSP
-#
-# Prints the name of the logical switch that contains LSP.
-lsp_to_ls () {
-    case $1 in dnl (
-        lp?[[11]]) echo ls1 ;; dnl (
-        lp?[[12]]) echo ls2 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   net_add n1
+   for i in 1 2; do
+       sim_add hv$i
+       as hv$i
+       ovs-vsctl add-br br-phys
+       ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+       ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:$i$i"
+       ovn_attach n1 br-phys 192.168.0.$i 24 $encap
+
+       ovs-vsctl add-port br-int vif$i$i -- \
+           set Interface vif$i$i external-ids:iface-id=lp$i$i \
+                                 options:tx_pcap=hv$i/vif$i$i-tx.pcap \
+                                 options:rxq_pcap=hv$i/vif$i$i-rx.pcap \
+                                 ofport-request=$i$i
+
+       lsp_name=lp$i$i
+       ls_name=$(lsp_to_ls $lsp_name)
+
+       ovn-nbctl lsp-add $ls_name $lsp_name
+       ovn-nbctl lsp-set-addresses $lsp_name "f0:00:00:00:00:$i$i 192.168.$i.$i"
+       ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:00:$i$i
+
+       OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+
+   done
+
+   ovn-nbctl ls-add ls-underlay
+   ovn-nbctl lsp-add ls-underlay ln3 "" 1000
+   ovn-nbctl lsp-set-addresses ln3 unknown
+   ovn-nbctl lsp-set-type ln3 localnet
+   ovn-nbctl lsp-set-options ln3 network_name=phys
+
+   ovn-nbctl ls-add ls-north
+   ovn-nbctl lsp-add ls-north ln4 "" 1000
+   ovn-nbctl lsp-set-addresses ln4 unknown
+   ovn-nbctl lsp-set-type ln4 localnet
+   ovn-nbctl lsp-set-options ln4 network_name=phys
+
+   # Add a VM on ls-north
+   ovn-nbctl lsp-add ls-north lp-north
+   ovn-nbctl lsp-set-addresses lp-north "f0:f0:00:00:00:11 172.31.0.10"
+   ovn-nbctl lsp-set-port-security lp-north f0:f0:00:00:00:11
+
+   # Add 3rd hypervisor
+   sim_add hv3
+   as hv3 ovs-vsctl add-br br-phys
+   as hv3 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+   as hv3 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:33"
+   as hv3 ovn_attach n1 br-phys 192.168.0.3 24 $encap
+
+   # Add 4th hypervisor
+   sim_add hv4
+   as hv4 ovs-vsctl add-br br-phys
+   as hv4 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+   as hv4 ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:aa:bb:cc:dd:ee:44"
+   as hv4 ovn_attach n1 br-phys 192.168.0.4 24 $encap
+
+   as hv4 ovs-vsctl add-port br-int vif-north -- \
+           set Interface vif-north external-ids:iface-id=lp-north \
+                                 options:tx_pcap=hv4/vif-north-tx.pcap \
+                                 options:rxq_pcap=hv4/vif-north-rx.pcap \
+                                 ofport-request=44
+
+   ovn-nbctl lr-add router
+   ovn-nbctl lrp-add router router-to-ls1 00:00:01:01:02:03 192.168.1.3/24
+   ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24
+   ovn-nbctl lrp-add router router-to-underlay 00:00:01:01:02:07 172.31.0.1/24
+
+   ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router \
+             options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
+   ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router \
+             options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
+   ovn-nbctl lsp-add ls-underlay underlay-to-router -- set Logical_Switch_Port \
+                                 underlay-to-router type=router \
+                                 options:router-port=router-to-underlay \
+                                 -- lsp-set-addresses underlay-to-router router
+
+   ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
+   ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
+
+   ovn-nbctl --wait=sb sync
+
+
+   OVN_POPULATE_ARP
+
+   # lsp_to_ls LSP
+   #
+   # Prints the name of the logical switch that contains LSP.
+   lsp_to_ls () {
+       case ${1} in dnl (
+           lp?[[11]]) echo ls1 ;; dnl (
+           lp?[[12]]) echo ls2 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-vif_to_ls () {
-    case $1 in dnl (
-        vif?[[11]]) echo ls1 ;; dnl (
-        vif?[[12]]) echo ls2 ;; dnl (
-        vif-north) echo ls-north ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   vif_to_ls () {
+       case ${1} in dnl (
+           vif?[[11]]) echo ls1 ;; dnl (
+           vif?[[12]]) echo ls2 ;; dnl (
+           vif-north) echo ls-north ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-hv_to_num () {
-    case $1 in dnl (
-        hv1) echo 1 ;; dnl (
-        hv2) echo 2 ;; dnl (
-        hv3) echo 3 ;; dnl (
-        hv4) echo 4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   hv_to_num () {
+       case ${1} in dnl (
+           hv1) echo 1 ;; dnl (
+           hv2) echo 2 ;; dnl (
+           hv3) echo 3 ;; dnl (
+           hv4) echo 4 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-vif_to_num () {
-    case $1 in dnl (
-        vif22) echo 22 ;; dnl (
-        vif21) echo 21 ;; dnl (
-        vif11) echo 11 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   vif_to_num () {
+       case ${1} in dnl (
+           vif22) echo 22 ;; dnl (
+           vif21) echo 21 ;; dnl (
+           vif11) echo 11 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-vif_to_hv () {
-    case $1 in dnl (
-        vif[[1]]?) echo hv1 ;; dnl (
-        vif[[2]]?) echo hv2 ;; dnl (
-        vif-north) echo hv4 ;; dnl (
-        *) AT_FAIL_IF([:]) ;;
-    esac
-}
+   vif_to_hv () {
+       case ${1} in dnl (
+           vif[[1]]?) echo hv1 ;; dnl (
+           vif[[2]]?) echo hv2 ;; dnl (
+           vif-north) echo hv4 ;; dnl (
+           *) AT_FAIL_IF([:]) ;;
+       esac
+   }
 
-vif_to_lrp () {
-    echo router-to-`vif_to_ls $1`
-}
+   vif_to_lrp () {
+       echo router-to-`vif_to_ls ${1}`
+   }
 
-ip_to_hex() {
-       printf "%02x%02x%02x%02x" "$@"
-}
+   ip_to_hex() {
+          printf "%02x%02x%02x%02x" "${@}"
+   }
 
 
-test_ip() {
-        # This packet has bad checksums but logical L3 routing doesn't check.
-        local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 outport=$6
-        local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-        shift; shift; shift; shift; shift
-        hv=`vif_to_hv $inport`
-        as $hv ovs-appctl netdev-dummy/receive $inport $packet
-        in_ls=`vif_to_ls $inport`
-        for outport; do
-            out_ls=`vif_to_ls $outport`
-            if test $in_ls = $out_ls; then
-                # Ports on the same logical switch receive exactly the same packet.
-                echo $packet
-            else
-                # Routing decrements TTL and updates source and dest MAC
-                # (and checksum).
-                out_lrp=`vif_to_lrp $outport`
-                # For North-South, packet will come via gateway chassis, i.e hv3
-                if test $inport = vif-north; then
-                    echo f0000000001100000101020308004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
-                fi
-                if test $outport = vif-north; then
-                    echo f0f00000001100000101020708004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 >> $outport.expected
-                fi
-            fi >> $outport.expected
-        done
-}
+   test_ip() {
+           # This packet has bad checksums but logical L3 routing doesn't check.
+           local inport=${1} src_mac=${2} dst_mac=${3} src_ip=${4} dst_ip=${5} outport=${6}
+           local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+           shift; shift; shift; shift; shift
+           hv=`vif_to_hv $inport`
+           as $hv ovs-appctl netdev-dummy/receive $inport $packet
+           in_ls=`vif_to_ls $inport`
+           for outport; do
+               out_ls=`vif_to_ls $outport`
+               if test $in_ls = $out_ls; then
+                   # Ports on the same logical switch receive exactly the same packet.
+                   echo $packet
+               else
+                   # Routing decrements TTL and updates source and dest MAC
+                   # (and checksum).
+                   out_lrp=`vif_to_lrp $outport`
+                   # For North-South, packet will come via gateway chassis, i.e hv3
+                   if test $inport = vif-north; then
+                       echo f0000000001100000101020308004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+                   fi
+                   if test $outport = vif-north; then
+                       echo f0f00000001100000101020708004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+                   fi
+               fi >> $outport.expected
+           done
+   }
 
-# Dump a bunch of info helpful for debugging if there's a failure.
+   # Dump a bunch of info helpful for debugging if there's a failure.
 
-echo "------ OVN dump ------"
-ovn-nbctl show
-ovn-sbctl show
-ovn-sbctl list port_binding
-ovn-sbctl list mac_binding
+   echo "------ OVN dump ------"
+   ovn-nbctl show
+   ovn-sbctl show
+   ovn-sbctl list port_binding
+   ovn-sbctl list mac_binding
 
-echo "------ hv1 dump ------"
-as hv1 ovs-vsctl show
-as hv1 ovs-vsctl list Open_Vswitch
+   echo "------ hv1 dump ------"
+   as hv1 ovs-vsctl show
+   as hv1 ovs-vsctl list Open_Vswitch
 
-echo "------ hv2 dump ------"
-as hv2 ovs-vsctl show
-as hv2 ovs-vsctl list Open_Vswitch
+   echo "------ hv2 dump ------"
+   as hv2 ovs-vsctl show
+   as hv2 ovs-vsctl list Open_Vswitch
 
-echo "------ hv3 dump ------"
-as hv3 ovs-vsctl show
-as hv3 ovs-vsctl list Open_Vswitch
+   echo "------ hv3 dump ------"
+   as hv3 ovs-vsctl show
+   as hv3 ovs-vsctl list Open_Vswitch
 
-echo "------ hv4 dump ------"
-as hv4 ovs-vsctl show
-as hv4 ovs-vsctl list Open_Vswitch
+   echo "------ hv4 dump ------"
+   as hv4 ovs-vsctl show
+   as hv4 ovs-vsctl list Open_Vswitch
 
-echo "Send traffic North to South"
+   echo "Send traffic North to South"
 
-sip=`ip_to_hex 172 31 0 10`
-dip=`ip_to_hex 192 168 1 1`
-test_ip vif-north f0f000000011 000001010207 $sip $dip vif11
+   sip=`ip_to_hex 172 31 0 10`
+   dip=`ip_to_hex 192 168 1 1`
+   test_ip vif-north f0f000000011 000001010207 $sip $dip vif11
 
-# Confirm that North to south traffic works fine.
-OVN_CHECK_PACKETS([hv1/vif11-tx.pcap], [vif11.expected])
+   # Confirm that North to south traffic works fine.
+   OVN_CHECK_PACKETS([hv1/vif11-tx.pcap], [vif11.expected])
 
-echo "Send traffic South to Nouth"
-sip=`ip_to_hex 192 168 1 1`
-dip=`ip_to_hex 172 31 0 10`
-test_ip vif11 f00000000011 000001010203 $sip $dip vif-north
+   echo "Send traffic South to Nouth"
+   sip=`ip_to_hex 192 168 1 1`
+   dip=`ip_to_hex 172 31 0 10`
+   test_ip vif11 f00000000011 000001010203 $sip $dip vif-north
 
-# Confirm that South to North traffic works fine.
-OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
+   # Confirm that South to North traffic works fine.
+   OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
 
-# Confirm that packets did not go out via tunnel port.
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=32 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
+   # Confirm that packets did not go out via tunnel port.
+   AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=32 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
 ]])
 
-# Confirm that packet went out via localnet port
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=65 | grep priority=150 | grep src=00:00:01:01:02:07 | grep n_packets=1 | wc -l], [0], [[1
+   # Confirm that packet went out via localnet port
+   AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=65 | grep priority=150 | grep src=00:00:01:01:02:07 | grep n_packets=1 | wc -l], [0], [[1
 ]])
 
-echo "----------- Post Traffic hv1 dump -----------"
-as hv1 ovs-ofctl dump-flows br-int
-as hv1 ovs-ofctl show br-phys
-as hv1 ovs-appctl fdb/show br-phys
+   echo "----------- Post Traffic hv1 dump -----------"
+   as hv1 ovs-ofctl dump-flows br-int
+   as hv1 ovs-ofctl show br-phys
+   as hv1 ovs-appctl fdb/show br-phys
 
-echo "----------- Post Traffic hv2 dump -----------"
-as hv2 ovs-ofctl dump-flows br-int
-as hv2 ovs-ofctl show br-phys
-as hv2 ovs-appctl fdb/show br-phys
+   echo "----------- Post Traffic hv2 dump -----------"
+   as hv2 ovs-ofctl dump-flows br-int
+   as hv2 ovs-ofctl show br-phys
+   as hv2 ovs-appctl fdb/show br-phys
 
-echo "----------- Post Traffic hv3 dump -----------"
-as hv3 ovs-ofctl dump-flows br-int
-as hv3 ovs-ofctl show br-phys
-as hv3 ovs-appctl fdb/show br-phys
+   echo "----------- Post Traffic hv3 dump -----------"
+   as hv3 ovs-ofctl dump-flows br-int
+   as hv3 ovs-ofctl show br-phys
+   as hv3 ovs-appctl fdb/show br-phys
 
-echo "----------- Post Traffic hv4 dump -----------"
-as hv4 ovs-ofctl dump-flows br-int
-as hv4 ovs-ofctl show br-phys
-as hv4 ovs-appctl fdb/show br-phys
+   echo "----------- Post Traffic hv4 dump -----------"
+   as hv4 ovs-ofctl dump-flows br-int
+   as hv4 ovs-ofctl show br-phys
+   as hv4 ovs-appctl fdb/show br-phys
 
-OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
+   OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
 
-AT_CLEANUP
+   AT_CLEANUP])
+
+DVR_N_S_PING([geneve])
+DVR_N_S_PING([vxlan])
 
 AT_SETUP([ovn -- ARP lookup before learning])
 AT_KEYWORDS([virtual ports])