diff --git a/SOURCES/ovn23.03.patch b/SOURCES/ovn23.03.patch
index c66abc6..9478c3b 100644
--- a/SOURCES/ovn23.03.patch
+++ b/SOURCES/ovn23.03.patch
@@ -1,3 +1,26 @@
+diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml
+index c82b23a1f..1d554cd03 100644
+--- a/.github/workflows/ovn-kubernetes.yml
++++ b/.github/workflows/ovn-kubernetes.yml
+@@ -56,7 +56,7 @@ jobs:
+     name: e2e
+     if: github.event_name != 'schedule'
+     runs-on: ubuntu-20.04
+-    timeout-minutes: 120
++    timeout-minutes: 220
+     strategy:
+       fail-fast: false
+       matrix:
+@@ -137,6 +137,9 @@ jobs:
+       working-directory: src/github.com/ovn-org/ovn-kubernetes
+ 
+     - name: Run Tests
++      # e2e tests take ~60 minutes normally, 120 should be more than enough
++      # set 180 for control-plane tests as these might take a while
++      timeout-minutes: ${{ matrix.target == 'control-plane' && 180 || 120 }}
+       run: |
+         make -C test ${{ matrix.target }}
+       working-directory: src/github.com/ovn-org/ovn-kubernetes
 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
 index 0f8d9d193..edf4fb2fd 100644
 --- a/.github/workflows/test.yml
@@ -13,10 +36,10 @@ index 0f8d9d193..edf4fb2fd 100644
        ARCH:        ${{ matrix.cfg.arch }}
        CC:          ${{ matrix.cfg.compiler }}
 diff --git a/NEWS b/NEWS
-index 5e8aed06d..60c460a05 100644
+index 5e8aed06d..d7ba71ef5 100644
 --- a/NEWS
 +++ b/NEWS
-@@ -1,3 +1,11 @@
+@@ -1,3 +1,17 @@
 +OVN v23.03.1 - xx xxx xxxx
 +--------------------------
 +  - CT entries are not flushed by default anymore whenever a load balancer
@@ -24,6 +47,12 @@ index 5e8aed06d..60c460a05 100644
 +    restore the previous behavior.  Disabled by default.
 +  - Always allow IPv6 Router Discovery, Neighbor Discovery, and Multicast
 +    Listener Discovery protocols, regardless of ACLs defined.
++  - Send ICMP Fragmentation Needed packets back to offending ports when
++    communicating with multichassis ports using frames that don't fit through a
++    tunnel. This is done only for logical switches that are attached to a
++    physical network via a localnet port, in which case multichassis ports may
++    have an effective MTU different from regular ports and hence may need this
++    mechanism to maintain connectivity with other peers in the network.
 +
  OVN v23.03.0 - 03 Mar 2023
  --------------------------
@@ -42,10 +71,21 @@ index b51d0f01e..0ba9e8d7e 100644
  AC_CONFIG_AUX_DIR([build-aux])
  AC_CONFIG_HEADERS([config.h])
 diff --git a/controller/binding.c b/controller/binding.c
-index 5df62baef..bd810f669 100644
+index 5df62baef..8fce6fc3f 100644
 --- a/controller/binding.c
 +++ b/controller/binding.c
-@@ -746,6 +746,19 @@ local_binding_get_lport_ofport(const struct shash *local_bindings,
+@@ -57,6 +57,10 @@ struct claimed_port {
+ static struct shash _claimed_ports = SHASH_INITIALIZER(&_claimed_ports);
+ static struct sset _postponed_ports = SSET_INITIALIZER(&_postponed_ports);
+ 
++static void
++remove_additional_chassis(const struct sbrec_port_binding *pb,
++                          const struct sbrec_chassis *chassis_rec);
++
+ struct sset *
+ get_postponed_ports(void)
+ {
+@@ -746,6 +750,19 @@ local_binding_get_lport_ofport(const struct shash *local_bindings,
              u16_to_ofp(lbinding->iface->ofport[0]) : 0;
  }
  
@@ -65,7 +105,7 @@ index 5df62baef..bd810f669 100644
  bool
  local_binding_is_up(struct shash *local_bindings, const char *pb_name,
                      const struct sbrec_chassis *chassis_rec)
-@@ -783,6 +796,7 @@ local_binding_is_down(struct shash *local_bindings, const char *pb_name,
+@@ -783,6 +800,7 @@ local_binding_is_down(struct shash *local_bindings, const char *pb_name,
          } else if (b_lport->pb->chassis) {
              VLOG_DBG("lport %s already claimed by other chassis",
                       b_lport->pb->logical_port);
@@ -73,7 +113,7 @@ index 5df62baef..bd810f669 100644
          }
      }
  
-@@ -834,6 +848,38 @@ local_binding_set_up(struct shash *local_bindings, const char *pb_name,
+@@ -834,6 +852,38 @@ local_binding_set_up(struct shash *local_bindings, const char *pb_name,
      }
  }
  
@@ -112,7 +152,7 @@ index 5df62baef..bd810f669 100644
  void
  local_binding_set_down(struct shash *local_bindings, const char *pb_name,
                         const struct sbrec_chassis *chassis_rec,
-@@ -853,7 +899,6 @@ local_binding_set_down(struct shash *local_bindings, const char *pb_name,
+@@ -853,7 +903,6 @@ local_binding_set_down(struct shash *local_bindings, const char *pb_name,
  
      if (!sb_readonly && b_lport && b_lport->pb->n_up && b_lport->pb->up[0] &&
              (!b_lport->pb->chassis || b_lport->pb->chassis == chassis_rec)) {
@@ -120,7 +160,45 @@ index 5df62baef..bd810f669 100644
          binding_lport_set_down(b_lport, sb_readonly);
          LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
              binding_lport_set_down(b_lport, sb_readonly);
-@@ -1239,7 +1284,9 @@ claim_lport(const struct sbrec_port_binding *pb,
+@@ -1028,6 +1077,26 @@ set_pb_chassis_in_sbrec(const struct sbrec_port_binding *pb,
+     }
+ }
+ 
++void
++set_pb_additional_chassis_in_sbrec(const struct sbrec_port_binding *pb,
++                                   const struct sbrec_chassis *chassis_rec,
++                                   bool is_set)
++{
++    if (!is_additional_chassis(pb, chassis_rec)) {
++        VLOG_INFO("Claiming lport %s for this additional chassis.",
++                  pb->logical_port);
++        for (size_t i = 0; i < pb->n_mac; i++) {
++            VLOG_INFO("%s: Claiming %s", pb->logical_port, pb->mac[i]);
++        }
++        sbrec_port_binding_update_additional_chassis_addvalue(pb, chassis_rec);
++        if (pb->chassis == chassis_rec) {
++            sbrec_port_binding_set_chassis(pb, NULL);
++        }
++    } else if (!is_set) {
++        remove_additional_chassis(pb, chassis_rec);
++    }
++}
++
+ bool
+ local_bindings_pb_chassis_is_set(struct shash *local_bindings,
+                                  const char *pb_name,
+@@ -1228,8 +1297,8 @@ claim_lport(const struct sbrec_port_binding *pb,
+                 }
+                 set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+             } else {
+-                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
+-                                          sb_readonly);
++                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
++                                          sb_readonly, can_bind);
+             }
+             register_claim_timestamp(pb->logical_port, now);
+             sset_find_and_delete(postponed_ports, pb->logical_port);
+@@ -1239,29 +1308,19 @@ claim_lport(const struct sbrec_port_binding *pb,
                      return false;
                  }
              } else {
@@ -129,9 +207,35 @@ index 5df62baef..bd810f669 100644
 +                    !smap_get_bool(&iface_rec->external_ids,
 +                                   OVN_INSTALLED_EXT_ID, false)) {
                      if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
-                                               sb_readonly);
+-                                              sb_readonly);
++                                              iface_rec, sb_readonly,
++                                              can_bind);
                  }
-@@ -1464,9 +1511,11 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
+             }
+         }
+     } else if (can_bind == CAN_BIND_AS_ADDITIONAL) {
+         if (!is_additional_chassis(pb, chassis_rec)) {
+-            if (sb_readonly) {
+-                return false;
+-            }
+-
+-            VLOG_INFO("Claiming lport %s for this additional chassis.",
+-                      pb->logical_port);
+-            for (size_t i = 0; i < pb->n_mac; i++) {
+-                VLOG_INFO("%s: Claiming %s", pb->logical_port, pb->mac[i]);
+-            }
+-
+-            sbrec_port_binding_update_additional_chassis_addvalue(pb,
+-                                                                  chassis_rec);
+-            if (pb->chassis == chassis_rec) {
+-                sbrec_port_binding_set_chassis(pb, NULL);
+-            }
++            if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
++                                      sb_readonly, can_bind);
+             update_tracked = true;
+         }
+     }
+@@ -1464,9 +1523,11 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
              const char *requested_chassis_option = smap_get(
                  &pb->options, "requested-chassis");
              VLOG_INFO_RL(&rl,
@@ -145,7 +249,25 @@ index 5df62baef..bd810f669 100644
          }
      }
  
-@@ -2288,6 +2337,11 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
+@@ -2030,7 +2091,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
+         free(lnet_lport);
+     }
+ 
+-    /* Run through external lport list to see if these are external ports
++    /* Run through external lport list to see if there are external ports
+      * on local datapaths discovered from above loop, and update the
+      * corresponding local datapath accordingly. */
+     struct lport *ext_lport;
+@@ -2039,7 +2100,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
+         free(ext_lport);
+     }
+ 
+-    /* Run through multichassis lport list to see if these are ports
++    /* Run through multichassis lport list to see if there are ports
+      * on local datapaths discovered from above loop, and update the
+      * corresponding local datapath accordingly. */
+     struct lport *multichassis_lport;
+@@ -2288,6 +2349,11 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
                  return false;
              }
          }
@@ -157,7 +279,7 @@ index 5df62baef..bd810f669 100644
  
      } else if (lbinding && b_lport && b_lport->type == LP_LOCALPORT) {
          /* lbinding is associated with a localport.  Remove it from the
-@@ -2558,6 +2612,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
+@@ -2558,6 +2624,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
      if (ld) {
          remove_pb_from_local_datapath(pb,
                                        b_ctx_out, ld);
@@ -165,7 +287,7 @@ index 5df62baef..bd810f669 100644
          return;
      }
  
-@@ -2581,6 +2636,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
+@@ -2581,6 +2648,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
              remove_pb_from_local_datapath(pb, b_ctx_out,
                                            ld);
          }
@@ -173,7 +295,7 @@ index 5df62baef..bd810f669 100644
      }
  }
  
-@@ -2627,6 +2683,11 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
+@@ -2627,6 +2695,11 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
      }
  
      handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
@@ -185,7 +307,7 @@ index 5df62baef..bd810f669 100644
      return true;
  }
  
-@@ -3314,6 +3375,24 @@ binding_lport_delete(struct shash *binding_lports,
+@@ -3314,6 +3387,24 @@ binding_lport_delete(struct shash *binding_lports,
      binding_lport_destroy(b_lport);
  }
  
@@ -210,7 +332,7 @@ index 5df62baef..bd810f669 100644
  static void
  binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly)
  {
-@@ -3331,6 +3410,7 @@ binding_lport_set_down(struct binding_lport *b_lport, bool sb_readonly)
+@@ -3331,6 +3422,7 @@ binding_lport_set_down(struct binding_lport *b_lport, bool sb_readonly)
      if (sb_readonly || !b_lport || !b_lport->pb->n_up || !b_lport->pb->up[0]) {
          return;
      }
@@ -219,7 +341,7 @@ index 5df62baef..bd810f669 100644
      bool up = false;
      sbrec_port_binding_set_up(b_lport->pb, &up, 1);
 diff --git a/controller/binding.h b/controller/binding.h
-index 6c3a98b02..5b73c6a4b 100644
+index 6c3a98b02..46e618b97 100644
 --- a/controller/binding.h
 +++ b/controller/binding.h
 @@ -159,6 +159,14 @@ bool local_binding_is_up(struct shash *local_bindings, const char *pb_name,
@@ -237,10 +359,15 @@ index 6c3a98b02..5b73c6a4b 100644
  void local_binding_set_up(struct shash *local_bindings, const char *pb_name,
                            const struct sbrec_chassis *chassis_rec,
                            const char *ts_now_str, bool sb_readonly,
-@@ -195,6 +203,14 @@ void set_pb_chassis_in_sbrec(const struct sbrec_port_binding *pb,
+@@ -194,6 +202,18 @@ bool is_additional_chassis(const struct sbrec_port_binding *pb,
+ void set_pb_chassis_in_sbrec(const struct sbrec_port_binding *pb,
                               const struct sbrec_chassis *chassis_rec,
                               bool is_set);
- 
++void
++set_pb_additional_chassis_in_sbrec(const struct sbrec_port_binding *pb,
++                                   const struct sbrec_chassis *chassis_rec,
++                                   bool is_set);
++
 +void remove_ovn_installed_for_uuid(const struct ovsrec_interface_table *,
 +                                   const struct uuid *);
 +
@@ -248,10 +375,9 @@ index 6c3a98b02..5b73c6a4b 100644
 +                           const struct sbrec_port_binding_table *pb_table,
 +                           const char *iface_id,
 +                           const struct uuid *pb_uuid);
-+
+ 
  /* Corresponds to each Port_Binding.type. */
  enum en_lport_type {
-     LP_UNKNOWN,
 diff --git a/controller/encaps.c b/controller/encaps.c
 index 2662eaf98..b69d72584 100644
 --- a/controller/encaps.c
@@ -363,10 +489,25 @@ index 867c6f28c..3e58b3c82 100644
 +
  #endif /* controller/encaps.h */
 diff --git a/controller/if-status.c b/controller/if-status.c
-index d1c14ac30..8503e5daa 100644
+index d1c14ac30..2b2eb1679 100644
 --- a/controller/if-status.c
 +++ b/controller/if-status.c
-@@ -54,44 +54,54 @@ VLOG_DEFINE_THIS_MODULE(if_status);
+@@ -18,12 +18,14 @@
+ #include "binding.h"
+ #include "if-status.h"
+ #include "ofctrl-seqno.h"
++#include "ovsport.h"
+ #include "simap.h"
+ 
+ #include "lib/hmapx.h"
+ #include "lib/util.h"
+ #include "timeval.h"
+ #include "openvswitch/vlog.h"
++#include "lib/vswitch-idl.h"
+ #include "lib/ovn-sb-idl.h"
+ 
+ VLOG_DEFINE_THIS_MODULE(if_status);
+@@ -54,44 +56,54 @@ VLOG_DEFINE_THIS_MODULE(if_status);
   */
  
  enum if_state {
@@ -450,7 +591,7 @@ index d1c14ac30..8503e5daa 100644
   * | |   +----------------------+                                        | | |
   * | |                 |  V  ^                                           | | |
   * | |                 |  |  | handle_claims()                           | | |
-@@ -109,38 +119,63 @@ static const char *if_state_names[] = {
+@@ -109,43 +121,69 @@ static const char *if_state_names[] = {
   * |     |                      |   - remove ovn-installed from ovsdb    | | |
   * |     |                      |  mgr_update()                          | | |
   * |     +----------------------+   - sbrec_update_chassis if needed     | | |
@@ -538,7 +679,13 @@ index d1c14ac30..8503e5daa 100644
      enum if_state state;    /* State of the interface in the state machine. */
      uint32_t install_seqno; /* Seqno at which this interface is expected to
                               * be fully programmed in OVS.  Only used in state
-@@ -155,6 +190,9 @@ struct if_status_mgr {
+                              * OIF_INSTALL_FLOWS.
+                              */
++    uint16_t mtu;           /* Extracted from OVS interface.mtu field. */
+ };
+ 
+ static uint64_t ifaces_usage;
+@@ -155,6 +193,9 @@ struct if_status_mgr {
      /* All local interfaces, mapping from 'iface-id' to 'struct ovs_iface'. */
      struct shash ifaces;
  
@@ -548,10 +695,17 @@ index d1c14ac30..8503e5daa 100644
      /* All local interfaces, stored per state. */
      struct hmapx ifaces_per_state[OIF_MAX];
  
-@@ -170,15 +208,20 @@ struct if_status_mgr {
- static struct ovs_iface *ovs_iface_create(struct if_status_mgr *,
-                                           const char *iface_id,
-                                           enum if_state );
+@@ -167,18 +208,24 @@ struct if_status_mgr {
+     uint32_t iface_seqno;
+ };
+ 
+-static struct ovs_iface *ovs_iface_create(struct if_status_mgr *,
+-                                          const char *iface_id,
+-                                          enum if_state );
++static struct ovs_iface *
++ovs_iface_create(struct if_status_mgr *, const char *iface_id,
++                 const struct ovsrec_interface *iface_rec,
++                 enum if_state);
 +static void add_to_ovn_uninstall_hash(struct if_status_mgr *, const char *,
 +                                      const struct uuid *);
  static void ovs_iface_destroy(struct if_status_mgr *, struct ovs_iface *);
@@ -569,7 +723,7 @@ index d1c14ac30..8503e5daa 100644
  struct if_status_mgr *
  if_status_mgr_create(void)
  {
-@@ -189,6 +232,7 @@ if_status_mgr_create(void)
+@@ -189,6 +236,7 @@ if_status_mgr_create(void)
          hmapx_init(&mgr->ifaces_per_state[i]);
      }
      shash_init(&mgr->ifaces);
@@ -577,7 +731,7 @@ index d1c14ac30..8503e5daa 100644
      return mgr;
  }
  
-@@ -202,6 +246,11 @@ if_status_mgr_clear(struct if_status_mgr *mgr)
+@@ -202,6 +250,11 @@ if_status_mgr_clear(struct if_status_mgr *mgr)
      }
      ovs_assert(shash_is_empty(&mgr->ifaces));
  
@@ -589,7 +743,7 @@ index d1c14ac30..8503e5daa 100644
      for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
          ovs_assert(hmapx_is_empty(&mgr->ifaces_per_state[i]));
      }
-@@ -212,6 +261,7 @@ if_status_mgr_destroy(struct if_status_mgr *mgr)
+@@ -212,6 +265,7 @@ if_status_mgr_destroy(struct if_status_mgr *mgr)
  {
      if_status_mgr_clear(mgr);
      shash_destroy(&mgr->ifaces);
@@ -597,15 +751,32 @@ index d1c14ac30..8503e5daa 100644
      for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
          hmapx_destroy(&mgr->ifaces_per_state[i]);
      }
-@@ -231,6 +281,7 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
-         iface = ovs_iface_create(mgr, iface_id, OIF_CLAIMED);
+@@ -222,27 +276,35 @@ void
+ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+                           const struct sbrec_port_binding *pb,
+                           const struct sbrec_chassis *chassis_rec,
+-                          bool sb_readonly)
++                          const struct ovsrec_interface *iface_rec,
++                          bool sb_readonly, enum can_bind bind_type)
+ {
+     const char *iface_id = pb->logical_port;
+     struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
+ 
+     if (!iface) {
+-        iface = ovs_iface_create(mgr, iface_id, OIF_CLAIMED);
++        iface = ovs_iface_create(mgr, iface_id, iface_rec, OIF_CLAIMED);
      }
  
 +    memcpy(&iface->pb_uuid, &pb->header_.uuid, sizeof(iface->pb_uuid));
      if (!sb_readonly) {
-         set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+-        set_pb_chassis_in_sbrec(pb, chassis_rec, true);
++        if (bind_type == CAN_BIND_AS_MAIN) {
++            set_pb_chassis_in_sbrec(pb, chassis_rec, true);
++        } else if (bind_type == CAN_BIND_AS_ADDITIONAL) {
++            set_pb_additional_chassis_in_sbrec(pb, chassis_rec, true);
++        }
      }
-@@ -238,11 +289,13 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+ 
      switch (iface->state) {
      case OIF_CLAIMED:
      case OIF_INSTALL_FLOWS:
@@ -619,7 +790,7 @@ index d1c14ac30..8503e5daa 100644
          ovs_iface_set_state(mgr, iface, OIF_CLAIMED);
          break;
      case OIF_MAX:
-@@ -271,9 +324,10 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
+@@ -271,9 +333,10 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
      switch (iface->state) {
      case OIF_CLAIMED:
      case OIF_INSTALL_FLOWS:
@@ -633,7 +804,7 @@ index d1c14ac30..8503e5daa 100644
      case OIF_MARK_UP:
      case OIF_INSTALLED:
          /* Properly mark interfaces "down" if their flows were already
-@@ -282,6 +336,7 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
+@@ -282,6 +345,7 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
          ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
          break;
      case OIF_MARK_DOWN:
@@ -641,7 +812,7 @@ index d1c14ac30..8503e5daa 100644
          /* Nothing to do here. */
          break;
      case OIF_MAX:
-@@ -302,9 +357,10 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+@@ -302,9 +366,10 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
      switch (iface->state) {
      case OIF_CLAIMED:
      case OIF_INSTALL_FLOWS:
@@ -655,7 +826,7 @@ index d1c14ac30..8503e5daa 100644
      case OIF_MARK_UP:
      case OIF_INSTALLED:
          /* Properly mark interfaces "down" if their flows were already
-@@ -313,6 +369,7 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+@@ -313,6 +378,7 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
          ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
          break;
      case OIF_MARK_DOWN:
@@ -663,7 +834,7 @@ index d1c14ac30..8503e5daa 100644
          /* Nothing to do here. */
          break;
      case OIF_MAX:
-@@ -346,12 +403,34 @@ if_status_handle_claims(struct if_status_mgr *mgr,
+@@ -346,12 +412,34 @@ if_status_handle_claims(struct if_status_mgr *mgr,
      return rc;
  }
  
@@ -698,7 +869,7 @@ index d1c14ac30..8503e5daa 100644
      if (!binding_data) {
          return;
      }
-@@ -359,6 +438,17 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+@@ -359,6 +447,17 @@ if_status_mgr_update(struct if_status_mgr *mgr,
      struct shash *bindings = &binding_data->bindings;
      struct hmapx_node *node;
  
@@ -716,7 +887,7 @@ index d1c14ac30..8503e5daa 100644
      /* Interfaces in OIF_MARK_UP/INSTALL_FLOWS state have already set their
       * pb->chassis. However, the update might still be in fly (confirmation
       * not received yet) or pb->chassis was overwitten by another chassis.
-@@ -390,6 +480,10 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+@@ -390,6 +489,10 @@ if_status_mgr_update(struct if_status_mgr *mgr,
      HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_MARK_DOWN]) {
          struct ovs_iface *iface = node->data;
  
@@ -727,7 +898,7 @@ index d1c14ac30..8503e5daa 100644
          if (!sb_readonly) {
              local_binding_set_pb(bindings, iface->id, chassis_rec,
                                   NULL, false);
-@@ -437,6 +531,21 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+@@ -437,6 +540,21 @@ if_status_mgr_update(struct if_status_mgr *mgr,
          }
      }
  
@@ -749,7 +920,7 @@ index d1c14ac30..8503e5daa 100644
      /* Register for a notification about flows being installed in OVS for all
       * newly claimed interfaces for which pb->chassis has been updated.
       * Request a seqno update when the flows for new interfaces have been
-@@ -450,10 +559,23 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+@@ -450,10 +568,23 @@ if_status_mgr_update(struct if_status_mgr *mgr,
      }
  }
  
@@ -773,7 +944,7 @@ index d1c14ac30..8503e5daa 100644
                    bool sb_readonly, bool ovs_readonly)
  {
      struct ofctrl_acked_seqnos *acked_seqnos =
-@@ -471,12 +593,25 @@ if_status_mgr_run(struct if_status_mgr *mgr,
+@@ -471,12 +602,25 @@ if_status_mgr_run(struct if_status_mgr *mgr,
                                            iface->install_seqno)) {
              continue;
          }
@@ -800,7 +971,7 @@ index d1c14ac30..8503e5daa 100644
                                    sb_readonly, ovs_readonly);
  }
  
-@@ -492,6 +627,18 @@ ovs_iface_account_mem(const char *iface_id, bool erase)
+@@ -492,8 +636,46 @@ ovs_iface_account_mem(const char *iface_id, bool erase)
      }
  }
  
@@ -816,10 +987,42 @@ index d1c14ac30..8503e5daa 100644
 +    }
 +}
 +
++uint16_t
++if_status_mgr_iface_get_mtu(const struct if_status_mgr *mgr,
++                            const char *iface_id)
++{
++    const struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
++    return iface ? iface->mtu : 0;
++}
++
++bool
++if_status_mgr_iface_update(const struct if_status_mgr *mgr,
++                           const struct ovsrec_interface *iface_rec)
++{
++    const char *iface_id = smap_get(&iface_rec->external_ids, "iface-id");
++    if (!iface_id) {
++        return false;
++    }
++    uint16_t mtu = get_iface_mtu(iface_rec);
++    struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
++    if (iface && iface->mtu != mtu) {
++        iface->mtu = mtu;
++        return true;
++    }
++    return false;
++}
++
  static struct ovs_iface *
  ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
++                 const struct ovsrec_interface *iface_rec,
                   enum if_state state)
-@@ -506,6 +653,16 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
+ {
+     struct ovs_iface *iface = xzalloc(sizeof *iface);
+@@ -503,9 +685,20 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
+     shash_add_nocopy(&mgr->ifaces, iface->id, iface);
+     ovs_iface_set_state(mgr, iface, state);
+     ovs_iface_account_mem(iface_id, false);
++    if_status_mgr_iface_update(mgr, iface_rec);
      return iface;
  }
  
@@ -836,7 +1039,7 @@ index d1c14ac30..8503e5daa 100644
  static void
  ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
  {
-@@ -521,6 +678,23 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
+@@ -521,6 +714,23 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
      free(iface);
  }
  
@@ -860,7 +1063,7 @@ index d1c14ac30..8503e5daa 100644
  static void
  ovs_iface_set_state(struct if_status_mgr *mgr, struct ovs_iface *iface,
                      enum if_state state)
-@@ -539,6 +713,7 @@ static void
+@@ -539,6 +749,7 @@ static void
  if_status_mgr_update_bindings(struct if_status_mgr *mgr,
                                struct local_binding_data *binding_data,
                                const struct sbrec_chassis *chassis_rec,
@@ -868,7 +1071,7 @@ index d1c14ac30..8503e5daa 100644
                                bool sb_readonly, bool ovs_readonly)
  {
      if (!binding_data) {
-@@ -558,7 +733,17 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
+@@ -558,7 +769,17 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
                                 sb_readonly, ovs_readonly);
      }
  
@@ -888,18 +1091,29 @@ index d1c14ac30..8503e5daa 100644
       * module.
       */
 diff --git a/controller/if-status.h b/controller/if-status.h
-index 5bd187a25..8ba80acd9 100644
+index 5bd187a25..15624bcfa 100644
 --- a/controller/if-status.h
 +++ b/controller/if-status.h
-@@ -17,6 +17,7 @@
+@@ -17,8 +17,10 @@
  #define IF_STATUS_H 1
  
  #include "openvswitch/shash.h"
 +#include "lib/vswitch-idl.h"
  
  #include "binding.h"
- 
-@@ -35,9 +36,13 @@ void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id);
++#include "lport.h"
+ 
+ struct if_status_mgr;
+ struct simap;
+@@ -29,15 +31,20 @@ void if_status_mgr_destroy(struct if_status_mgr *);
+ void if_status_mgr_claim_iface(struct if_status_mgr *,
+                                const struct sbrec_port_binding *pb,
+                                const struct sbrec_chassis *chassis_rec,
+-                               bool sb_readonly);
++                               const struct ovsrec_interface *iface_rec,
++                               bool sb_readonly, enum can_bind bind_type);
+ void if_status_mgr_release_iface(struct if_status_mgr *, const char *iface_id);
+ void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id);
  
  void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
                            const struct sbrec_chassis *chassis,
@@ -913,19 +1127,41 @@ index 5bd187a25..8ba80acd9 100644
                         bool sb_readonly, bool ovs_readonly);
  void if_status_mgr_get_memory_usage(struct if_status_mgr *mgr,
                                      struct simap *usage);
-@@ -48,5 +53,8 @@ bool if_status_handle_claims(struct if_status_mgr *mgr,
+@@ -48,5 +55,12 @@ bool if_status_handle_claims(struct if_status_mgr *mgr,
                               const struct sbrec_chassis *chassis_rec,
                               struct hmap *tracked_datapath,
                               bool sb_readonly);
 +void if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr,
 +                                        const char *name,
 +                                        const struct uuid *uuid);
++uint16_t if_status_mgr_iface_get_mtu(const struct if_status_mgr *mgr,
++                                     const char *iface_id);
++bool if_status_mgr_iface_update(const struct if_status_mgr *mgr,
++                                const struct ovsrec_interface *iface_rec);
  
  # endif /* controller/if-status.h */
 diff --git a/controller/lflow.c b/controller/lflow.c
-index 6a98b19e1..0b071138d 100644
+index 6a98b19e1..22faaf013 100644
 --- a/controller/lflow.c
 +++ b/controller/lflow.c
+@@ -397,7 +397,7 @@ consider_lflow_for_added_as_ips__(
+                             : OFTABLE_LOG_EGRESS_PIPELINE);
+     uint8_t ptable = first_ptable + lflow->table_id;
+     uint8_t output_ptable = (ingress
+-                             ? OFTABLE_REMOTE_OUTPUT
++                             ? OFTABLE_OUTPUT_INIT
+                              : OFTABLE_SAVE_INPORT);
+ 
+     uint64_t ovnacts_stub[1024 / 8];
+@@ -1067,7 +1067,7 @@ consider_logical_flow__(const struct sbrec_logical_flow *lflow,
+                             : OFTABLE_LOG_EGRESS_PIPELINE);
+     uint8_t ptable = first_ptable + lflow->table_id;
+     uint8_t output_ptable = (ingress
+-                             ? OFTABLE_REMOTE_OUTPUT
++                             ? OFTABLE_OUTPUT_INIT
+                              : OFTABLE_SAVE_INPORT);
+ 
+     /* Parse OVN logical actions.
 @@ -1729,6 +1729,7 @@ add_lb_vip_hairpin_flows(const struct ovn_controller_lb *lb,
  
  static void
@@ -997,6 +1233,100 @@ index 6a98b19e1..0b071138d 100644
                      local_datapaths, &match, &ofpacts, flow_table);
              }
          }
+diff --git a/controller/lflow.h b/controller/lflow.h
+index dd742257b..2472dec29 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -63,27 +63,36 @@ struct uuid;
+  *
+  * These are heavily documented in ovn-architecture(7), please update it if
+  * you make any changes. */
+-#define OFTABLE_PHY_TO_LOG            0
+-#define OFTABLE_LOG_INGRESS_PIPELINE  8 /* First of LOG_PIPELINE_LEN tables. */
+-#define OFTABLE_REMOTE_OUTPUT        37
+-#define OFTABLE_LOCAL_OUTPUT         38
+-#define OFTABLE_CHECK_LOOPBACK       39
+-#define OFTABLE_LOG_EGRESS_PIPELINE  40 /* First of LOG_PIPELINE_LEN tables. */
+-#define OFTABLE_SAVE_INPORT          64
+-#define OFTABLE_LOG_TO_PHY           65
+-#define OFTABLE_MAC_BINDING          66
+-#define OFTABLE_MAC_LOOKUP           67
+-#define OFTABLE_CHK_LB_HAIRPIN       68
+-#define OFTABLE_CHK_LB_HAIRPIN_REPLY 69
+-#define OFTABLE_CT_SNAT_HAIRPIN      70
+-#define OFTABLE_GET_FDB              71
+-#define OFTABLE_LOOKUP_FDB           72
+-#define OFTABLE_CHK_IN_PORT_SEC      73
+-#define OFTABLE_CHK_IN_PORT_SEC_ND   74
+-#define OFTABLE_CHK_OUT_PORT_SEC     75
+-#define OFTABLE_ECMP_NH_MAC          76
+-#define OFTABLE_ECMP_NH              77
+-#define OFTABLE_CHK_LB_AFFINITY      78
++#define OFTABLE_PHY_TO_LOG                0
++
++/* Start of LOG_PIPELINE_LEN tables. */
++#define OFTABLE_LOG_INGRESS_PIPELINE      8
++#define OFTABLE_OUTPUT_LARGE_PKT_DETECT  37
++#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 38
++#define OFTABLE_REMOTE_OUTPUT            39
++#define OFTABLE_LOCAL_OUTPUT             40
++#define OFTABLE_CHECK_LOOPBACK           41
++
++/* Start of the OUTPUT section of the pipeline. */
++#define OFTABLE_OUTPUT_INIT OFTABLE_OUTPUT_LARGE_PKT_DETECT
++
++/* Start of LOG_PIPELINE_LEN tables. */
++#define OFTABLE_LOG_EGRESS_PIPELINE      42
++#define OFTABLE_SAVE_INPORT              64
++#define OFTABLE_LOG_TO_PHY               65
++#define OFTABLE_MAC_BINDING              66
++#define OFTABLE_MAC_LOOKUP               67
++#define OFTABLE_CHK_LB_HAIRPIN           68
++#define OFTABLE_CHK_LB_HAIRPIN_REPLY     69
++#define OFTABLE_CT_SNAT_HAIRPIN          70
++#define OFTABLE_GET_FDB                  71
++#define OFTABLE_LOOKUP_FDB               72
++#define OFTABLE_CHK_IN_PORT_SEC          73
++#define OFTABLE_CHK_IN_PORT_SEC_ND       74
++#define OFTABLE_CHK_OUT_PORT_SEC         75
++#define OFTABLE_ECMP_NH_MAC              76
++#define OFTABLE_ECMP_NH                  77
++#define OFTABLE_CHK_LB_AFFINITY          78
+ 
+ struct lflow_ctx_in {
+     struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath;
+diff --git a/controller/local_data.c b/controller/local_data.c
+index acaf1de6d..cf0b21bb1 100644
+--- a/controller/local_data.c
++++ b/controller/local_data.c
+@@ -22,6 +22,7 @@
+ #include "lib/util.h"
+ #include "lib/vswitch-idl.h"
+ #include "openvswitch/vlog.h"
++#include "socket-util.h"
+ 
+ /* OVN includes. */
+ #include "encaps.h"
+@@ -447,6 +448,7 @@ local_nonvif_data_run(const struct ovsrec_bridge *br_int,
+                 tun->chassis_id = xstrdup(tunnel_id);
+                 tun->ofport = u16_to_ofp(ofport);
+                 tun->type = tunnel_type;
++                tun->is_ipv6 = ip ? addr_is_ipv6(ip) : false;
+ 
+                 free(hash_id);
+                 free(ip);
+diff --git a/controller/local_data.h b/controller/local_data.h
+index 748f009aa..ad0fa7f94 100644
+--- a/controller/local_data.h
++++ b/controller/local_data.h
+@@ -133,6 +133,7 @@ struct chassis_tunnel {
+     char *chassis_id;
+     ofp_port_t ofport;
+     enum chassis_tunnel_type type;
++    bool is_ipv6;
+ };
+ 
+ void local_nonvif_data_run(const struct ovsrec_bridge *br_int,
 diff --git a/controller/mirror.c b/controller/mirror.c
 index 665736966..0e5885e9b 100644
 --- a/controller/mirror.c
@@ -1069,6 +1399,75 @@ index 665736966..0e5885e9b 100644
  
      struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
      ovsrec_port_set_name(port, port_name);
+diff --git a/controller/ofctrl.c b/controller/ofctrl.c
+index b1ba1c743..64a444ff6 100644
+--- a/controller/ofctrl.c
++++ b/controller/ofctrl.c
+@@ -766,13 +766,18 @@ ofctrl_get_mf_field_id(void)
+ 
+ /* Runs the OpenFlow state machine against 'br_int', which is local to the
+  * hypervisor on which we are running.  Attempts to negotiate a Geneve option
+- * field for class OVN_GENEVE_CLASS, type OVN_GENEVE_TYPE. */
+-void
++ * field for class OVN_GENEVE_CLASS, type OVN_GENEVE_TYPE.
++ *
++ * Returns 'true' if an OpenFlow reconnect happened; 'false' otherwise.
++ */
++bool
+ ofctrl_run(const struct ovsrec_bridge *br_int,
+            const struct ovsrec_open_vswitch_table *ovs_table,
+            struct shash *pending_ct_zones)
+ {
+     char *target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int->name);
++    bool reconnected = false;
++
+     if (strcmp(target, rconn_get_target(swconn))) {
+         VLOG_INFO("%s: connecting to switch", target);
+         rconn_connect(swconn, target, target);
+@@ -782,10 +787,12 @@ ofctrl_run(const struct ovsrec_bridge *br_int,
+     rconn_run(swconn);
+ 
+     if (!rconn_is_connected(swconn)) {
+-        return;
++        return reconnected;
+     }
++
+     if (seqno != rconn_get_connection_seqno(swconn)) {
+         seqno = rconn_get_connection_seqno(swconn);
++        reconnected = true;
+         state = S_NEW;
+ 
+         /* Reset the state of any outstanding ct flushes to resend them. */
+@@ -855,6 +862,8 @@ ofctrl_run(const struct ovsrec_bridge *br_int,
+          * point, so ensure that we come back again without waiting. */
+         poll_immediate_wake();
+     }
++
++    return reconnected;
+ }
+ 
+ void
+@@ -909,6 +918,7 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
+     } else if (type == OFPTYPE_ERROR) {
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+         log_openflow_rl(&rl, VLL_INFO, oh, "OpenFlow error");
++        rconn_reconnect(swconn);
+     } else {
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+         log_openflow_rl(&rl, VLL_DBG, oh, "OpenFlow packet ignored");
+diff --git a/controller/ofctrl.h b/controller/ofctrl.h
+index f5751e3ee..105f9370b 100644
+--- a/controller/ofctrl.h
++++ b/controller/ofctrl.h
+@@ -51,7 +51,7 @@ struct ovn_desired_flow_table {
+ void ofctrl_init(struct ovn_extend_table *group_table,
+                  struct ovn_extend_table *meter_table,
+                  int inactivity_probe_interval);
+-void ofctrl_run(const struct ovsrec_bridge *br_int,
++bool ofctrl_run(const struct ovsrec_bridge *br_int,
+                 const struct ovsrec_open_vswitch_table *,
+                 struct shash *pending_ct_zones);
+ enum mf_field_id ofctrl_get_mf_field_id(void);
 diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
 index ab52e2d34..f61f43008 100644
 --- a/controller/ovn-controller.8.xml
@@ -1112,10 +1511,18 @@ index ab52e2d34..f61f43008 100644
        </dd>
  
 diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 2d18bbfca..44a4518b9 100644
+index 2d18bbfca..ead789fb9 100644
 --- a/controller/ovn-controller.c
 +++ b/controller/ovn-controller.c
-@@ -712,7 +712,7 @@ get_snat_ct_zone(const struct sbrec_datapath_binding *dp)
+@@ -60,6 +60,7 @@
+ #include "lib/ovn-dirs.h"
+ #include "lib/ovn-sb-idl.h"
+ #include "lib/ovn-util.h"
++#include "ovsport.h"
+ #include "patch.h"
+ #include "vif-plug.h"
+ #include "vif-plug-provider.h"
+@@ -712,7 +713,7 @@ get_snat_ct_zone(const struct sbrec_datapath_binding *dp)
  }
  
  static void
@@ -1124,7 +1531,7 @@ index 2d18bbfca..44a4518b9 100644
                  const struct hmap *local_datapaths,
                  struct simap *ct_zones, unsigned long *ct_zone_bitmap,
                  struct shash *pending_ct_zones)
-@@ -725,9 +725,9 @@ update_ct_zones(const struct shash *binding_lports,
+@@ -725,9 +726,9 @@ update_ct_zones(const struct shash *binding_lports,
      unsigned long unreq_snat_zones_map[BITMAP_N_LONGS(MAX_CT_ZONES)];
      struct simap unreq_snat_zones = SIMAP_INITIALIZER(&unreq_snat_zones);
  
@@ -1137,7 +1544,72 @@ index 2d18bbfca..44a4518b9 100644
      }
  
      /* Local patched datapath (gateway routers) need zones assigned. */
-@@ -2010,7 +2010,11 @@ addr_sets_update(const struct sbrec_address_set_table *address_set_table,
+@@ -1060,6 +1061,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd_status);
++    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_mtu);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_type);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_options);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport);
+@@ -1158,6 +1160,56 @@ en_ofctrl_is_connected_run(struct engine_node *node, void *data)
+     engine_set_node_state(node, EN_UNCHANGED);
+ }
+ 
++struct ed_type_if_status_mgr {
++    const struct if_status_mgr *manager;
++    const struct ovsrec_interface_table *iface_table;
++};
++
++static void *
++en_if_status_mgr_init(struct engine_node *node OVS_UNUSED,
++                      struct engine_arg *arg OVS_UNUSED)
++{
++    struct ed_type_if_status_mgr *data = xzalloc(sizeof *data);
++    return data;
++}
++
++static void
++en_if_status_mgr_cleanup(void *data OVS_UNUSED)
++{
++}
++
++static void
++en_if_status_mgr_run(struct engine_node *node, void *data_)
++{
++    enum engine_node_state state = EN_UNCHANGED;
++    struct ed_type_if_status_mgr *data = data_;
++    struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx;
++    data->manager = ctrl_ctx->if_mgr;
++    data->iface_table = EN_OVSDB_GET(engine_get_input("OVS_interface", node));
++
++    const struct ovsrec_interface *iface;
++    OVSREC_INTERFACE_TABLE_FOR_EACH (iface, data->iface_table) {
++        if (if_status_mgr_iface_update(data->manager, iface)) {
++            state = EN_UPDATED;
++        }
++    }
++    engine_set_node_state(node, state);
++}
++
++static bool
++if_status_mgr_ovs_interface_handler(struct engine_node *node, void *data)
++{
++    struct ed_type_if_status_mgr *data_ = data;
++
++    const struct ovsrec_interface *iface;
++    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface, data_->iface_table) {
++        if (if_status_mgr_iface_update(data_->manager, iface)) {
++            engine_set_node_state(node, EN_UPDATED);
++        }
++    }
++    return true;
++}
++
+ /* This engine node is to wrap the OVS_interface input and maintain a copy of
+  * the old version of data for the column external_ids.
+  *
+@@ -2010,7 +2062,11 @@ addr_sets_update(const struct sbrec_address_set_table *address_set_table,
          if (sbrec_address_set_is_deleted(as)) {
              expr_const_sets_remove(addr_sets, as->name);
              sset_add(deleted, as->name);
@@ -1150,7 +1622,7 @@ index 2d18bbfca..44a4518b9 100644
              struct expr_constant_set *cs_old = shash_find_data(addr_sets,
                                                                 as->name);
              if (!cs_old) {
-@@ -2381,7 +2385,7 @@ en_ct_zones_run(struct engine_node *node, void *data)
+@@ -2381,7 +2437,7 @@ en_ct_zones_run(struct engine_node *node, void *data)
          EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
  
      restore_ct_zones(bridge_table, ovs_table, ct_zones_data);
@@ -1159,7 +1631,7 @@ index 2d18bbfca..44a4518b9 100644
                      &ct_zones_data->current, ct_zones_data->bitmap,
                      &ct_zones_data->pending);
  
-@@ -2471,8 +2475,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data)
+@@ -2471,8 +2527,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data)
          SHASH_FOR_EACH (shash_node, &tdp->lports) {
              struct tracked_lport *t_lport = shash_node->data;
              if (strcmp(t_lport->pb->type, "")
@@ -1172,7 +1644,7 @@ index 2d18bbfca..44a4518b9 100644
                  continue;
              }
  
-@@ -2697,7 +2703,8 @@ static void
+@@ -2697,7 +2755,8 @@ static void
  lb_data_removed_five_tuples_add(struct ed_type_lb_data *lb_data,
                                  const struct ovn_controller_lb *lb)
  {
@@ -1182,7 +1654,7 @@ index 2d18bbfca..44a4518b9 100644
          return;
      }
  
-@@ -2716,7 +2723,8 @@ static void
+@@ -2716,7 +2775,8 @@ static void
  lb_data_removed_five_tuples_remove(struct ed_type_lb_data *lb_data,
                                     const struct ovn_controller_lb *lb)
  {
@@ -1192,7 +1664,143 @@ index 2d18bbfca..44a4518b9 100644
          return;
      }
  
-@@ -5071,7 +5079,8 @@ main(int argc, char *argv[])
+@@ -4048,6 +4108,9 @@ static void init_physical_ctx(struct engine_node *node,
+     const struct ed_type_mff_ovn_geneve *ed_mff_ovn_geneve =
+         engine_get_input_data("mff_ovn_geneve", node);
+ 
++    const struct ovsrec_interface_table *ovs_interface_table =
++        EN_OVSDB_GET(engine_get_input("if_status_mgr", node));
++
+     const struct ovsrec_open_vswitch_table *ovs_table =
+         EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
+     const struct ovsrec_bridge_table *bridge_table =
+@@ -4072,6 +4135,7 @@ static void init_physical_ctx(struct engine_node *node,
+     p_ctx->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
+     p_ctx->sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath;
+     p_ctx->port_binding_table = port_binding_table;
++    p_ctx->ovs_interface_table = ovs_interface_table;
+     p_ctx->mc_group_table = multicast_group_table;
+     p_ctx->br_int = br_int;
+     p_ctx->chassis_table = chassis_table;
+@@ -4085,6 +4149,9 @@ static void init_physical_ctx(struct engine_node *node,
+     p_ctx->patch_ofports = &non_vif_data->patch_ofports;
+     p_ctx->chassis_tunnels = &non_vif_data->chassis_tunnels;
+ 
++    struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx;
++    p_ctx->if_mgr = ctrl_ctx->if_mgr;
++
+     pflow_output_get_debug(node, &p_ctx->debug);
+ }
+ 
+@@ -4128,6 +4195,63 @@ en_pflow_output_run(struct engine_node *node, void *data)
+     engine_set_node_state(node, EN_UPDATED);
+ }
+ 
++static bool
++pflow_output_if_status_mgr_handler(struct engine_node *node,
++                                   void *data)
++{
++    struct ed_type_pflow_output *pfo = data;
++    struct ed_type_runtime_data *rt_data =
++        engine_get_input_data("runtime_data", node);
++    struct ed_type_non_vif_data *non_vif_data =
++        engine_get_input_data("non_vif_data", node);
++    struct ed_type_if_status_mgr *if_mgr_data =
++        engine_get_input_data("if_status_mgr", node);
++
++    struct physical_ctx p_ctx;
++    init_physical_ctx(node, rt_data, non_vif_data, &p_ctx);
++
++    const struct ovsrec_interface *iface;
++    OVSREC_INTERFACE_TABLE_FOR_EACH_TRACKED (iface, if_mgr_data->iface_table) {
++        const char *iface_id = smap_get(&iface->external_ids, "iface-id");
++        if (!iface_id) {
++            continue;
++        }
++
++        const struct sbrec_port_binding *pb = lport_lookup_by_name(
++            p_ctx.sbrec_port_binding_by_name, iface_id);
++        if (!pb) {
++            continue;
++        }
++        if (pb->n_additional_chassis) {
++            /* Update flows for all ports in datapath. */
++            struct sbrec_port_binding *target =
++                sbrec_port_binding_index_init_row(
++                    p_ctx.sbrec_port_binding_by_datapath);
++            sbrec_port_binding_index_set_datapath(target, pb->datapath);
++
++            const struct sbrec_port_binding *binding;
++            SBREC_PORT_BINDING_FOR_EACH_EQUAL (
++                    binding, target, p_ctx.sbrec_port_binding_by_datapath) {
++                bool removed = sbrec_port_binding_is_deleted(binding);
++                if (!physical_handle_flows_for_lport(binding, removed, &p_ctx,
++                                                     &pfo->flow_table)) {
++                    return false;
++                }
++            }
++            sbrec_port_binding_index_destroy_row(target);
++        } else {
++            /* If any multichassis ports, update flows for the port. */
++            bool removed = sbrec_port_binding_is_deleted(pb);
++            if (!physical_handle_flows_for_lport(pb, removed, &p_ctx,
++                                                 &pfo->flow_table)) {
++                return false;
++            }
++        }
++        engine_set_node_state(node, EN_UPDATED);
++    }
++    return true;
++}
++
+ static bool
+ pflow_output_sb_port_binding_handler(struct engine_node *node,
+                                      void *data)
+@@ -4611,6 +4735,7 @@ main(int argc, char *argv[])
+     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
+     ENGINE_NODE(northd_options, "northd_options");
+     ENGINE_NODE(dhcp_options, "dhcp_options");
++    ENGINE_NODE(if_status_mgr, "if_status_mgr");
+     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
+ 
+ #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
+@@ -4649,6 +4774,9 @@ main(int argc, char *argv[])
+     engine_add_input(&en_non_vif_data, &en_ovs_interface,
+                      non_vif_data_ovs_iface_handler);
+ 
++    engine_add_input(&en_if_status_mgr, &en_ovs_interface,
++                     if_status_mgr_ovs_interface_handler);
++
+     /* Note: The order of inputs is important, all OVS interface changes must
+      * be handled before any ct_zone changes.
+      */
+@@ -4659,6 +4787,8 @@ main(int argc, char *argv[])
+     engine_add_input(&en_pflow_output, &en_sb_chassis,
+                      pflow_lflow_output_sb_chassis_handler);
+ 
++    engine_add_input(&en_pflow_output, &en_if_status_mgr,
++                     pflow_output_if_status_mgr_handler);
+     engine_add_input(&en_pflow_output, &en_sb_port_binding,
+                      pflow_output_sb_port_binding_handler);
+     engine_add_input(&en_pflow_output, &en_sb_multicast_group,
+@@ -5061,8 +5191,14 @@ main(int argc, char *argv[])
+ 
+             if (br_int) {
+                 ct_zones_data = engine_get_data(&en_ct_zones);
+-                if (ct_zones_data) {
+-                    ofctrl_run(br_int, ovs_table, &ct_zones_data->pending);
++                if (ct_zones_data && ofctrl_run(br_int, ovs_table,
++                                                &ct_zones_data->pending)) {
++                    static struct vlog_rate_limit rl
++                            = VLOG_RATE_LIMIT_INIT(1, 1);
++
++                    VLOG_INFO_RL(&rl, "OVS OpenFlow connection reconnected,"
++                                      "force recompute.");
++                    engine_set_force_recompute(true);
+                 }
+ 
+                 if (chassis) {
+@@ -5071,7 +5207,8 @@ main(int argc, char *argv[])
                                 chassis,
                                 sbrec_sb_global_first(ovnsb_idl_loop.idl),
                                 ovs_table,
@@ -1202,7 +1810,7 @@ index 2d18bbfca..44a4518b9 100644
  
                      stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
                                      time_msec());
-@@ -5225,6 +5234,11 @@ main(int argc, char *argv[])
+@@ -5225,6 +5362,11 @@ main(int argc, char *argv[])
                      stopwatch_start(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME,
                                      time_msec());
                      if_status_mgr_update(if_mgr, binding_data, chassis,
@@ -1214,7 +1822,7 @@ index 2d18bbfca..44a4518b9 100644
                                           !ovnsb_idl_txn);
                      stopwatch_stop(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME,
                                     time_msec());
-@@ -5254,11 +5268,12 @@ main(int argc, char *argv[])
+@@ -5254,11 +5396,12 @@ main(int argc, char *argv[])
                      stopwatch_start(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
                                      time_msec());
                      if_status_mgr_run(if_mgr, binding_data, chassis,
@@ -1228,7 +1836,7 @@ index 2d18bbfca..44a4518b9 100644
              }
  
              if (!engine_has_run()) {
-@@ -5449,6 +5464,7 @@ loop_done:
+@@ -5449,6 +5592,7 @@ loop_done:
      binding_destroy();
      patch_destroy();
      mirror_destroy();
@@ -1236,10 +1844,651 @@ index 2d18bbfca..44a4518b9 100644
      if_status_mgr_destroy(if_mgr);
      shash_destroy(&vif_plug_deleted_iface_ids);
      shash_destroy(&vif_plug_changed_iface_ids);
+@@ -5466,6 +5610,7 @@ loop_done:
+         free(cli_system_id);
+     }
+     service_stop();
++    ovsrcu_exit();
+ 
+     exit(retval);
+ }
+diff --git a/controller/ovsport.c b/controller/ovsport.c
+index ec38c3fca..ebcb9cb6d 100644
+--- a/controller/ovsport.c
++++ b/controller/ovsport.c
+@@ -264,3 +264,12 @@ maintain_interface_smap_column(
+         }
+     }
+ }
++
++uint16_t
++get_iface_mtu(const struct ovsrec_interface *iface)
++{
++    if (!iface || !iface->n_mtu || iface->mtu[0] <= 0) {
++        return 0;
++    }
++    return (uint16_t) iface->mtu[0];
++}
+diff --git a/controller/ovsport.h b/controller/ovsport.h
+index e355ff7ff..c40c1855a 100644
+--- a/controller/ovsport.h
++++ b/controller/ovsport.h
+@@ -57,4 +57,6 @@ const struct ovsrec_port * ovsport_lookup_by_interfaces(
+ const struct ovsrec_port * ovsport_lookup_by_interface(
+         struct ovsdb_idl_index *, struct ovsrec_interface *);
+ 
++uint16_t get_iface_mtu(const struct ovsrec_interface *);
++
+ #endif /* lib/ovsport.h */
+diff --git a/controller/physical.c b/controller/physical.c
+index ec861f49c..d19eb9200 100644
+--- a/controller/physical.c
++++ b/controller/physical.c
+@@ -41,6 +41,7 @@
+ #include "lib/ovn-sb-idl.h"
+ #include "lib/ovn-util.h"
+ #include "ovn/actions.h"
++#include "if-status.h"
+ #include "physical.h"
+ #include "pinctrl.h"
+ #include "openvswitch/shash.h"
+@@ -91,6 +92,7 @@ physical_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+ 
+     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name);
++    ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_mtu);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_ofport);
+     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_external_ids);
+ }
+@@ -876,12 +878,12 @@ put_local_common_flows(uint32_t dp_key,
+ 
+     uint32_t port_key = pb->tunnel_key;
+ 
+-    /* Table 38, priority 100.
++    /* Table 40, priority 100.
+      * =======================
+      *
+      * Implements output to local hypervisor.  Each flow matches a
+      * logical output port on the local hypervisor, and resubmits to
+-     * table 39.
++     * table 41.
+      */
+ 
+     ofpbuf_clear(ofpacts_p);
+@@ -891,13 +893,13 @@ put_local_common_flows(uint32_t dp_key,
+ 
+     put_zones_ofpacts(zone_ids, ofpacts_p);
+ 
+-    /* Resubmit to table 39. */
++    /* Resubmit to table 41. */
+     put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
+     ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
+                     pb->header_.uuid.parts[0], &match, ofpacts_p,
+                     &pb->header_.uuid);
+ 
+-    /* Table 39, Priority 100.
++    /* Table 41, Priority 100.
+      * =======================
+      *
+      * Drop packets whose logical inport and outport are the same
+@@ -1104,6 +1106,240 @@ setup_activation_strategy(const struct sbrec_port_binding *binding,
+     }
+ }
+ 
++/*
++ * Insert a flow to determine if an IP packet is too big for the corresponding
++ * egress interface.
++ */
++static void
++determine_if_pkt_too_big(struct ovn_desired_flow_table *flow_table,
++                         const struct sbrec_port_binding *binding,
++                         const struct sbrec_port_binding *mcp,
++                         uint16_t mtu, bool is_ipv6, int direction)
++{
++    struct ofpbuf ofpacts;
++    ofpbuf_init(&ofpacts, 0);
++
++    /* Store packet too large flag in reg9[1]. */
++    struct match match;
++    match_init_catchall(&match);
++    match_set_dl_type(&match, htons(is_ipv6 ? ETH_TYPE_IPV6 : ETH_TYPE_IP));
++    match_set_metadata(&match, htonll(binding->datapath->tunnel_key));
++    match_set_reg(&match, direction - MFF_REG0, mcp->tunnel_key);
++
++    /* reg9[1] is REGBIT_PKT_LARGER as defined by northd */
++    struct ofpact_check_pkt_larger *pkt_larger =
++        ofpact_put_CHECK_PKT_LARGER(&ofpacts);
++    pkt_larger->pkt_len = mtu;
++    pkt_larger->dst.field = mf_from_id(MFF_REG9);
++    pkt_larger->dst.ofs = 1;
++
++    put_resubmit(OFTABLE_OUTPUT_LARGE_PKT_PROCESS, &ofpacts);
++    ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_DETECT, 100,
++                    binding->header_.uuid.parts[0], &match, &ofpacts,
++                    &binding->header_.uuid);
++    ofpbuf_uninit(&ofpacts);
++}
++
++/*
++ * Insert a flow to reply with ICMP error for IP packets that are too big for
++ * the corresponding egress interface.
++ */
++/*
++ * NOTE(ihrachys) This reimplements icmp_error as found in
++ * build_icmperr_pkt_big_flows. We may look into reusing the existing OVN
++ * action for this flow in the future.
++ */
++static void
++reply_imcp_error_if_pkt_too_big(struct ovn_desired_flow_table *flow_table,
++                                const struct sbrec_port_binding *binding,
++                                const struct sbrec_port_binding *mcp,
++                                uint16_t mtu, bool is_ipv6, int direction)
++{
++    struct match match;
++    match_init_catchall(&match);
++    match_set_dl_type(&match, htons(is_ipv6 ? ETH_TYPE_IPV6 : ETH_TYPE_IP));
++    match_set_metadata(&match, htonll(binding->datapath->tunnel_key));
++    match_set_reg(&match, direction - MFF_REG0, mcp->tunnel_key);
++    match_set_reg_masked(&match, MFF_REG9 - MFF_REG0, 1 << 1, 1 << 1);
++
++    /* Return ICMP error with a part of the original IP packet included. */
++    struct ofpbuf ofpacts;
++    ofpbuf_init(&ofpacts, 0);
++    size_t oc_offset = encode_start_controller_op(
++        ACTION_OPCODE_ICMP, true, NX_CTLR_NO_METER, &ofpacts);
++
++    struct ofpbuf inner_ofpacts;
++    ofpbuf_init(&inner_ofpacts, 0);
++
++    /* The error packet is no longer too large, set REGBIT_PKT_LARGER = 0 */
++    /* reg9[1] is REGBIT_PKT_LARGER as defined by northd */
++    ovs_be32 value = htonl(0);
++    ovs_be32 mask = htonl(1 << 1);
++    ofpact_put_set_field(
++        &inner_ofpacts, mf_from_id(MFF_REG9), &value, &mask);
++
++    /* The new error packet is delivered locally */
++    /* REGBIT_EGRESS_LOOPBACK = 1 */
++    value = htonl(1 << MLF_ALLOW_LOOPBACK_BIT);
++    mask = htonl(1 << MLF_ALLOW_LOOPBACK_BIT);
++    ofpact_put_set_field(
++        &inner_ofpacts, mf_from_id(MFF_LOG_FLAGS), &value, &mask);
++
++    /* eth.src <-> eth.dst */
++    put_stack(MFF_ETH_DST, ofpact_put_STACK_PUSH(&inner_ofpacts));
++    put_stack(MFF_ETH_SRC, ofpact_put_STACK_PUSH(&inner_ofpacts));
++    put_stack(MFF_ETH_DST, ofpact_put_STACK_POP(&inner_ofpacts));
++    put_stack(MFF_ETH_SRC, ofpact_put_STACK_POP(&inner_ofpacts));
++
++    /* ip.src <-> ip.dst */
++    put_stack(is_ipv6 ? MFF_IPV6_DST : MFF_IPV4_DST,
++        ofpact_put_STACK_PUSH(&inner_ofpacts));
++    put_stack(is_ipv6 ? MFF_IPV6_SRC : MFF_IPV4_SRC,
++        ofpact_put_STACK_PUSH(&inner_ofpacts));
++    put_stack(is_ipv6 ? MFF_IPV6_DST : MFF_IPV4_DST,
++        ofpact_put_STACK_POP(&inner_ofpacts));
++    put_stack(is_ipv6 ? MFF_IPV6_SRC : MFF_IPV4_SRC,
++        ofpact_put_STACK_POP(&inner_ofpacts));
++
++    /* ip.ttl = 255 */
++    struct ofpact_ip_ttl *ip_ttl = ofpact_put_SET_IP_TTL(&inner_ofpacts);
++    ip_ttl->ttl = 255;
++
++    uint16_t frag_mtu = mtu - ETHERNET_OVERHEAD;
++    size_t frag_mtu_oc_offset;
++    if (is_ipv6) {
++        /* icmp6.type = 2 (Packet Too Big) */
++        /* icmp6.code = 0 */
++        uint8_t icmp_type = 2;
++        uint8_t icmp_code = 0;
++        ofpact_put_set_field(
++            &inner_ofpacts, mf_from_id(MFF_ICMPV6_TYPE), &icmp_type, NULL);
++        ofpact_put_set_field(
++            &inner_ofpacts, mf_from_id(MFF_ICMPV6_CODE), &icmp_code, NULL);
++
++        /* icmp6.frag_mtu */
++        frag_mtu_oc_offset = encode_start_controller_op(
++            ACTION_OPCODE_PUT_ICMP6_FRAG_MTU, true, NX_CTLR_NO_METER,
++            &inner_ofpacts);
++        ovs_be32 frag_mtu_ovs = htonl(frag_mtu);
++        ofpbuf_put(&inner_ofpacts, &frag_mtu_ovs, sizeof(frag_mtu_ovs));
++    } else {
++        /* icmp4.type = 3 (Destination Unreachable) */
++        /* icmp4.code = 4 (Fragmentation Needed) */
++        uint8_t icmp_type = 3;
++        uint8_t icmp_code = 4;
++        ofpact_put_set_field(
++            &inner_ofpacts, mf_from_id(MFF_ICMPV4_TYPE), &icmp_type, NULL);
++        ofpact_put_set_field(
++            &inner_ofpacts, mf_from_id(MFF_ICMPV4_CODE), &icmp_code, NULL);
++
++        /* icmp4.frag_mtu = */
++        frag_mtu_oc_offset = encode_start_controller_op(
++            ACTION_OPCODE_PUT_ICMP4_FRAG_MTU, true, NX_CTLR_NO_METER,
++            &inner_ofpacts);
++        ovs_be16 frag_mtu_ovs = htons(frag_mtu);
++        ofpbuf_put(&inner_ofpacts, &frag_mtu_ovs, sizeof(frag_mtu_ovs));
++    }
++    encode_finish_controller_op(frag_mtu_oc_offset, &inner_ofpacts);
++
++    /* Finally, submit the ICMP error back to the ingress pipeline */
++    put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, &inner_ofpacts);
++
++    /* Attach nested actions to ICMP error controller handler */
++    ofpacts_put_openflow_actions(inner_ofpacts.data, inner_ofpacts.size,
++                                 &ofpacts, OFP15_VERSION);
++
++    /* Finalize the ICMP error controller handler */
++    encode_finish_controller_op(oc_offset, &ofpacts);
++
++    ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_PROCESS, 100,
++                    binding->header_.uuid.parts[0], &match, &ofpacts,
++                    &binding->header_.uuid);
++
++    ofpbuf_uninit(&inner_ofpacts);
++    ofpbuf_uninit(&ofpacts);
++}
++
++static uint16_t
++get_tunnel_overhead(struct chassis_tunnel const *tun)
++{
++    uint16_t overhead = 0;
++    enum chassis_tunnel_type type = tun->type;
++    if (type == GENEVE) {
++        overhead += GENEVE_TUNNEL_OVERHEAD;
++    } else if (type == STT) {
++        overhead += STT_TUNNEL_OVERHEAD;
++    } else if (type == VXLAN) {
++        overhead += VXLAN_TUNNEL_OVERHEAD;
++    } else {
++        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
++        VLOG_WARN_RL(&rl, "Unknown tunnel type %d, can't determine overhead "
++                          "size for Path MTU Discovery", type);
++        return 0;
++    }
++    overhead += tun->is_ipv6? IPV6_HEADER_LEN : IP_HEADER_LEN;
++    return overhead;
++}
++
++static uint16_t
++get_effective_mtu(const struct sbrec_port_binding *mcp,
++                  struct ovs_list *remote_tunnels,
++                  const struct if_status_mgr *if_mgr)
++{
++    /* Use interface MTU as a base for calculation */
++    uint16_t iface_mtu = if_status_mgr_iface_get_mtu(if_mgr,
++                                                     mcp->logical_port);
++    if (!iface_mtu) {
++        return 0;
++    }
++
++    /* Iterate over all peer tunnels and find the biggest tunnel overhead */
++    uint16_t overhead = 0;
++    struct tunnel *tun;
++    LIST_FOR_EACH (tun, list_node, remote_tunnels) {
++        overhead = MAX(overhead, get_tunnel_overhead(tun->tun));
++    }
++    if (!overhead) {
++        return 0;
++    }
++
++    return iface_mtu - overhead;
++}
++
++static void
++handle_pkt_too_big_for_ip_version(struct ovn_desired_flow_table *flow_table,
++                                  const struct sbrec_port_binding *binding,
++                                  const struct sbrec_port_binding *mcp,
++                                  uint16_t mtu, bool is_ipv6)
++{
++    /* ingress */
++    determine_if_pkt_too_big(flow_table, binding, mcp, mtu, is_ipv6,
++                             MFF_LOG_INPORT);
++    reply_imcp_error_if_pkt_too_big(flow_table, binding, mcp, mtu, is_ipv6,
++                                    MFF_LOG_INPORT);
++
++    /* egress */
++    determine_if_pkt_too_big(flow_table, binding, mcp, mtu, is_ipv6,
++                             MFF_LOG_OUTPORT);
++    reply_imcp_error_if_pkt_too_big(flow_table, binding, mcp, mtu, is_ipv6,
++                                    MFF_LOG_OUTPORT);
++}
++
++static void
++handle_pkt_too_big(struct ovn_desired_flow_table *flow_table,
++                   struct ovs_list *remote_tunnels,
++                   const struct sbrec_port_binding *binding,
++                   const struct sbrec_port_binding *mcp,
++                   const struct if_status_mgr *if_mgr)
++{
++    uint16_t mtu = get_effective_mtu(mcp, remote_tunnels, if_mgr);
++    if (!mtu) {
++        return;
++    }
++    handle_pkt_too_big_for_ip_version(flow_table, binding, mcp, mtu, false);
++    handle_pkt_too_big_for_ip_version(flow_table, binding, mcp, mtu, true);
++}
++
+ static void
+ enforce_tunneling_for_multichassis_ports(
+     struct local_datapath *ld,
+@@ -1111,7 +1347,8 @@ enforce_tunneling_for_multichassis_ports(
+     const struct sbrec_chassis *chassis,
+     const struct hmap *chassis_tunnels,
+     enum mf_field_id mff_ovn_geneve,
+-    struct ovn_desired_flow_table *flow_table)
++    struct ovn_desired_flow_table *flow_table,
++    const struct if_status_mgr *if_mgr)
+ {
+     if (shash_is_empty(&ld->multichassis_ports)) {
+         return;
+@@ -1156,6 +1393,8 @@ enforce_tunneling_for_multichassis_ports(
+                         binding->header_.uuid.parts[0], &match, &ofpacts,
+                         &binding->header_.uuid);
+         ofpbuf_uninit(&ofpacts);
++
++        handle_pkt_too_big(flow_table, tuns, binding, mcp, if_mgr);
+     }
+ 
+     struct tunnel *tun_elem;
+@@ -1177,6 +1416,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                       const struct sbrec_port_binding *binding,
+                       const struct sbrec_chassis *chassis,
+                       const struct physical_debug *debug,
++                      const struct if_status_mgr *if_mgr,
+                       struct ovn_desired_flow_table *flow_table,
+                       struct ofpbuf *ofpacts_p)
+ {
+@@ -1233,12 +1473,12 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+             || ha_chassis_group_is_active(binding->ha_chassis_group,
+                                           active_tunnels, chassis))) {
+ 
+-        /* Table 38, priority 100.
++        /* Table 40, priority 100.
+          * =======================
+          *
+          * Implements output to local hypervisor.  Each flow matches a
+          * logical output port on the local hypervisor, and resubmits to
+-         * table 39.  For ports of type "chassisredirect", the logical
++         * table 41.  For ports of type "chassisredirect", the logical
+          * output port is changed from the "chassisredirect" port to the
+          * underlying distributed port. */
+ 
+@@ -1275,7 +1515,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                                                     ct_zones);
+             put_zones_ofpacts(&zone_ids, ofpacts_p);
+ 
+-            /* Resubmit to table 39. */
++            /* Resubmit to table 41. */
+             put_resubmit(OFTABLE_CHECK_LOOPBACK, ofpacts_p);
+         }
+ 
+@@ -1491,7 +1731,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                                               ofport, flow_table);
+         }
+ 
+-        /* Table 39, priority 160.
++        /* Table 41, priority 160.
+          * =======================
+          *
+          * Do not forward local traffic from a localport to a localnet port.
+@@ -1561,13 +1801,13 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+             }
+         }
+ 
+-        /* Table 37, priority 150.
++        /* Table 39, priority 150.
+          * =======================
+          *
+          * Handles packets received from ports of type "localport".  These
+          * ports are present on every hypervisor.  Traffic that originates at
+          * one should never go over a tunnel to a remote hypervisor,
+-         * so resubmit them to table 38 for local delivery. */
++         * so resubmit them to table 40 for local delivery. */
+         if (!strcmp(binding->type, "localport")) {
+             ofpbuf_clear(ofpacts_p);
+             put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
+@@ -1581,7 +1821,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+         }
+     } else if (access_type == PORT_LOCALNET) {
+         /* Remote port connected by localnet port */
+-        /* Table 38, priority 100.
++        /* Table 40, priority 100.
+          * =======================
+          *
+          * Implements switching to localnet port. Each flow matches a
+@@ -1596,14 +1836,16 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ 
+         put_load(localnet_port->tunnel_key, MFF_LOG_OUTPORT, 0, 32, ofpacts_p);
+ 
+-        /* Resubmit to table 38. */
++        /* Resubmit to table 40. */
+         put_resubmit(OFTABLE_LOCAL_OUTPUT, ofpacts_p);
+         ofctrl_add_flow(flow_table, OFTABLE_LOCAL_OUTPUT, 100,
+                         binding->header_.uuid.parts[0],
+                         &match, ofpacts_p, &binding->header_.uuid);
+ 
+-        enforce_tunneling_for_multichassis_ports(
+-            ld, binding, chassis, chassis_tunnels, mff_ovn_geneve, flow_table);
++        enforce_tunneling_for_multichassis_ports(ld, binding, chassis,
++                                                 chassis_tunnels,
++                                                 mff_ovn_geneve, flow_table,
++                                                 if_mgr);
+ 
+         /* No more tunneling to set up. */
+         goto out;
+@@ -1613,7 +1855,7 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+     const char *redirect_type = smap_get(&binding->options,
+                                          "redirect-type");
+ 
+-    /* Table 38, priority 100.
++    /* Table 40, priority 100.
+      * =======================
+      *
+      * Handles traffic that needs to be sent to a remote hypervisor.  Each
+@@ -1841,7 +2083,7 @@ consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+         }
+     }
+ 
+-    /* Table 38, priority 100.
++    /* Table 40, priority 100.
+      * =======================
+      *
+      * Handle output to the local logical ports in the multicast group, if
+@@ -1857,7 +2099,7 @@ consider_mc_group(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                         &match, &ofpacts, &mc->header_.uuid);
+     }
+ 
+-    /* Table 37, priority 100.
++    /* Table 39, priority 100.
+      * =======================
+      *
+      * Handle output to the remote chassis in the multicast group, if
+@@ -1908,7 +2150,7 @@ physical_eval_port_binding(struct physical_ctx *p_ctx,
+                           p_ctx->patch_ofports,
+                           p_ctx->chassis_tunnels,
+                           pb, p_ctx->chassis, &p_ctx->debug,
+-                          flow_table, &ofpacts);
++                          p_ctx->if_mgr, flow_table, &ofpacts);
+     ofpbuf_uninit(&ofpacts);
+ }
+ 
+@@ -2032,10 +2274,10 @@ physical_run(struct physical_ctx *p_ctx,
+                               p_ctx->patch_ofports,
+                               p_ctx->chassis_tunnels, binding,
+                               p_ctx->chassis, &p_ctx->debug,
+-                              flow_table, &ofpacts);
++                              p_ctx->if_mgr, flow_table, &ofpacts);
+     }
+ 
+-    /* Handle output to multicast groups, in tables 37 and 38. */
++    /* Handle output to multicast groups, in tables 40 and 41. */
+     const struct sbrec_multicast_group *mc;
+     SBREC_MULTICAST_GROUP_TABLE_FOR_EACH (mc, p_ctx->mc_group_table) {
+         consider_mc_group(p_ctx->sbrec_port_binding_by_name,
+@@ -2056,7 +2298,7 @@ physical_run(struct physical_ctx *p_ctx,
+      * 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 38 to handle
++     * tunnel key data where possible, then resubmit to table 40 to handle
+      * packets to the local hypervisor. */
+     struct chassis_tunnel *tun;
+     HMAP_FOR_EACH (tun, hmap_node, p_ctx->chassis_tunnels) {
+@@ -2158,27 +2400,52 @@ physical_run(struct physical_ctx *p_ctx,
+      */
+     add_default_drop_flow(p_ctx, OFTABLE_PHY_TO_LOG, flow_table);
+ 
+-    /* Table 37, priority 150.
++    /* Table 37-38, priority 0.
++     * ========================
++     *
++     * Default resubmit actions for OFTABLE_OUTPUT_LARGE_PKT_* tables.
++     */
++    struct match match;
++    match_init_catchall(&match);
++    ofpbuf_clear(&ofpacts);
++    put_resubmit(OFTABLE_REMOTE_OUTPUT, &ofpacts);
++    ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_DETECT, 0, 0, &match,
++                    &ofpacts, hc_uuid);
++
++    match_init_catchall(&match);
++    match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
++                         MLF_ALLOW_LOOPBACK, MLF_ALLOW_LOOPBACK);
++    ofpbuf_clear(&ofpacts);
++    put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
++    ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_PROCESS, 10, 0,
++                    &match, &ofpacts, hc_uuid);
++
++    match_init_catchall(&match);
++    ofpbuf_clear(&ofpacts);
++    put_resubmit(OFTABLE_REMOTE_OUTPUT, &ofpacts);
++    ofctrl_add_flow(flow_table, OFTABLE_OUTPUT_LARGE_PKT_PROCESS, 0, 0, &match,
++                    &ofpacts, hc_uuid);
++
++    /* Table 39, priority 150.
+      * =======================
+      *
+      * Handles packets received from a VXLAN tunnel which get resubmitted to
+      * OFTABLE_LOG_INGRESS_PIPELINE due to lack of needed metadata in VXLAN,
+-     * explicitly skip sending back out any tunnels and resubmit to table 38
++     * explicitly skip sending back out any tunnels and resubmit to table 40
+      * for local delivery, except packets which have MLF_ALLOW_LOOPBACK bit
+      * set.
+      */
+-    struct match match;
+     match_init_catchall(&match);
+     match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, MLF_RCV_FROM_RAMP,
+                          MLF_RCV_FROM_RAMP | MLF_ALLOW_LOOPBACK);
+ 
+-    /* Resubmit to table 38. */
++    /* Resubmit to table 40. */
+     ofpbuf_clear(&ofpacts);
+     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
+     ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
+                     &match, &ofpacts, hc_uuid);
+ 
+-    /* Table 37, priority 150.
++    /* Table 39, priority 150.
+      * =======================
+      *
+      * Packets that should not be sent to other hypervisors.
+@@ -2186,13 +2453,13 @@ physical_run(struct physical_ctx *p_ctx,
+     match_init_catchall(&match);
+     match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
+                          MLF_LOCAL_ONLY, MLF_LOCAL_ONLY);
+-    /* Resubmit to table 38. */
++    /* Resubmit to table 40. */
+     ofpbuf_clear(&ofpacts);
+     put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
+     ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
+                     &match, &ofpacts, hc_uuid);
+ 
+-    /* Table 37, Priority 0.
++    /* Table 39, Priority 0.
+      * =======================
+      *
+      * Resubmit packets that are not directed at tunnels or part of a
+@@ -2203,18 +2470,18 @@ physical_run(struct physical_ctx *p_ctx,
+     ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 0, 0, &match,
+                     &ofpacts, hc_uuid);
+ 
+-    /* Table 38, priority 0.
++    /* Table 40, priority 0.
+      * ======================
+      *
+      * Drop packets that do not match previous flows.
+      */
+     add_default_drop_flow(p_ctx, OFTABLE_LOCAL_OUTPUT, flow_table);
+ 
+-    /* Table 39, Priority 0.
++    /* Table 41, Priority 0.
+      * =======================
+      *
+      * Resubmit packets that don't output to the ingress port (already checked
+-     * in table 38) to the logical egress pipeline, clearing the logical
++     * in table 40) to the logical egress pipeline, clearing the logical
+      * registers (for consistent behavior with packets that get tunneled). */
+     match_init_catchall(&match);
+     ofpbuf_clear(&ofpacts);
+diff --git a/controller/physical.h b/controller/physical.h
+index f450dca94..1f1ed55ef 100644
+--- a/controller/physical.h
++++ b/controller/physical.h
+@@ -52,11 +52,13 @@ struct physical_ctx {
+     struct ovsdb_idl_index *sbrec_port_binding_by_name;
+     struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
+     const struct sbrec_port_binding_table *port_binding_table;
++    const struct ovsrec_interface_table *ovs_interface_table;
+     const struct sbrec_multicast_group_table *mc_group_table;
+     const struct ovsrec_bridge *br_int;
+     const struct sbrec_chassis_table *chassis_table;
+     const struct sbrec_chassis *chassis;
+     const struct sset *active_tunnels;
++    const struct if_status_mgr *if_mgr;
+     struct hmap *local_datapaths;
+     struct sset *local_lports;
+     const struct simap *ct_zones;
 diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 795847729..761783562 100644
+index 795847729..cd9760f07 100644
 --- a/controller/pinctrl.c
 +++ b/controller/pinctrl.c
+@@ -627,7 +627,7 @@ set_actions_and_enqueue_msg(struct rconn *swconn,
+ }
+ 
+ /* Forwards a packet to 'out_port_key' even if that's on a remote
+- * hypervisor, i.e., the packet is re-injected in table OFTABLE_REMOTE_OUTPUT.
++ * hypervisor, i.e., the packet is re-injected in table OFTABLE_OUTPUT_INIT.
+  */
+ static void
+ pinctrl_forward_pkt(struct rconn *swconn, int64_t dp_key,
+@@ -644,7 +644,7 @@ pinctrl_forward_pkt(struct rconn *swconn, int64_t dp_key,
+ 
+     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+     resubmit->in_port = OFPP_CONTROLLER;
+-    resubmit->table_id = OFTABLE_REMOTE_OUTPUT;
++    resubmit->table_id = OFTABLE_OUTPUT_INIT;
+ 
+     struct ofputil_packet_out po = {
+         .packet = dp_packet_data(pkt),
+@@ -870,7 +870,7 @@ pinctrl_parse_dhcpv6_advt(struct rconn *swconn, const struct flow *ip_flow,
+              0, 32, &ofpacts);
+     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
+     resubmit->in_port = OFPP_CONTROLLER;
+-    resubmit->table_id = OFTABLE_REMOTE_OUTPUT;
++    resubmit->table_id = OFTABLE_OUTPUT_INIT;
+ 
+     struct ofputil_packet_out po = {
+         .packet = dp_packet_data(&packet),
+@@ -1499,7 +1499,7 @@ buffered_push_packet(struct buffered_packets *bp,
+ 
+     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts);
+     resubmit->in_port = OFPP_CONTROLLER;
+-    resubmit->table_id = OFTABLE_REMOTE_OUTPUT;
++    resubmit->table_id = OFTABLE_OUTPUT_INIT;
+ 
+     bi->p = packet;
+ 
 @@ -2444,19 +2444,19 @@ compose_out_dhcpv6_opts(struct ofpbuf *userdata,
                          struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid)
  {
@@ -1263,7 +2512,21 @@ index 795847729..761783562 100644
          case DHCPV6_OPT_SERVER_ID_CODE:
          {
              /* The Server Identifier option carries a DUID
-@@ -7190,7 +7190,9 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
+@@ -2988,6 +2988,13 @@ pinctrl_handle_dns_lookup(
+         goto exit;
+     }
+ 
++    /* Check if there is an additional record present, which is unsupported */
++    if (in_dns_header->arcount) {
++        VLOG_DBG_RL(&rl, "Received DNS query with additional records, which"
++                    " is unsupported");
++        goto exit;
++    }
++
+     struct udp_header *in_udp = dp_packet_l4(pkt_in);
+     size_t udp_len = ntohs(in_udp->udp_len);
+     size_t l4_len = dp_packet_l4_size(pkt_in);
+@@ -7190,7 +7197,9 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
          pinctrl_send_bfd_tx_msg(swconn, entry, false);
  
          tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx);
@@ -1288,10 +2551,42 @@ index 11a07dd38..02a9953ba 100644
  ovn (23.03.0-1) unstable; urgency=low
  
     * New upstream version
+diff --git a/include/ovn/actions.h b/include/ovn/actions.h
+index 28479ede1..c973fce9c 100644
+--- a/include/ovn/actions.h
++++ b/include/ovn/actions.h
+@@ -895,6 +895,9 @@ void ovnacts_free(struct ovnact[], size_t ovnacts_len);
+ char *ovnact_op_to_string(uint32_t);
+ int encode_ra_dnssl_opt(char *data, char *buf, int buf_len);
+ 
++size_t encode_start_controller_op(enum action_opcode opcode, bool pause,
++                                  uint32_t meter_id, struct ofpbuf *ofpacts);
++void encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts);
+ void encode_controller_op(enum action_opcode opcode, uint32_t meter_id,
+                           struct ofpbuf *ofpacts);
+ 
 diff --git a/lib/actions.c b/lib/actions.c
-index 781549d75..2b566c85e 100644
+index 781549d75..ec27223f9 100644
 --- a/lib/actions.c
 +++ b/lib/actions.c
+@@ -79,7 +79,7 @@ ovnact_init(struct ovnact *ovnact, enum ovnact_type type, size_t len)
+     ovnact->len = len;
+ }
+ 
+-static size_t
++size_t
+ encode_start_controller_op(enum action_opcode opcode, bool pause,
+                            uint32_t meter_id, struct ofpbuf *ofpacts)
+ {
+@@ -100,7 +100,7 @@ encode_start_controller_op(enum action_opcode opcode, bool pause,
+     return ofs;
+ }
+ 
+-static void
++void
+ encode_finish_controller_op(size_t ofs, struct ofpbuf *ofpacts)
+ {
+     struct ofpact_controller *oc = ofpbuf_at_assert(ofpacts, ofs, sizeof *oc);
 @@ -2882,26 +2882,26 @@ static void
  encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
                           struct ofpbuf *ofpacts)
@@ -1479,6 +2774,324 @@ index 2b20bc380..d718ed39a 100644
  /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
  #define OVN_DHCP_MSG_DECLINE        4
  #define OVN_DHCP_MSG_RELEASE        7
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index a1a418a24..7510fda4b 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -28,6 +28,13 @@
+ #define ROUTE_ORIGIN_CONNECTED "connected"
+ #define ROUTE_ORIGIN_STATIC "static"
+ 
++#define ETH_CRC_LENGTH 4
++#define ETHERNET_OVERHEAD (ETH_HEADER_LEN + ETH_CRC_LENGTH)
++
++#define GENEVE_TUNNEL_OVERHEAD 38
++#define STT_TUNNEL_OVERHEAD 18
++#define VXLAN_TUNNEL_OVERHEAD 30
++
+ struct eth_addr;
+ struct nbrec_logical_router_port;
+ struct ovsrec_flow_sample_collector_set_table;
+diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
+index 6e33901a8..20f0d8a82 100644
+--- a/northd/en-sync-sb.c
++++ b/northd/en-sync-sb.c
+@@ -22,7 +22,6 @@
+ #include "openvswitch/util.h"
+ 
+ #include "en-sync-sb.h"
+-#include "include/ovn/expr.h"
+ #include "lib/inc-proc-eng.h"
+ #include "lib/lb.h"
+ #include "lib/ovn-nb-idl.h"
+@@ -34,8 +33,15 @@
+ 
+ VLOG_DEFINE_THIS_MODULE(en_sync_to_sb);
+ 
++/* This is just a type wrapper to enforce that it has to be sorted. */
++struct sorted_addresses {
++    const char **arr;
++    size_t n;
++};
++
++
+ static void sync_addr_set(struct ovsdb_idl_txn *ovnsb_txn, const char *name,
+-                          const char **addrs, size_t n_addrs,
++                          struct sorted_addresses *addresses,
+                           struct shash *sb_address_sets);
+ static void sync_addr_sets(const struct nbrec_address_set_table *,
+                            const struct nbrec_port_group_table *,
+@@ -44,11 +50,17 @@ static void sync_addr_sets(const struct nbrec_address_set_table *,
+                            struct hmap *datapaths);
+ static const struct sbrec_address_set *sb_address_set_lookup_by_name(
+     struct ovsdb_idl_index *, const char *name);
+-static void update_sb_addr_set(const char **nb_addresses, size_t n_addresses,
++static void update_sb_addr_set(struct sorted_addresses *,
+                                const struct sbrec_address_set *);
+ static void build_port_group_address_set(const struct nbrec_port_group *,
+                                          struct svec *ipv4_addrs,
+                                          struct svec *ipv6_addrs);
++static struct sorted_addresses
++sorted_addresses_from_nbrec(const struct nbrec_address_set *nb_as);
++static struct sorted_addresses
++sorted_addresses_from_svec(struct svec *addresses);
++static struct sorted_addresses
++sorted_addresses_from_sset(struct sset *addresses);
+ 
+ void *
+ en_sync_to_sb_init(struct engine_node *node OVS_UNUSED,
+@@ -133,8 +145,9 @@ sync_to_sb_addr_set_nb_address_set_handler(struct engine_node *node,
+         if (!sb_addr_set) {
+             return false;
+         }
+-        update_sb_addr_set((const char **) nb_addr_set->addresses,
+-                           nb_addr_set->n_addresses, sb_addr_set);
++        struct sorted_addresses addrs =
++                sorted_addresses_from_nbrec(nb_addr_set);
++        update_sb_addr_set(&addrs, sb_addr_set);
+     }
+ 
+     return true;
+@@ -180,10 +193,14 @@ sync_to_sb_addr_set_nb_port_group_handler(struct engine_node *node,
+         struct svec ipv4_addrs = SVEC_EMPTY_INITIALIZER;
+         struct svec ipv6_addrs = SVEC_EMPTY_INITIALIZER;
+         build_port_group_address_set(nb_pg, &ipv4_addrs, &ipv6_addrs);
+-        update_sb_addr_set((const char **) ipv4_addrs.names, ipv4_addrs.n,
+-                           sb_addr_set_v4);
+-        update_sb_addr_set((const char **) ipv6_addrs.names, ipv6_addrs.n,
+-                           sb_addr_set_v6);
++
++        struct sorted_addresses ipv4_addrs_sorted =
++                sorted_addresses_from_svec(&ipv4_addrs);
++        struct sorted_addresses ipv6_addrs_sorted =
++                sorted_addresses_from_svec(&ipv6_addrs);
++
++        update_sb_addr_set(&ipv4_addrs_sorted, sb_addr_set_v4);
++        update_sb_addr_set(&ipv6_addrs_sorted, sb_addr_set_v6);
+ 
+         free(ipv4_addrs_name);
+         free(ipv6_addrs_name);
+@@ -197,7 +214,7 @@ sync_to_sb_addr_set_nb_port_group_handler(struct engine_node *node,
+ /* static functions. */
+ static void
+ sync_addr_set(struct ovsdb_idl_txn *ovnsb_txn, const char *name,
+-              const char **addrs, size_t n_addrs,
++              struct sorted_addresses *addresses,
+               struct shash *sb_address_sets)
+ {
+     const struct sbrec_address_set *sb_address_set;
+@@ -206,10 +223,10 @@ sync_addr_set(struct ovsdb_idl_txn *ovnsb_txn, const char *name,
+     if (!sb_address_set) {
+         sb_address_set = sbrec_address_set_insert(ovnsb_txn);
+         sbrec_address_set_set_name(sb_address_set, name);
+-        sbrec_address_set_set_addresses(sb_address_set,
+-                                        addrs, n_addrs);
++        sbrec_address_set_set_addresses(sb_address_set, addresses->arr,
++                                        addresses->n);
+     } else {
+-        update_sb_addr_set(addrs, n_addrs, sb_address_set);
++        update_sb_addr_set(addresses, sb_address_set);
+     }
+ }
+ 
+@@ -243,8 +260,11 @@ sync_addr_sets(const struct nbrec_address_set_table *nb_address_set_table,
+ 
+     /* Service monitor MAC. */
+     const char *svc_monitor_macp = northd_get_svc_monitor_mac();
+-    sync_addr_set(ovnsb_txn, "svc_monitor_mac", &svc_monitor_macp, 1,
+-                     &sb_address_sets);
++    struct sorted_addresses svc = {
++            .arr = &svc_monitor_macp,
++            .n = 1,
++    };
++    sync_addr_set(ovnsb_txn, "svc_monitor_mac", &svc, &sb_address_sets);
+ 
+     /* sync port group generated address sets first */
+     const struct nbrec_port_group *nb_port_group;
+@@ -255,14 +275,16 @@ sync_addr_sets(const struct nbrec_address_set_table *nb_address_set_table,
+         build_port_group_address_set(nb_port_group, &ipv4_addrs, &ipv6_addrs);
+         char *ipv4_addrs_name = xasprintf("%s_ip4", nb_port_group->name);
+         char *ipv6_addrs_name = xasprintf("%s_ip6", nb_port_group->name);
++
++        struct sorted_addresses ipv4_addrs_sorted =
++                sorted_addresses_from_svec(&ipv4_addrs);
++        struct sorted_addresses ipv6_addrs_sorted =
++                sorted_addresses_from_svec(&ipv6_addrs);
++
+         sync_addr_set(ovnsb_txn, ipv4_addrs_name,
+-                      /* "char **" is not compatible with "const char **" */
+-                      (const char **) ipv4_addrs.names,
+-                      ipv4_addrs.n, &sb_address_sets);
++                      &ipv4_addrs_sorted, &sb_address_sets);
+         sync_addr_set(ovnsb_txn, ipv6_addrs_name,
+-                      /* "char **" is not compatible with "const char **" */
+-                      (const char **) ipv6_addrs.names,
+-                      ipv6_addrs.n, &sb_address_sets);
++                      &ipv6_addrs_sorted, &sb_address_sets);
+         free(ipv4_addrs_name);
+         free(ipv6_addrs_name);
+         svec_destroy(&ipv4_addrs);
+@@ -279,27 +301,26 @@ sync_addr_sets(const struct nbrec_address_set_table *nb_address_set_table,
+         if (sset_count(&od->lb_ips->ips_v4_reachable)) {
+             char *ipv4_addrs_name = lr_lb_address_set_name(od->tunnel_key,
+                                                            AF_INET);
+-            const char **ipv4_addrs =
+-                sset_array(&od->lb_ips->ips_v4_reachable);
+ 
+-            sync_addr_set(ovnsb_txn, ipv4_addrs_name, ipv4_addrs,
+-                          sset_count(&od->lb_ips->ips_v4_reachable),
+-                          &sb_address_sets);
++            struct sorted_addresses ipv4_addrs_sorted =
++                    sorted_addresses_from_sset(&od->lb_ips->ips_v4_reachable);
++
++            sync_addr_set(ovnsb_txn, ipv4_addrs_name,
++                          &ipv4_addrs_sorted, &sb_address_sets);
++            free(ipv4_addrs_sorted.arr);
+             free(ipv4_addrs_name);
+-            free(ipv4_addrs);
+         }
+ 
+         if (sset_count(&od->lb_ips->ips_v6_reachable)) {
+             char *ipv6_addrs_name = lr_lb_address_set_name(od->tunnel_key,
+                                                            AF_INET6);
+-            const char **ipv6_addrs =
+-                sset_array(&od->lb_ips->ips_v6_reachable);
++            struct sorted_addresses ipv6_addrs_sorted =
++                    sorted_addresses_from_sset(&od->lb_ips->ips_v6_reachable);
+ 
+-            sync_addr_set(ovnsb_txn, ipv6_addrs_name, ipv6_addrs,
+-                          sset_count(&od->lb_ips->ips_v6_reachable),
+-                          &sb_address_sets);
++            sync_addr_set(ovnsb_txn, ipv6_addrs_name,
++                          &ipv6_addrs_sorted, &sb_address_sets);
++            free(ipv6_addrs_sorted.arr);
+             free(ipv6_addrs_name);
+-            free(ipv6_addrs);
+         }
+     }
+ 
+@@ -308,10 +329,10 @@ sync_addr_sets(const struct nbrec_address_set_table *nb_address_set_table,
+     const struct nbrec_address_set *nb_address_set;
+     NBREC_ADDRESS_SET_TABLE_FOR_EACH (nb_address_set,
+                                       nb_address_set_table) {
++        struct sorted_addresses addrs =
++                sorted_addresses_from_nbrec(nb_address_set);
+         sync_addr_set(ovnsb_txn, nb_address_set->name,
+-            /* "char **" is not compatible with "const char **" */
+-            (const char **) nb_address_set->addresses,
+-            nb_address_set->n_addresses, &sb_address_sets);
++                      &addrs, &sb_address_sets);
+     }
+ 
+     struct shash_node *node;
+@@ -323,48 +344,39 @@ sync_addr_sets(const struct nbrec_address_set_table *nb_address_set_table,
+ }
+ 
+ static void
+-update_sb_addr_set(const char **nb_addresses, size_t n_addresses,
++update_sb_addr_set(struct sorted_addresses *nb_addresses,
+                    const struct sbrec_address_set *sb_as)
+ {
+-    struct expr_constant_set *cs_nb_as =
+-        expr_constant_set_create_integers(
+-            (const char *const *) nb_addresses, n_addresses);
+-    struct expr_constant_set *cs_sb_as =
+-        expr_constant_set_create_integers(
+-            (const char *const *) sb_as->addresses, sb_as->n_addresses);
+-
+-    struct expr_constant_set *addr_added = NULL;
+-    struct expr_constant_set *addr_deleted = NULL;
+-    expr_constant_set_integers_diff(cs_sb_as, cs_nb_as, &addr_added,
+-                                    &addr_deleted);
+-
+-    struct ds ds = DS_EMPTY_INITIALIZER;
+-    if (addr_added && addr_added->n_values) {
+-        for (size_t i = 0; i < addr_added->n_values; i++) {
+-            ds_clear(&ds);
+-            expr_constant_format(&addr_added->values[i], EXPR_C_INTEGER, &ds);
+-            sbrec_address_set_update_addresses_addvalue(sb_as, ds_cstr(&ds));
++    size_t nb_index, sb_index;
++
++    const char **nb_arr = nb_addresses->arr;
++    char **sb_arr = sb_as->addresses;
++    size_t nb_n = nb_addresses->n;
++    size_t sb_n = sb_as->n_addresses;
++
++    for (nb_index = sb_index = 0; nb_index < nb_n && sb_index < sb_n;) {
++        int cmp = strcmp(nb_arr[nb_index], sb_arr[sb_index]);
++        if (cmp < 0) {
++            sbrec_address_set_update_addresses_addvalue(sb_as,
++                                                        nb_arr[nb_index]);
++            nb_index++;
++        } else if (cmp > 0) {
++            sbrec_address_set_update_addresses_delvalue(sb_as,
++                                                        sb_arr[sb_index]);
++            sb_index++;
++        } else {
++            nb_index++;
++            sb_index++;
+         }
+     }
+ 
+-    if (addr_deleted && addr_deleted->n_values) {
+-        for (size_t i = 0; i < addr_deleted->n_values; i++) {
+-            ds_clear(&ds);
+-            expr_constant_format(&addr_deleted->values[i],
+-                                 EXPR_C_INTEGER, &ds);
+-            sbrec_address_set_update_addresses_delvalue(sb_as, ds_cstr(&ds));
+-        }
++    for (; nb_index < nb_n; nb_index++) {
++        sbrec_address_set_update_addresses_addvalue(sb_as, nb_arr[nb_index]);
+     }
+ 
+-    ds_destroy(&ds);
+-    expr_constant_set_destroy(cs_nb_as);
+-    free(cs_nb_as);
+-    expr_constant_set_destroy(cs_sb_as);
+-    free(cs_sb_as);
+-    expr_constant_set_destroy(addr_added);
+-    free(addr_added);
+-    expr_constant_set_destroy(addr_deleted);
+-    free(addr_deleted);
++    for (; sb_index < sb_n; sb_index++) {
++        sbrec_address_set_update_addresses_delvalue(sb_as, sb_arr[sb_index]);
++    }
+ }
+ 
+ static void
+@@ -403,3 +415,32 @@ sb_address_set_lookup_by_name(struct ovsdb_idl_index *sbrec_addr_set_by_name,
+ 
+     return retval;
+ }
++
++static struct sorted_addresses
++sorted_addresses_from_nbrec(const struct nbrec_address_set *nb_as)
++{
++    /* The DB is already sorted. */
++    return (struct sorted_addresses) {
++        .arr = (const char **) nb_as->addresses,
++        .n = nb_as->n_addresses,
++    };
++}
++
++static struct sorted_addresses
++sorted_addresses_from_svec(struct svec *addresses)
++{
++    svec_sort(addresses);
++    return (struct sorted_addresses) {
++        .arr = (const char **) addresses->names,
++        .n = addresses->n,
++    };
++}
++
++static struct sorted_addresses
++sorted_addresses_from_sset(struct sset *addresses)
++{
++    return (struct sorted_addresses) {
++        .arr = sset_sort(addresses),
++        .n = sset_count(addresses),
++    };
++}
 diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
 index d23993a55..fd025c92b 100644
 --- a/northd/inc-proc-northd.c
@@ -1532,10 +3145,19 @@ index d23993a55..fd025c92b 100644
 +    ds_destroy(&ds);
 +}
 diff --git a/northd/northd.c b/northd/northd.c
-index 7ad4cdfad..045282fac 100644
+index 7ad4cdfad..66f14e9dd 100644
 --- a/northd/northd.c
 +++ b/northd/northd.c
-@@ -432,6 +432,13 @@ build_chassis_features(const struct northd_input *input_data,
+@@ -239,6 +239,8 @@ enum ovn_stage {
+  * one of the logical router's own IP addresses. */
+ #define REGBIT_EGRESS_LOOPBACK  "reg9[0]"
+ /* Register to store the result of check_pkt_larger action. */
++/* This register is also used by ovn-controller in
++ * OFTABLE_OUTPUT_LARGE_PKT_DETECT table, for a similar goal. */
+ #define REGBIT_PKT_LARGER        "reg9[1]"
+ #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
+ #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
+@@ -432,6 +434,13 @@ build_chassis_features(const struct northd_input *input_data,
      const struct sbrec_chassis *chassis;
  
      SBREC_CHASSIS_TABLE_FOR_EACH (chassis, input_data->sbrec_chassis) {
@@ -1549,7 +3171,7 @@ index 7ad4cdfad..045282fac 100644
          bool ct_no_masked_label =
              smap_get_bool(&chassis->other_config,
                            OVN_FEATURE_CT_NO_MASKED_LABEL,
-@@ -552,7 +559,7 @@ free_chassis_queueid(struct hmap *set, const struct uuid *uuid,
+@@ -552,7 +561,7 @@ free_chassis_queueid(struct hmap *set, const struct uuid *uuid,
  static inline bool
  port_has_qos_params(const struct smap *opts)
  {
@@ -1558,7 +3180,7 @@ index 7ad4cdfad..045282fac 100644
              smap_get(opts, "qos_burst"));
  }
  
-@@ -1641,6 +1648,10 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
+@@ -1641,6 +1650,10 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
           * use it. */
          hmap_remove(ports, &port->key_node);
  
@@ -1569,7 +3191,7 @@ index 7ad4cdfad..045282fac 100644
          for (int i = 0; i < port->n_lsp_addrs; i++) {
              destroy_lport_addresses(&port->lsp_addrs[i]);
          }
-@@ -3881,7 +3892,7 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
+@@ -3881,7 +3894,7 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
      const char *ct_lb_action =
          features->ct_no_masked_label ? "ct_lb_mark" : "ct_lb";
      bool reject = !lb_vip->n_backends && lb_vip->empty_backend_rej;
@@ -1578,7 +3200,7 @@ index 7ad4cdfad..045282fac 100644
  
      if (lb_vip_nb->lb_health_check) {
          ds_put_format(action, "%s(backends=", ct_lb_action);
-@@ -5779,20 +5790,24 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
+@@ -5779,20 +5792,24 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
       * know about the connection, as the icmp request went through the logical
       * router on hostA, not hostB. This would only work with distributed
       * conntrack state across all chassis. */
@@ -1613,7 +3235,7 @@ index 7ad4cdfad..045282fac 100644
  }
  
  static void
-@@ -5867,7 +5882,8 @@ build_pre_acls(struct ovn_datapath *od, const struct hmap *port_groups,
+@@ -5867,7 +5884,8 @@ build_pre_acls(struct ovn_datapath *od, const struct hmap *port_groups,
          }
          for (size_t i = 0; i < od->n_localnet_ports; i++) {
              skip_port_from_conntrack(od, od->localnet_ports[i],
@@ -1623,7 +3245,7 @@ index 7ad4cdfad..045282fac 100644
                                       110, lflows);
          }
  
-@@ -6036,10 +6052,17 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
+@@ -6036,10 +6054,17 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
                                   110, lflows);
      }
@@ -1645,7 +3267,7 @@ index 7ad4cdfad..045282fac 100644
      }
  
      /* Do not sent statless flows via conntrack */
-@@ -6700,6 +6723,8 @@ build_port_group_lswitches(struct northd_input *input_data,
+@@ -6700,6 +6725,8 @@ build_port_group_lswitches(struct northd_input *input_data,
      }
  }
  
@@ -1654,7 +3276,7 @@ index 7ad4cdfad..045282fac 100644
  static void
  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
             struct hmap *lflows, const struct hmap *port_groups,
-@@ -6847,20 +6872,26 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
+@@ -6847,20 +6874,26 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
          ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
                        ds_cstr(&match), ct_out_acl_action);
  
@@ -1689,7 +3311,7 @@ index 7ad4cdfad..045282fac 100644
      /* Ingress or Egress ACL Table (Various priorities). */
      for (size_t i = 0; i < od->nbs->n_acls; i++) {
          struct nbrec_acl *acl = od->nbs->acls[i];
-@@ -7089,7 +7120,9 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb,
+@@ -7089,7 +7122,9 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb,
   * - load balancing affinity check:
   *   table=lr_in_lb_aff_check, priority=100
   *      match=(new_lb_match)
@@ -1700,7 +3322,7 @@ index 7ad4cdfad..045282fac 100644
   *
   * - load balancing:
   *   table=lr_in_dnat, priority=150
-@@ -7130,16 +7163,11 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+@@ -7130,16 +7165,11 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
          return;
      }
  
@@ -1718,7 +3340,7 @@ index 7ad4cdfad..045282fac 100644
  
      bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
      const char *ip_match = ipv6 ? "ip6" : "ip4";
-@@ -7155,6 +7183,20 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+@@ -7155,6 +7185,20 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
          ct_flag = "; force_snat";
      }
  
@@ -1739,7 +3361,7 @@ index 7ad4cdfad..045282fac 100644
      /* Prepare common part of affinity LB and affinity learn action. */
      ds_put_format(&aff_action, "%s = %s; ", reg_vip, lb_vip->vip_str);
      ds_put_cstr(&aff_action_learn, "commit_lb_aff(vip = \"");
-@@ -7252,6 +7294,7 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+@@ -7252,6 +7296,7 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
      ds_destroy(&aff_action_learn);
      ds_destroy(&aff_match);
      ds_destroy(&aff_match_learn);
@@ -1747,6 +3369,73 @@ index 7ad4cdfad..045282fac 100644
  }
  
  /* Builds the logical switch flows related to load balancer affinity.
+@@ -7628,38 +7673,36 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
+ static void
+ build_vtep_hairpin(struct ovn_datapath *od, struct hmap *lflows)
+ {
+-    /* Ingress Pre-ARP flows for VTEP hairpining traffic. Priority 1000:
+-     * Packets that received from non-VTEP ports should continue processing. */
++    if (!od->has_vtep_lports) {
++        /* There is no need in these flows if datapath has no vtep lports. */
++        return;
++    }
+ 
++    /* Ingress Pre-ARP flows for VTEP hairpining traffic. Priority 1000:
++     * Packets received from VTEP ports must go directly to L2LKP table.
++     */
+     char *action = xasprintf("next(pipeline=ingress, table=%d);",
+                              ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));
+-    /* send all traffic from VTEP directly to L2LKP table. */
+     ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1000,
+                   REGBIT_FROM_RAMP" == 1", action);
+     free(action);
+ 
+-    struct ds match = DS_EMPTY_INITIALIZER;
+-    size_t n_ports = od->n_router_ports;
+-    bool dp_has_l3dgw_ports = false;
+-    for (int i = 0; i < n_ports; i++) {
+-        if (is_l3dgw_port(od->router_ports[i]->peer)) {
+-            ds_put_format(&match, "%sis_chassis_resident(%s)%s",
+-                          i == 0 ? REGBIT_FROM_RAMP" == 1 && (" : "",
+-                          od->router_ports[i]->peer->cr_port->json_key,
+-                          i < n_ports - 1 ? " || " : ")");
+-            dp_has_l3dgw_ports = true;
+-        }
+-    }
+-
+     /* Ingress pre-arp flow for traffic from VTEP (ramp) switch.
+     * Priority 2000: Packets, that were received from VTEP (ramp) switch and
+     * router ports of current datapath are l3dgw ports and they reside on
+     * current chassis, should be passed to next table for ARP/ND hairpin
+-    * processing.
+-    */
+-    if (dp_has_l3dgw_ports) {
+-        ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 2000, ds_cstr(&match),
+-                      "next;");
++    * processing. */
++    struct ds match = DS_EMPTY_INITIALIZER;
++    for (int i = 0; i < od->n_router_ports; i++) {
++        struct ovn_port *op = od->router_ports[i]->peer;
++        if (is_l3dgw_port(op)) {
++            ds_clear(&match);
++            ds_put_format(&match,
++                          REGBIT_FROM_RAMP" == 1 && is_chassis_resident(%s)",
++                          op->cr_port->json_key);
++            ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 2000,
++                          ds_cstr(&match), "next;");
++        }
+     }
+     ds_destroy(&match);
+ }
+@@ -8877,7 +8920,7 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
+     if (od->nbs) {
+ 
+         ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110,
+-                      "eth.dst == $svc_monitor_mac",
++                      "eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)",
+                       "handle_svc_check(inport);");
+ 
+         struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
 @@ -10450,10 +10493,8 @@ enum lrouter_nat_lb_flow_type {
  
  struct lrouter_nat_lb_flows_ctx {
@@ -1966,7 +3655,33 @@ index 7ad4cdfad..045282fac 100644
  }
  
  /* Handles the match criteria and actions in logical flow
-@@ -12814,8 +12808,7 @@ build_gateway_redirect_flows_for_lrouter(
+@@ -11698,6 +11692,25 @@ build_neigh_learning_flows_for_lrouter(
+         ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
+                       ds_cstr(actions));
+ 
++        if (!learn_from_arp_request) {
++            /* Add flow to skip GARP LLA if we don't know it already.
++             * From RFC 2461, section 4.4, Neighbor Advertisement Message
++             * Format, the Destination Address should be:
++             *   For solicited advertisements, the Source Address of
++             *   an invoking Neighbor Solicitation or, if the
++             *   solicitation's Source Address is the unspecified
++             *   address, the all-nodes multicast address. */
++            ds_clear(actions);
++            ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
++                                   " = lookup_nd(inport, ip6.src, nd.tll); "
++                                   REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
++                                   " = lookup_nd_ip(inport, ip6.src); next;");
++            ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
++                          "nd_na && ip6.src == fe80::/10 "
++                          "&& ip6.dst == ff00::/8",
++                          ds_cstr(actions));
++        }
++
+         ds_clear(actions);
+         ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
+                       " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
+@@ -12814,8 +12827,7 @@ build_gateway_redirect_flows_for_lrouter(
          for (int j = 0; j < od->n_nat_entries; j++) {
              const struct ovn_nat *nat = &od->nat_entries[j];
  
@@ -1976,7 +3691,7 @@ index 7ad4cdfad..045282fac 100644
                  (!nat->nb->allowed_ext_ips && !nat->nb->exempted_ext_ips)) {
                  continue;
              }
-@@ -13038,9 +13031,27 @@ build_misc_local_traffic_drop_flows_for_lrouter(
+@@ -13038,9 +13050,27 @@ build_misc_local_traffic_drop_flows_for_lrouter(
          ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
                        "eth.bcast", debug_drop_action());
  
@@ -2005,7 +3720,7 @@ index 7ad4cdfad..045282fac 100644
  
          /* Pass other traffic not already handled to the next table for
           * routing. */
-@@ -13224,7 +13235,7 @@ build_ipv6_input_flows_for_lrouter_port(
+@@ -13224,7 +13254,7 @@ build_ipv6_input_flows_for_lrouter_port(
                            "outport = %s; flags.loopback = 1; output; };",
                            ds_cstr(&ip_ds), op->json_key);
              ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
@@ -2014,7 +3729,7 @@ index 7ad4cdfad..045282fac 100644
                      copp_meter_get(COPP_ICMP6_ERR, op->od->nbr->copp,
                                     meter_groups),
                      &op->nbrp->header_);
-@@ -13352,7 +13363,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
+@@ -13352,7 +13382,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
                            "outport = %s; flags.loopback = 1; output; };",
                            ds_cstr(&ip_ds), op->json_key);
              ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
@@ -2023,7 +3738,7 @@ index 7ad4cdfad..045282fac 100644
                      copp_meter_get(COPP_ICMP4_ERR, op->od->nbr->copp,
                                     meter_groups),
                      &op->nbrp->header_);
-@@ -13597,13 +13608,13 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13597,13 +13627,13 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
          return;
      }
  
@@ -2039,7 +3754,7 @@ index 7ad4cdfad..045282fac 100644
              ds_put_format(actions, "next;");
          } else {
              ds_put_cstr(actions, "ct_snat;");
-@@ -13628,7 +13639,7 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13628,7 +13658,7 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
                            l3dgw_port->cr_port->json_key);
          }
  
@@ -2048,7 +3763,7 @@ index 7ad4cdfad..045282fac 100644
              ds_put_format(actions, "next;");
          } else {
              ds_put_cstr(actions, "ct_snat_in_czone;");
-@@ -13670,7 +13681,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13670,7 +13700,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
      * IP address that needs to be DNATted from a external IP address
      * to a logical IP address. */
      if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) {
@@ -2057,7 +3772,7 @@ index 7ad4cdfad..045282fac 100644
  
          if (od->is_gw_router) {
              /* Packet when it goes from the initiator to destination.
-@@ -13692,7 +13703,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13692,7 +13722,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
                  ds_put_format(actions, "flags.force_snat_for_dnat = 1; ");
              }
  
@@ -2066,7 +3781,7 @@ index 7ad4cdfad..045282fac 100644
                  ds_put_format(actions, "flags.loopback = 1; "
                                "ip%s.dst=%s; next;",
                                is_v6 ? "6" : "4", nat->logical_ip);
-@@ -13782,8 +13793,7 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13782,8 +13812,7 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
                        ETH_ADDR_ARGS(mac));
      }
  
@@ -2076,7 +3791,7 @@ index 7ad4cdfad..045282fac 100644
          ds_put_format(actions, "next;");
      } else {
          ds_put_format(actions,
-@@ -13839,7 +13849,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13839,7 +13868,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
          return;
      }
  
@@ -2085,7 +3800,7 @@ index 7ad4cdfad..045282fac 100644
      if (od->is_gw_router) {
          ds_clear(match);
          ds_put_format(match, "ip && ip%s.src == %s",
-@@ -13905,7 +13915,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+@@ -13905,7 +13934,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
                            ETH_ADDR_ARGS(mac));
          }
  
@@ -2094,7 +3809,7 @@ index 7ad4cdfad..045282fac 100644
              ds_put_format(actions, "ip%s.src=%s; next;",
                            is_v6 ? "6" : "4", nat->external_ip);
          } else {
-@@ -14217,10 +14227,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+@@ -14217,10 +14246,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
      ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
      ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
  
@@ -2109,7 +3824,7 @@ index 7ad4cdfad..045282fac 100644
       *
       * Allow traffic that is related to an existing conntrack entry.
       * At the same time apply NAT for this traffic.
-@@ -14231,16 +14241,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+@@ -14231,16 +14260,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
       * that's generated from a non-listening UDP port.  */
      if (od->has_lb_vip && features->ct_lb_related) {
          ds_clear(match);
@@ -2126,7 +3841,7 @@ index 7ad4cdfad..045282fac 100644
          ds_put_format(match, " && %s.skip_snat == 1", ct_flag_reg);
          ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 70, ds_cstr(match),
                        "flags.skip_snat_for_lb = 1; ct_commit_nat;");
-@@ -14251,10 +14255,34 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+@@ -14251,10 +14274,34 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
                        "flags.force_snat_for_lb = 1; ct_commit_nat;");
  
          ds_truncate(match, match_len);
@@ -2163,7 +3878,7 @@ index 7ad4cdfad..045282fac 100644
      }
  
      /* If the router has load balancer or DNAT rules, re-circulate every packet
-@@ -14267,6 +14295,9 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+@@ -14267,6 +14314,9 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
       * flag set. Some NICs are unable to offload these flows.
       */
      if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
@@ -2174,7 +3889,7 @@ index 7ad4cdfad..045282fac 100644
                        "ip", "flags.loopback = 1; ct_dnat;");
          ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 50,
 diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 2eab2c4ae..e16d7d080 100644
+index 2eab2c4ae..5b3559d45 100644
 --- a/northd/ovn-northd.8.xml
 +++ b/northd/ovn-northd.8.xml
 @@ -748,6 +748,12 @@
@@ -2203,7 +3918,46 @@ index 2eab2c4ae..e16d7d080 100644
      </ul>
  
      <p>
-@@ -2056,6 +2056,16 @@ output;
+@@ -1090,24 +1090,28 @@
+     <ul>
+       <li>
+         <p>
+-          For each distributed gateway router port <var>RP</var> attached to
+-          the logical switch, a priority-2000 flow is added with the match
+-          <code>reg0[14] == 1 &amp;&amp; is_chassis_resident(<var>RP</var>)
+-          </code> and action <code>next;</code> to pass the traffic to the
+-          next table to respond to the ARP requests for the router port IPs.
++          If logical switch has attached logical switch port of <var>vtep</var>
++          type, then for each distributed gateway router port <var>RP</var>
++          attached to this logical switch and has chassis redirect port
++          <var>cr-RP</var>, a priority-2000 flow is added with the match
++          <pre>
++<code>reg0[14] == 1 &amp;&amp; is_chassis_resident(<var>cr-RP</var>)</code>
++          </pre>
++          and action <code>next;</code>.
+         </p>
+ 
+         <p>
+           <code>reg0[14]</code> register bit is set in the ingress L2 port
+-           security check table for traffic received from HW VTEP (ramp)
+-           ports.
++          security check table for traffic received from HW VTEP (ramp) ports.
+         </p>
+       </li>
+ 
+       <li>
+-        A priority-1000 flow that matches on <code>reg0[14]</code> register
+-        bit for the traffic received from HW VTEP (ramp) ports.  This traffic
+-        is passed to ingress table ls_in_l2_lkup.
++        If logical switch has attached logical switch port of <var>vtep</var>
++        type, then a priority-1000 flow that matches on
++        <code>reg0[14]</code> register bit for the traffic received from HW
++        VTEP (ramp) ports.  This traffic is passed to ingress table
++        ls_in_l2_lkup.
+       </li>
+       <li>
+         A priority-1 flow that hairpins traffic matched by non-default
+@@ -2056,6 +2060,16 @@ output;
        db="OVN_Northbound"/> table.
      </p>
  
@@ -2220,7 +3974,7 @@ index 2eab2c4ae..e16d7d080 100644
      <h3>Egress Table 2: Pre-stateful</h3>
  
      <p>
-@@ -2098,6 +2108,12 @@ output;
+@@ -2098,6 +2112,12 @@ output;
        <code>to-lport</code> ACLs.
      </p>
  
@@ -2233,7 +3987,7 @@ index 2eab2c4ae..e16d7d080 100644
      <p>
        In addition, the following flows are added.
      </p>
-@@ -3066,10 +3082,18 @@ nd.tll = <var>external_mac</var>;
+@@ -3066,10 +3086,18 @@ nd.tll = <var>external_mac</var>;
          broadcast address.  By definition this traffic should not be forwarded.
        </li>
  
@@ -2253,7 +4007,7 @@ index 2eab2c4ae..e16d7d080 100644
            == <var>P</var> &amp;&amp; ip.ttl == {0, 1} &amp;&amp;
            !ip.later_frag</code> matches packets whose TTL has expired, with the
            following actions to send an ICMP time exceeded reply for IPv4 and
-@@ -3282,35 +3306,16 @@ icmp6 {
+@@ -3282,35 +3310,16 @@ icmp6 {
      </p>
  
      <p>
@@ -2294,7 +4048,7 @@ index 2eab2c4ae..e16d7d080 100644
      </p>
  
      <p>
-@@ -3349,10 +3354,11 @@ icmp6 {
+@@ -3349,10 +3358,11 @@ icmp6 {
          column, that includes a L4 port <var>PORT</var> of protocol
          <var>P</var> and IPv4 or IPv6 address <var>VIP</var>, a priority-100
          flow that matches on <code>ct.new &amp;&amp; ip &amp;&amp;
@@ -2309,7 +4063,7 @@ index 2eab2c4ae..e16d7d080 100644
        </li>
  
        <li>
-@@ -3385,9 +3391,8 @@ icmp6 {
+@@ -3385,9 +3395,8 @@ icmp6 {
          column, that includes a L4 port <var>PORT</var> of protocol
          <var>P</var> and IPv4 or IPv6 address <var>VIP</var>, a priority-150
          flow that matches on <code>reg9[6] == 1 &amp;&amp; ct.new &amp;&amp;
@@ -2321,7 +4075,7 @@ index 2eab2c4ae..e16d7d080 100644
          <code>ct_lb_mark(<var>args</var>) </code>, where <var>args</var>
          contains comma separated IP addresses (and optional port numbers)
          to load balance to.  The address family of the IP addresses of
-@@ -3410,56 +3415,25 @@ icmp6 {
+@@ -3410,56 +3419,25 @@ icmp6 {
            Router with gateway port in <code>OVN_Northbound</code> database that
            includes a L4 port <var>PORT</var> of protocol <var>P</var> and IPv4
            or IPv6 address <var>VIP</var>, a priority-120 flow that matches on
@@ -2385,7 +4139,7 @@ index 2eab2c4ae..e16d7d080 100644
        </li>
  
        <li>
-@@ -3467,42 +3441,17 @@ icmp6 {
+@@ -3467,42 +3445,17 @@ icmp6 {
            For all the configured load balancing rules for a router in
            <code>OVN_Northbound</code> database that includes just an IP address
            <var>VIP</var> to match on, a priority-110 flow that matches on
@@ -2433,7 +4187,7 @@ index 2eab2c4ae..e16d7d080 100644
          </p>
  
          <p>
-@@ -3529,7 +3478,20 @@ icmp6 {
+@@ -3529,7 +3482,20 @@ icmp6 {
              with an action of <code>ct_commit_nat;</code>, if the router
              has load balancer assigned to it. Along with two priority 70 flows
              that match <code>skip_snat</code> and <code>force_snat</code>
@@ -2455,7 +4209,7 @@ index 2eab2c4ae..e16d7d080 100644
          </p>
        </li>
      </ul>
-@@ -4721,6 +4683,11 @@ nd_ns {
+@@ -4721,6 +4687,11 @@ nd_ns {
      <h3>Egress Table 1: UNDNAT on Gateway Routers</h3>
  
      <ul>
@@ -2467,7 +4221,7 @@ index 2eab2c4ae..e16d7d080 100644
        <li>
          For all IP packets, a priority-50 flow with an action
          <code>flags.loopback = 1; ct_dnat;</code>.
-@@ -4998,7 +4965,19 @@ nd_ns {
+@@ -4998,7 +4969,19 @@ nd_ns {
        </li>
      </ul>
  
@@ -2488,7 +4242,7 @@ index 2eab2c4ae..e16d7d080 100644
  
      <p>
        For distributed logical routers where one of the logical router
-@@ -5070,7 +5049,7 @@ clone {
+@@ -5070,7 +5053,7 @@ clone {
        </li>
      </ul>
  
@@ -2497,10 +4251,42 @@ index 2eab2c4ae..e16d7d080 100644
  
      <p>
        Packets that reach this table are ready for delivery.  It contains:
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 5f895b053..7d24648ff 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -33,6 +33,7 @@
+ #include "lib/ovn-l7.h"
+ #include "lib/ovn-nb-idl.h"
+ #include "lib/ovn-sb-idl.h"
++#include "lib/ovs-rcu.h"
+ #include "openvswitch/poll-loop.h"
+ #include "simap.h"
+ #include "stopwatch.h"
+@@ -1048,6 +1049,8 @@ main(int argc, char *argv[])
+     ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
+     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
+     service_stop();
++    run_update_worker_pool(0);
++    ovsrcu_exit();
+ 
+     exit(res);
+ }
 diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
-index cb1064f71..86c6258e0 100644
+index cb1064f71..a2a87ec28 100644
 --- a/ovn-architecture.7.xml
 +++ b/ovn-architecture.7.xml
+@@ -1233,8 +1233,8 @@
+         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 37,
+-        these packets are resubmitted to table 38 for local delivery by
++        to determine the output port(s); when the packet reaches table 39,
++        these packets are resubmitted to table 40 for local delivery by
+         checking a MLF_RCV_FROM_RAMP flag, which is set when the packet
+         arrives from a ramp tunnel.
+       </p>
 @@ -1318,7 +1318,7 @@
          output port is known. These pieces of information are obtained
          from the tunnel encapsulation metadata (see <code>Tunnel
@@ -2510,6 +4296,210 @@ index cb1064f71..86c6258e0 100644
        </p>
      </li>
  
+@@ -1439,38 +1439,42 @@
+ 
+     <li>
+       <p>
+-        OpenFlow tables 37 through 39 implement the <code>output</code> action
+-        in the logical ingress pipeline.  Specifically, table 37 handles
+-        packets to remote hypervisors, table 38 handles packets to the local
+-        hypervisor, and table 39 checks whether packets whose logical ingress
+-        and egress port are the same should be discarded.
++        OpenFlow tables 37 through 41 implement the <code>output</code> action
++        in the logical ingress pipeline.  Specifically, table 37 serves as an
++        entry point to egress pipeline. Table 37 detects IP packets that are
++        too big for a corresponding interface. Table 38 produces ICMPv4
++        Fragmentation Needed (or ICMPv6 Too Big) errors and deliver them back
++        to the offending port. table 39 handles packets to remote hypervisors,
++        table 40 handles packets to the local hypervisor, and table 41 checks
++        whether packets whose logical ingress and egress port are the same
++        should be discarded.
+       </p>
+ 
+       <p>
+         Logical patch ports are a special case.  Logical patch ports do not
+         have a physical location and effectively reside on every hypervisor.
+-        Thus, flow table 38, for output to ports on the local hypervisor,
++        Thus, flow table 40, for output to ports on the local hypervisor,
+         naturally implements output to unicast logical patch ports too.
+         However, applying the same logic to a logical patch port that is part
+         of a logical multicast group yields packet duplication, because each
+         hypervisor that contains a logical port in the multicast group will
+         also output the packet to the logical patch port.  Thus, multicast
+-        groups implement output to logical patch ports in table 37.
++        groups implement output to logical patch ports in table 39.
+       </p>
+ 
+       <p>
+-        Each flow in table 37 matches on a logical output port for unicast or
++        Each flow in table 39 matches on a logical output port for unicast or
+         multicast logical ports that include a logical port on a remote
+         hypervisor.  Each flow's actions implement sending a packet to the port
+         it matches.  For unicast logical output ports on remote hypervisors,
+         the actions set the tunnel key to the correct value, then send the
+         packet on the tunnel port to the correct hypervisor.  (When the remote
+         hypervisor receives the packet, table 0 there will recognize it as a
+-        tunneled packet and pass it along to table 38.)  For multicast logical
++        tunneled packet and pass it along to table 40.)  For multicast logical
+         output ports, the actions send one copy of the packet to each remote
+         hypervisor, in the same way as for unicast destinations.  If a
+         multicast group includes a logical port or ports on the local
+-        hypervisor, then its actions also resubmit to table 38.  Table 37 also
++        hypervisor, then its actions also resubmit to table 40.  Table 39 also
+         includes:
+       </p>
+ 
+@@ -1478,7 +1482,7 @@
+         <li>
+           A higher-priority rule to match packets received from ramp switch
+           tunnels, based on flag MLF_RCV_FROM_RAMP, and resubmit these packets
+-          to table 38 for local delivery.  Packets received from ramp switch
++          to table 40 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.
+@@ -1486,7 +1490,7 @@
+         <li>
+           A higher-priority rule to match packets received from ports of type
+           <code>localport</code>, based on the logical input port, and resubmit
+-          these packets to table 38 for local delivery.  Ports of type
++          these packets to table 40 for local delivery.  Ports of type
+           <code>localport</code> exist on every hypervisor and by definition
+           their traffic should never go out through a tunnel.
+         </li>
+@@ -1501,41 +1505,41 @@
+           packets, the packets only need to be delivered to local ports.
+         </li>
+         <li>
+-          A fallback flow that resubmits to table 38 if there is no other
++          A fallback flow that resubmits to table 40 if there is no other
+           match.
+         </li>
+       </ul>
+ 
+       <p>
+-        Flows in table 38 resemble those in table 37 but for logical ports that
++        Flows in table 40 resemble those in table 39 but for logical ports that
+         reside locally rather than remotely.  For unicast logical output ports
+-        on the local hypervisor, the actions just resubmit to table 39.  For
++        on the local hypervisor, the actions just resubmit to table 41.  For
+         multicast output ports that include one or more logical ports on the
+         local hypervisor, for each such logical port <var>P</var>, the actions
+         change the logical output port to <var>P</var>, then resubmit to table
+-        39.
++        41.
+       </p>
+ 
+       <p>
+         A special case is that when a localnet port exists on the datapath,
+         remote port is connected by switching to the localnet port. In this
+-        case, instead of adding a flow in table 37 to reach the remote port, a
+-        flow is added in table 38 to switch the logical outport to the localnet
+-        port, and resubmit to table 38 as if it were unicasted to a logical
++        case, instead of adding a flow in table 39 to reach the remote port, a
++        flow is added in table 40 to switch the logical outport to the localnet
++        port, and resubmit to table 40 as if it were unicasted to a logical
+         port on the local hypervisor.
+       </p>
+ 
+       <p>
+-        Table 39 matches and drops packets for which the logical input and
++        Table 41 matches and drops packets for which the logical input and
+         output ports are the same and the MLF_ALLOW_LOOPBACK flag is not
+         set. It also drops MLF_LOCAL_ONLY packets directed to a localnet port.
+-        It resubmits other packets to table 40.
++        It resubmits other packets to table 42.
+       </p>
+     </li>
+ 
+     <li>
+       <p>
+-        OpenFlow tables 40 through 63 execute the logical egress pipeline from
++        OpenFlow tables 42 through 62 execute the logical egress pipeline from
+         the <code>Logical_Flow</code> table in the OVN Southbound database.
+         The egress pipeline can perform a final stage of validation before
+         packet delivery.  Eventually, it may execute an <code>output</code>
+@@ -1554,7 +1558,7 @@
+     <li>
+      <p>
+        Table 64 bypasses OpenFlow loopback when MLF_ALLOW_LOOPBACK is set.
+-       Logical loopback was handled in table 39, but OpenFlow by default also
++       Logical loopback was handled in table 41, but OpenFlow by default also
+        prevents loopback to the OpenFlow ingress port.  Thus, when
+        MLF_ALLOW_LOOPBACK is set, OpenFlow table 64 saves the OpenFlow ingress
+        port, sets it to zero, resubmits to table 65 for logical-to-physical
+@@ -1592,8 +1596,8 @@
+     traverse tables 0 to 65 as described in the previous section
+     <code>Architectural Physical Life Cycle of a Packet</code>, using the
+     logical datapath representing the logical switch that the sender is
+-    attached to.  At table 37, the packet will use the fallback flow that
+-    resubmits locally to table 38 on the same hypervisor.  In this case,
++    attached to.  At table 39, the packet will use the fallback flow that
++    resubmits locally to table 40 on the same hypervisor.  In this case,
+     all of the processing from table 0 to table 65 occurs on the hypervisor
+     where the sender resides.
+   </p>
+@@ -1624,7 +1628,7 @@
+   <p>
+     The packet traverses tables 8 to 65 a third and final time.  If the
+     destination VM or container resides on a remote hypervisor, then table
+-    37 will send the packet on a tunnel port from the sender's hypervisor
++    39 will send the packet on a tunnel port from the sender's hypervisor
+     to the remote hypervisor.  Finally table 65 will output the packet
+     directly to the destination VM or container.
+   </p>
+@@ -1651,9 +1655,9 @@
+     When a hypervisor processes a packet on a logical datapath
+     representing a logical switch, and the logical egress port is a
+     <code>l3gateway</code> port representing connectivity to a gateway
+-    router, the packet will match a flow in table 37 that sends the
++    router, the packet will match a flow in table 39 that sends the
+     packet on a tunnel port to the chassis where the gateway router
+-    resides.  This processing in table 37 is done in the same manner as
++    resides.  This processing in table 39 is done in the same manner as
+     for VIFs.
+   </p>
+ 
+@@ -1746,21 +1750,21 @@
+     chassis, one additional mechanism is required.  When a packet
+     leaves the ingress pipeline and the logical egress port is the
+     distributed gateway port, one of two different sets of actions is
+-    required at table 37:
++    required at table 39:
+   </p>
+ 
+   <ul>
+     <li>
+       If the packet can be handled locally on the sender's hypervisor
+       (e.g. one-to-one NAT traffic), then the packet should just be
+-      resubmitted locally to table 38, in the normal manner for
++      resubmitted locally to table 40, in the normal manner for
+       distributed logical patch ports.
+     </li>
+ 
+     <li>
+       However, if the packet needs to be handled on the chassis
+       associated with the distributed gateway port (e.g. one-to-many
+-      SNAT traffic or non-NAT traffic), then table 37 must send the
++      SNAT traffic or non-NAT traffic), then table 39 must send the
+       packet on a tunnel port to that chassis.
+     </li>
+   </ul>
+@@ -1772,11 +1776,11 @@
+     egress port to the type <code>chassisredirect</code> logical port is
+     simply a way to indicate that although the packet is destined for
+     the distributed gateway port, it needs to be redirected to a
+-    different chassis.  At table 37, packets with this logical egress
+-    port are sent to a specific chassis, in the same way that table 37
++    different chassis.  At table 39, packets with this logical egress
++    port are sent to a specific chassis, in the same way that table 39
+     directs packets whose logical egress port is a VIF or a type
+     <code>l3gateway</code> port to different chassis.  Once the packet
+-    arrives at that chassis, table 38 resets the logical egress port to
++    arrives at that chassis, table 40 resets the logical egress port to
+     the value representing the distributed gateway port.  For each
+     distributed gateway port, there is one type
+     <code>chassisredirect</code> port, in addition to the distributed
 diff --git a/ovn-nb.xml b/ovn-nb.xml
 index 8d56d0c6e..35acda107 100644
 --- a/ovn-nb.xml
@@ -2572,7 +4562,7 @@ index d281f861c..6c4c6621c 100644
  [Install]
  WantedBy=multi-user.target
 diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index bbe142ae3..27fc44232 100644
+index bbe142ae3..dd7eda516 100644
 --- a/tests/ovn-controller.at
 +++ b/tests/ovn-controller.at
 @@ -493,7 +493,8 @@ check ovn-nbctl --wait=hv sync
@@ -2616,6 +4606,579 @@ index bbe142ae3..27fc44232 100644
  
  cat hv1/ovn-controller.log
  
+@@ -868,7 +873,7 @@ meta=$(ovn-sbctl get datapath ls1 tunnel_key)
+ port=$(ovn-sbctl get port_binding ls1-rp tunnel_key)
+ check ovn-nbctl lrp-add lr0 rp-ls1 00:00:01:01:02:03 192.168.1.254/24
+ 
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int | grep table=38 | grep -q "reg15=0x${port},metadata=0x${meta}"])
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int | grep table=40 | grep -q "reg15=0x${port},metadata=0x${meta}"])
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -912,14 +917,14 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.2 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.3 actions=drop
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$i
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$i
+ ])
+ done
+ 
+@@ -934,15 +939,15 @@ for i in $(seq 10); do
+     check ovn-nbctl remove address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 9; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}'], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10 actions=drop
+ ])
+     fi
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((10 - $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((10 - $i))
+ ])
+     fi
+ done
+@@ -960,7 +965,7 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i,10.0.1.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.2 actions=drop
+@@ -970,7 +975,7 @@ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.1.2 actions=dr
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.1.3 actions=drop
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i * 2))
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i * 2))
+ ])
+ done
+ 
+@@ -987,11 +992,11 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.21,10.0.0.22 -- \
+                 remove address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.21], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.21], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.22], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.22], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.10], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.10], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1003,9 +1008,9 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl remove address_set as1 addresses 10.0.0.21,10.0.0.22 -- \
+                 add address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.21], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.22], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.10], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.21], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.22], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.10], [0], [1
+ ])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+@@ -1018,9 +1023,9 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.21 -- \
+                 remove address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.21], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.21], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.10], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.10], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1032,12 +1037,12 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.22,10.0.0.23 -- \
+                 remove address_set as1 addresses 10.0.0.9,10.0.0.8
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.22], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.22], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.23], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.23], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.8], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.9], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.8], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.9], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1085,7 +1090,7 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 1; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,tp_dst=111 actions=drop
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,tp_dst=222 actions=drop
+@@ -1093,12 +1098,12 @@ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,tp_dst=33
+ ])
+     else
+         # (1 conj_id flow + 3 tp_dst flows) = 4 extra flows
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i + 4))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i + 4))
+ ])
+     fi
+ 
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1124,17 +1129,17 @@ for i in $(seq 10); do
+     check ovn-nbctl remove address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     elif test "$i" = 9; then
+         # no conjunction left
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,tp_dst=111 actions=drop
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,tp_dst=222 actions=drop
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,tp_dst=333 actions=drop
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((14 - $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((14 - $i))
+ ])
+     fi
+ done
+@@ -1150,7 +1155,7 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i,10.0.1.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1166,7 +1171,7 @@ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,tp_dst=222 actions=conjun
+ priority=1100,tcp,reg15=0x$port_key,metadata=0x$dp_key,tp_dst=333 actions=conjunction,2/2)
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i * 2 + 4))
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i * 2 + 4))
+ ])
+ done
+ 
+@@ -1182,11 +1187,11 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.21,10.0.0.22 -- \
+                 remove address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.21], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.21], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.22], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.22], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.10], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.10], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1198,9 +1203,9 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl remove address_set as1 addresses 10.0.0.21,10.0.0.22 -- \
+                 add address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.21], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.22], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.10], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.21], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.22], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.10], [0], [1
+ ])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+@@ -1213,9 +1218,9 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.21 -- \
+                 remove address_set as1 addresses 10.0.0.10
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.21], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.21], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.10], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.10], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1227,12 +1232,12 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.22,10.0.0.23 -- \
+                 remove address_set as1 addresses 10.0.0.9,10.0.0.8
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.22], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.22], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c 10\.0\.0\.23], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c 10\.0\.0\.23], [0], [1
+ ])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.8], [1], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10\.0\.0\.9], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.8], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10\.0\.0\.9], [1], [ignore])
+ 
+ reprocess_count_new=$(read_counter consider_logical_flow)
+ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [0
+@@ -1282,18 +1287,18 @@ for i in $(seq 10); do
+                     add address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 1; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,nw_dst=10.0.0.6 actions=drop
+ ])
+     else
+         # (1 conj_id + nw_src * i + nw_dst * i) = 1 + i*2 flows
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i*2 + 1))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i*2 + 1))
+ ])
+     fi
+ 
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1321,15 +1326,15 @@ for i in $(seq 10); do
+                     remove address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     elif test "$i" = 9; then
+         # no conjunction left
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,nw_dst=10.0.0.15 actions=drop
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((21 - $i*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((21 - $i*2))
+ ])
+     fi
+ done
+@@ -1350,14 +1355,14 @@ for i in $(seq 2 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,nw_dst=10.0.0.6 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.2,nw_dst=10.0.0.6 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.3,nw_dst=10.0.0.6 actions=drop
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$i
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$i
+ ])
+ done
+ 
+@@ -1376,16 +1381,16 @@ for i in $(seq 10); do
+     check ovn-nbctl remove address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 9; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}'], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,nw_dst=10.0.0.6 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,nw_dst=10.0.0.7 actions=drop
+ ])
+     elif test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     else
+         # 2 dst + (10 - i) src + 1 conj_id
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((10 - $i + 3))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((10 - $i + 3))
+ ])
+     fi
+ done
+@@ -1439,18 +1444,18 @@ for i in $(seq 10); do
+                     add address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 1; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_dst=10.0.0.6 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1 actions=drop
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i*2))
+ ])
+     fi
+ 
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1477,9 +1482,9 @@ for i in $(seq 10); do
+                     remove address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((20 - $i*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((20 - $i*2))
+ ])
+     fi
+ done
+@@ -1535,21 +1540,21 @@ for i in $(seq 10); do
+                     add address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 1; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1 actions=drop
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.6 actions=drop
+ ])
+     elif test "$i" -lt 6; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i*2))
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((5 + $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((5 + $i))
+ ])
+     fi
+ 
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1576,12 +1581,12 @@ for i in $(seq 10); do
+                     remove address_set as2 addresses 10.0.0.$j
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     elif test "$i" -lt 6; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((15 - $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((15 - $i))
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((10 - ($i - 5)*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((10 - ($i - 5)*2))
+ ])
+     fi
+ done
+@@ -1633,18 +1638,18 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 1; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.1,nw_dst=10.0.0.1 actions=drop
+ ])
+     else
+         # (1 conj_id + nw_src * i + nw_dst * i) = 1 + i*2 flows
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i*2 + 1))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i*2 + 1))
+ ])
+     fi
+ 
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1670,15 +1675,15 @@ for i in $(seq 10); do
+     check ovn-nbctl remove address_set as1 addresses 10.0.0.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 10; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     elif test "$i" = 9; then
+         # no conjunction left
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.0.10,nw_dst=10.0.0.10 actions=drop
+ ])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((21 - $i*2))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((21 - $i*2))
+ ])
+     fi
+ done
+@@ -1694,7 +1699,7 @@ for i in $(seq 10); do
+     check ovn-nbctl add address_set as1 addresses 10.0.0.$i,10.0.1.$i
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | \
+             sed -r 's/conjunction.*,/conjunction,/' | \
+             sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1713,7 +1718,7 @@ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.1.2 actions=co
+ priority=1100,ip,reg15=0x$port_key,metadata=0x$dp_key,nw_src=10.0.1.3 actions=conjunction,2/2)
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$(($i * 4 + 1))
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$(($i * 4 + 1))
+ ])
+ done
+ 
+@@ -1734,7 +1739,7 @@ check ovn-nbctl --wait=hv sync
+ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.4,10.0.0.5
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+     grep -v reply | awk '{print $7, $8}' | \
+     sed -r 's/conjunction.*,/conjunction,/' | \
+     sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1758,7 +1763,7 @@ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [1
+ # Delete 2 IPs
+ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl --wait=hv remove address_set as1 addresses 10.0.0.4,10.0.0.5
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+     grep -v reply | awk '{print $7, $8}' | \
+     sed -r 's/conjunction.*,/conjunction,/' | \
+     sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1816,7 +1821,7 @@ check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" && ip4.src == $as
+ check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" && ip4.src == $as2 && tcp && tcp.dst == {201, 202}' drop
+ 
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+     grep -v reply | awk '{print $7, $8}' | \
+     sed -r 's/conjunction.[[0-9]]*,/conjunction,/g' | \
+     sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1841,7 +1846,7 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl add address_set as1 addresses 10.0.0.14,10.0.0.33 -- \
+                 add address_set as2 addresses 10.0.0.24,10.0.0.33
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+     grep -v reply | awk '{print $7, $8}' | \
+     sed -r 's/conjunction.[[0-9]]*,/conjunction,/g' | \
+     sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1872,7 +1877,7 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ check ovn-nbctl remove address_set as1 addresses 10.0.0.14,10.0.0.33 -- \
+                 remove address_set as2 addresses 10.0.0.24,10.0.0.33
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+     grep -v reply | awk '{print $7, $8}' | \
+     sed -r 's/conjunction.[[0-9]]*,/conjunction,/g' | \
+     sed -r 's/conj_id=.*,/conj_id=,/' | sort], [0], [dnl
+@@ -1937,14 +1942,14 @@ for i in $(seq 5); do
+     check ovn-nbctl add address_set as1 addresses "aa\:aa\:aa\:aa\:aa\:0$i"
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,reg15=0x$port_key,metadata=0x$dp_key,dl_src=aa:aa:aa:aa:aa:01 actions=drop
+ priority=1100,reg15=0x$port_key,metadata=0x$dp_key,dl_src=aa:aa:aa:aa:aa:02 actions=drop
+ priority=1100,reg15=0x$port_key,metadata=0x$dp_key,dl_src=aa:aa:aa:aa:aa:03 actions=drop
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$i
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$i
+ ])
+ done
+ 
+@@ -1958,17 +1963,17 @@ reprocess_count_old=$(read_counter consider_logical_flow)
+ for i in $(seq 5); do
+     check ovn-nbctl remove address_set as1 addresses "aa\:aa\:aa\:aa\:aa\:0$i"
+     check ovn-nbctl --wait=hv sync
+-    ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"
++    ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"
+     if test "$i" = 4; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}'], [0], [dnl
+ priority=1100,reg15=0x$port_key,metadata=0x$dp_key,dl_src=aa:aa:aa:aa:aa:05 actions=drop
+ ])
+     fi
+     if test "$i" = 5; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((5 - $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((5 - $i))
+ ])
+     fi
+ done
+@@ -2018,14 +2023,14 @@ for i in $(seq 5); do
+     check ovn-nbctl add address_set as1 addresses "ff\:\:0$i"
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 3; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}' | sort], [0], [dnl
+ priority=1100,ipv6,reg15=0x$port_key,metadata=0x$dp_key,ipv6_src=ff::1 actions=drop
+ priority=1100,ipv6,reg15=0x$port_key,metadata=0x$dp_key,ipv6_src=ff::2 actions=drop
+ priority=1100,ipv6,reg15=0x$port_key,metadata=0x$dp_key,ipv6_src=ff::3 actions=drop
+ ])
+     fi
+-    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$i
++    AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$i
+ ])
+ done
+ 
+@@ -2040,15 +2045,15 @@ for i in $(seq 5); do
+     check ovn-nbctl remove address_set as1 addresses "ff\:\:0$i"
+     check ovn-nbctl --wait=hv sync
+     if test "$i" = 4; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44,reg15=0x$port_key | \
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46,reg15=0x$port_key | \
+             grep -v reply | awk '{print $7, $8}'], [0], [dnl
+ priority=1100,ipv6,reg15=0x$port_key,metadata=0x$dp_key,ipv6_src=ff::5 actions=drop
+ ])
+     fi
+     if test "$i" = 5; then
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep "priority=1100"], [1], [ignore])
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep "priority=1100"], [1], [ignore])
+     else
+-        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [$((5 - $i))
++        AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [$((5 - $i))
+ ])
+     fi
+ done
 @@ -2060,6 +2065,57 @@ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [2
  OVN_CLEANUP([hv1])
  AT_CLEANUP
@@ -2643,7 +5206,7 @@ index bbe142ae3..27fc44232 100644
 +ovn-nbctl create address_set name=as1 addresses=8.8.8.8
 +check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" && ip4.src == $as1' drop
 +check ovn-nbctl --wait=hv sync
-+AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [1
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0], [1
 +])
 +
 +# pause ovn-northd
@@ -2659,13 +5222,13 @@ index bbe142ae3..27fc44232 100644
 +# undefined. This test runs the scenario ten times to make sure different
 +# orders are covered and handled properly.
 +
-+flow_count=$(ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100")
++flow_count=$(ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100")
 +for i in $(seq 10); do
 +    # Delete and recreate the SB address set with same name and an extra IP.
 +    addrs_=$(fetch_column address_set addresses name=as1)
 +    addrs=${addrs_// /,}
 +    AT_CHECK([ovn-sbctl destroy address_set as1 -- create address_set name=as1 addresses=$addrs,1.1.1.$i], [0], [ignore])
-+    OVS_WAIT_UNTIL([test $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100") = "$(($i + 1))"])
++    OVS_WAIT_UNTIL([test $(as hv1 ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100") = "$(($i + 1))"])
 +done
 +
 +OVN_CLEANUP([hv1])
@@ -2772,7 +5335,7 @@ index 2fffe1850..478a32f5a 100644
  AT_CHECK([ovsdb-tool create ovn-nb.db $abs_top_srcdir/ovn-nb.ovsschema])
  
 diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index 3fa02d2b3..846f10e88 100644
+index 3fa02d2b3..93854dfdc 100644
 --- a/tests/ovn-northd.at
 +++ b/tests/ovn-northd.at
 @@ -2486,6 +2486,7 @@ check ovn-nbctl --wait=sb \
@@ -2796,7 +5359,31 @@ index 3fa02d2b3..846f10e88 100644
    table=8 (ls_in_acl          ), priority=65535, match=(1), action=(next;)
  ])
  
-@@ -3757,18 +3761,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -2871,7 +2875,6 @@ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort | sed 's/tabl
+ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/g'], [0], [dnl
+   table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_hairpin      ), priority=1    , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
+-  table=??(ls_in_hairpin      ), priority=1000 , match=(reg0[[14]] == 1), action=(next(pipeline=ingress, table=??);)
+ ])
+ 
+ check ovn-nbctl -- ls-lb-del sw0 lb0
+@@ -2887,7 +2890,6 @@ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort | sed 's/tabl
+ 
+ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/g'], [0], [dnl
+   table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+-  table=??(ls_in_hairpin      ), priority=1000 , match=(reg0[[14]] == 1), action=(next(pipeline=ingress, table=??);)
+ ])
+ 
+ check ovn-nbctl -- add load_balancer_group $lbg load_balancer $lb0
+@@ -2908,7 +2910,6 @@ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort | sed 's/tabl
+ AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort | sed 's/table=../table=??/g'], [0], [dnl
+   table=??(ls_in_hairpin      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_hairpin      ), priority=1    , match=((reg0[[6]] == 1 || reg0[[12]] == 1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
+-  table=??(ls_in_hairpin      ), priority=1000 , match=(reg0[[14]] == 1), action=(next(pipeline=ingress, table=??);)
+ ])
+ 
+ AT_CLEANUP
+@@ -3757,18 +3758,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -2822,7 +5409,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -3788,18 +3792,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -3788,18 +3789,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -2848,7 +5435,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -3813,6 +3817,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+@@ -3813,6 +3814,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -2856,7 +5443,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -3838,18 +3843,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -3838,18 +3840,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -2882,7 +5469,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -3864,6 +3869,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+@@ -3864,6 +3866,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -2890,7 +5477,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -3902,18 +3908,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -3902,18 +3905,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -2916,7 +5503,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -3929,6 +3935,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+@@ -3929,6 +3932,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -2924,7 +5511,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -3953,14 +3960,13 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -3953,14 +3957,13 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -2943,7 +5530,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
  
-@@ -3970,6 +3976,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sed 's/table=./t
+@@ -3970,6 +3973,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sed 's/table=./t
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -2951,7 +5538,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -4111,6 +4118,7 @@ check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+@@ -4111,6 +4115,7 @@ check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
  check ovn-nbctl --wait=sb sync
  
  check_stateful_flows() {
@@ -2959,7 +5546,7 @@ index 3fa02d2b3..846f10e88 100644
      ovn-sbctl dump-flows sw0 > sw0flows
      AT_CAPTURE_FILE([sw0flows])
  
-@@ -4144,12 +4152,12 @@ check_stateful_flows() {
+@@ -4144,12 +4149,12 @@ check_stateful_flows() {
    table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_mark.blocked = 0; ct_label.label = reg3; }; next;)
  ])
  
@@ -2975,7 +5562,7 @@ index 3fa02d2b3..846f10e88 100644
    table=1 (ls_out_pre_lb      ), priority=110  , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
    table=1 (ls_out_pre_lb      ), priority=110  , match=(reg0[[16]] == 1), action=(next;)
  ])
-@@ -4169,13 +4177,13 @@ check_stateful_flows() {
+@@ -4169,13 +4174,13 @@ check_stateful_flows() {
  ])
  }
  
@@ -2991,7 +5578,7 @@ index 3fa02d2b3..846f10e88 100644
  
  # Remove load balancers from sw0
  check ovn-nbctl ls-lb-del sw0 lb0
-@@ -4231,6 +4239,15 @@ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl
+@@ -4231,6 +4236,15 @@ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl
    table=7 (ls_out_stateful    ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_mark.blocked = 0; ct_label.label = reg3; }; next;)
  ])
  
@@ -3007,7 +5594,88 @@ index 3fa02d2b3..846f10e88 100644
  AT_CLEANUP
  ])
  
-@@ -5211,25 +5228,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -4871,7 +4885,7 @@ check ovn-nbctl lsp-set-options ls2-ro2 router-port=ro2-ls2
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4883,7 +4897,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:01), action=(outport = "ls2-ro2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:02), action=(outport = "vm2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4903,7 +4917,7 @@ check ovn-nbctl --wait=sb lr-nat-add ro2 snat 20.0.0.200 192.168.2.200/30
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4916,7 +4930,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:01), action=(outport = "ls2-ro2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:02), action=(outport = "vm2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4937,7 +4951,7 @@ check ovn-nbctl --wait=sb lr-nat-add ro2 snat 40.0.0.200 192.168.2.148/30
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4951,7 +4965,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+ ovn-sbctl lflow-list ls2 > ls2_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:01), action=(outport = "ls2-ro2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:02:02), action=(outport = "vm2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4970,7 +4984,7 @@ ovn-nbctl --wait=sb lr-lb-add ro1 lb1
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -4988,7 +5002,7 @@ ovn-nbctl --wait=sb lb-add lb1 192.168.4.100:80 10.0.0.10:80
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -5012,7 +5026,7 @@ ovn-nbctl --wait=sb lrp-set-gateway-chassis ro1-ls1 chassis-1 30
+ ovn-sbctl lflow-list ls1 > ls1_lflows
+ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:01), action=(outport = "ls1-ro1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:01:02), action=(outport = "vm1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -5211,25 +5225,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3044,7 +5712,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5284,25 +5299,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -5284,25 +5296,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3081,7 +5749,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5314,6 +5327,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+@@ -5314,6 +5324,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -3089,7 +5757,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -5349,25 +5363,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -5349,25 +5360,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3126,7 +5794,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5379,6 +5391,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+@@ -5379,6 +5388,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -3134,7 +5802,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -5416,28 +5429,25 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -5416,28 +5426,25 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3176,7 +5844,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5449,6 +5459,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+@@ -5449,6 +5456,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -3184,7 +5852,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -5496,31 +5507,27 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -5496,31 +5504,27 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3231,7 +5899,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5532,6 +5539,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+@@ -5532,6 +5536,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -3239,7 +5907,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -5572,18 +5580,17 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+@@ -5572,18 +5577,17 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
  
  AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
    table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3264,7 +5932,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5594,6 +5601,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+@@ -5594,6 +5598,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
  
  AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
    table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
@@ -3272,7 +5940,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
  ])
  
-@@ -5634,9 +5642,11 @@ ovn-sbctl set service_monitor $sm_vip2 status=offline
+@@ -5634,9 +5639,11 @@ ovn-sbctl set service_monitor $sm_vip2 status=offline
  
  AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
    table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
@@ -3286,7 +5954,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5646,9 +5656,11 @@ check ovn-nbctl --wait=sb set load_balancer lb5 options:skip_snat=true
+@@ -5646,9 +5653,11 @@ check ovn-nbctl --wait=sb set load_balancer lb5 options:skip_snat=true
  
  AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
    table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
@@ -3300,7 +5968,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -5660,9 +5672,58 @@ check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="route
+@@ -5660,9 +5669,58 @@ check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="route
  
  AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
    table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
@@ -3361,7 +6029,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -6692,11 +6753,12 @@ dnl Flows to skip TTL == {0, 1} check for IGMP and MLD packets.
+@@ -6692,11 +6750,12 @@ dnl Flows to skip TTL == {0, 1} check for IGMP and MLD packets.
  AT_CHECK([grep -e 'lr_in_ip_input    ' lrflows | grep -e 'igmp' -e 'mld' -e 'ip.ttl == {0, 1}' | sed 's/table=../table=??/'], [0], [dnl
    table=??(lr_in_ip_input     ), priority=120  , match=((mldv1 || mldv2) && ip.ttl == 1), action=(next;)
    table=??(lr_in_ip_input     ), priority=120  , match=(igmp && ip.ttl == 1), action=(next;)
@@ -3379,7 +6047,7 @@ index 3fa02d2b3..846f10e88 100644
  ])
  
  dnl Flows to "route" (statically forward) without decrementing TTL for
-@@ -6755,6 +6817,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+@@ -6755,6 +6814,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
    table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
    table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
    table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
@@ -3387,7 +6055,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -6809,6 +6872,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+@@ -6809,6 +6869,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
    table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
    table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
    table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
@@ -3395,7 +6063,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -6863,6 +6927,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+@@ -6863,6 +6924,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
    table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
    table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
    table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
@@ -3403,7 +6071,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -7154,11 +7219,14 @@ flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:
+@@ -7154,11 +7216,14 @@ flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:
  AS_BOX([No ACL, default_acl_drop not set])
  check ovn-nbctl --wait=sb sync
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -3418,7 +6086,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
-@@ -7173,11 +7241,14 @@ output("lsp2");
+@@ -7173,11 +7238,14 @@ output("lsp2");
  AS_BOX([No ACL, default_acl_drop false])
  check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -3433,7 +6101,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
-@@ -7192,11 +7263,14 @@ output("lsp2");
+@@ -7192,11 +7260,14 @@ output("lsp2");
  AS_BOX([No ACL, default_acl_drop true])
  check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
@@ -3448,7 +6116,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
-@@ -7218,12 +7292,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7218,12 +7289,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3464,7 +6132,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7240,12 +7317,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7240,12 +7314,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3480,7 +6148,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7262,12 +7342,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7262,12 +7339,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
    table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3496,7 +6164,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7292,6 +7375,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7292,6 +7372,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
    table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
    table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
@@ -3504,7 +6172,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -7343,13 +7427,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
+@@ -7343,13 +7424,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3521,7 +6189,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7365,13 +7452,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
+@@ -7365,13 +7449,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3538,7 +6206,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7387,13 +7477,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
+@@ -7387,13 +7474,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3555,7 +6223,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7418,6 +7511,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7418,6 +7508,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
    table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
    table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
@@ -3563,7 +6231,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -7469,13 +7563,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
+@@ -7469,13 +7560,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3580,7 +6248,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7491,13 +7588,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
+@@ -7491,13 +7585,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3597,7 +6265,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7513,13 +7613,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
+@@ -7513,13 +7610,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
  AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
    table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
    table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
@@ -3614,7 +6282,7 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
    table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
-@@ -7542,6 +7645,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+@@ -7542,6 +7642,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
    table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
    table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
    table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
@@ -3622,7 +6290,61 @@ index 3fa02d2b3..846f10e88 100644
    table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
    table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-@@ -7886,8 +7990,10 @@ check ovn-nbctl                                               \
+@@ -7719,7 +7820,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_unknown   ), priority=0    , match=(1), action=(output;)
+   table=??(ls_in_l2_unknown   ), priority=50   , match=(outport == "none"), action=(drop;)
+@@ -7744,7 +7845,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0p1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0p2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -7770,7 +7871,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0p1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0p2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -7797,7 +7898,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:01), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0p2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -7824,7 +7925,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:01), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0p2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -7854,7 +7955,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
+   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
+   table=??(ls_in_l2_lkup      ), priority=0    , match=(1), action=(outport = get_fdb(eth.dst); next;)
+-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(handle_svc_check(inport);)
++  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == $svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:01), action=(outport = "sw0p1"; output;)
+   table=??(ls_in_l2_lkup      ), priority=50   , match=(eth.dst == 00:00:00:00:00:02), action=(outport = "sw0p2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+@@ -7886,8 +7987,10 @@ check ovn-nbctl                                               \
  AS_BOX([No chassis registered - use ct_lb_mark and ct_mark.natted])
  check ovn-nbctl --wait=sb sync
  AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
@@ -3635,7 +6357,7 @@ index 3fa02d2b3..846f10e88 100644
    table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb_mark;)
    table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb_mark;)
    table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb_mark(backends=42.42.42.2);)
-@@ -7898,8 +8004,10 @@ AS_BOX([Chassis registered that doesn't support ct_lb_mark - use ct_lb and ct_la
+@@ -7898,8 +8001,10 @@ AS_BOX([Chassis registered that doesn't support ct_lb_mark - use ct_lb and ct_la
  check ovn-sbctl chassis-add hv geneve 127.0.0.1
  check ovn-nbctl --wait=sb sync
  AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
@@ -3648,7 +6370,7 @@ index 3fa02d2b3..846f10e88 100644
    table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb;)
    table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb;)
    table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb(backends=42.42.42.2);)
-@@ -7910,8 +8018,10 @@ AS_BOX([Chassis upgrades and supports ct_lb_mark - use ct_lb_mark and ct_mark.na
+@@ -7910,8 +8015,10 @@ AS_BOX([Chassis upgrades and supports ct_lb_mark - use ct_lb_mark and ct_mark.na
  check ovn-sbctl set chassis hv other_config:ct-no-masked-label=true
  check ovn-nbctl --wait=sb sync
  AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
@@ -3661,7 +6383,7 @@ index 3fa02d2b3..846f10e88 100644
    table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb_mark;)
    table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb_mark;)
    table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb_mark(backends=42.42.42.2);)
-@@ -8244,15 +8354,17 @@ AT_CAPTURE_FILE([R1flows])
+@@ -8244,15 +8351,17 @@ AT_CAPTURE_FILE([R1flows])
  
  AT_CHECK([grep "lr_in_lb_aff_check" R1flows | sort], [0], [dnl
    table=6 (lr_in_lb_aff_check ), priority=0    , match=(1), action=(next;)
@@ -3682,7 +6404,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -8270,11 +8382,13 @@ AT_CAPTURE_FILE([R1flows_skip_snat])
+@@ -8270,11 +8379,13 @@ AT_CAPTURE_FILE([R1flows_skip_snat])
  
  AT_CHECK([grep "lr_in_dnat " R1flows_skip_snat | sort], [0], [dnl
    table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
@@ -3698,7 +6420,7 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -8289,11 +8403,13 @@ AT_CAPTURE_FILE([R1flows_force_snat])
+@@ -8289,11 +8400,13 @@ AT_CAPTURE_FILE([R1flows_force_snat])
  
  AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sort], [0], [dnl
    table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
@@ -3714,7 +6436,31 @@ index 3fa02d2b3..846f10e88 100644
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -8569,12 +8685,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows0
+@@ -8330,8 +8443,9 @@ rm -f northd/ovn-northd.log
+ check as northd ovn-appctl -t NORTHD_TYPE vlog/reopen
+ check as northd ovn-appctl -t NORTHD_TYPE vlog/set jsonrpc:dbg
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl add address_set $foo_as_uuid addresses 1.1.1.3
+-wait_column '1.1.1.1 1.1.1.2 1.1.1.3' Address_Set addresses name=foo
++check ovn-nbctl add address_set $foo_as_uuid addresses 1.1.1.3 -- \
++                add address_set $foo_as_uuid addresses 1.1.2.1/4
++wait_column '1.1.1.1 1.1.1.2 1.1.1.3 1.1.2.1/4' Address_Set addresses name=foo
+ 
+ # There should be no recompute of the sync_to_sb_addr_set engine node .
+ AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats sync_to_sb_addr_set recompute], [0], [0
+@@ -8341,8 +8455,9 @@ AT_CHECK([grep transact northd/ovn-northd.log | grep Address_Set | \
+ grep -c mutate], [0], [1
+ ])
+ 
+-check ovn-nbctl add address_set $foo_as_uuid addresses \
+-1.1.1.4 -- remove address_set $foo_as_uuid addresses 1.1.1.1
++check ovn-nbctl add address_set $foo_as_uuid addresses 1.1.1.4 -- \
++                remove address_set $foo_as_uuid addresses 1.1.1.1 -- \
++                remove address_set $foo_as_uuid addresses 1.1.2.1/4
+ wait_column '1.1.1.2 1.1.1.3 1.1.1.4' Address_Set addresses name=foo
+ 
+ # There should be no recompute of the sync_to_sb_addr_set engine node .
+@@ -8569,12 +8684,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows0
  
  AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows0], [0], [dnl
    table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3732,7 +6478,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=? (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -8588,6 +8705,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows0 | grep "priority=65532"],
+@@ -8588,6 +8704,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows0 | grep "priority=65532"],
    table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
    table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
    table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
@@ -3740,7 +6486,7 @@ index 3fa02d2b3..846f10e88 100644
    table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
  ])
  
-@@ -8599,10 +8717,12 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows1
+@@ -8599,10 +8716,12 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows1
  
  AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows1], [0], [dnl
    table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3756,7 +6502,7 @@ index 3fa02d2b3..846f10e88 100644
  ])
  
  AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"], [0], [dnl
-@@ -8614,6 +8734,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"],
+@@ -8614,6 +8733,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"],
    table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
    table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
    table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
@@ -3764,7 +6510,7 @@ index 3fa02d2b3..846f10e88 100644
    table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
  ])
  
-@@ -8625,12 +8746,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows2
+@@ -8625,12 +8745,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows2
  
  AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows2], [0], [dnl
    table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
@@ -3782,7 +6528,7 @@ index 3fa02d2b3..846f10e88 100644
    table=? (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
    table=? (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
  ])
-@@ -8644,8 +8766,104 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows2 | grep "priority=65532"],
+@@ -8644,8 +8765,104 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows2 | grep "priority=65532"],
    table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
    table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
    table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
@@ -3888,10 +6634,189 @@ index 3fa02d2b3..846f10e88 100644
 +AT_CLEANUP
 +])
 diff --git a/tests/ovn.at b/tests/ovn.at
-index 55de7c85b..3515a1e3c 100644
+index 55de7c85b..ed91d32d0 100644
 --- a/tests/ovn.at
 +++ b/tests/ovn.at
-@@ -5753,7 +5753,7 @@ check ovn-nbctl --wait=hv sync
+@@ -992,10 +992,10 @@ next(pipeline=ingress, table=11);
+ 
+ next(pipeline=egress);
+     formats as next(pipeline=egress, table=11);
+-    encodes as resubmit(,51)
++    encodes as resubmit(,53)
+ 
+ next(pipeline=egress, table=5);
+-    encodes as resubmit(,45)
++    encodes as resubmit(,47)
+ 
+ next(table=10);
+     formats as next(10);
+@@ -4414,24 +4414,13 @@ response=${sha}${lrpmac}08060001080006040002${lrpmac}${tpa}${sha}${spa}
+ echo $response >> 3.expected
+ 
+ # First ensure basic flow contents are as we expect.
+-AT_CHECK([ovn-sbctl lflow-list lsw0 | grep 'reg0[\[14\]]' | sort | sed 's/table=../table=??/g' | sed 's/is_chassis_resident([[^)]]*)/is_chassis_resident("??")/g'], [0], [dnl
++AT_CHECK([ovn-sbctl lflow-list lsw0 | grep 'reg0[\[14\]]' | sort | sed 's/table=../table=??/g'], [0], [dnl
+   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "lp-vtep"), action=(reg0[[14]] = 1; next(pipeline=ingress, table=??);)
+   table=??(ls_in_hairpin      ), priority=1000 , match=(reg0[[14]] == 1), action=(next(pipeline=ingress, table=??);)
+-  table=??(ls_in_hairpin      ), priority=2000 , match=(reg0[[14]] == 1 && (is_chassis_resident("??") || is_chassis_resident("??"))), action=(next;)
++  table=??(ls_in_hairpin      ), priority=2000 , match=(reg0[[14]] == 1 && is_chassis_resident("cr-lrp1")), action=(next;)
++  table=??(ls_in_hairpin      ), priority=2000 , match=(reg0[[14]] == 1 && is_chassis_resident("cr-lrp2")), action=(next;)
+ ])
+ 
+-# We've ensured that the expected hairpin flows are present
+-# and that the expected number of "is_chassis_resident" fields are in
+-# the flow. Now we need to ensure the contents are correct.
+-# Unfortunately, the order of the "is_chassis_resident" fields is
+-# unpredictable. Therefore we sort them so the order is predictable.
+-actual_chassis=$(ovn-sbctl lflow-list lsw0 | grep 'ls_in_hairpin' | grep 'priority=2000' | grep -o 'is_chassis_resident([[^)]]*)' | sort)
+-
+-expected_chassis='is_chassis_resident("cr-lrp1")
+-is_chassis_resident("cr-lrp2")'
+-
+-check test "$expected_chassis" = "$actual_chassis"
+-
+ # dump information with counters
+ echo "------ OVN dump ------"
+ ovn-nbctl show
+@@ -5055,6 +5044,7 @@ AT_CLEANUP
+ 
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IP relocation using GARP request])
++AT_SKIP_IF([test $HAVE_SCAPY = no])
+ ovn_start
+ 
+ # Logical network:
+@@ -5154,7 +5144,9 @@ 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
+-    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
++    local packet=$(fmt_pkt "Ether(dst='${dst_mac}', src='${src_mac}')/ \
++                            IP(dst='${dst_ip}', src='${src_ip}')/ \
++                            UDP(sport=53, dport=4369)")
+     shift; shift; shift; shift; shift
+     hv=hv`vif_to_hv $inport`
+     as $hv ovs-appctl netdev-dummy/receive vif$inport $packet
+@@ -5169,7 +5161,9 @@ test_ip() {
+             # Routing decrements TTL and updates source and dest MAC
+             # (and checksum).
+             out_lrp=`vif_to_lrp $outport`
+-            echo f000000000${outport}00000000ff0${out_lrp}08004500001c00000000"3f1101"00${src_ip}${dst_ip}0035111100080000
++            echo $(fmt_pkt "Ether(dst='f0:00:00:00:00:${outport}', src='00:00:00:00:ff:${out_lrp}')/ \
++                            IP(src='${src_ip}', dst='${dst_ip}', ttl=63)/ \
++                            UDP(sport=53, dport=4369)")
+         fi >> $outport.expected
+     done
+ }
+@@ -5185,8 +5179,10 @@ test_ip() {
+ # 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}
++    local inport=$1 sha=$2 spa=$3 tpa=$3
++    local request=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
++                             ARP(hwsrc='${sha}', hwdst='ff:ff:ff:ff:ff:ff', psrc='${spa}', pdst='${tpa}')")
++
+     hv=hv`vif_to_hv $inport`
+     as $hv ovs-appctl netdev-dummy/receive vif$inport $request
+ 
+@@ -5199,53 +5195,72 @@ test_arp() {
+             echo $request >> $i$j$k.expected
+         fi
+     done
++}
+ 
+-    # Expect to receive the reply, if any.
+-    if test X$reply_ha != X; then
+-        lrp=`vif_to_lrp $inport`
+-        local reply=${sha}00000000ff0${lrp}08060001080006040002${reply_ha}${tpa}${sha}${spa}
+-        echo $reply >> $inport.expected
+-    fi
++test_na() {
++    local inport=$1 sha=$2 spa=$3
++    local request=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='${sha}')/ \
++                             IPv6(dst='ff01::1', src='${spa}')/ \
++                             ICMPv6ND_NA(tgt='${spa}')")
++
++    hv=hv`vif_to_hv $inport`
++    as $hv ovs-appctl netdev-dummy/receive vif$inport $request
++
++    # Expect to receive the broadcast ARP on the other logical switch ports if
++    # IP address is not configured to the switch patch port.
++    local i=`vif_to_ls $inport`
++    local j
++    for j in 1 2; do
++        if test $i$j != $inport; then
++            echo $request >> $i$j$k.expected
++        fi
++    done
+ }
+ 
+-# lp11 send GARP request to announce ownership of 192.168.1.100.
++# lp11 send GARP request to announce ownership of 192.168.1.100 and fe80::abcd:1.
+ 
+-sha=f00000000011
+-spa=`ip_to_hex 192 168 1 100`
+-tpa=$spa
++sha="f0:00:00:00:00:11"
++spa="192.168.1.100"
++spa6="fe80::abcd:1"
+ 
+ # When always_learn_from_arp_request=false, the new mac-binding will not be learned
+ # through GARP request.
+ ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=false
+ 
+-test_arp 11 $sha $spa $tpa
++test_arp 11 $sha $spa
++test_na 11 $sha $spa6
+ sleep 1
+-check_row_count MAC_Binding 0 ip="192.168.1.100"
++check_row_count MAC_Binding 0 ip="$spa"
++check_row_count MAC_Binding 0 ip=\"$spa6\"
+ 
+ # When always_learn_from_arp_request=true, the new mac-binding will be learned.
+ ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=true
+ 
+-test_arp 11 $sha $spa $tpa
+-OVS_WAIT_UNTIL([test `ovn-sbctl find mac_binding ip="192.168.1.100" | wc -l` -gt 0])
++test_arp 11 $sha $spa
++test_na 11 $sha $spa6
++wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
+ ovn-nbctl --wait=hv sync
+ 
+ # Send an IP packet from lp21 to 192.168.1.100, which should go to lp11.
+ 
+-smac=f00000000021
+-dmac=00000000ff02
+-sip=`ip_to_hex 192 168 2 11`
+-dip=`ip_to_hex 192 168 1 100`
++smac="f0:00:00:00:00:21"
++dmac="00:00:00:00:ff:02"
++sip="192.168.2.11"
++dip="192.168.1.100"
+ test_ip 21 $smac $dmac $sip $dip 11
+ 
+-# lp12 send GARP request to announce ownership of 192.168.1.100.
++# lp12 send GARP request to announce ownership of 192.168.1.100 and fe80::abcd:1.
+ 
+ # Even when always_learn_from_arp_request=false, the existing mac-binding should be
+ # updated through GARP request.
+ ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=false
+ 
+-sha=f00000000012
+-test_arp 12 $sha $spa $tpa
+-wait_row_count MAC_Binding 1 ip="192.168.1.100" mac='"f0:00:00:00:00:12"'
++sha="f0:00:00:00:00:12"
++test_arp 12 $sha $spa
++test_na 11 $sha $spa6
++wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
+ ovn-nbctl --wait=hv sync
+ # give to the hv the time to send queued ip packets
+ sleep 1
+@@ -5753,7 +5768,7 @@ check ovn-nbctl --wait=hv sync
  packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
          ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
          udp && udp.src==53 && udp.dst==4369"
@@ -3900,7 +6825,7 @@ index 55de7c85b..3515a1e3c 100644
  
  
  echo "---------NB dump-----"
-@@ -5803,7 +5803,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
+@@ -5803,7 +5818,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
          ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
          udp && udp.src==53 && udp.dst==4369"
  
@@ -3909,7 +6834,7 @@ index 55de7c85b..3515a1e3c 100644
  # The 2nd packet sent shound not be received.
  OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
  
-@@ -7741,7 +7741,6 @@ ls3_p1_mac=00:00:00:01:02:05
+@@ -7741,7 +7756,6 @@ ls3_p1_mac=00:00:00:01:02:05
  check ovn-nbctl --wait=hv lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop
  
  # Check logical flow
@@ -3917,7 +6842,7 @@ index 55de7c85b..3515a1e3c 100644
  AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl
  1
  ])
-@@ -7751,15 +7750,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+@@ -7751,15 +7765,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
  
@@ -3936,7 +6861,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected to drop the packet.
  $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
-@@ -7770,7 +7766,7 @@ AT_FAIL_IF([test "$rcvd_packet" != ""])
+@@ -7770,7 +7781,7 @@ AT_FAIL_IF([test "$rcvd_packet" != ""])
  check ovn-nbctl --wait=hv lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow
  
  # Check logical flow
@@ -3945,7 +6870,7 @@ index 55de7c85b..3515a1e3c 100644
  2
  ])
  
-@@ -7778,15 +7774,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l]
+@@ -7778,15 +7789,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l]
  packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
@@ -3965,7 +6890,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected packet has TTL decreased by 1
  expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
-@@ -7802,7 +7795,7 @@ check ovn-nbctl --wait=hv lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.ds
+@@ -7802,7 +7810,7 @@ check ovn-nbctl --wait=hv lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.ds
  # Check logical flow
  AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
      grep "192.168.1.0" | \
@@ -3974,7 +6899,7 @@ index 55de7c85b..3515a1e3c 100644
  1
  ])
  
-@@ -7810,21 +7803,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
+@@ -7810,21 +7818,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
  packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
@@ -4000,7 +6925,7 @@ index 55de7c85b..3515a1e3c 100644
  echo "packet hit reroute policy"
  
  # Expected packet has TTL decreased by 1
-@@ -7927,9 +7911,7 @@ ls3_p1_mac=00:00:00:01:02:05
+@@ -7927,9 +7926,7 @@ ls3_p1_mac=00:00:00:01:02:05
  check ovn-nbctl --wait=sb lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop
  
  # Check logical flow
@@ -4011,7 +6936,7 @@ index 55de7c85b..3515a1e3c 100644
  1
  ])
  
-@@ -7938,15 +7920,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+@@ -7938,15 +7935,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
  
@@ -4030,7 +6955,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected to drop the packet.
  $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
-@@ -7956,9 +7935,7 @@ AT_FAIL_IF([test -s vif2.packets])
+@@ -7956,9 +7950,7 @@ AT_FAIL_IF([test -s vif2.packets])
  check ovn-nbctl --wait=sb lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow
  
  # Check logical flow
@@ -4041,7 +6966,7 @@ index 55de7c85b..3515a1e3c 100644
  2
  ])
  
-@@ -7966,16 +7943,12 @@ AT_CHECK([grep lr_in_policy sbflows2 | grep "2001" | wc -l], [0], [dnl
+@@ -7966,16 +7958,12 @@ AT_CHECK([grep lr_in_policy sbflows2 | grep "2001" | wc -l], [0], [dnl
  packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
@@ -4062,7 +6987,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected packet has TTL decreased by 1
  expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
-@@ -7989,11 +7962,9 @@ OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected])
+@@ -7989,11 +7977,9 @@ OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected])
  check ovn-nbctl --wait=sb lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2
  
  # Check logical flow
@@ -4076,7 +7001,7 @@ index 55de7c85b..3515a1e3c 100644
  1
  ])
  
-@@ -8001,19 +7972,12 @@ AT_CHECK([grep lr_in_policy sbflows4 | \
+@@ -8001,19 +7987,12 @@ AT_CHECK([grep lr_in_policy sbflows4 | \
  packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
         ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
         udp && udp.src==53 && udp.dst==4369"
@@ -4100,7 +7025,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected packet has TTL decreased by 1
  expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac &&
-@@ -9531,73 +9495,73 @@ AT_CAPTURE_FILE([sbflows])
+@@ -9531,73 +9510,73 @@ AT_CAPTURE_FILE([sbflows])
  packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
          ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
          tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==80"
@@ -4184,9 +7109,86 @@ index 55de7c85b..3515a1e3c 100644
 -as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
 +OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
  
- OVS_WAIT_UNTIL([ test 8 = $(grep -c 'acl_log' hv/ovn-controller.log) ])
+ OVS_WAIT_UNTIL([ test 8 = $(grep -c 'acl_log' hv/ovn-controller.log) ])
+ 
+@@ -10206,14 +10185,21 @@ AT_CHECK([test ! -z $foo2_zoneid])
+ bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2)
+ AT_CHECK([test ! -z $bar2_zoneid])
+ 
+-ovn-nbctl lsp-del bar2
++# When a port is removed from a logical switch, the ct-zone is flushed, then
++# the ct-zone-id is removed from external_ids. This is done in two steps(
++# ct-zone-id is removed when the transaction flushing the ct_zone is complete).
++# ovn-nbctl --wait=hv sync does not take this into account, and hence we need
++# two "wait=hv" before we are sure that the ct-zone-id is removed from
++# external_ids.
++ovn-nbctl --wait=hv lsp-del bar2
+ ovn-nbctl --wait=hv sync
+ 
+ bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int external_ids:ct-zone-bar2)
+ AT_CHECK([test  -z $bar2_zoneid])
+ 
+ # Add back bar2
+-ovn-nbctl lsp-add bar bar2 vm2 1 \
++# Same comment as above: two "wait=hv" are needed.
++ovn-nbctl --wait=hv lsp-add bar bar2 vm2 1 \
+ -- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3"
+ wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+@@ -11214,7 +11200,7 @@ hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ov
+ hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
+ 
+ OVS_WAIT_UNTIL([
+-    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport")
++    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=39 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport")
+ ])
+ 
+ test_ip_packet()
+@@ -11324,7 +11310,7 @@ AT_CHECK(
+ ])
+ 
+ OVS_WAIT_UNTIL([
+-    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport")
++    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=39 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport")
+ ])
+ 
+ test_ip_packet gw2 gw1 0
+@@ -11502,7 +11488,7 @@ hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ov
+ hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0)
+ 
+ OVS_WAIT_UNTIL([
+-    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport")
++    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=39 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport")
+ ])
+ 
+ test_ip_packet()
+@@ -11582,7 +11568,7 @@ AT_CHECK([ovn-nbctl --wait=hv \
+ ])
+ 
+ OVS_WAIT_UNTIL([
+-    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport")
++    test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=39 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport")
+ ])
  
-@@ -12254,7 +12218,7 @@ nexthop_mac="f00000010204"
+ test_ip_packet gw2 gw1
+@@ -11748,12 +11734,12 @@ AT_CAPTURE_FILE([hv2flows])
+ 
+ AT_CHECK(
+   [# Check that redirect mapping is programmed only on hv2
+-   grep table=38 hv1flows | grep =0x3,metadata=0x1 | wc -l
+-   grep table=38 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
++   grep table=40 hv1flows | grep =0x3,metadata=0x1 | wc -l
++   grep table=40 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
+ 
+    # Check that hv1 sends chassisredirect port traffic to hv2
+-   grep table=37 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
+-   grep table=37 hv2flows | grep =0x3,metadata=0x1 | wc -l
++   grep table=39 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
++   grep table=39 hv2flows | grep =0x3,metadata=0x1 | wc -l
+ 
+    # Check that arp reply on distributed gateway port is only programmed on hv2
+    grep arp hv1flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l
+@@ -12254,7 +12240,7 @@ nexthop_mac="f00000010204"
  AS_BOX([Send ip packet from foo1 to 8.8.8.8])
  src_mac="f00000010203"
  dst_mac="000001010203"
@@ -4195,7 +7197,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AS_BOX([Wait for GARPs announcing gw IP to arrive])
  OVS_WAIT_UNTIL([
-@@ -12265,15 +12229,12 @@ grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
+@@ -12265,15 +12251,12 @@ grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
  AS_BOX([Verify VLAN tagged packet on bridge connecting hv1 and hv2])
  # VLAN tagged packet with router port(192.168.1.1) MAC as destination MAC
  # is expected on bridge connecting hv1 and hv2
@@ -4213,7 +7215,18 @@ index 55de7c85b..3515a1e3c 100644
  echo $expected > hv3-vif1.expected
  
  check as hv1 ovs-appctl dpctl/del-flows
-@@ -12304,7 +12265,7 @@ cat hv1-br-ex_n2.expected > expout
+@@ -12284,8 +12267,8 @@ as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+ as hv1 ovs-appctl ofproto/trace br-int in_port=hv1-vif1 $packet
+ sleep 2
+ 
+-AS_BOX([On hv1, table 37 check that no packet goes via the tunnel port])
+-OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 \
++AS_BOX([On hv1, table 40 check that no packet goes via the tunnel port])
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 \
+ | grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0
+ ]])
+ 
+@@ -12304,7 +12287,7 @@ cat hv1-br-ex_n2.expected > expout
  AT_CHECK([sort hv1-br-ex_n2], [0], [expout])
  
  AS_BOX([Check expected packet on nexthop interface])
@@ -4222,20 +7235,30 @@ index 55de7c85b..3515a1e3c 100644
  cat hv3-vif1.expected > expout
  AT_CHECK([sort hv3-vif1], [0], [expout])
  
-@@ -13268,30 +13229,27 @@ as hv2 ovs-ofctl dump-flows br-int table=37
+@@ -13260,38 +13243,35 @@ echo $hv2_gw1_ofport
+ echo $hv2_gw2_ofport
+ 
+ echo "--- hv1 ---"
+-as hv1 ovs-ofctl dump-flows br-int table=37
++as hv1 ovs-ofctl dump-flows br-int table=39
+ 
+ echo "--- hv2 ---"
+-as hv2 ovs-ofctl dump-flows br-int table=37
++as hv2 ovs-ofctl dump-flows br-int table=39
+ 
  gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
  gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
  
 -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
  | wc -l], [0], [1
  ])
  
 -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
  | wc -l], [0], [1
  ])
@@ -4265,25 +7288,25 @@ index 55de7c85b..3515a1e3c 100644
  
  # check that the chassis redirect port has been claimed by the gw1 chassis
  wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
-@@ -13314,13 +13272,13 @@ wait_for_ports_up
+@@ -13314,13 +13294,13 @@ wait_for_ports_up
  check ovn-nbctl --wait=hv sync
  
  # we make sure that the hypervisors noticed, and inverted the slave ports
 -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv1_gw2_ofport,$hv1_gw1_ofport \
  | wc -l], [0], [1
  ])
  
 -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv2_gw2_ofport,$hv2_gw1_ofport \
  | wc -l], [0], [1
  ])
  
-@@ -13372,11 +13330,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+@@ -13372,11 +13352,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
  ]])
  
  # make sure that flows for handling the outside router port reside on gw2 now
@@ -4299,7 +7322,7 @@ index 55de7c85b..3515a1e3c 100644
  ]])
  
  # disconnect GW2 from the network, GW1 should take over
-@@ -13386,12 +13344,12 @@ as main ovs-vsctl del-port n1 $port
+@@ -13386,12 +13366,12 @@ as main ovs-vsctl del-port n1 $port
  
  bfd_dump
  
@@ -4317,14 +7340,14 @@ index 55de7c85b..3515a1e3c 100644
  ]])
  
  # check that the chassis redirect port has been reclaimed by the gw1 chassis
-@@ -13470,45 +13428,16 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
+@@ -13470,45 +13450,16 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
  wait_row_count HA_Chassis_Group 1
  wait_row_count HA_Chassis 2
  
 -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
 -| wc -l], [0], [1
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
 +| wc -l], [0], [0
  ])
@@ -4332,7 +7355,7 @@ index 55de7c85b..3515a1e3c 100644
 -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
 -| wc -l], [0], [1
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
 +| wc -l], [0], [0
  ])
@@ -4369,19 +7392,19 @@ index 55de7c85b..3515a1e3c 100644
  # Re add the ovs ports.
  for i in 1 2; do
      as hv$i
-@@ -13519,6 +13448,34 @@ for i in 1 2; do
+@@ -13519,6 +13470,34 @@ for i in 1 2; do
          ofport-request=1
  done
  
 +# Re-add gw2
 +as gw2 ovn_attach n1 br-phys 192.168.0.1
 +
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
 +| wc -l], [0], [1
 +])
 +
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
 +| wc -l], [0], [1
 +])
@@ -4404,20 +7427,20 @@ index 55de7c85b..3515a1e3c 100644
  hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
  hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2)
  exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid"
-@@ -13527,29 +13484,18 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
+@@ -13527,29 +13506,18 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
  # Increase the priority of gw2
  ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
  
 -OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv1_gw2_ofport,$hv1_gw1_ofport \
  | wc -l], [0], [1
  ])
  
 -OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
 -grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
-+OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=39 | \
 +grep active_backup | grep members:$hv2_gw2_ofport,$hv2_gw1_ofport \
  | wc -l], [0], [1
  ])
@@ -4439,7 +7462,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # check BFD enablement on tunnel ports from gw1 #########
  as gw1
-@@ -13588,11 +13534,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+@@ -13588,11 +13556,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
  ]])
  
  # make sure that flows for handling the outside router port reside on gw2 now
@@ -4455,7 +7478,7 @@ index 55de7c85b..3515a1e3c 100644
  ]])
  
  # disconnect GW2 from the network, GW1 should take over
-@@ -13603,11 +13549,11 @@ as main ovs-vsctl del-port n1 $port
+@@ -13603,11 +13571,11 @@ as main ovs-vsctl del-port n1 $port
  bfd_dump
  
  # make sure that flows for handling the outside router port reside on gw2 now
@@ -4471,7 +7494,7 @@ index 55de7c85b..3515a1e3c 100644
  ]])
  
  # check that the chassis redirect port has been reclaimed by the gw1 chassis
-@@ -13889,6 +13835,133 @@ OVN_CLEANUP([gw1],[gw2],[hv1])
+@@ -13889,6 +13857,133 @@ OVN_CLEANUP([gw1],[gw2],[hv1])
  AT_CLEANUP
  ])
  
@@ -4605,7 +7628,390 @@ index 55de7c85b..3515a1e3c 100644
  OVN_FOR_EACH_NORTHD([
  AT_SETUP([IPv6 Neighbor Solicitation for unknown MAC])
  AT_KEYWORDS([ovn-nd_ns for unknown mac])
-@@ -17210,7 +17283,7 @@ test_icmp() {
+@@ -14162,10 +14257,12 @@ wait_column "$hv1_uuid" Port_Binding requested_chassis logical_port=lsp0
+ wait_column "$hv2_uuid" Port_Binding additional_chassis logical_port=lsp0
+ wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=lsp0
+ 
+-# Check ovn-installed updated for main chassis
++# Check ovn-installed updated for both chassis
+ wait_for_ports_up
+-OVS_WAIT_UNTIL([test `as hv1 ovs-vsctl get Interface lsp0 external_ids:ovn-installed` = '"true"'])
+-OVS_WAIT_UNTIL([test x`as hv2 ovs-vsctl get Interface lsp0 external_ids:ovn-installed` = x])
++
++for hv in hv1 hv2; do
++    OVS_WAIT_UNTIL([test `as $hv ovs-vsctl get Interface lsp0 external_ids:ovn-installed` = '"true"'])
++done
+ 
+ # Check that setting iface:encap-ip populates Port_Binding:additional_encap
+ wait_row_count Encap 2 chassis_name=hv1
+@@ -14192,7 +14289,7 @@ wait_column "$hv2_uuid" Port_Binding requested_chassis logical_port=lsp0
+ wait_column "" Port_Binding additional_chassis logical_port=lsp0
+ wait_column "" Port_Binding requested_additional_chassis logical_port=lsp0
+ 
+-# Check ovn-installed updated for main chassis and not for other chassis
++# Check ovn-installed updated for main chassis and removed from additional chassis
+ wait_for_ports_up
+ OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface lsp0 external_ids:ovn-installed` = '"true"'])
+ OVS_WAIT_UNTIL([test x`as hv1 ovs-vsctl get Interface lsp0 external_ids:ovn-installed` = x])
+@@ -15071,6 +15168,327 @@ OVN_CLEANUP([hv1],[hv2],[hv3])
+ AT_CLEANUP
+ ])
+ 
++m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
++  [OVN_FOR_EACH_NORTHD([
++   AT_SETUP([localnet connectivity with multiple requested-chassis, path mtu discovery (ip=$1, tunnel=$2, mtu=$3)])
++   AT_KEYWORDS([multi-chassis])
++   AT_SKIP_IF([test $HAVE_SCAPY = no])
++
++   ovn_start
++
++   net_add n1
++   for i in 1 2; do
++       sim_add hv$i
++       as hv$i
++       check ovs-vsctl add-br br-phys
++       if test "x$1" = "xipv6"; then
++           ovn_attach n1 br-phys fd00::$i 64 $2
++       else
++           ovn_attach n1 br-phys 192.168.0.$i 24 $2
++       fi
++       check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
++   done
++
++   first_mac=00:00:00:00:00:01
++   second_mac=00:00:00:00:00:02
++   multi1_mac=00:00:00:00:00:f0
++   multi2_mac=00:00:00:00:00:f1
++   first_ip=10.0.0.1
++   second_ip=10.0.0.2
++   multi1_ip=10.0.0.10
++   multi2_ip=10.0.0.20
++   first_ip6=abcd::1
++   second_ip6=abcd::2
++   multi1_ip6=abcd::f0
++   multi2_ip6=abcd::f1
++
++   check ovn-nbctl ls-add ls0
++   check ovn-nbctl lsp-add ls0 first
++   check ovn-nbctl lsp-add ls0 second
++   check ovn-nbctl lsp-add ls0 multi1
++   check ovn-nbctl lsp-add ls0 multi2
++   check ovn-nbctl lsp-set-addresses first "${first_mac} ${first_ip} ${first_ip6}"
++   check ovn-nbctl lsp-set-addresses second "${second_mac} ${second_ip} ${second_ip6}"
++   check ovn-nbctl lsp-set-addresses multi1 "${multi1_mac} ${multi1_ip} ${multi1_ip6}"
++   check ovn-nbctl lsp-set-addresses multi2 "${multi2_mac} ${multi2_ip} ${multi2_ip6}"
++
++   check ovn-nbctl lsp-add ls0 public
++   check ovn-nbctl lsp-set-type public localnet
++   check ovn-nbctl lsp-set-addresses public unknown
++   check ovn-nbctl lsp-set-options public network_name=phys
++
++   check ovn-nbctl lsp-set-options first requested-chassis=hv1
++   check ovn-nbctl lsp-set-options second requested-chassis=hv2
++   check ovn-nbctl lsp-set-options multi1 requested-chassis=hv1,hv2
++   check ovn-nbctl lsp-set-options multi2 requested-chassis=hv1,hv2
++
++   as hv1 check ovs-vsctl -- add-port br-int first -- \
++       set Interface first external-ids:iface-id=first \
++       options:tx_pcap=hv1/first-tx.pcap \
++       options:rxq_pcap=hv1/first-rx.pcap \
++       ofport-request=1
++   as hv2 check ovs-vsctl -- add-port br-int second -- \
++       set Interface second external-ids:iface-id=second \
++       options:tx_pcap=hv2/second-tx.pcap \
++       options:rxq_pcap=hv2/second-rx.pcap \
++       ofport-request=2
++
++   # Create interfaces for multichassis ports on both hv1 and hv2
++   for hv in hv1 hv2; do
++       for i in 1 2; do
++           as $hv check ovs-vsctl -- add-port br-int multi${i} -- \
++               set Interface multi${i} external-ids:iface-id=multi${i} \
++               options:tx_pcap=$hv/multi${i}-tx.pcap \
++               options:rxq_pcap=$hv/multi${i}-rx.pcap \
++               ofport-request=${i}00
++       done
++   done
++
++   send_ip_packet() {
++       local inport=${1} hv=${2} eth_src=${3} eth_dst=${4} ipv4_src=${5} ipv4_dst=${6} data=${7} fail=${8} mtu=${9:-$3}
++       packet=$(fmt_pkt "
++           Ether(dst='${eth_dst}', src='${eth_src}') /
++           IP(src='${ipv4_src}', dst='${ipv4_dst}') /
++           ICMP(type=8) / bytes.fromhex('${data}')
++       ")
++       as hv${hv} ovs-appctl netdev-dummy/receive ${inport} ${packet}
++       if [[ x"${fail}" != x0 ]]; then
++         original_ip_frame=$(fmt_pkt "
++           IP(src='${ipv4_src}', dst='${ipv4_dst}') /
++           ICMP(type=8) / bytes.fromhex('${data}')
++         ")
++         # IP(flags=2) means DF (Don't Fragment) = 1
++         # ICMP(type=3, code=4) means Destination Unreachable, Fragmentation Needed
++         packet=$(fmt_pkt "
++             Ether(dst='${eth_src}', src='${eth_dst}') /
++             IP(src='${ipv4_dst}', dst='${ipv4_src}', ttl=255, flags=2, id=0) /
++             ICMP(type=3, code=4, nexthopmtu=${mtu}) /
++             bytes.fromhex('${original_ip_frame:0:$((534 * 2))}')
++         ")
++       fi
++       echo ${packet}
++   }
++
++   send_ip6_packet() {
++       local inport=${1} hv=${2} eth_src=${3} eth_dst=${4} ipv6_src=${5} ipv6_dst=${6} data=${7} fail=${8} mtu=${9:-$3}
++       packet=$(fmt_pkt "
++           Ether(dst='${eth_dst}', src='${eth_src}') /
++           IPv6(src='${ipv6_src}', dst='${ipv6_dst}') /
++           ICMPv6EchoRequest() / bytes.fromhex('${data}')
++       ")
++       as hv${hv} ovs-appctl netdev-dummy/receive ${inport} ${packet}
++       if [[ x"${fail}" != x0 ]]; then
++         original_ip_frame=$(fmt_pkt "
++           IPv6(src='${ipv6_src}', dst='${ipv6_dst}') /
++           ICMPv6EchoRequest() / bytes.fromhex('${data}')
++         ")
++         packet=$(fmt_pkt "
++             Ether(dst='${eth_src}', src='${eth_dst}') /
++             IPv6(src='${ipv6_dst}', dst='${ipv6_src}', hlim=255) /
++             ICMPv6PacketTooBig(mtu=${mtu}) /
++             bytes.fromhex('${original_ip_frame:0:$((1218 * 2))}')
++         ")
++       fi
++       echo ${packet}
++   }
++
++   reset_env() {
++       for port in first multi1 multi2; do
++           as hv1 reset_pcap_file $port hv1/$port
++       done
++       for port in second multi1 multi2; do
++           as hv2 reset_pcap_file $port hv2/$port
++       done
++       for port in hv1/multi1 hv2/multi1 hv1/multi2 hv2/multi2 hv1/first hv2/second; do
++           : > $port.expected
++       done
++   }
++
++   check_pkts() {
++       for port in hv1/multi1 hv2/multi1 hv1/multi2 hv2/multi2 hv1/first hv2/second; do
++           OVN_CHECK_PACKETS_REMOVE_BROADCAST([${port}-tx.pcap], [${port}.expected])
++       done
++   }
++
++   payload() {
++       echo $(cat /dev/urandom | tr -cd 'a-f0-9' | head -c ${1})
++   }
++
++   wait_for_ports_up
++   OVN_POPULATE_ARP
++
++   reset_env
++
++   AS_BOX([Packets of proper size are delivered from multichassis to regular ports])
++
++   len=1000
++   packet=$(send_ip_packet multi1 1 $multi1_mac $first_mac $multi1_ip $first_ip $(payload $len) 0)
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip_packet multi1 1 $multi1_mac $second_mac $multi1_ip $second_ip $(payload $len) 0)
++   echo $packet >> hv2/second.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $first_mac $multi1_ip6 $first_ip6 $(payload $len) 0)
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $second_mac $multi1_ip6 $second_ip6 $(payload $len) 0)
++   echo $packet >> hv2/second.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([Oversized packets are not delivered from multichassis to regular ports])
++
++   len=3000
++   packet=$(send_ip_packet multi1 1 $multi1_mac $first_mac $multi1_ip $first_ip $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip_packet multi1 1 $multi1_mac $second_mac $multi1_ip $second_ip $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $first_mac $multi1_ip6 $first_ip6 $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $second_mac $multi1_ip6 $second_ip6 $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([Packets of proper size are delivered from regular to multichassis ports])
++
++   len=1000
++   packet=$(send_ip_packet first 1 $first_mac $multi1_mac $first_ip $multi1_ip $(payload $len) 0)
++   echo $packet >> hv1/multi1.expected
++   echo $packet >> hv2/multi1.expected
++
++   packet=$(send_ip_packet second 2 $second_mac $multi1_mac $second_ip $multi1_ip $(payload $len) 0)
++   echo $packet >> hv1/multi1.expected
++   echo $packet >> hv2/multi1.expected
++
++   packet=$(send_ip6_packet first 1 $first_mac $multi1_mac $first_ip6 $multi1_ip6 $(payload $len) 0)
++   echo $packet >> hv1/multi1.expected
++   echo $packet >> hv2/multi1.expected
++
++   packet=$(send_ip6_packet second 2 $second_mac $multi1_mac $second_ip6 $multi1_ip6 $(payload $len) 0)
++   echo $packet >> hv1/multi1.expected
++   echo $packet >> hv2/multi1.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([Oversized packets are not delivered from regular to multichassis ports])
++
++   len=3000
++   packet=$(send_ip_packet first 1 $first_mac $multi1_mac $first_ip $multi1_ip $(payload $len) 1)
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip_packet second 2 $second_mac $multi1_mac $second_ip $multi1_ip $(payload $len) 1)
++   echo $packet >> hv2/second.expected
++
++   packet=$(send_ip6_packet first 1 $first_mac $multi1_mac $first_ip6 $multi1_ip6 $(payload $len) 1)
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip6_packet second 2 $second_mac $multi1_mac $second_ip6 $multi1_ip6 $(payload $len) 1)
++   echo $packet >> hv2/second.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([Packets of proper size are delivered from multichassis to multichassis ports])
++
++   len=1000
++   packet=$(send_ip_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip $multi2_ip $(payload $len) 0)
++   echo $packet >> hv1/multi2.expected
++   echo $packet >> hv2/multi2.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip6 $multi2_ip6 $(payload $len) 0)
++   echo $packet >> hv1/multi2.expected
++   echo $packet >> hv2/multi2.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([Oversized packets are not delivered from multichassis to multichassis ports])
++
++   len=3000
++   packet=$(send_ip_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip $multi2_ip $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip6 $multi2_ip6 $(payload $len) 1)
++   echo $packet >> hv1/multi1.expected
++
++   check_pkts
++   reset_env
++
++   AS_BOX([MTU updates are honored in ICMP Path MTU calculation])
++
++   set_mtu() {
++       local hv=${1} iface=${2} new_mtu=${3}
++
++       iface_uuid=$(as ${hv} ovs-vsctl --bare --columns _uuid find Interface name=${iface})
++       check as ${hv} ovs-vsctl set interface ${iface_uuid} mtu_request=${new_mtu}
++   }
++
++   set_mtu_for_all_ports() {
++       for port in multi1 multi2 first; do
++           set_mtu hv1 ${port} ${1}
++       done
++       for port in multi1 multi2 second; do
++           set_mtu hv2 ${port} ${1}
++       done
++   }
++
++   initial_mtu=1500  # all interfaces are 1500 by default
++   new_mtu=1400
++   set_mtu_for_all_ports ${new_mtu}
++   mtu_diff=$((${initial_mtu} - ${new_mtu}))
++
++   len=3000
++   expected_ip_mtu=$(($3 - ${mtu_diff}))
++   packet=$(send_ip_packet first 1 $first_mac $multi1_mac $first_ip $multi1_ip $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip_packet second 2 $second_mac $multi1_mac $second_ip $multi1_ip $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv2/second.expected
++
++   packet=$(send_ip6_packet first 1 $first_mac $multi1_mac $first_ip6 $multi1_ip6 $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/first.expected
++
++   packet=$(send_ip6_packet second 2 $second_mac $multi1_mac $second_ip6 $multi1_ip6 $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv2/second.expected
++
++   packet=$(send_ip_packet multi1 1 $multi1_mac $first_mac $multi1_ip $first_ip $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip_packet multi1 1 $multi1_mac $second_mac $multi1_ip $second_ip $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $first_mac $multi1_ip6 $first_ip6 $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $second_mac $multi1_ip6 $second_ip6 $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip $multi2_ip $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   packet=$(send_ip6_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip6 $multi2_ip6 $(payload $len) 1 ${expected_ip_mtu})
++   echo $packet >> hv1/multi1.expected
++
++   check_pkts
++
++   OVN_CLEANUP([hv1],[hv2])
++
++   AT_CLEANUP
++   ])])
++
++# NOTE(ihar) no STT variants because it's not supported by upstream kernels
++MULTICHASSIS_PATH_MTU_DISCOVERY_TEST([ipv4], [geneve], [1424])
++MULTICHASSIS_PATH_MTU_DISCOVERY_TEST([ipv6], [geneve], [1404])
++MULTICHASSIS_PATH_MTU_DISCOVERY_TEST([ipv4], [vxlan], [1432])
++MULTICHASSIS_PATH_MTU_DISCOVERY_TEST([ipv6], [vxlan], [1412])
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([options:activation-strategy for logical port])
+ AT_KEYWORDS([multi-chassis])
+@@ -16278,25 +16696,25 @@ sleep 2
+ # Get total number of ipv4 packets that received on ovs
+ 
+ # sender side
+-flow=$(as hv1 ovs-ofctl dump-flows br-int table=44 | grep priority=2002|grep ip,metadata=0x1)
++flow=$(as hv1 ovs-ofctl dump-flows br-int table=46 | grep priority=2002|grep ip,metadata=0x1)
+ n_pkts="$(echo $flow|awk -F',' '{ print $4 }'|awk -F'=' '{ print $2 }')"
+ check test $n_pkts -eq 1
+ 
+ # receiver side
+-flow=$(as hv2 ovs-ofctl dump-flows br-int table=44 | grep priority=2002|grep ip,metadata=0x1)
++flow=$(as hv2 ovs-ofctl dump-flows br-int table=46 | grep priority=2002|grep ip,metadata=0x1)
+ n_pkts="$(echo $flow|awk -F',' '{ print $4 }'|awk -F'=' '{ print $2 }')"
+ check test $n_pkts -eq 1
+ 
+ # Get total number of ipv6 packets that received on ovs
+ 
+ # sender side
+-flow=$(as hv1 ovs-ofctl dump-flows br-int table=44 | grep priority=2002|grep ipv6,metadata=0x1)
++flow=$(as hv1 ovs-ofctl dump-flows br-int table=46 | grep priority=2002|grep ipv6,metadata=0x1)
+ n_pkts="$(echo $flow|awk -F',' '{ print $4 }'|awk -F'=' '{ print $2 }')"
+ check test $n_pkts -eq 1
+ 
+ 
+ # receiver side
+-flow=$(as hv2 ovs-ofctl dump-flows br-int table=44 | grep priority=2002|grep ipv6,metadata=0x1)
++flow=$(as hv2 ovs-ofctl dump-flows br-int table=46 | grep priority=2002|grep ipv6,metadata=0x1)
+ n_pkts="$(echo $flow|awk -F',' '{ print $4 }'|awk -F'=' '{ print $2 }')"
+ check test $n_pkts -eq 1
+ 
+@@ -17210,7 +17628,7 @@ test_icmp() {
                    icmp4.code==0"
      shift; shift; shift; shift; shift; shift
      hv=hv`vif_to_hv $inport`
@@ -4614,7 +8020,165 @@ index 55de7c85b..3515a1e3c 100644
      in_ls=`vif_to_ls $inport`
      in_lrp=`vif_to_lrp $inport`
      for outport; do
-@@ -18276,7 +18349,7 @@ AT_SETUP([TTL exceeded])
+@@ -17856,17 +18274,17 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
+ check ovn-nbctl --wait=hv sync
+ 
+ # Check OVS flows, the less restrictive flows should have been installed.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+     grep "priority=1003" | \
+     sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+     sed 's/conj_id=[[0-9]]*,/conj_id=xxx,/g' | sort], [0], [dnl
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
+ ])
+ 
+ # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+@@ -17901,17 +18319,17 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
+ check ovn-nbctl --wait=hv sync
+ 
+ # Check OVS flows, the second less restrictive allow ACL should have been installed.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+     grep "priority=1003" | \
+     sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+     sed 's/conj_id=[[0-9]]*,/conj_id=xxx,/g' | sort], [0], [dnl
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
+ ])
+ 
+ # Remove the less restrictive allow ACL.
+@@ -17919,17 +18337,17 @@ check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
+ check ovn-nbctl --wait=hv sync
+ 
+ # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+     grep "priority=1003" | \
+     sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+     sed 's/conj_id=[[0-9]]*,/conj_id=xxx,/g' | sort], [0], [dnl
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
+ ])
+ 
+ # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+@@ -17959,17 +18377,17 @@ check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
+ check ovn-nbctl --wait=hv sync
+ 
+ # Check OVS flows, the less restrictive flows should have been installed.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+    grep "priority=1003" | \
+    sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+    sed 's/conj_id=[[0-9]]*,/conj_id=xxx,/g' | sort], [0], [dnl
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
+ ])
+ 
+ # Add another ACL that overlaps with the existing less restrictive ones.
+@@ -17980,20 +18398,20 @@ check ovn-nbctl --wait=hv sync
+ # with an additional conjunction action.
+ #
+ # New non-conjunctive flows should be added to match on 'udp'.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+    grep "priority=1003" | \
+    sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+    sed 's/conj_id=[[0-9]]*,/conj_id=xxx,/g' | sort], [0], [dnl
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45)
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction()
+- table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
+- table=44, priority=1003,udp,metadata=0x1 actions=resubmit(,45)
+- table=44, priority=1003,udp6,metadata=0x1 actions=resubmit(,45)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,conj_id=xxx,ip,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,47)
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction()
++ table=46, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction()
++ table=46, priority=1003,udp,metadata=0x1 actions=resubmit(,47)
++ table=46, priority=1003,udp6,metadata=0x1 actions=resubmit(,47)
+ ])
+ 
+ OVN_CLEANUP([hv1])
+@@ -18048,17 +18466,17 @@ check ovn-nbctl acl-add pg1 to-lport 100 'outport == @pg1 && ip4.src == $as2' al
+ 
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-ovs-ofctl dump-flows br-int table=44
+-AT_CHECK([test `ovs-ofctl dump-flows br-int table=44 | grep -c conj_id` = 2])
++ovs-ofctl dump-flows br-int table=46
++AT_CHECK([test `ovs-ofctl dump-flows br-int table=46 | grep -c conj_id` = 2])
+ 
+ echo -------
+ # Add another address in as1, so that the 1st ACL will now generate 2 conjunctions.
+ ovn-nbctl set address_set as1 addresses="10.0.0.1,10.0.0.2"
+ check ovn-nbctl --wait=hv sync
+ 
+-ovs-ofctl dump-flows br-int table=44
++ovs-ofctl dump-flows br-int table=46
+ # There should be 3 conjunctions in total (2 from 1st ACL + 1 from 2nd ACL)
+-AT_CHECK([test `ovs-ofctl dump-flows br-int table=44 | grep -c conj_id` = 3])
++AT_CHECK([test `ovs-ofctl dump-flows br-int table=46 | grep -c conj_id` = 3])
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -18276,7 +18694,7 @@ AT_SETUP([TTL exceeded])
  AT_KEYWORDS([ttl-exceeded])
  ovn_start
  
@@ -4623,7 +8187,7 @@ index 55de7c85b..3515a1e3c 100644
  #
  # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with
  # ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified and TTL set to 1.
-@@ -18292,6 +18365,7 @@ test_ip_packet() {
+@@ -18292,6 +18710,7 @@ test_ip_packet() {
      local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_router=$7 ip_chksum=$8
      local exp_ip_chksum=$9 exp_icmp_chksum=${10}
      shift 10
@@ -4631,7 +8195,7 @@ index 55de7c85b..3515a1e3c 100644
  
      local ip_ttl=01
      local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}
-@@ -18300,27 +18374,31 @@ test_ip_packet() {
+@@ -18300,27 +18719,31 @@ test_ip_packet() {
      local icmp_type_code_response=0b00
      local icmp_data=00000000
      local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data}
@@ -4669,7 +8233,7 @@ index 55de7c85b..3515a1e3c 100644
  
      as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
  }
-@@ -18343,6 +18421,8 @@ for i in 1 2; do
+@@ -18343,6 +18766,8 @@ for i in 1 2; do
              options:tx_pcap=hv$i/vif$i-tx.pcap \
              options:rxq_pcap=hv$i/vif$i-rx.pcap \
              ofport-request=$i
@@ -4678,7 +8242,7 @@ index 55de7c85b..3515a1e3c 100644
  done
  
  ovn-nbctl lr-add lr0
-@@ -18358,10 +18438,22 @@ OVN_POPULATE_ARP
+@@ -18358,10 +18783,22 @@ OVN_POPULATE_ARP
  wait_for_ports_up
  ovn-nbctl --wait=hv sync
  
@@ -4703,7 +8267,7 @@ index 55de7c85b..3515a1e3c 100644
  OVN_CLEANUP([hv1], [hv2])
  AT_CLEANUP
  ])
-@@ -18656,7 +18748,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
+@@ -18656,7 +19093,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
         udp && udp.src==53 && udp.dst==4369"
  
  # Start by Sending the packet and make sure it makes it there as expected
@@ -4712,7 +8276,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected packet has TTL decreased by 1
  expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
-@@ -18670,7 +18762,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+@@ -18670,7 +19107,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
  as hv2 ovs-appctl -t ovn-controller exit
  
  # Now send the packet again. This time, it should not arrive.
@@ -4721,7 +8285,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
  
-@@ -19552,7 +19644,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
+@@ -19552,7 +19989,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
         udp && udp.src==53 && udp.dst==4369"
  
  # Start by Sending the packet and make sure it makes it there as expected
@@ -4730,7 +8294,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Expected packet has TTL decreased by 1
  expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
-@@ -19566,7 +19658,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+@@ -19566,7 +20003,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
  as hv2 ovs-appctl -t ovn-controller exit --restart
  
  # Now send the packet again. This time, it should still arrive
@@ -4739,7 +8303,7 @@ index 55de7c85b..3515a1e3c 100644
  
  cat expected expected > expected2
  
-@@ -19705,7 +19797,7 @@ test_ip_packet_larger() {
+@@ -19705,7 +20142,7 @@ test_ip_packet_larger() {
      # Set the packet length to 114.
      pkt_len=0072
      packet=${dst_mac}${src_mac}08004500${pkt_len}000000004001c3dd
@@ -4748,7 +8312,7 @@ index 55de7c85b..3515a1e3c 100644
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-@@ -19729,10 +19821,10 @@ test_ip_packet_larger() {
+@@ -19729,10 +20166,10 @@ test_ip_packet_larger() {
          # Packet to expect at br-phys.
          src_mac="000020201213"
          dst_mac="00000012af11"
@@ -4762,7 +8326,7 @@ index 55de7c85b..3515a1e3c 100644
          expected=${expected}000000000000000000000000000000000000
          expected=${expected}000000000000000000000000000000000000
          expected=${expected}000000000000000000000000000000000000
-@@ -19793,7 +19885,7 @@ test_ip_packet_larger_ext() {
+@@ -19793,7 +20230,7 @@ test_ip_packet_larger_ext() {
      # Set the packet length to 114.
      pkt_len=0072
      packet=${dst_mac}${src_mac}08004500${pkt_len}000000004001${checksum}
@@ -4771,7 +8335,7 @@ index 55de7c85b..3515a1e3c 100644
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
      orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
-@@ -19810,7 +19902,7 @@ test_ip_packet_larger_ext() {
+@@ -19810,7 +20247,7 @@ test_ip_packet_larger_ext() {
      dst_ip=`ip_to_hex 172 168 0 4`
      # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload))
      reply_pkt_len=008e
@@ -4780,7 +8344,7 @@ index 55de7c85b..3515a1e3c 100644
      icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01${reply_checksum}
      icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu)
      icmp_reply=${icmp_reply}4500${pkt_len}000000004001${checksum}
-@@ -19985,10 +20077,10 @@ OVS_WAIT_FOR_OUTPUT([
+@@ -19985,10 +20422,10 @@ OVS_WAIT_FOR_OUTPUT([
  ])
  
  AS_BOX([testing ingress traffic mtu 100 - IPv4])
@@ -4793,7 +8357,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AS_BOX([testing ingress traffic mtu 100 - IPv6])
  test_ip6_packet_larger_ext 1 000020201213 20000000000000000000000000000001 100 cc7a
-@@ -20055,10 +20147,10 @@ OVS_WAIT_FOR_OUTPUT([
+@@ -20055,10 +20492,10 @@ OVS_WAIT_FOR_OUTPUT([
  ])
  
  AS_BOX([testing ingress traffic mtu 100 for gw router - IPv4])
@@ -4806,7 +8370,29 @@ index 55de7c85b..3515a1e3c 100644
  
  OVN_CLEANUP([hv1])
  AT_CLEANUP
-@@ -21116,7 +21208,7 @@ check_virtual_offlows_not_present hv2
+@@ -21012,9 +21449,9 @@ check_virtual_offlows_present() {
+     lr0_dp_key=$(printf "%x" $(fetch_column Datapath_Binding tunnel_key external_ids:name=lr0))
+     lr0_public_dp_key=$(printf "%x" $(fetch_column Port_Binding tunnel_key logical_port=lr0-public))
+ 
+-    AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
+- table=44, priority=2000,ip,metadata=0x$sw0_dp_key actions=resubmit(,45)
+- table=44, priority=2000,ipv6,metadata=0x$sw0_dp_key actions=resubmit(,45)
++    AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | grep "priority=2000"], [0], [dnl
++ table=46, priority=2000,ip,metadata=0x$sw0_dp_key actions=resubmit(,47)
++ table=46, priority=2000,ipv6,metadata=0x$sw0_dp_key actions=resubmit(,47)
+ ])
+ 
+     AT_CHECK_UNQUOTED([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
+@@ -21025,7 +21462,7 @@ check_virtual_offlows_present() {
+ 
+ check_virtual_offlows_not_present() {
+     hv=$1
+-    AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl
++    AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=47 | ofctl_strip_all | grep "priority=2000"], [1], [dnl
+ ])
+ 
+     AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \
+@@ -21116,7 +21553,7 @@ check_virtual_offlows_not_present hv2
  send_garp 1 1 $eth_src $eth_dst $spa $tpa
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4815,7 +8401,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
  logical_port=sw0-vir) = xsw0-p1])
-@@ -21184,7 +21276,7 @@ tpa=$(ip_to_hex 10 0 0 10)
+@@ -21184,7 +21621,7 @@ tpa=$(ip_to_hex 10 0 0 10)
  send_garp 1 2 $eth_src $eth_dst $spa $tpa
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4824,7 +8410,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
  logical_port=sw0-vir) = xsw0-p3])
-@@ -21217,7 +21309,7 @@ tpa=$(ip_to_hex 10 0 0 10)
+@@ -21217,7 +21654,7 @@ tpa=$(ip_to_hex 10 0 0 10)
  send_garp 2 1 $eth_src $eth_dst $spa $tpa
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4833,7 +8419,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
  logical_port=sw0-vir) = xsw0-p2])
-@@ -21249,7 +21341,7 @@ tpa=$(ip_to_hex 10 0 0 4)
+@@ -21249,7 +21686,7 @@ tpa=$(ip_to_hex 10 0 0 4)
  send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4842,7 +8428,7 @@ index 55de7c85b..3515a1e3c 100644
  sleep 1
  
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
-@@ -21275,7 +21367,7 @@ check_virtual_offlows_not_present hv2
+@@ -21275,7 +21712,7 @@ check_virtual_offlows_not_present hv2
  as hv1 ovs-vsctl del-port hv1-vif1
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4851,7 +8437,7 @@ index 55de7c85b..3515a1e3c 100644
  sleep 1
  
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
-@@ -21310,7 +21402,7 @@ send_arp_reply 2 1 $eth_src $eth_dst $spa $tpa
+@@ -21310,7 +21747,7 @@ send_arp_reply 2 1 $eth_src $eth_dst $spa $tpa
  sleep 1
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4860,7 +8446,7 @@ index 55de7c85b..3515a1e3c 100644
  sleep 1
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
  logical_port=sw0-vir) = xsw0-p2])
-@@ -21335,7 +21427,7 @@ check_virtual_offlows_not_present hv1
+@@ -21335,7 +21772,7 @@ check_virtual_offlows_not_present hv1
  ovn-nbctl lsp-del sw0-p2
  
  OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
@@ -4869,7 +8455,7 @@ index 55de7c85b..3515a1e3c 100644
  AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
  logical_port=sw0-vir) = x])
  
-@@ -21516,7 +21608,7 @@ AT_CAPTURE_FILE([offlows])
+@@ -21516,7 +21953,7 @@ AT_CAPTURE_FILE([offlows])
  packet0="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:00:00:21 &&
           ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.1.100 &&
           tcp && tcp.src==10000 && tcp.dst==80"
@@ -4878,7 +8464,7 @@ index 55de7c85b..3515a1e3c 100644
  ovn-nbctl --wait=hv
  
  ovn-sbctl list controller_event > events
-@@ -21545,7 +21637,7 @@ packet1="inport==\"sw1-p0\" && eth.src==00:00:00:00:00:33 && eth.dst==00:00:00:0
+@@ -21545,7 +21982,7 @@ packet1="inport==\"sw1-p0\" && eth.src==00:00:00:00:00:33 && eth.dst==00:00:00:0
           ip4 && ip.ttl==64 && ip4.src==192.168.2.11 && ip4.dst==192.168.2.100 &&
           tcp && tcp.src==10000 && tcp.dst==80"
  
@@ -4887,7 +8473,7 @@ index 55de7c85b..3515a1e3c 100644
  ovn-nbctl --wait=hv
  ovn-sbctl list controller_event
  uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}')
-@@ -21561,7 +21653,7 @@ packet2="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:
+@@ -21561,7 +21998,7 @@ packet2="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:
           ip6 && ip.ttl==64 && ip6.src==2001::11 && ip6.dst==2001::10 &&
           tcp && tcp.src==10000 && tcp.dst==50051"
  
@@ -4896,7 +8482,16 @@ index 55de7c85b..3515a1e3c 100644
  ovn-nbctl --wait=hv
  ovn-sbctl list controller_event
  uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}')
-@@ -23744,7 +23836,7 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+@@ -23619,7 +24056,7 @@ m4_define([DVR_N_S_PING],
+    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=38 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
++   AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=39 | grep NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
+ ]])
+ 
+    # Confirm that packet went out via localnet port
+@@ -23744,7 +24181,7 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
  
  wait_row_count MAC_Binding 1
  
@@ -4905,7 +8500,7 @@ index 55de7c85b..3515a1e3c 100644
  list mac_binding], [0], [lr0-sw0
  10.0.0.30
  50:54:00:00:00:03
-@@ -23791,7 +23883,7 @@ grep table_id=10 | wc -l`])
+@@ -23791,7 +24228,7 @@ grep table_id=10 | wc -l`])
  
  check_row_count MAC_Binding 1
  
@@ -4914,7 +8509,7 @@ index 55de7c85b..3515a1e3c 100644
  list mac_binding], [0], [lr0-sw0
  10.0.0.30
  50:54:00:00:00:13
-@@ -23820,7 +23912,7 @@ OVS_WAIT_UNTIL(
+@@ -23820,7 +24257,7 @@ OVS_WAIT_UNTIL(
  | wc -l`]
  )
  
@@ -4923,7 +8518,7 @@ index 55de7c85b..3515a1e3c 100644
  find mac_binding ip=10.0.0.50], [0], [lr0-sw0
  10.0.0.50
  50:54:00:00:00:33
-@@ -24377,7 +24469,7 @@ AT_CAPTURE_FILE([sbflows2])
+@@ -24377,7 +24814,7 @@ AT_CAPTURE_FILE([sbflows2])
  OVS_WAIT_FOR_OUTPUT(
    [ovn-sbctl dump-flows > sbflows2
     ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
@@ -4932,7 +8527,7 @@ index 55de7c85b..3515a1e3c 100644
  ])
  
  # get the svc monitor mac.
-@@ -24419,8 +24511,7 @@ AT_CHECK(
+@@ -24419,8 +24856,7 @@ AT_CHECK(
  AT_CAPTURE_FILE([sbflows4])
  ovn-sbctl dump-flows lr0 > sbflows4
  AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
@@ -4942,7 +8537,7 @@ index 55de7c85b..3515a1e3c 100644
  ])
  
  # Delete sw0-p1
-@@ -24576,7 +24667,7 @@ AT_CAPTURE_FILE([sbflows2])
+@@ -24576,7 +25012,7 @@ AT_CAPTURE_FILE([sbflows2])
  OVS_WAIT_FOR_OUTPUT(
    [ovn-sbctl dump-flows > sbflows2
     ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
@@ -4951,7 +8546,7 @@ index 55de7c85b..3515a1e3c 100644
  ])
  
  # get the svc monitor mac.
-@@ -24618,8 +24709,7 @@ AT_CHECK(
+@@ -24618,8 +25054,7 @@ AT_CHECK(
  AT_CAPTURE_FILE([sbflows4])
  ovn-sbctl dump-flows lr0 > sbflows4
  AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
@@ -4961,7 +8556,7 @@ index 55de7c85b..3515a1e3c 100644
  ])
  
  # Delete sw0-p1
-@@ -25447,7 +25537,7 @@ for s_az in $(seq 1 $n_az); do
+@@ -25447,7 +25882,7 @@ for s_az in $(seq 1 $n_az); do
                      udp && udp.src==53 && udp.dst==4369"
              echo "sending: $packet"
              AT_CHECK([ovn_trace --ovs "$packet" > ${s_az}-${d_az}-$i.ovn-trace])
@@ -4970,7 +8565,7 @@ index 55de7c85b..3515a1e3c 100644
              ovs_inport=$(ovs-vsctl --bare --columns=ofport find Interface external-ids:iface-id="$ovn_inport")
  
              ovs_packet=$(echo $packet | ovstest test-ovn expr-to-packets)
-@@ -26002,7 +26092,7 @@ for i in $(seq 5001 5010); do
+@@ -26002,7 +26437,7 @@ for i in $(seq 5001 5010); do
      packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
              ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.123 &&
              tcp && tcp.src==$i && tcp.dst==80"
@@ -4979,7 +8574,7 @@ index 55de7c85b..3515a1e3c 100644
  
      for j in 1 2; do
          # Assume all packets go to lsp2${j}.
-@@ -26121,7 +26211,7 @@ wait_for_ports_up
+@@ -26121,7 +26556,7 @@ wait_for_ports_up
  # Test 1
  packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
          ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==2.2.2.2 && icmp"
@@ -4988,7 +8583,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Assume no packets go neither to lsp21 nor to lsp22.
  > expected_lsp21
-@@ -26151,7 +26241,7 @@ done
+@@ -26151,7 +26586,7 @@ done
  # Test 2
  packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
          ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==1.1.1.1 && icmp"
@@ -4997,7 +8592,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Assume all packets go to lsp22.
  exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:22 &&
-@@ -26181,7 +26271,7 @@ done
+@@ -26181,7 +26616,7 @@ done
  # Test 3
  packet="inport==\"lsp21\" && eth.src==f0:00:00:00:02:21 && eth.dst==00:00:00:01:02:01 &&
          ip4 && ip.ttl==64 && ip4.src==192.168.2.21 && ip4.dst==2.2.2.2 && icmp"
@@ -5006,7 +8601,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Assume all packets go to lsp21.
  exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 &&
-@@ -26278,7 +26368,7 @@ wait_for_ports_up
+@@ -26278,7 +26713,7 @@ wait_for_ports_up
  # test 1
  packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
          ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.21 && icmp"
@@ -5015,7 +8610,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Assume all packets go to lsp21.
  exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 && ip4 &&
-@@ -26312,7 +26402,7 @@ ovs-vsctl set interface hv1-vif2 options:tx_pcap=hv1/vif2-tx.pcap
+@@ -26312,7 +26747,7 @@ ovs-vsctl set interface hv1-vif2 options:tx_pcap=hv1/vif2-tx.pcap
  # test 2
  packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
          ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.200 && icmp"
@@ -5024,7 +8619,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Assume all packets go to lsp11.
  exp_packet="eth.src==00:00:00:01:01:01 && eth.dst==f0:00:00:00:01:11 && ip4 &&
-@@ -26417,7 +26507,7 @@ for i in $(seq 1 2); do
+@@ -26417,7 +26852,7 @@ for i in $(seq 1 2); do
      packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} &&
              eth.dst==00:00:00:01:0${i}:01 && ip4 && ip.ttl==64 &&
              ip4.src==192.168.${i}.${i}1 && ip4.dst==10.0.0.1 && icmp"
@@ -5033,7 +8628,7 @@ index 55de7c85b..3515a1e3c 100644
  
      # Assume all packets go to lsp${di}1.
      exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} &&
-@@ -26530,7 +26620,7 @@ for i in $(seq 1 2); do
+@@ -26530,7 +26965,7 @@ for i in $(seq 1 2); do
      packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} &&
              eth.dst==00:00:00:01:0${i}:01 && ip6 && ip.ttl==64 &&
              ip6.src==2001:db8:${i}::${i}1 && ip6.dst==2001:db8:2000::1 && icmp6"
@@ -5042,7 +8637,7 @@ index 55de7c85b..3515a1e3c 100644
  
      # Assume all packets go to lsp${di}1.
      exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} && ip6 &&
-@@ -26650,7 +26740,7 @@ dst_ip=172.16.1.11
+@@ -26650,7 +27085,7 @@ dst_ip=172.16.1.11
  packet="inport==\"lsp11\" && eth.src==$src_mac && eth.dst==$dst_mac &&
          ip4 && ip.ttl==64 && ip4.src==$src_ip && ip4.dst==$dst_ip &&
          udp && udp.src==53 && udp.dst==4369"
@@ -5051,7 +8646,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Check if the packet hit the forwarding group policy
  AT_CAPTURE_FILE([offlows2])
-@@ -27173,7 +27263,7 @@ ovn_attach n1 br-phys 192.168.0.1
+@@ -27173,7 +27608,7 @@ ovn_attach n1 br-phys 192.168.0.1
  
  # Chassis hv1 should add flows for the ls1 datapath in table 8 (ls_in_port_sec_l2).
  dp_key=$(ovn-sbctl --bare --columns tunnel_key list Datapath_Binding ls1)
@@ -5060,7 +8655,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVN_CLEANUP([hv1])
  AT_CLEANUP
-@@ -27199,7 +27289,7 @@ ovs-vsctl add-br br-phys
+@@ -27199,7 +27634,7 @@ ovs-vsctl add-br br-phys
  ovn_attach n1 br-phys 192.168.0.1
  
  # Port_Binding should be released.
@@ -5069,7 +8664,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVN_CLEANUP([hv1])
  AT_CLEANUP
-@@ -27332,22 +27422,24 @@ sleep 5
+@@ -27332,22 +27767,24 @@ sleep 5
  send_ipv4_pkt() {
      local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
      local ip_src=$5 ip_dst=$6
@@ -5099,7 +8694,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AT_CAPTURE_FILE([offlows2])
  OVS_WAIT_UNTIL([
-@@ -27364,7 +27456,8 @@ AT_CHECK([
+@@ -27364,7 +27801,8 @@ AT_CHECK([
  
  # Send the pkt from sw0-port2. Packet should not be marked.
  send_ipv4_pkt hv1 hv1-vif2 505400000004 00000000ff01 \
@@ -5109,7 +8704,7 @@ index 55de7c85b..3515a1e3c 100644
  
  AT_CHECK([
      test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
-@@ -27398,7 +27491,8 @@ AT_CHECK([
+@@ -27398,7 +27836,8 @@ AT_CHECK([
  
  ovn-nbctl set logical_router_policy $pol1 options:pkt_mark=2
  send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \
@@ -5119,7 +8714,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVS_WAIT_UNTIL([
      test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=23 | \
-@@ -27431,7 +27525,8 @@ AT_CHECK([
+@@ -27431,7 +27870,8 @@ AT_CHECK([
  # Send with src ip 10.0.0.5. The reroute policy should be hit
  # and the packet should be marked with 5.
  send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \
@@ -5129,7 +8724,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVS_WAIT_UNTIL([
      test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
-@@ -27443,7 +27538,7 @@ OVS_WAIT_UNTIL([
+@@ -27443,7 +27883,7 @@ OVS_WAIT_UNTIL([
  src_ip6=aef00000000000000000000000000004
  dst_ip6=bef00000000000000000000000000004
  
@@ -5138,7 +8733,7 @@ index 55de7c85b..3515a1e3c 100644
  
  OVS_WAIT_UNTIL([
      test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
-@@ -27463,7 +27558,7 @@ AT_CHECK([
+@@ -27463,7 +27903,7 @@ AT_CHECK([
  src_ip6=aef00000000000000000000000000004
  dst_ip6=bef00000000000000000000000000005
  
@@ -5147,7 +8742,64 @@ index 55de7c85b..3515a1e3c 100644
  
  OVS_WAIT_UNTIL([
      test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
-@@ -28737,7 +28832,7 @@ src_mac="f00000000102"
+@@ -27970,22 +28410,22 @@ AT_CHECK([test ! -z $p1_zoneid])
+ p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
+ AT_CHECK([test ! -z $p2_zoneid])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw1_dpkey},\
+ reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 1])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw1_dpkey},\
+ reg15=0x${p2_dpkey} | grep "load:0x${p2_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
+ 
+ ovs-vsctl set interface hv1-vif1 external_ids:iface-id=foo
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xdown])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
+ 
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
+@@ -27997,16 +28437,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
+ AT_CHECK([test ! -z $p1_zoneid])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq 1])
+ 
+ ovs-vsctl del-port hv1-vif2
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
+ 
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0])
+ 
+ p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g')
+@@ -28014,7 +28454,7 @@ AT_CHECK([test -z $p2_zoneid])
+ 
+ ovn-nbctl lsp-del sw0-p1
+ 
+-OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
++OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=40,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
+ 
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g')
+@@ -28737,7 +29177,7 @@ src_mac="f00000000102"
  dst_mac="000000000101"
  src_ip=`ip_to_hex 10 0 1 2`
  dst_ip=`ip_to_hex 10 0 1 1`
@@ -5156,7 +8808,7 @@ index 55de7c85b..3515a1e3c 100644
  as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
  
  # Even after configuring a router owned IP for SNAT, no packet-ins should
-@@ -28763,7 +28858,7 @@ src_mac="f00000000202"
+@@ -28763,7 +29203,7 @@ src_mac="f00000000202"
  dst_mac="000000000201"
  src_ip=`ip_to_hex 10 0 2 2`
  dst_ip=`ip_to_hex 10 0 1 1`
@@ -5165,7 +8817,7 @@ index 55de7c85b..3515a1e3c 100644
  as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
  
  # Still no packet-ins should reach ovn-controller.
-@@ -29548,7 +29643,9 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw1-p1) = xup])
+@@ -29548,7 +29988,9 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw1-p1) = xup])
  
  check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
  check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
@@ -5175,7 +8827,7 @@ index 55de7c85b..3515a1e3c 100644
  check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
  
  AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
-@@ -29839,6 +29936,119 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -
+@@ -29839,6 +30281,119 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -
   table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
  ])
  
@@ -5295,7 +8947,149 @@ index 55de7c85b..3515a1e3c 100644
  # Check backwards compatibility with ovn-northd versions that don't store the
  # original destination tuple.
  #
-@@ -31743,7 +31953,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
+@@ -30354,46 +30909,46 @@ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+ check ovn-nbctl --wait=hv sync
+ 
+ # Check OVS flows are installed properly.
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=46 | ofctl_strip_all | \
+     grep "priority=2002" | grep conjunction | \
+     sed 's/conjunction([[^)]]*)/conjunction()/g' | \
+     sed 's/reg15=0x[[1-9]]/reg15=0xN/g' | sort], [0], [dnl
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x10/0xfff0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x100/0xff00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x1000/0xf000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2/0xfffe actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x20/0xffe0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x200/0xfe00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2000/0xe000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4/0xfffc actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x40/0xffc0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x400/0xfc00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4000/0xc000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8/0xfff8 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x80/0xff80 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x800/0xf800 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8000/0x8000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=1 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x100/0x100,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x10/0xfff0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x100/0xff00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x1000/0xf000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2/0xfffe actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x20/0xffe0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x200/0xfe00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2000/0xe000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4/0xfffc actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x40/0xffc0 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x400/0xfc00 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4000/0xc000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8/0xfff8 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x80/0xff80 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x800/0xf800 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8000/0x8000 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=1 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
+- table=44, priority=2002,udp,reg0=0x80/0x80,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x10/0xfff0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x100/0xff00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x1000/0xf000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2/0xfffe actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x20/0xffe0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x200/0xfe00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2000/0xe000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4/0xfffc actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x40/0xffc0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x400/0xfc00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4000/0xc000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8/0xfff8 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x80/0xff80 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x800/0xf800 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8000/0x8000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.4,tp_dst=1 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x100/0x100,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x10/0xfff0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x100/0xff00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x1000/0xf000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2/0xfffe actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x20/0xffe0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x200/0xfe00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x2000/0xe000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4/0xfffc actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x40/0xffc0 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x400/0xfc00 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x4000/0xc000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8/0xfff8 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x80/0xff80 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x800/0xf800 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=0x8000/0x8000 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.4,tp_dst=1 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
++ table=46, priority=2002,udp,reg0=0x80/0x80,reg15=0xN,metadata=0x1,nw_src=192.168.47.4 actions=conjunction()
+ ])
+ 
+ OVN_CLEANUP([hv1])
+@@ -31568,7 +32123,7 @@ ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0 external_ids:iface-id=l
+ ovs-vsctl add-port br-int lsp0-1 -- set interface lsp0-1 external_ids:iface-id=lsp0-1
+ 
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 22])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 22])
+ 
+ # Save the current lflow_run counter
+ lflow_run=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
+@@ -31578,7 +32133,7 @@ lflow_run=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
+ # 1. Remove half of the ports from pg1. The excepted conjunction flows should be:
+ #    2 + 10 = 12
+ check ovn-nbctl --wait=hv pg-set-ports pg1 $(for i in 0 1 2 3 4; do for j in 0 1; do echo lsp${i}-${j}; done; done)
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 12])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 12])
+ 
+ # 2. Unbind lsp0-0. The there shouldn't be any conjunction flows because the
+ #    port group const set should have only one member (lsp0-1). And the total
+@@ -31586,25 +32141,25 @@ AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l
+ #    10.
+ ovs-vsctl del-port br-int lsp0-0
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 0])
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep 192.168 | wc -l) == 10])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 0])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep 192.168 | wc -l) == 10])
+ 
+ # 3. Rebind lsp0-0. The expected conjunction flows are back to 12.
+ ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0 external_ids:iface-id=lsp0-0
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 12])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 12])
+ 
+ # 4. Bind a lsp (lsp9-0) that doesn't belong to pg1, should not see any change.
+ ovs-vsctl add-port br-int lsp9-0 -- set interface lsp9-0 external_ids:iface-id=lsp9-0
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 12])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 12])
+ 
+ # 5. Bind another 2 lsps (lsp1-0 lsp1-1) that belong to pg1 and on a different
+ #    LS (ls1), should see conjunction flows doubled (12 x 2 = 24)
+ ovs-vsctl add-port br-int lsp1-0 -- set interface lsp1-0 external_ids:iface-id=lsp1-0
+ ovs-vsctl add-port br-int lsp1-1 -- set interface lsp1-1 external_ids:iface-id=lsp1-1
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 24])
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 24])
+ 
+ # 6. Simulate a SB port-group "del and add" notification to ovn-controller in the
+ #    same IDL iteration. ovn-controller should still program the same flows. In
+@@ -31629,7 +32184,7 @@ for i in $(seq 1 10); do
+     check ovn-nbctl --wait=hv sync
+ 
+     # Finally check flow count is the same as before.
+-    AT_CHECK([test $(ovs-ofctl dump-flows br-int table=44 | grep conjunction | wc -l) == 24])
++    AT_CHECK([test $(ovs-ofctl dump-flows br-int table=46 | grep conjunction | wc -l) == 24])
+ done
+ 
+ # Make sure all the above was performed with I-P (no recompute)
+@@ -31743,7 +32298,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
         ip4.src==10.0.0.100 && ip4.dst==20.0.0.200 &&
         udp && udp.src==53 && udp.dst==4369"
  
@@ -5304,7 +9098,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Check if packet hit the drop rule
  AT_CHECK([ovs-ofctl dump-flows br-int | grep "nw_dst=20.0.0.0/24" | \
-@@ -31770,7 +31980,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
+@@ -31770,7 +32325,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
         ip4.src==10.0.0.100 && ip4.dst==20.0.0.200 &&
         udp && udp.src==53 && udp.dst==4369"
  
@@ -5313,7 +9107,7 @@ index 55de7c85b..3515a1e3c 100644
  
  # Check if packet hit the drop rule
  AT_CHECK([ovs-ofctl dump-flows br-int "nw_src=10.0.0.0/24" | \
-@@ -31857,7 +32067,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$bcast_mac &&
+@@ -31857,7 +32412,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$bcast_mac &&
         arp.op==1 && arp.sha==$ls1_p1_mac && arp.spa==$ls1_p1_ip &&
         arp.tha==$bcast_mac && arp.tpa==$proxy_ip1"
  
@@ -5322,7 +9116,53 @@ index 55de7c85b..3515a1e3c 100644
  
  as hv1 ovs-ofctl dump-flows br-int| grep 169.254.239.254 | grep priority=50 > debug1
  AT_CAPTURE_FILE([debug1])
-@@ -32108,7 +32318,6 @@ ovn-nbctl lrp-set-gateway-chassis DR-S3 hv4
+@@ -31916,8 +32471,8 @@ check ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip4.src == 10.0.
+ 
+ # The first ACL should be programmed, but the second one shouldn't.
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.111], [0], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.122], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.111], [0], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.122], [1], [ignore])
+ 
+ # Now create the lport lp2.
+ check ovn-nbctl lsp-add lsw0 lp2 \
+@@ -31925,12 +32480,12 @@ check ovn-nbctl lsp-add lsw0 lp2 \
+ 
+ check ovn-nbctl --wait=hv sync
+ # Now the second ACL should be programmed.
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.122], [0], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.122], [0], [ignore])
+ 
+ # Remove the lport lp2 again, the OVS flow for the second ACL should be
+ # removed.
+ check ovn-nbctl --wait=hv lsp-del lp2
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.122], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.122], [1], [ignore])
+ 
+ # Test similar scenario but when the referenced lport is not bound locally.
+ 
+@@ -31944,8 +32499,8 @@ check ovn-nbctl acl-add lsw0 to-lport 1002 'inport == "lp4" && ip4.dst == 10.0.0
+ 
+ # The ACL for lp3 should be programmed, but the one for lp4 shouldn't.
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.133], [0], [ignore])
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.144], [1], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.133], [0], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.144], [1], [ignore])
+ 
+ # Now create the lport lp4.
+ check ovn-nbctl lsp-add lsw0 lp4 \
+@@ -31953,7 +32508,7 @@ check ovn-nbctl lsp-add lsw0 lp4 \
+ 
+ # Now the ACL for lp4 should be programmed.
+ check ovn-nbctl --wait=hv sync
+-AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep 10.0.0.144], [0], [ignore])
++AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep 10.0.0.144], [0], [ignore])
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -32108,7 +32663,6 @@ ovn-nbctl lrp-set-gateway-chassis DR-S3 hv4
  
  ovn-nbctl --wait=sb sync
  OVN_POPULATE_ARP
@@ -5330,7 +9170,7 @@ index 55de7c85b..3515a1e3c 100644
  vif_to_ls () {
      case ${1} in dnl (
          vif?[[11]]) echo ls ;; dnl (
-@@ -32222,6 +32431,9 @@ echo "Send Dummy ARP"
+@@ -32222,6 +32776,9 @@ echo "Send Dummy ARP"
  sip=`ip_to_hex 172 16 1 10`
  tip=`ip_to_hex 172 16 1 50`
  test_arp vif-north1 f0f000000011 $sip $tip
@@ -5340,7 +9180,7 @@ index 55de7c85b..3515a1e3c 100644
  
  echo "Send traffic North to South"
  sip=`ip_to_hex 172 16 1 10`
-@@ -32242,6 +32454,9 @@ echo "Send Dummy ARP"
+@@ -32242,6 +32799,9 @@ echo "Send Dummy ARP"
  sip=`ip_to_hex 10 0 0 10`
  tip=`ip_to_hex 10 0 0 50`
  test_arp vif-north2 f0f000000022 $sip $tip
@@ -5350,7 +9190,7 @@ index 55de7c85b..3515a1e3c 100644
  
  echo "Send traffic South to North2"
  sip=`ip_to_hex 20 0 0 10`
-@@ -32255,6 +32470,9 @@ echo "Send Dummy ARP"
+@@ -32255,6 +32815,9 @@ echo "Send Dummy ARP"
  sip=`ip_to_hex 192 168 0 10`
  tip=`ip_to_hex 192 168 0 50`
  test_arp vif-north3 f0f000000033 $sip $tip
@@ -5360,7 +9200,24 @@ index 55de7c85b..3515a1e3c 100644
  
  echo "Send traffic South to North3"
  sip=`ip_to_hex 20 0 0 10`
-@@ -34926,7 +35144,8 @@ check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp
+@@ -33384,7 +33947,7 @@ check ovn-nbctl --wait=hv sync
+ # Use constants so that if tables or registers change, this test can
+ # be updated easily.
+ DNAT_TABLE=15
+-SNAT_TABLE=43
++SNAT_TABLE=45
+ DNAT_ZONE_REG="NXM_NX_REG11[[0..15]]"
+ SNAT_ZONE_REG="NXM_NX_REG12[[0..15]]"
+ 
+@@ -33929,6 +34492,7 @@ m4_define([MULTIPLE_OVS_INT],
+        ovs-ofctl dump-flows br-int | grep $cookie |
+            sed -e 's/duration=[[0-9.]]*s, //g' |
+            sed -e 's/idle_age=[[0-9]]*, //g' |
++           sed -e 's/hard_age=[[0-9]]*, //g' |
+            sed -e 's/n_packets=[[0-9]]*, //g' |
+            sed -e 's/n_bytes=[[0-9]]*, //g'
+    }
+@@ -34926,7 +35490,8 @@ check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp
  wait_for_ports_up
  ovn-nbctl --wait=hv sync
  
@@ -5370,7 +9227,7 @@ index 55de7c85b..3515a1e3c 100644
  check ovn-nbctl ls-lb-add sw lb1
  
  # Remove a single backend
-@@ -34949,7 +35168,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.0.10:0, backend=192.168.
+@@ -34949,7 +35514,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.0.10:0, backend=192.168.
  AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.0.10:0, backend=192.168.10.30:0, protocol=0" hv1/ovn-controller.log], [0])
  
  # Check flush for LB with port and protocol
@@ -5380,7 +9237,7 @@ index 55de7c85b..3515a1e3c 100644
  check ovn-nbctl ls-lb-add sw lb1
  check ovn-nbctl lb-del lb1
  check ovn-nbctl --wait=hv sync
-@@ -34958,7 +35178,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.30.10:80, backend=192.16
+@@ -34958,7 +35524,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.30.10:80, backend=192.16
  AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.30.10:80, backend=192.168.40.20:8090, protocol=17" hv1/ovn-controller.log], [0])
  
  # Check recompute when LB is no longer local
@@ -5390,7 +9247,7 @@ index 55de7c85b..3515a1e3c 100644
  check ovn-nbctl ls-lb-add sw lb1
  check ovs-vsctl remove interface p1 external_ids iface-id
  check ovn-appctl inc-engine/recompute
-@@ -34968,6 +35189,193 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.50.10:80, backend=192.16
+@@ -34968,6 +35535,193 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.50.10:80, backend=192.16
  
  AT_CHECK([test "$(grep -c "Flushing CT for 5-tuple" hv1/ovn-controller.log)" = "6"], [0])
  
@@ -5684,9 +9541,18 @@ index d65f359a6..b8c5ae9ad 100644
  # NETNS_DAEMONIZE([namespace], [command], [pidfile])
  #
 diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
-index dd4996041..3c3e5bc61 100644
+index dd4996041..a1aee3313 100644
 --- a/tests/system-ovn-kmod.at
 +++ b/tests/system-ovn-kmod.at
+@@ -172,7 +172,7 @@ ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192
+ 
+ ovn-nbctl list load_balancer
+ ovn-sbctl dump-flows R2
+-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=41 | \
++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
+ grep 'nat(src=20.0.0.2)'])
+ 
+ dnl Test load-balancing that includes L4 ports in NAT.
 @@ -215,3 +215,139 @@ as
  OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
  /connection dropped.*/d"])
@@ -5828,7 +9694,7 @@ index dd4996041..3c3e5bc61 100644
 +AT_CLEANUP
 +])
 diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 84a459d6a..40f808515 100644
+index 84a459d6a..0b6e9f602 100644
 --- a/tests/system-ovn.at
 +++ b/tests/system-ovn.at
 @@ -1569,7 +1569,6 @@ bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1.4 -c)
@@ -5859,6 +9725,24 @@ index 84a459d6a..40f808515 100644
  else
      AT_CHECK([test $bar3_ct -eq 0])
  fi
+@@ -2246,7 +2243,7 @@ ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.16
+ 
+ ovn-nbctl list load_balancer
+ ovn-sbctl dump-flows R2
+-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | \
+ grep 'nat(src=20.0.0.2)'])
+ 
+ check ovs-appctl dpctl/flush-conntrack
+@@ -2285,7 +2282,7 @@ ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.16
+ 
+ ovn-nbctl list load_balancer
+ ovn-sbctl dump-flows R2
+-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | \
+ grep 'nat(src=20.0.0.2)'])
+ 
+ rm -f wget*.log
 @@ -4850,9 +4847,9 @@ NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp ${filter} > lsp.pcap 2>tcpdump_
  OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
  
@@ -5885,6 +9769,24 @@ index 84a459d6a..40f808515 100644
  
  # Check hairpin traffic.
  OVS_WAIT_UNTIL([
+@@ -5084,7 +5081,7 @@ OVS_WAIT_UNTIL([
+ ])
+ 
+ OVS_WAIT_UNTIL([
+-    n_pkt=$(ovs-ofctl dump-flows br-int table=44 | grep -v n_packets=0 | \
++    n_pkt=$(ovs-ofctl dump-flows br-int table=46 | grep -v n_packets=0 | \
+ grep controller | grep tp_dst=84 -c)
+     test $n_pkt -eq 1
+ ])
+@@ -5334,7 +5331,7 @@ OVS_WAIT_UNTIL([
+ ])
+ 
+ OVS_WAIT_UNTIL([
+-    n_pkt=$(ovs-ofctl dump-flows br-int table=44 | grep -v n_packets=0 | \
++    n_pkt=$(ovs-ofctl dump-flows br-int table=46 | grep -v n_packets=0 | \
+ grep controller | grep tp_dst=84 -c)
+     test $n_pkt -eq 1
+ ])
 @@ -7190,7 +7187,7 @@ NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
  check ovn-nbctl --may-exist meter-add acl-meter drop 10 pktps 0
  ip netns exec sw01 scapy -H <<-EOF
@@ -6146,7 +10048,7 @@ index 84a459d6a..40f808515 100644
  OVS_WAIT_FOR_OUTPUT([
      for i in `seq 1 20`; do
          ip netns exec foo1 wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
-@@ -10690,6 +10734,191 @@ check ovn-nbctl lb-del lb2
+@@ -10690,6 +10734,535 @@ check ovn-nbctl lb-del lb2
  
  OVS_WAIT_UNTIL([test "$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | wc -l)" = "0"])
  
@@ -6335,13 +10237,22 @@ index 84a459d6a..40f808515 100644
 +        test "${total_icmp1_pkts}" -gt "${total_icmp_pkts}"
 +])
 +
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -10706,3 +10935,605 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
- /connection dropped.*/d"])
- AT_CLEANUP
- ])
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
 +
 +# This tests port->up/down and ovn-installed after adding and removing Ports and Interfaces.
 +# 3 Conditions x 3 tests:
@@ -6670,22 +10581,13 @@ index 84a459d6a..40f808515 100644
 +check_ports_up
 +check_ports_bound
 +
-+OVS_APP_EXIT_AND_WAIT([ovn-controller])
-+
-+as ovn-sb
-+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-+
-+as ovn-nb
-+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-+
-+as northd
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-+
-+as
-+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-+/connection dropped.*/d"])
-+AT_CLEANUP
-+])
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+ 
+ as ovn-sb
+@@ -10706,3 +11279,338 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ /connection dropped.*/d"])
+ AT_CLEANUP
+ ])
 +
 +OVN_FOR_EACH_NORTHD([
 +AT_SETUP([ovn mirroring])
@@ -6744,6 +10646,9 @@ index 84a459d6a..40f808515 100644
 +ovn-nbctl mirror-add mirror0 gre 1 to-lport 172.16.0.100
 +ovn-nbctl lsp-attach-mirror bar1 mirror0
 +
++OVN_POPULATE_ARP
++check ovn-nbctl --wait=hv sync
++
 +ADD_NAMESPACES(mirror)
 +ADD_VETH(mirror, mirror, br-mirror, "2003::b/64", "f0:00:00:01:07:06", \
 +         "2003::1", "nodad", "172.16.0.100/24", "172.16.0.1")
@@ -6767,7 +10672,8 @@ index 84a459d6a..40f808515 100644
 +
 +ovn-nbctl mirror-del mirror0
 +ovn-nbctl mirror-add mirror1 gre 2 to-lport 2003::b
-+ovn-nbctl lsp-attach-mirror bar1 mirror1
++
++ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror1
 +
 +NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
 +OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
@@ -6786,7 +10692,7 @@ index 84a459d6a..40f808515 100644
 +
 +ovn-nbctl mirror-del mirror1
 +ovn-nbctl mirror-add mirror2 erspan 3 to-lport 172.16.0.100
-+ovn-nbctl lsp-attach-mirror bar1 mirror2
++ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror2
 +
 +NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip[[22:2]]=0x88be > erspan_mirror4.pcap 2>erspan_mirror4_error &])
 +OVS_WAIT_UNTIL([grep "listening" erspan_mirror4_error])
@@ -6804,7 +10710,7 @@ index 84a459d6a..40f808515 100644
 +
 +ovn-nbctl mirror-del mirror2
 +ovn-nbctl mirror-add mirror3 erspan 4 to-lport 2003::b
-+ovn-nbctl lsp-attach-mirror bar1 mirror3
++ovn-nbctl --wait=hv lsp-attach-mirror bar1 mirror3
 +
 +NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip6[[42:2]]=0x88be > erspan_mirror6.pcap 2>erspan_mirror6_error &])
 +OVS_WAIT_UNTIL([grep "listening" erspan_mirror6_error])
@@ -6821,7 +10727,7 @@ index 84a459d6a..40f808515 100644
 +killall tcpdump
 +
 +uuid=$(fetch_column nb:mirror _uuid name="mirror3")
-+ovn-nbctl set mirror $uuid type=gre
++ovn-nbctl --wait=hv set mirror $uuid type=gre
 +
 +NS_CHECK_EXEC([mirror], [tcpdump -c 3 -l -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
 +OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
@@ -6944,6 +10850,79 @@ index 84a459d6a..40f808515 100644
 +
 +AT_CLEANUP
 +])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Traffic to router port via LLA])
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-phys], [set Bridge br-phys fail-mode=standalone])
++
++# Set external-ids in br-int needed for ovn-controller
++ovs-vsctl \
++        -- set Open_vSwitch . external-ids:system-id=hv1 \
++        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
++        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
++        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
++        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
++
++start_daemon ovn-controller
++
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-ls0 00:00:00:00:00:01 fd00::1/64
++
++check ovn-nbctl ls-add ls0
++check ovn-nbctl lsp-add ls0 vif0 \
++    -- lsp-set-addresses vif0 "00:00:00:00:00:02 fd00::2"
++check ovn-nbctl lsp-add ls0 ls0-lr0 \
++    -- lsp-set-type ls0-lr0 router \
++    -- lsp-set-addresses ls0-lr0 router \
++    -- lsp-set-options ls0-lr0 router-port=lr0-ls0
++
++ADD_NAMESPACES(vif0)
++ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1")
++OVS_WAIT_UNTIL([test "$(ip netns exec vif0 ip a | grep fe80:: | grep tentative)" = ""])
++
++check ovn-nbctl set logical_router lr0 options:always_learn_from_arp_request=false
++
++OVN_POPULATE_ARP
++wait_for_ports_up
++check ovn-nbctl --wait=sb sync
++
++NS_CHECK_EXEC([vif0], [ping -q -c 3 -i 0.3 -w 2 fe80::200:ff:fe00:1 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++check_row_count mac_binding 1 mac=\"00:00:00:00:00:02\"
++ovn-sbctl --all destroy mac_binding
++
++ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=true
++
++NS_CHECK_EXEC([vif0], [ping -q -c 3 -i 0.3 -w 2 fe80::200:ff:fe00:1 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++check_row_count mac_binding 1 mac=\"00:00:00:00:00:02\"
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++
++AT_CLEANUP
++])
 diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt
 index d7bd21e0d..0d90765c9 100644
 --- a/utilities/containers/py-requirements.txt
diff --git a/SPECS/ovn23.03.spec b/SPECS/ovn23.03.spec
index e3ac702..de8ca26 100644
--- a/SPECS/ovn23.03.spec
+++ b/SPECS/ovn23.03.spec
@@ -51,7 +51,7 @@ Summary: Open Virtual Network support
 Group: System Environment/Daemons
 URL: http://www.ovn.org/
 Version: 23.03.0
-Release: 50%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Release: 69%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
 Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
 Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
 
@@ -524,6 +524,82 @@ fi
 %{_unitdir}/ovn-controller-vtep.service
 
 %changelog
+* Fri Jun 09 2023 Igor Zhukov <ivzhukov@sbercloud.ru> - 23.03.0-69
+- call ovsrcu_exit() before exit in ovn-northd and ovn-controller to make valgrind happy
+[Upstream: a3aba935cda359db5d9c99e8ea9ba688e4f888bc]
+
+* Thu Jun 08 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-68
+- controller: Turn OFTABLE_OUTPUT_INIT into an alias.
+[Upstream: 8c274866a29534c6ecb80f0242798edbb078bfda]
+
+* Thu Jun 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-67
+- Implement MTU Path Discovery for multichassis ports
+[Upstream: 44e07200a8f04b70bbcba20d2b5346aa840b4f40]
+
+* Thu Jun 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-66
+- Add new egress tables to accommodate for too-big packets handling
+[Upstream: 44d6692e28478e4e971de09045f42cc5c3000540]
+
+* Thu Jun 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-65
+- if-status: track interfaces for additional chassis
+[Upstream: 57f15c6d78b4fbcd9ead81328e06a714b75942f0]
+
+* Thu Jun 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-64
+- Track interface MTU in if-status-mgr
+[Upstream: e6f097fe148deb3f60c2e2fc57e80f91986f248e]
+
+* Thu Jun 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-63
+- Track ip version of tunnel in chassis_tunnel struct
+[Upstream: c8fccfa720b7d8e176be05bfd37fd6e74764ee3d]
+
+* Thu Jun 08 2023 Ales Musil <amusil@redhat.com> - 23.03.0-62
+- northd: Add logical flow to skip GARP with LLA (#2211240)
+[Upstream: dc0b0b55d93cb2516f1759b65f198485597d4575]
+
+* Thu Jun 08 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-61
+- northd: match only on supported protocols to handle_svc_check (#1913162)
+[Upstream: 822861db016d9360d6a88a486d5db8d5936e66fa]
+
+* Thu Jun 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-60
+- tests: Fixed "nested containers" test
+[Upstream: f914cf2cab4b4a872d246961c6521cd8a48f2bd3]
+
+* Thu Jun 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-59
+- tests: fix flaky Multiple OVS interfaces bound to same logical ports
+[Upstream: 8f29930c22687c3247a784e1e2fe4a6dc0fdd86c]
+
+* Thu Jun 08 2023 Ales Musil <amusil@redhat.com> - 23.03.0-58
+- system-tests: Prevent flakiness in ovn mirroring
+[Upstream: 352041d0fa66772d86980f46c63e023c40286723]
+
+* Thu Jun 08 2023 Ales Musil <amusil@redhat.com> - 23.03.0-57
+- northd: Fix address set incremental processing (#2196885)
+[Upstream: e8baef1c0fc671fe4e2d3af63de979e22a0c899d]
+
+* Tue May 30 2023 Brian Haley <haleyb.dev@gmail.com> - 23.03.0-56
+- controller: Ignore DNS queries with RRs
+[Upstream: 529ea698f5d1d2b7083210318cfc0a64b701ca62]
+
+* Tue May 30 2023 Ales Musil <amusil@redhat.com> - 23.03.0-55
+- ci: ovn-kubernetes: Align the timeouts with u/s ovnk
+[Upstream: ecc0a06af3bb47fe49ee897896af35a0efe33486]
+
+* Tue May 30 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-54
+- controller: Handle OpenFlow errors. (#2134880)
+[Upstream: 158463b94f5b6c37a37d9755ba9d5ef7473d35d3]
+
+* Tue May 30 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-53
+- controller: fix typo in comments
+[Upstream: f24e9bf7b4b5822e0d37a4382fe49607c132a3ee]
+
+* Tue May 30 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-52
+- northd: build vtep hairpin lflows only for lswitches with vtep lports
+[Upstream: 222f74acea04c9ae46bb3767ff256f8249ee7ab8]
+
+* Tue May 30 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-51
+- northd: fix ls_in_hairpin l3dgw flow generation
+[Upstream: e4f8547be8774c085ef212fbed3e5e76e77d661c]
+
 * Fri May 26 2023 Han Zhou <hzhou@ovn.org> - 23.03.0-50
 - ovn-controller.c: Fix assertion failure during address set update.
 [Upstream: 777786f38a61041898891ccbb3f139b0552e5794]