diff --git a/.ovn.metadata b/.ovn.metadata
index 21e5af6..a8083bb 100644
--- a/.ovn.metadata
+++ b/.ovn.metadata
@@ -1,5 +1,5 @@
 002450621b33c5690060345b0aac25bc2426d675  SOURCES/docutils-0.12.tar.gz
-d5a30334edfe265936a26db900a8838c83ef4043  SOURCES/openvswitch-0187ead.tar.gz
-28d5de798f88964283b144cdd8cb226059d403dd  SOURCES/ovn-23.06.1.tar.gz
+19829758c492a62983f4536c63e4654e9af934c2  SOURCES/openvswitch-ec1d730.tar.gz
+1ad2a2bdb68e5e984310a8519d190350001f433a  SOURCES/ovn-23.09.0.tar.gz
 d34f96421a86004aa5d26ecf975edefd09f948b1  SOURCES/Pygments-1.4.tar.gz
 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a  SOURCES/Sphinx-1.1.3.tar.gz
diff --git a/SOURCES/ovn23.06.patch b/SOURCES/ovn23.06.patch
deleted file mode 100644
index ccd46e5..0000000
--- a/SOURCES/ovn23.06.patch
+++ /dev/null
@@ -1,669 +0,0 @@
-diff --git a/NEWS b/NEWS
-index ef000ef7b..0056bb175 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,6 @@
-+OVN v23.06.2 - xx xxx xxxx
-+--------------------------
-+
- OVN v23.06.1 - 29 Aug 2023
- --------------------------
-   - Bug fixes
-diff --git a/configure.ac b/configure.ac
-index fc4ba024a..3ed093ebe 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -13,7 +13,7 @@
- # limitations under the License.
- 
- AC_PREREQ(2.63)
--AC_INIT(ovn, 23.06.1, bugs@openvswitch.org)
-+AC_INIT(ovn, 23.06.2, bugs@openvswitch.org)
- AC_CONFIG_MACRO_DIR([m4])
- AC_CONFIG_AUX_DIR([build-aux])
- AC_CONFIG_HEADERS([config.h])
-diff --git a/controller/lflow.c b/controller/lflow.c
-index 22faaf013..718b4a27a 100644
---- a/controller/lflow.c
-+++ b/controller/lflow.c
-@@ -2513,10 +2513,10 @@ build_in_port_sec_default_flows(const struct sbrec_port_binding *pb,
-      *       investigation.
-      *
-      * Eg.  If there are below OF rules in the same table
--     * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135,
-+     * (1) priority=90,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135,
-      *     icmp_code=0,nd_sll=fa:16:3e:94:05:98
-      *     actions=load:0->NXM_NX_REG10[12]
--     * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=225,icmp_type=135,
-+     * (2) priority=80,icmp6,reg14=0x1,metadata=0x1,nw_ttl=255,icmp_type=135,
-      *     icmp_code=0 actions=load:1->NXM_NX_REG10[12]
-      *
-      * An IPv6 NS packet with nd_sll = fa:16:3e:94:05:98 is matching on the
-@@ -2801,7 +2801,7 @@ build_in_port_sec_nd_flows(const struct sbrec_port_binding *pb,
-     reset_match_for_port_sec_flows(pb, MFF_LOG_INPORT, m);
-     match_set_dl_type(m, htons(ETH_TYPE_IPV6));
-     match_set_nw_proto(m, IPPROTO_ICMPV6);
--    match_set_nw_ttl(m, 225);
-+    match_set_nw_ttl(m, 255);
-     match_set_icmp_type(m, 135);
-     match_set_icmp_code(m, 0);
- 
-diff --git a/controller/ofctrl.c b/controller/ofctrl.c
-index 64a444ff6..a1676a788 100644
---- a/controller/ofctrl.c
-+++ b/controller/ofctrl.c
-@@ -16,6 +16,7 @@
- #include <config.h>
- #include "bitmap.h"
- #include "byte-order.h"
-+#include "coverage.h"
- #include "dirs.h"
- #include "dp-packet.h"
- #include "flow.h"
-@@ -55,6 +56,8 @@
- 
- VLOG_DEFINE_THIS_MODULE(ofctrl);
- 
-+COVERAGE_DEFINE(ofctrl_msg_too_long);
-+
- /* An OpenFlow flow. */
- struct ovn_flow {
-     /* Key. */
-@@ -1273,6 +1276,37 @@ ofctrl_add_flow_metered(struct ovn_desired_flow_table *desired_flows,
-                                       meter_id, as_info, true);
- }
- 
-+struct ofpact_ref {
-+    struct hmap_node hmap_node;
-+    struct ofpact *ofpact;
-+};
-+
-+static struct ofpact_ref *
-+ofpact_ref_find(const struct hmap *refs, const struct ofpact *ofpact)
-+{
-+    uint32_t hash = hash_bytes(ofpact, ofpact->len, 0);
-+
-+    struct ofpact_ref *ref;
-+    HMAP_FOR_EACH_WITH_HASH (ref, hmap_node, hash, refs) {
-+        if (ofpacts_equal(ref->ofpact, ref->ofpact->len,
-+                          ofpact, ofpact->len)) {
-+            return ref;
-+        }
-+    }
-+
-+    return NULL;
-+}
-+
-+static void
-+ofpact_refs_destroy(struct hmap *refs)
-+{
-+    struct ofpact_ref *ref;
-+    HMAP_FOR_EACH_POP (ref, hmap_node, refs) {
-+        free(ref);
-+    }
-+    hmap_destroy(refs);
-+}
-+
- /* Either add a new flow, or append actions on an existing flow. If the
-  * flow existed, a new link will also be created between the new sb_uuid
-  * and the existing flow. */
-@@ -1292,6 +1326,21 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
-                            meter_id);
-     existing = desired_flow_lookup_conjunctive(desired_flows, &f->flow);
-     if (existing) {
-+        struct hmap existing_conj = HMAP_INITIALIZER(&existing_conj);
-+
-+        struct ofpact *ofpact;
-+        OFPACT_FOR_EACH (ofpact, existing->flow.ofpacts,
-+                         existing->flow.ofpacts_len) {
-+            if (ofpact->type != OFPACT_CONJUNCTION) {
-+                continue;
-+            }
-+
-+            struct ofpact_ref *ref = xmalloc(sizeof *ref);
-+            ref->ofpact = ofpact;
-+            uint32_t hash = hash_bytes(ofpact, ofpact->len, 0);
-+            hmap_insert(&existing_conj, &ref->hmap_node, hash);
-+        }
-+
-         /* There's already a flow with this particular match and action
-          * 'conjunction'. Append the action to that flow rather than
-          * adding a new flow.
-@@ -1301,7 +1350,15 @@ ofctrl_add_or_append_flow(struct ovn_desired_flow_table *desired_flows,
-         ofpbuf_use_stub(&compound, compound_stub, sizeof(compound_stub));
-         ofpbuf_put(&compound, existing->flow.ofpacts,
-                    existing->flow.ofpacts_len);
--        ofpbuf_put(&compound, f->flow.ofpacts, f->flow.ofpacts_len);
-+
-+        OFPACT_FOR_EACH (ofpact, f->flow.ofpacts, f->flow.ofpacts_len) {
-+            if (ofpact->type != OFPACT_CONJUNCTION ||
-+                !ofpact_ref_find(&existing_conj, ofpact)) {
-+                ofpbuf_put(&compound, ofpact, OFPACT_ALIGN(ofpact->len));
-+            }
-+        }
-+
-+        ofpact_refs_destroy(&existing_conj);
- 
-         mem_stats.desired_flow_usage -= desired_flow_size(existing);
-         free(existing->flow.ofpacts);
-@@ -1769,6 +1826,18 @@ ovn_flow_log(const struct ovn_flow *f, const char *action)
-     }
- }
- 
-+static void
-+ovn_flow_log_size_err(const struct ovn_flow *f)
-+{
-+    COVERAGE_INC(ofctrl_msg_too_long);
-+
-+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-+
-+    char *s = ovn_flow_to_string(f);
-+    VLOG_ERR_RL(&rl, "The FLOW_MOD message is too big: %s", s);
-+    free(s);
-+}
-+
- static void
- ovn_flow_uninit(struct ovn_flow *f)
- {
-@@ -1888,15 +1957,27 @@ encode_bundle_add(struct ofpbuf *msg, struct ofputil_bundle_ctrl_msg *bc)
-     return ofputil_encode_bundle_add(OFP15_VERSION, &bam);
- }
- 
--static void
-+static bool
- add_flow_mod(struct ofputil_flow_mod *fm,
-              struct ofputil_bundle_ctrl_msg *bc,
-              struct ovs_list *msgs)
- {
-     struct ofpbuf *msg = encode_flow_mod(fm);
-     struct ofpbuf *bundle_msg = encode_bundle_add(msg, bc);
-+
-+    uint32_t flow_mod_len = msg->size;
-+    uint32_t bundle_len = bundle_msg->size;
-+
-     ofpbuf_delete(msg);
-+
-+    if (flow_mod_len > UINT16_MAX || bundle_len > UINT16_MAX) {
-+        ofpbuf_delete(bundle_msg);
-+
-+        return false;
-+    }
-+
-     ovs_list_push_back(msgs, &bundle_msg->list_node);
-+    return true;
- }
- 
- /* group_table. */
-@@ -2235,7 +2316,10 @@ installed_flow_add(struct ovn_flow *d,
-         .new_cookie = htonll(d->cookie),
-         .command = OFPFC_ADD,
-     };
--    add_flow_mod(&fm, bc, msgs);
-+
-+    if (!add_flow_mod(&fm, bc, msgs)) {
-+        ovn_flow_log_size_err(d);
-+    }
- }
- 
- static void
-@@ -2259,7 +2343,7 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d,
-         /* Use OFPFC_ADD so that cookie can be updated. */
-         fm.command = OFPFC_ADD;
-     }
--    add_flow_mod(&fm, bc, msgs);
-+    bool result = add_flow_mod(&fm, bc, msgs);
- 
-     /* Replace 'i''s actions and cookie by 'd''s. */
-     mem_stats.installed_flow_usage -= i->ofpacts_len - d->ofpacts_len;
-@@ -2267,6 +2351,10 @@ installed_flow_mod(struct ovn_flow *i, struct ovn_flow *d,
-     i->ofpacts = xmemdup(d->ofpacts, d->ofpacts_len);
-     i->ofpacts_len = d->ofpacts_len;
-     i->cookie = d->cookie;
-+
-+    if (!result) {
-+        ovn_flow_log_size_err(i);
-+    }
- }
- 
- static void
-@@ -2280,7 +2368,10 @@ installed_flow_del(struct ovn_flow *i,
-         .table_id = i->table_id,
-         .command = OFPFC_DELETE_STRICT,
-     };
--    add_flow_mod(&fm, bc, msgs);
-+
-+    if (!add_flow_mod(&fm, bc, msgs)) {
-+        ovn_flow_log_size_err(i);
-+    }
- }
- 
- static void
-diff --git a/debian/changelog b/debian/changelog
-index e1d505a7e..e147d113c 100644
---- a/debian/changelog
-+++ b/debian/changelog
-@@ -1,3 +1,9 @@
-+OVN (23.06.2-1) unstable; urgency=low
-+   [ OVN team ]
-+   * New upstream version
-+
-+ -- OVN team <dev@openvswitch.org>  Tue, 29 Aug 2023 11:28:59 -0400
-+
- OVN (23.06.1-1) unstable; urgency=low
-    [ OVN team ]
-    * New upstream version
-diff --git a/northd/northd.c b/northd/northd.c
-index af871e260..3d6449c88 100644
---- a/northd/northd.c
-+++ b/northd/northd.c
-@@ -259,7 +259,6 @@ enum ovn_stage {
- #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
- #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
- #define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
--#define REGBIT_KNOWN_ECMP_NH    "reg9[5]"
- #define REGBIT_KNOWN_LB_SESSION "reg9[6]"
- 
- /* Register to store the eth address associated to a router port for packets
-@@ -367,8 +366,7 @@ enum ovn_stage {
-  * |     |   EGRESS_LOOPBACK/        | G |     UNUSED      |
-  * | R9  |   PKT_LARGER/             | 4 |                 |
-  * |     |   LOOKUP_NEIGHBOR_RESULT/ |   |                 |
-- * |     |   SKIP_LOOKUP_NEIGHBOR/   |   |                 |
-- * |     |   KNOWN_ECMP_NH}          |   |                 |
-+ * |     |   SKIP_LOOKUP_NEIGHBOR}   |   |                 |
-  * |     |                           |   |                 |
-  * |     | REG_ORIG_TP_DPORT_ROUTER  |   |                 |
-  * |     |                           |   |                 |
-@@ -10326,15 +10324,13 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
-                   cidr);
-     free(cidr);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100,
--            ds_cstr(&base_match),
--            REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh_mac(); ct_next;",
--            &st_route->header_);
-+                             ds_cstr(&base_match), "ct_next;",
-+                             &st_route->header_);
- 
-     /* And packets that go out over an ECMP route need conntrack */
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, 100,
--            ds_cstr(route_match),
--            REGBIT_KNOWN_ECMP_NH" = chk_ecmp_nh(); ct_next;",
--            &st_route->header_);
-+                             ds_cstr(route_match), "ct_next;",
-+                             &st_route->header_);
- 
-     /* Save src eth and inport in ct_label for packets that arrive over
-      * an ECMP route.
-@@ -10347,9 +10343,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
-@@ -10360,9 +10355,8 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = udp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
-@@ -10373,53 +10367,49 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
- 
-     ds_clear(&match);
-     ds_put_format(&match,
--            "%s && (!ct.rpl && ct.est) && tcp && "REGBIT_KNOWN_ECMP_NH" == 0",
-+            "%s && (!ct.rpl && ct.est) && tcp",
-             ds_cstr(&base_match));
-     ds_clear(&actions);
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = tcp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
- 
-     ds_clear(&match);
-     ds_put_format(&match,
--            "%s && (!ct.rpl && ct.est) && udp && "REGBIT_KNOWN_ECMP_NH" == 0",
-+            "%s && (!ct.rpl && ct.est) && udp",
-             ds_cstr(&base_match));
-     ds_clear(&actions);
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = udp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
-     ds_clear(&match);
-     ds_put_format(&match,
--            "%s && (!ct.rpl && ct.est) && sctp && "REGBIT_KNOWN_ECMP_NH" == 0",
-+            "%s && (!ct.rpl && ct.est) && sctp",
-             ds_cstr(&base_match));
-     ds_clear(&actions);
-     ds_put_format(&actions,
-             "ct_commit { ct_label.ecmp_reply_eth = eth.src; "
-             " %s = %" PRId64 ";}; "
--            "commit_ecmp_nh(ipv6 = %s, proto = sctp); next;",
--            ct_ecmp_reply_port_match, out_port->sb->tunnel_key,
--            IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "false" : "true");
-+            "next;",
-+            ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 100,
-                             ds_cstr(&match), ds_cstr(&actions),
-                             &st_route->header_);
-@@ -10428,7 +10418,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows,
-      * for where to route the packet.
-      */
-     ds_put_format(&ecmp_reply,
--                  "ct.rpl && "REGBIT_KNOWN_ECMP_NH" == 1 && %s == %"PRId64,
-+                  "ct.rpl && %s == %"PRId64,
-                   ct_ecmp_reply_port_match, out_port->sb->tunnel_key);
-     ds_clear(&match);
-     ds_put_format(&match, "%s && %s", ds_cstr(&ecmp_reply),
-@@ -10853,6 +10843,10 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
- {
-     bool ipv4 = lb_vip->address_family == AF_INET;
-     const char *ip_match = ipv4 ? "ip4" : "ip6";
-+    char *aff_action[LROUTER_NAT_LB_FLOW_MAX] = {
-+        [LROUTER_NAT_LB_FLOW_SKIP_SNAT]  = "flags.skip_snat_for_lb = 1; ",
-+        [LROUTER_NAT_LB_FLOW_FORCE_SNAT] = "flags.force_snat_for_lb = 1; ",
-+    };
- 
-     int prio = 110;
- 
-@@ -10925,15 +10919,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-     ctx.new_action[LROUTER_NAT_LB_FLOW_SKIP_SNAT] = ds_cstr(&skip_snat_act);
-     ctx.new_action[LROUTER_NAT_LB_FLOW_FORCE_SNAT] = ds_cstr(&force_snat_act);
- 
--    enum {
--        LROUTER_NAT_LB_AFF            = LROUTER_NAT_LB_FLOW_MAX,
--        LROUTER_NAT_LB_AFF_FORCE_SNAT = LROUTER_NAT_LB_FLOW_MAX + 1,
--    };
--    unsigned long *dp_bitmap[LROUTER_NAT_LB_FLOW_MAX + 2];
-+    unsigned long *gw_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX];
-+    unsigned long *aff_dp_bitmap[LROUTER_NAT_LB_FLOW_MAX];
- 
-     size_t bitmap_len = ods_size(lr_datapaths);
--    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
--        dp_bitmap[i] = bitmap_allocate(bitmap_len);
-+    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) {
-+        gw_dp_bitmap[i] = bitmap_allocate(bitmap_len);
-+        aff_dp_bitmap[i] = bitmap_allocate(bitmap_len);
-     }
- 
-     /* Group gw router since we do not have datapath dependency in
-@@ -10954,18 +10946,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-         }
- 
-         if (!od->n_l3dgw_ports) {
--            bitmap_set1(dp_bitmap[type], index);
-+            bitmap_set1(gw_dp_bitmap[type], index);
-         } else {
-             build_distr_lrouter_nat_flows_for_lb(&ctx, type, od);
-         }
- 
-         if (lb->affinity_timeout) {
--            if (!lport_addresses_is_empty(&od->lb_force_snat_addrs) ||
--                od->lb_force_snat_router_ip) {
--                bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT], index);
--            } else {
--                bitmap_set1(dp_bitmap[LROUTER_NAT_LB_AFF], index);
--            }
-+            bitmap_set1(aff_dp_bitmap[type], index);
-         }
- 
-         if (sset_contains(&od->external_ips, lb_vip->vip_str)) {
-@@ -10988,33 +10975,20 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
- 
-     for (size_t type = 0; type < LROUTER_NAT_LB_FLOW_MAX; type++) {
-         build_gw_lrouter_nat_flows_for_lb(&ctx, type, lr_datapaths,
--                                          dp_bitmap[type]);
-+                                          gw_dp_bitmap[type]);
-+        build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
-+                                   aff_action[type], aff_dp_bitmap[type],
-+                                   lr_datapaths);
-     }
- 
--    /* LB affinity flows for datapaths where CMS has specified
--     * force_snat_for_lb floag option.
--     */
--    build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
--                               "flags.force_snat_for_lb = 1; ",
--                               dp_bitmap[LROUTER_NAT_LB_AFF_FORCE_SNAT],
--                               lr_datapaths);
--
--    /* LB affinity flows for datapaths where CMS has specified
--     * skip_snat_for_lb floag option or regular datapaths.
--     */
--    char *lb_aff_action =
--        lb->skip_snat ? "flags.skip_snat_for_lb = 1; " : NULL;
--    build_lb_affinity_lr_flows(lflows, lb, lb_vip, ds_cstr(match),
--                               lb_aff_action, dp_bitmap[LROUTER_NAT_LB_AFF],
--                               lr_datapaths);
--
-     ds_destroy(&unsnat_match);
-     ds_destroy(&undnat_match);
-     ds_destroy(&skip_snat_act);
-     ds_destroy(&force_snat_act);
- 
--    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX + 2; i++) {
--        bitmap_free(dp_bitmap[i]);
-+    for (size_t i = 0; i < LROUTER_NAT_LB_FLOW_MAX; i++) {
-+        bitmap_free(gw_dp_bitmap[i]);
-+        bitmap_free(aff_dp_bitmap[i]);
-     }
- }
- 
-@@ -11761,7 +11735,13 @@ build_gateway_get_l2_hdr_size(struct ovn_port *op)
-             struct ovn_port *localnet_port = peer->od->localnet_ports[i];
-             const struct nbrec_logical_switch_port *nbsp = localnet_port->nbsp;
- 
--            if (nbsp && nbsp->n_tag_request > 0) {
-+            if (!nbsp || !nbsp->tag_request) {
-+                continue;
-+            }
-+
-+            if (nbsp->tag_request[0] ||
-+                (nbsp->parent_name && nbsp->parent_name[0])) {
-+                /* Valid tag. */
-                 return VLAN_ETH_HEADER_LEN;
-             }
-         }
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index 35c79cd69..60cb947c4 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -6193,6 +6193,23 @@ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort],
-   table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
- ])
- 
-+# tag 0 requires a parent port
-+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port tag_request=0
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl
-+  table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
-+  table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
-+])
-+
-+check ovn-nbctl --wait=sb set Logical_Switch_Port ext-port parent_name=ext-parent-port
-+
-+ovn-sbctl dump-flows lr0 > lr0flows
-+AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" | sort], [0], [dnl
-+  table=0 (lr_in_admission    ), priority=50   , match=(eth.dst == 00:00:20:20:12:13 && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
-+  table=0 (lr_in_admission    ), priority=50   , match=(eth.mcast && inport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1518); xreg0[[0..47]] = 00:00:20:20:12:13; next;)
-+])
-+
- AT_CLEANUP
- ])
- 
-@@ -6236,24 +6253,16 @@ AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.
-   table=??(lr_in_ip_routing_ecmp), priority=150  , match=(reg8[[0..15]] == 0), action=(next;)
- ])
- 
--AT_CHECK([grep -e "lr_in_ecmp_stateful".*commit_ecmp_nh lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && sctp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;)
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && tcp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;)
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (!ct.rpl && ct.est) && udp && reg9[[5]] == 0), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;)
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && sctp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = sctp); next;)
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && tcp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = tcp); next;)
--  table=??(lr_in_ecmp_stateful), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1 && (ct.new && !ct.est) && udp), action=(ct_commit { ct_label.ecmp_reply_eth = eth.src;  ct_label.ecmp_reply_port = 1;}; commit_ecmp_nh(ipv6 = false, proto = udp); next;)
--])
--
--AT_CHECK([grep -e "lr_in_defrag".*chk_ecmp_nh* lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
--  table=??(lr_in_defrag       ), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(reg9[[5]] = chk_ecmp_nh_mac(); ct_next;)
--  table=??(lr_in_defrag       ), priority=100  , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(reg9[[5]] = chk_ecmp_nh(); ct_next;)
-+AT_CHECK([grep -e "lr_in_defrag" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
-+  table=??(lr_in_defrag       ), priority=0    , match=(1), action=(next;)
-+  table=??(lr_in_defrag       ), priority=100  , match=(inport == "lr0-public" && ip4.src == 1.0.0.1), action=(ct_next;)
-+  table=??(lr_in_defrag       ), priority=100  , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ct_next;)
- ])
- 
- dnl The chassis was created with other_config:ct-no-masked-label=false, the flows
- dnl should be using ct_label.ecmp_reply_port.
- AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl
--  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && reg9[[5]] == 1 && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
-+  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && ct_label.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
- ])
- 
- dnl Simulate an ovn-controller upgrade to a version that supports
-@@ -6263,7 +6272,7 @@ check ovn-sbctl set chassis ch1 other_config:ct-no-masked-label=true
- check ovn-nbctl --wait=sb sync
- ovn-sbctl dump-flows lr0 > lr0flows
- AT_CHECK([grep -e "lr_in_arp_resolve.*ecmp" lr0flows | sed 's/table=../table=??/'], [0], [dnl
--  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && reg9[[5]] == 1 && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
-+  table=??(lr_in_arp_resolve  ), priority=200  , match=(ct.rpl && ct_mark.ecmp_reply_port == 1), action=(push(xxreg1); xxreg1 = ct_label; eth.dst = xxreg1[[32..79]]; pop(xxreg1); next;)
- ])
- 
- # add ecmp route with wrong nexthop
-@@ -8807,6 +8816,26 @@ AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sed 's/table=../table=??/' | s
-   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;)
- ])
- 
-+AS_BOX([Test LR flows - lb_force_snat_ip="172.16.0.1" + skip_snat=true])
-+check ovn-nbctl --wait=sb set logical_router R1 options:lb_force_snat_ip="172.16.0.1"
-+check ovn-nbctl --wait=sb set load_balancer lb0 options:skip_snat=true
-+
-+ovn-sbctl dump-flows R1 > R1flows_force_skip_snat
-+AT_CAPTURE_FILE([R1flows_force_skip_snat])
-+
-+AT_CHECK([grep "lr_in_dnat " R1flows_force_skip_snat | sed 's/table=../table=??/' | sort], [0], [dnl
-+  table=??(lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+  table=??(lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; skip_snat);)
-+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 10.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; skip_snat);)
-+  table=??(lr_in_dnat         ), priority=150  , match=(reg9[[6]] == 1 && ct.new && ip4 && reg4 == 20.0.0.2 && reg8[[0..15]] == 80), action=(reg0 = 172.16.0.10; flags.skip_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; skip_snat);)
-+  table=??(lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
-+  table=??(lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
-+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
-+  table=??(lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;)
-+  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;)
-+])
-+
- AT_CLEANUP
- ])
- 
-diff --git a/tests/ovn.at b/tests/ovn.at
-index 48dbc3347..acda8514e 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -34023,10 +34023,10 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x
-  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135 actions=load:0->NXM_NX_REG10[[12]]
-  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]]
-  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
- 
- check_port_sec_offlows hv1 74
- 
-@@ -34060,12 +34060,12 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p1_key,metadata=0x1 actions=load:0x
-  table=74, priority=80,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136 actions=load:0x1->NXM_NX_REG10[[12]]
-  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:03,arp_spa=10.0.0.3,arp_sha=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-  table=74, priority=90,arp,reg14=0x$sw0p1_key,metadata=0x1,dl_src=00:00:00:00:00:13,arp_spa=10.0.0.13,arp_sha=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:03 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p1_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]" > hv1_t74_flows.expected
- 
- check_port_sec_offlows hv1 74
- 
-@@ -34144,13 +34144,13 @@ echo " table=74, priority=80,arp,reg14=0x$sw0p2_key,metadata=0x1 actions=load:0x
-  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:04,icmp_type=136,icmp_code=0,nd_target=2000::/64,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
-  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-  table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,dl_src=00:00:00:00:00:13,icmp_type=136,icmp_code=0,nd_target=aef0::4,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-- table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=225,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=135,icmp_code=0,nd_sll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:13,nd_tll=00:00:00:00:00:13 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:00 actions=load:0->NXM_NX_REG10[[12]]
-+ table=74, priority=90,icmp6,reg14=0x$sw0p2_key,metadata=0x1,nw_ttl=255,icmp_type=136,icmp_code=0,nd_target=fe80::200:ff:fe00:4,nd_tll=00:00:00:00:00:04 actions=load:0->NXM_NX_REG10[[12]]" > hv2_t74_flows.expected
- 
- check_port_sec_offlows hv2 74
- 
-diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
-index da3224bbc..5467d604d 100755
---- a/utilities/checkpatch.py
-+++ b/utilities/checkpatch.py
-@@ -196,7 +196,7 @@ skip_signoff_check = False
- #
- # Python isn't checked as flake8 performs these checks during build.
- line_length_blacklist = re.compile(
--    r'\.(am|at|etc|in|m4|mk|patch|py)$|^debian/.*$')
-+    r'\.(am|at|etc|in|m4|mk|patch|py|yml)$|^debian/.*$')
- 
- # Don't enforce a requirement that leading whitespace be all spaces on
- # files that include these characters in their name, since these kinds
diff --git a/SOURCES/ovn23.09.patch b/SOURCES/ovn23.09.patch
new file mode 100644
index 0000000..9802b63
--- /dev/null
+++ b/SOURCES/ovn23.09.patch
@@ -0,0 +1,9867 @@
+diff --git a/.ci/ci.sh b/.ci/ci.sh
+index 10f11939c..3f1b41ead 100755
+--- a/.ci/ci.sh
++++ b/.ci/ci.sh
+@@ -23,7 +23,7 @@ CONTAINER_WORKDIR="/workspace/ovn-tmp"
+ IMAGE_NAME=${IMAGE_NAME:-"ovn-org/ovn-tests"}
+ 
+ # Test variables
+-ARCH=${ARCH:-$(uname -i)}
++ARCH=${ARCH:-$(uname -m)}
+ CC=${CC:-gcc}
+ 
+ 
+@@ -105,6 +105,16 @@ function run_tests() {
+     "
+ }
+ 
++function check_clang_version_ge() {
++    lower=$1
++    version=$(clang --version | head -n1 | cut -d' ' -f3)
++    if ! echo -e "$lower\n$version" | sort -CV; then
++      return 1
++    fi
++
++    return 0
++}
++
+ options=$(getopt --options "" \
+     --long help,shell,archive-logs,jobs:,ovn-path:,ovs-path:,image-name:\
+     -- "${@}")
+@@ -149,7 +159,7 @@ while true; do
+ done
+ 
+ # Workaround for https://bugzilla.redhat.com/2153359
+-if [ "$ARCH" = "aarch64" ]; then
++if [ "$ARCH" = "aarch64" ] && ! check_clang_version_ge "16.0.0"; then
+     ASAN_OPTIONS="detect_leaks=0"
+ fi
+ 
+diff --git a/.ci/linux-build.sh b/.ci/linux-build.sh
+index 5a79a52da..7270b9e60 100755
+--- a/.ci/linux-build.sh
++++ b/.ci/linux-build.sh
+@@ -80,7 +80,7 @@ function configure_gcc()
+             # We should install gcc-multilib for x86 build, we cannot
+             # do it directly because gcc-multilib is not available
+             # for arm64
+-            sudo apt install -y gcc-multilib
++            sudo apt update && sudo apt install -y gcc-multilib
+         fi
+     fi
+ }
+diff --git a/.ci/linux-util.sh b/.ci/linux-util.sh
+new file mode 100755
+index 000000000..920345123
+--- /dev/null
++++ b/.ci/linux-util.sh
+@@ -0,0 +1,19 @@
++#!/bin/bash
++
++function free_up_disk_space_ubuntu()
++{
++    local pkgs='azure-cli aspnetcore-* dotnet-* ghc-* firefox*
++                google-chrome-stable google-cloud-cli libmono-* llvm-*
++                microsoft-edge-stable mono-* msbuild mysql-server-core-*
++                php-* php7* powershell* temurin-* zulu-*'
++
++    # Use apt patterns to only select real packages that match the names
++    # in the list above.
++    local pkgs=$(echo $pkgs | sed 's/[^ ]* */~n&/g')
++
++    sudo apt update && sudo apt-get --auto-remove -y purge $pkgs
++
++    local paths='/usr/local/lib/android/ /usr/share/dotnet/ /opt/ghc/
++                 /usr/local/share/boost/'
++    sudo rm -rf $paths
++}
+diff --git a/.ci/ovn-kubernetes/Dockerfile b/.ci/ovn-kubernetes/Dockerfile
+index 12f819017..eda8b6d02 100644
+--- a/.ci/ovn-kubernetes/Dockerfile
++++ b/.ci/ovn-kubernetes/Dockerfile
+@@ -81,6 +81,7 @@ RUN dnf install -y *.rpm && rm -f *.rpm
+ # install ovn-kubernetes binaries built in previous stage
+ RUN mkdir -p /usr/libexec/cni/
+ COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube /usr/bin/
++COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovnkube-identity /usr/bin/
+ COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-kube-util /usr/bin/
+ COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovndbchecker /usr/bin/
+ COPY --from=ovnkubebuilder /root/ovn-kubernetes/go-controller/_output/go/bin/ovn-k8s-cni-overlay /usr/libexec/cni/ovn-k8s-cni-overlay
+diff --git a/.cirrus.yml b/.cirrus.yml
+index bd4cd08aa..dd2aa1bfd 100644
+--- a/.cirrus.yml
++++ b/.cirrus.yml
+@@ -1,14 +1,33 @@
+-arm_unit_tests_task:
++compute_engine_instance:
++  image_project: ubuntu-os-cloud
++  image: family/ubuntu-2304-arm64
++  architecture: arm64
++  platform: linux
++  memory: 4G
++
++# Run separate task for the image build, so it's running only once outside
++# the test matrix.
++build_image_task:
++  install_dependencies_script:
++    - sudo apt update
++    - sudo apt install -y podman make
++
++  build_container_script:
++    - cd utilities/containers
++    - make ubuntu
++    - podman save -o /tmp/image.tar ovn-org/ovn-tests:ubuntu
++
++  upload_image_script:
++    - curl -s -X POST -T /tmp/image.tar http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO}
+ 
+-  arm_container:
+-    image: ghcr.io/ovn-org/ovn-tests:fedora
+-    memory: 4G
+-    cpu: 2
++arm_unit_tests_task:
++  depends_on:
++    - build_image
+ 
+   env:
+-    ARCH: aarch64
+     CIRRUS_CLONE_SUBMODULES: true
+     PATH: ${HOME}/bin:${HOME}/.local/bin:${PATH}
++    IMAGE_NAME: ovn-org/ovn-tests:ubuntu
+     matrix:
+       - CC: gcc
+         TESTSUITE: test
+@@ -31,5 +50,16 @@ arm_unit_tests_task:
+ 
+   name: ARM64 ${CC} ${TESTSUITE} ${TEST_RANGE}
+ 
++  install_dependencies_script:
++    - sudo apt update
++    - sudo apt install -y podman
++
++  download_cache_script:
++    - curl http://$CIRRUS_HTTP_CACHE_HOST/${CIRRUS_CHANGE_IN_REPO} -o /tmp/image.tar
++
++  load_image_script:
++    - podman load -i /tmp/image.tar
++    - rm -rf /tmp/image.tar
++
+   build_script:
+-    - ./.ci/linux-build.sh
++    - ./.ci/ci.sh --archive-logs
+diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml
+index 57e815ed8..bdd118087 100644
+--- a/.github/workflows/containers.yml
++++ b/.github/workflows/containers.yml
+@@ -15,7 +15,7 @@ env:
+ 
+ jobs:
+   container:
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+     strategy:
+       matrix:
+         distro: [ fedora, ubuntu ]
+diff --git a/.github/workflows/ovn-fake-multinode-tests.yml b/.github/workflows/ovn-fake-multinode-tests.yml
+index 75c5ca818..25610df53 100644
+--- a/.github/workflows/ovn-fake-multinode-tests.yml
++++ b/.github/workflows/ovn-fake-multinode-tests.yml
+@@ -13,7 +13,7 @@ concurrency:
+ jobs:
+   build:
+     name: Build ovn-fake-multinode image
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+     strategy:
+       matrix:
+         cfg:
+@@ -69,7 +69,7 @@ jobs:
+         path: /tmp/_output/ovn_${{ matrix.cfg.branch }}_image.tar
+ 
+   multinode-tests:
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+     timeout-minutes: 15
+     needs: [build]
+     strategy:
+@@ -99,13 +99,18 @@ jobs:
+       XDG_RUNTIME_DIR: ''
+ 
+     steps:
++    - name: Check out ovn
++      uses: actions/checkout@v3
++
+     - name: install required dependencies
+       run:  |
+         sudo apt update || true
+         sudo apt install -y ${{ env.dependencies }}
+ 
+     - name: Free up disk space
+-      run: sudo eatmydata apt-get remove --auto-remove -y aspnetcore-* dotnet-* libmono-* mono-* msbuild php-* php7* ghc-* zulu-*
++      run: |
++        . .ci/linux-util.sh
++        free_up_disk_space_ubuntu
+ 
+     - uses: actions/download-artifact@v3
+       with:
+@@ -153,7 +158,7 @@ jobs:
+     - name: set up python
+       uses: actions/setup-python@v4
+       with:
+-        python-version: '3.x'
++        python-version: '3.12'
+ 
+     - name: Check out ovn
+       uses: actions/checkout@v3
+diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml
+index 69ab0566d..1689396d6 100644
+--- a/.github/workflows/ovn-kubernetes.yml
++++ b/.github/workflows/ovn-kubernetes.yml
+@@ -24,7 +24,7 @@ env:
+ jobs:
+   build:
+     name: Build
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+     steps:
+     - name: Enable Docker experimental features
+       run: |
+@@ -62,7 +62,7 @@ jobs:
+   e2e:
+     name: e2e
+     if: github.event_name != 'schedule'
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+     timeout-minutes: 220
+     strategy:
+       fail-fast: false
+@@ -95,18 +95,14 @@ jobs:
+       KIND_IPV6_SUPPORT: "${{ matrix.ipfamily == 'IPv6' || matrix.ipfamily == 'dualstack' }}"
+     steps:
+ 
+-    - name: Free up disk space
+-      run: |
+-        sudo eatmydata apt-get purge --auto-remove -y \
+-          azure-cli aspnetcore-* dotnet-* ghc-* firefox \
+-          google-chrome-stable google-cloud-sdk \
+-          llvm-* microsoft-edge-stable mono-* \
+-          msbuild mysql-server-core-* php-* php7* \
+-          powershell temurin-* zulu-*
+-
+     - name: Check out ovn
+       uses: actions/checkout@v3
+ 
++    - name: Free up disk space
++      run: |
++        . .ci/linux-util.sh
++        free_up_disk_space_ubuntu
++
+     - name: Check out ovn-kubernetes
+       uses: actions/checkout@v3
+       with:
+diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
+index fe2a14c40..aa01dc7e9 100644
+--- a/.github/workflows/test.yml
++++ b/.github/workflows/test.yml
+@@ -80,10 +80,62 @@ jobs:
+       if: steps.dpdk_cache.outputs.cache-hit != 'true'
+       run: ./.ci/dpdk-build.sh
+ 
++  prepare-container:
++    # This job has the following matrix, x: Job trigger, y: Branch
++    # (scheduled jobs run only on main):
++    # +-------+-------------------+-------------------+
++    # |       | Push/Pull request |     Scheduled     |
++    # +-------+-------------------+-------------------+
++    # |  main |  ghcr.io - Ubuntu | ghcr.io - Fedora  |
++    # +-------+-------------------+-------------------+
++    # | !main |  Builds - Ubuntu  | xxxxxxxxxxxxxxxxx |
++    # +-------+-------------------+-------------------+
++    env:
++      DEPENDENCIES: podman
++    name: Prepare container
++    runs-on: ubuntu-22.04
++
++    steps:
++      - uses: actions/checkout@v3
++
++      - name: Update APT cache
++        run: sudo apt update
++
++      - name: Install dependencies
++        run: sudo apt install -y ${{ env.DEPENDENCIES }}
++
++      - name: Choose image distro
++        if: github.event_name == 'push' || github.event_name == 'pull_request'
++        run: |
++          echo "IMAGE_DISTRO=ubuntu" >> $GITHUB_ENV
++
++      - name: Choose image distro
++        if: github.event_name == 'schedule'
++        run: |
++          echo "IMAGE_DISTRO=fedora" >> $GITHUB_ENV
++
++      - name: Build container
++        if: github.ref_name != 'main'
++        run: make ${{ env.IMAGE_DISTRO }}
++        working-directory: utilities/containers
++
++      - name: Download container
++        if: github.ref_name == 'main'
++        run: podman pull ghcr.io/ovn-org/ovn-tests:${{ env.IMAGE_DISTRO }}
++
++      - name: Export image
++        run: podman save -o /tmp/image.tar ovn-org/ovn-tests
++
++      - name: Cache image
++        id: image_cache
++        uses: actions/cache@v3
++        with:
++          path: /tmp/image.tar
++          key: ${{ github.sha }}
++
+   build-linux:
+-    needs: build-dpdk
++    needs: [build-dpdk, prepare-container]
+     env:
+-      IMAGE_NAME:  ghcr.io/ovn-org/ovn-tests:ubuntu
+       ARCH:        ${{ matrix.cfg.arch }}
+       CC:          ${{ matrix.cfg.compiler }}
+       DPDK:        ${{ matrix.cfg.dpdk }}
+@@ -94,7 +146,7 @@ jobs:
+       SANITIZERS:  ${{ matrix.cfg.sanitizers }}
+ 
+     name: linux ${{ join(matrix.cfg.*, ' ') }}
+-    runs-on: ubuntu-latest
++    runs-on: ubuntu-22.04
+ 
+     strategy:
+       fail-fast: false
+@@ -157,13 +209,25 @@ jobs:
+             sort -V | tail -1)
+       working-directory: ovs
+ 
+-    - name: cache
++    - name: cache dpdk
+       if: matrix.cfg.dpdk != ''
+       uses: actions/cache@v3
+       with:
+         path: dpdk-dir
+         key: ${{ needs.build-dpdk.outputs.dpdk_key }}
+ 
++    - name: image cache
++      uses: actions/cache@v3
++      with:
++        path: /tmp/image.tar
++        key: ${{ github.sha }}
++
++    - name: load image
++      run: |
++        sudo podman load -i /tmp/image.tar
++        podman load -i /tmp/image.tar
++        rm -rf /tmp/image.tar
++
+     - name: build
+       if: ${{ startsWith(matrix.cfg.testsuite, 'system-test') }}
+       run: sudo -E ./.ci/ci.sh --archive-logs
+@@ -225,7 +289,7 @@ jobs:
+     - name: set up python
+       uses: actions/setup-python@v4
+       with:
+-        python-version: '3.x'
++        python-version: '3.12'
+     - name: prepare
+       run:  ./.ci/osx-prepare.sh
+     - name: build
+@@ -239,8 +303,8 @@ jobs:
+ 
+   build-linux-rpm:
+     name: linux rpm fedora
+-    runs-on: ubuntu-latest
+-    container: fedora:latest
++    runs-on: ubuntu-22.04
++    container: fedora:38
+     timeout-minutes: 30
+ 
+     strategy:
+diff --git a/.readthedocs.yaml b/.readthedocs.yaml
+new file mode 100644
+index 000000000..8c451663a
+--- /dev/null
++++ b/.readthedocs.yaml
+@@ -0,0 +1,26 @@
++# .readthedocs.yaml
++# Read the Docs configuration file.
++# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details.
++
++# Required.
++version: 2
++
++# Set the OS, Python version, etc.
++build:
++  os: ubuntu-22.04
++  tools:
++    python: "3.12"
++
++# Build documentation in the "Documentation/" directory with Sphinx.
++sphinx:
++  configuration: Documentation/conf.py
++  # Default HTML builder.
++  builder: "html"
++
++# Build all formats: HTML, PDF, ePub.
++formats: all
++
++# Declare the Python requirements.
++python:
++  install:
++  - requirements: Documentation/requirements.txt
+diff --git a/Documentation/conf.py b/Documentation/conf.py
+index f7eceaec8..f8fc0125f 100644
+--- a/Documentation/conf.py
++++ b/Documentation/conf.py
+@@ -12,16 +12,14 @@
+ # All configuration values have a default; values that are commented out
+ # serve to show the default.
+ 
++import importlib
+ import string
+ import sys
+ 
+-try:
+-    import ovs_sphinx_theme
+-    use_ovs_theme = True
+-except ImportError:
+-    print("Cannot find 'ovs-sphinx-theme' package. "
++use_rtd_theme = importlib.util.find_spec('sphinx_rtd_theme') is not None
++if not use_rtd_theme:
++    print("Cannot find 'sphinx_rtd_theme' package. "
+           "Falling back to default theme.")
+-    use_ovs_theme = False
+ 
+ # -- General configuration ------------------------------------------------
+ 
+@@ -48,7 +46,7 @@ master_doc = 'contents'
+ 
+ # General information about the project.
+ project = u'Open Virtual Network (OVN)'
+-copyright = u'2020, The Open Virtual Network (OVN) Development Community'
++copyright = u'2020-2023, The Open Virtual Network (OVN) Development Community'
+ author = u'The Open Virtual Network (OVN) Development Community'
+ 
+ # The version info for the project you're documenting, acts as replacement for
+@@ -89,14 +87,8 @@ linkcheck_anchors = False
+ # The theme to use for HTML and HTML Help pages.  See the documentation for
+ # a list of builtin themes.
+ #
+-if use_ovs_theme:
+-    html_theme = 'ovs'
+-
+-# Add any paths that contain custom themes here, relative to this directory.
+-if use_ovs_theme:
+-    html_theme_path = [ovs_sphinx_theme.get_theme_dir()]
+-else:
+-    html_theme_path = []
++if use_rtd_theme:
++    html_theme = 'sphinx_rtd_theme'
+ 
+ # The name of an image file (relative to this directory) to place at the top
+ # of the sidebar.
+diff --git a/Documentation/internals/documentation.rst b/Documentation/internals/documentation.rst
+index 72a120bef..59c18b3a2 100644
+--- a/Documentation/internals/documentation.rst
++++ b/Documentation/internals/documentation.rst
+@@ -41,17 +41,13 @@ variety of other output formats but also allows for things like
+ cross-referencing and indexing. for more information on the two, refer to the
+ :doc:`contributing/documentation-style`.
+ 
+-ovs-sphinx-theme
++sphinx_rtd_theme
+ ----------------
+ 
+-The documentation uses its own theme, `ovs-sphinx-theme`, which can be found on
+-GitHub__ and is published on pypi__. This is shared by Open vSwitch and OVN.
+-It is packaged separately to ensure all documentation gets the latest version
+-of the theme (assuming there are no major version bumps in that package). If
+-building locally and the package is installed, it will be used. If the package
+-is not installed, Sphinx will fallback to the default theme.
+-
+-The package is currently maintained by Stephen Finucane and Russell Bryant.
++The documentation uses `sphinx_rtd_theme`, which can be found on GitHub__ and
++is published on pypi__.  It is also packaged in major distributions.
++If building locally and the package is installed, it will be used.  If the
++package is not installed, Sphinx will fallback to the default theme.
+ 
+ Read the Docs
+ -------------
+@@ -72,6 +68,6 @@ modifications to this site, refer to the `GitHub project`__.
+ 
+ __ http://docutils.sourceforge.net/rst.html
+ __ http://www.sphinx-doc.org/
+-__ https://github.com/openvswitch/ovs-sphinx-theme
+-__ https://pypi.python.org/pypi/ovs-sphinx-theme
++__ https://github.com/readthedocs/sphinx_rtd_theme
++__ https://pypi.python.org/pypi/sphinx_rtd_theme
+ __ https://github.com/ovn-org/ovn-org.github.io
+diff --git a/Documentation/internals/release-process.rst b/Documentation/internals/release-process.rst
+index ec79fe48c..26d3f8d4d 100644
+--- a/Documentation/internals/release-process.rst
++++ b/Documentation/internals/release-process.rst
+@@ -64,6 +64,10 @@ Scheduling`_ for the timing of each stage:
+    branch.  Features to be added to release branches should be limited in scope
+    and risk and discussed on ovs-dev before creating the branch.
+ 
++   In order to keep the CI stable on the new release branch, the Ubuntu
++   container should be pinned to the current LTS version in the Dockerfile
++   e.g. registry.hub.docker.com/library/ubuntu:22.04.
++
+ 3. When committers come to rough consensus that the release is ready, they
+    release the .0 release on its branch, e.g. 25.09.0 for branch-25.09.  To
+    make the actual release, a committer pushes a signed tag named, e.g.
+diff --git a/Documentation/intro/install/general.rst b/Documentation/intro/install/general.rst
+index 589518846..dd8bf5c2c 100644
+--- a/Documentation/intro/install/general.rst
++++ b/Documentation/intro/install/general.rst
+@@ -134,10 +134,7 @@ following to obtain better warnings:
+ 
+ - clang, version 3.4 or later
+ 
+-- flake8 along with the hacking flake8 plugin (for Python code). The automatic
+-  flake8 check that runs against Python code has some warnings enabled that
+-  come from the "hacking" flake8 plugin. If it's not installed, the warnings
+-  just won't occur until it's run on a system with "hacking" installed.
++- flake8 (for Python code)
+ 
+ You may find the ovs-dev script found in ``ovs/utilities/ovs-dev.py`` useful.
+ 
+diff --git a/Documentation/requirements.txt b/Documentation/requirements.txt
+index 77130c6e0..63a7997f7 100644
+--- a/Documentation/requirements.txt
++++ b/Documentation/requirements.txt
+@@ -1,2 +1,2 @@
+-sphinx>=1.1,<2.0
+-ovs_sphinx_theme>=1.0,<1.1
++sphinx>=1.1
++sphinx_rtd_theme>=1.0,<2.0
+diff --git a/Makefile.am b/Makefile.am
+index b58d4a501..bfc9565e8 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -90,6 +90,7 @@ EXTRA_DIST = \
+ 	.ci/dpdk-build.sh \
+ 	.ci/dpdk-prepare.sh \
+ 	.ci/linux-build.sh \
++	.ci/linux-util.sh \
+ 	.ci/osx-build.sh \
+ 	.ci/osx-prepare.sh \
+ 	.ci/ovn-kubernetes/Dockerfile \
+@@ -99,6 +100,7 @@ EXTRA_DIST = \
+ 	.github/workflows/test.yml \
+ 	.github/workflows/ovn-kubernetes.yml \
+ 	.github/workflows/ovn-fake-multinode-tests.yml \
++	.readthedocs.yaml \
+ 	boot.sh \
+ 	$(MAN_FRAGMENTS) \
+ 	$(MAN_ROOTS) \
+@@ -414,16 +416,10 @@ ALL_LOCAL += flake8-check
+ #   F811 redefinition of unused <name> from line <N> (only from flake8 v2.0)
+ # D*** -- warnings from flake8-docstrings plugin
+ # H*** -- warnings from flake8 hacking plugin (custom style checks beyond PEP8)
+-#   H231 Python 3.x incompatible 'except x,y:' construct
+-#   H232 Python 3.x incompatible octal 077 should be written as 0o77
+-#   H233 Python 3.x incompatible use of print operator
+-#   H238 old style class declaration, use new style (inherit from `object`)
+-FLAKE8_SELECT = H231,H232,H233,H238
+ FLAKE8_IGNORE = E121,E123,E125,E126,E127,E128,E129,E131,E722,W503,W504,F811,D,H,I
+ flake8-check: $(FLAKE8_PYFILES)
+ 	$(FLAKE8_WERROR)$(AM_V_GEN) \
+ 	  src='$^' && \
+-	  flake8 $$src --select=$(FLAKE8_SELECT) $(FLAKE8_FLAGS) && \
+ 	  flake8 $$src --ignore=$(FLAKE8_IGNORE) $(FLAKE8_FLAGS) && \
+ 	  touch $@
+ endif
+diff --git a/NEWS b/NEWS
+index 75e046ab3..ed74d0f38 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,3 +1,11 @@
++OVN v23.09.2 - xx xxx xxxx
++--------------------------
++
++
++OVN v23.09.1 - 01 Dec 2023
++--------------------------
++  - Bug fixes
++
+ OVN v23.09.0 - 15 Sep 2023
+ ----------------------------
+   - Added FDB aging mechanism, that is disabled by default.
+diff --git a/configure.ac b/configure.ac
+index ab404b959..e4d5134dd 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -13,7 +13,7 @@
+ # limitations under the License.
+ 
+ AC_PREREQ(2.63)
+-AC_INIT(ovn, 23.09.0, bugs@openvswitch.org)
++AC_INIT(ovn, 23.09.2, bugs@openvswitch.org)
+ AC_CONFIG_MACRO_DIR([m4])
+ AC_CONFIG_AUX_DIR([build-aux])
+ AC_CONFIG_HEADERS([config.h])
+diff --git a/controller-vtep/ovn-controller-vtep.c b/controller-vtep/ovn-controller-vtep.c
+index 23b368179..4472e5285 100644
+--- a/controller-vtep/ovn-controller-vtep.c
++++ b/controller-vtep/ovn-controller-vtep.c
+@@ -306,10 +306,12 @@ parse_options(int argc, char *argv[])
+ 
+         switch (c) {
+         case 'd':
++            free(ovnsb_remote);
+             ovnsb_remote = xstrdup(optarg);
+             break;
+ 
+         case 'D':
++            free(vtep_remote);
+             vtep_remote = xstrdup(optarg);
+             break;
+ 
+diff --git a/controller/binding.c b/controller/binding.c
+index a521f2828..2afc5d48a 100644
+--- a/controller/binding.c
++++ b/controller/binding.c
+@@ -55,8 +55,13 @@ struct claimed_port {
+     long long int last_claimed;
+ };
+ 
++struct qos_port {
++    bool added;
++};
++
+ static struct shash _claimed_ports = SHASH_INITIALIZER(&_claimed_ports);
+ static struct sset _postponed_ports = SSET_INITIALIZER(&_postponed_ports);
++static struct shash _qos_ports = SHASH_INITIALIZER(&_qos_ports);
+ 
+ static void
+ remove_additional_chassis(const struct sbrec_port_binding *pb,
+@@ -218,6 +223,17 @@ get_qos_egress_port_interface(struct shash *bridge_mappings,
+     return NULL;
+ }
+ 
++static void
++add_or_del_qos_port(const char *ovn_port, bool add)
++{
++    struct qos_port *qos_port = shash_find_data(&_qos_ports, ovn_port);
++    if (!qos_port) {
++        qos_port = xzalloc(sizeof *qos_port);
++        shash_add(&_qos_ports, ovn_port, qos_port);
++    }
++    qos_port->added = add;
++}
++
+ /* 34359738360 == (2^32 - 1) * 8.  netdev_set_qos() doesn't support
+  * 64-bit rate netlink attributes, so the maximum value is 2^32 - 1
+  * bytes. The 'max-rate' config option is in bits, so multiplying by 8.
+@@ -225,7 +241,7 @@ get_qos_egress_port_interface(struct shash *bridge_mappings,
+  * can be unrecognized for certain NICs or reported too low for virtual
+  * interfaces. */
+ #define OVN_QOS_MAX_RATE    34359738360ULL
+-static void
++static bool
+ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
+                         const struct ovsrec_port *port,
+                         unsigned long long min_rate,
+@@ -239,7 +255,7 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
+     const struct ovsrec_qos *qos = port->qos;
+     if (qos && !smap_get_bool(&qos->external_ids, "ovn_qos", false)) {
+         /* External configured QoS, do not overwrite it. */
+-        return;
++        return false;
+     }
+ 
+     if (!qos) {
+@@ -282,22 +298,18 @@ add_ovs_qos_table_entry(struct ovsdb_idl_txn *ovs_idl_txn,
+     ovsrec_queue_verify_external_ids(queue);
+     ovsrec_queue_set_external_ids(queue, &external_ids);
+     smap_destroy(&external_ids);
++    return true;
+ }
+ 
+ static void
+-remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn,
+-                       const struct sbrec_port_binding *pb,
++remove_stale_qos_entry( const char *logical_port,
+                        struct ovsdb_idl_index *ovsrec_port_by_qos,
+                        const struct ovsrec_qos_table *qos_table,
+                        struct hmap *queue_map)
+ {
+-    if (!ovs_idl_txn) {
+-        return;
+-    }
+-
+     struct qos_queue *q = find_qos_queue(
+-            queue_map, hash_string(pb->logical_port, 0),
+-            pb->logical_port);
++            queue_map, hash_string(logical_port, 0),
++            logical_port);
+     if (!q) {
+         return;
+     }
+@@ -338,8 +350,12 @@ remove_stale_qos_entry(struct ovsdb_idl_txn *ovs_idl_txn,
+ 
+ static void
+ configure_qos(const struct sbrec_port_binding *pb,
+-              struct binding_ctx_in *b_ctx_in,
+-              struct binding_ctx_out *b_ctx_out)
++              struct ovsdb_idl_txn *ovs_idl_txn,
++              struct ovsdb_idl_index *ovsrec_port_by_qos,
++              const struct ovsrec_qos_table *qos_table,
++              struct hmap *qos_map,
++              const struct ovsrec_open_vswitch_table *ovs_table,
++              const struct ovsrec_bridge_table *bridge_table)
+ {
+     unsigned long long min_rate = smap_get_ullong(
+             &pb->options, "qos_min_rate", 0);
+@@ -351,20 +367,20 @@ configure_qos(const struct sbrec_port_binding *pb,
+ 
+     if ((!min_rate && !max_rate && !burst) || !queue_id) {
+         /* Qos is not configured for this port. */
+-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb,
+-                               b_ctx_in->ovsrec_port_by_qos,
+-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
++        remove_stale_qos_entry(pb->logical_port,
++                               ovsrec_port_by_qos,
++                               qos_table, qos_map);
+         return;
+     }
+ 
+     const char *network = smap_get(&pb->options, "qos_physical_network");
+     uint32_t hash = hash_string(pb->logical_port, 0);
+-    struct qos_queue *q = find_qos_queue(b_ctx_out->qos_map, hash,
++    struct qos_queue *q = find_qos_queue(qos_map, hash,
+                                          pb->logical_port);
+     if (!q || q->min_rate != min_rate || q->max_rate != max_rate ||
+         q->burst != burst || (network && strcmp(network, q->network))) {
+         struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings);
+-        add_ovs_bridge_mappings(b_ctx_in->ovs_table, b_ctx_in->bridge_table,
++        add_ovs_bridge_mappings(ovs_table, bridge_table,
+                                 &bridge_mappings);
+ 
+         const struct ovsrec_port *port = NULL;
+@@ -375,25 +391,58 @@ configure_qos(const struct sbrec_port_binding *pb,
+         }
+         if (iface) {
+             /* Add new QoS entries. */
+-            add_ovs_qos_table_entry(b_ctx_in->ovs_idl_txn, port, min_rate,
++            if (add_ovs_qos_table_entry(ovs_idl_txn, port, min_rate,
+                                     max_rate, burst, queue_id,
+-                                    pb->logical_port);
+-            if (!q) {
+-                q = xzalloc(sizeof *q);
+-                hmap_insert(b_ctx_out->qos_map, &q->node, hash);
+-                q->port = xstrdup(pb->logical_port);
+-                q->queue_id = queue_id;
++                                    pb->logical_port)) {
++                if (!q) {
++                    q = xzalloc(sizeof *q);
++                    hmap_insert(qos_map, &q->node, hash);
++                    q->port = xstrdup(pb->logical_port);
++                    q->queue_id = queue_id;
++                }
++                free(q->network);
++                q->network = network ? xstrdup(network) : NULL;
++                q->min_rate = min_rate;
++                q->max_rate = max_rate;
++                q->burst = burst;
+             }
+-            free(q->network);
+-            q->network = network ? xstrdup(network) : NULL;
+-            q->min_rate = min_rate;
+-            q->max_rate = max_rate;
+-            q->burst = burst;
+         }
+         shash_destroy(&bridge_mappings);
+     }
+ }
+ 
++void
++update_qos(struct ovsdb_idl_index *sbrec_port_binding_by_name,
++           struct ovsdb_idl_txn *ovs_idl_txn,
++           struct ovsdb_idl_index *ovsrec_port_by_qos,
++           const struct ovsrec_qos_table *qos_table,
++           struct hmap *qos_map,
++           const struct ovsrec_open_vswitch_table *ovs_table,
++           const struct ovsrec_bridge_table *bridge_table)
++{
++    /* Remove qos for any ports for which we could not do it before */
++    const struct sbrec_port_binding *pb;
++
++    struct shash_node *node;
++    SHASH_FOR_EACH_SAFE (node, &_qos_ports) {
++        struct qos_port *qos_port = (struct qos_port *) node->data;
++        if (qos_port->added) {
++            pb = lport_lookup_by_name(sbrec_port_binding_by_name,
++                                      node->name);
++            if (pb) {
++                configure_qos(pb, ovs_idl_txn, ovsrec_port_by_qos, qos_table,
++                              qos_map, ovs_table, bridge_table);
++            }
++        } else {
++            remove_stale_qos_entry(node->name,
++                                   ovsrec_port_by_qos,
++                                   qos_table, qos_map);
++        }
++        free(qos_port);
++        shash_delete(&_qos_ports, node);
++    }
++}
++
+ static const struct ovsrec_queue *
+ find_qos_queue_by_external_ids(const struct smap *external_ids,
+     struct ovsdb_idl_index *ovsrec_queue_by_external_ids)
+@@ -1144,33 +1193,22 @@ local_binding_set_pb(struct shash *local_bindings, const char *pb_name,
+  * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for
+  *   container and virtual ports).
+  *
+- * Returns false if lport is not claimed due to 'sb_readonly'.
+- * Returns true otherwise.
+- *
+  * Note:
+  *   Updates the 'pb->up' field only if it's explicitly set to 'false'.
+  *   This is to ensure compatibility with older versions of ovn-northd.
+  */
+-static bool
++void
+ claimed_lport_set_up(const struct sbrec_port_binding *pb,
+-                     const struct sbrec_port_binding *parent_pb,
+-                     bool sb_readonly)
++                     const struct sbrec_port_binding *parent_pb)
+ {
+-    /* When notify_up is false in claim_port(), no state is created
+-     * by if_status_mgr. In such cases, return false (i.e. trigger recompute)
+-     * if we can't update sb (because it is readonly).
+-     */
+     bool up = true;
+     if (!parent_pb || (parent_pb->n_up && parent_pb->up[0])) {
+-        if (!sb_readonly) {
+-            if (pb->n_up) {
+-                sbrec_port_binding_set_up(pb, &up, 1);
+-            }
+-        } else if (pb->n_up && !pb->up[0]) {
+-            return false;
++        if (pb->n_up) {
++            VLOG_INFO("Setting lport %s up in Southbound",
++                      pb->logical_port);
++            sbrec_port_binding_set_up(pb, &up, 1);
+         }
+     }
+-    return true;
+ }
+ 
+ typedef void (*set_func)(const struct sbrec_port_binding *pb,
+@@ -1250,7 +1288,7 @@ remove_additional_chassis(const struct sbrec_port_binding *pb,
+     remove_additional_encap_for_chassis(pb, chassis_rec);
+ }
+ 
+-static bool
++bool
+ lport_maybe_postpone(const char *port_name, long long int now,
+                      struct sset *postponed_ports)
+ {
+@@ -1273,7 +1311,7 @@ claim_lport(const struct sbrec_port_binding *pb,
+             const struct sbrec_port_binding *parent_pb,
+             const struct sbrec_chassis *chassis_rec,
+             const struct ovsrec_interface *iface_rec,
+-            bool sb_readonly, bool notify_up,
++            bool sb_readonly, bool is_vif,
+             struct hmap *tracked_datapaths,
+             struct if_status_mgr *if_mgr,
+             struct sset *postponed_ports)
+@@ -1298,39 +1336,27 @@ claim_lport(const struct sbrec_port_binding *pb,
+             }
+             update_tracked = true;
+ 
+-            if (!notify_up) {
+-                if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) {
+-                    return false;
+-                }
+-                if (sb_readonly) {
+-                    return false;
+-                }
+-                set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+-            } else {
+-                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
+-                                          sb_readonly, can_bind);
+-            }
++            if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
++                                      sb_readonly, can_bind, is_vif,
++                                      parent_pb);
+             register_claim_timestamp(pb->logical_port, now);
+             sset_find_and_delete(postponed_ports, pb->logical_port);
+         } else {
+-            if (!notify_up) {
+-                if (!claimed_lport_set_up(pb, parent_pb, sb_readonly)) {
+-                    return false;
+-                }
+-            } else {
+-                if ((pb->n_up && !pb->up[0]) ||
+-                    !smap_get_bool(&iface_rec->external_ids,
+-                                   OVN_INSTALLED_EXT_ID, false)) {
+-                    if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
+-                                              iface_rec, sb_readonly,
+-                                              can_bind);
+-                }
++            update_tracked = true;
++            if ((pb->n_up && !pb->up[0]) ||
++                (is_vif && !smap_get_bool(&iface_rec->external_ids,
++                                          OVN_INSTALLED_EXT_ID, false))) {
++                if_status_mgr_claim_iface(if_mgr, pb, chassis_rec,
++                                          iface_rec, sb_readonly,
++                                          can_bind, is_vif,
++                                          parent_pb);
+             }
+         }
+     } else if (can_bind == CAN_BIND_AS_ADDITIONAL) {
+         if (!is_additional_chassis(pb, chassis_rec)) {
+             if_status_mgr_claim_iface(if_mgr, pb, chassis_rec, iface_rec,
+-                                      sb_readonly, can_bind);
++                                      sb_readonly, can_bind, is_vif,
++                                      parent_pb);
+             update_tracked = true;
+         }
+     }
+@@ -1524,8 +1550,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
+                 tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED,
+                                            b_ctx_out->tracked_dp_bindings);
+             }
+-            if (b_lport->lbinding->iface && b_ctx_in->ovs_idl_txn) {
+-                configure_qos(pb, b_ctx_in, b_ctx_out);
++            if (b_lport->lbinding->iface) {
++                add_or_del_qos_port(pb->logical_port, true);
+             }
+         } else {
+             /* We could, but can't claim the lport. */
+@@ -1814,8 +1840,10 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb,
+                            b_ctx_out->postponed_ports);
+     }
+ 
+-    if (pb->chassis == b_ctx_in->chassis_rec ||
+-            is_additional_chassis(pb, b_ctx_in->chassis_rec)) {
++    if (pb->chassis == b_ctx_in->chassis_rec
++            || is_additional_chassis(pb, b_ctx_in->chassis_rec)
++            || if_status_is_port_claimed(b_ctx_out->if_mgr,
++                                         pb->logical_port)) {
+         return release_lport(pb, b_ctx_in->chassis_rec,
+                              !b_ctx_in->ovnsb_idl_txn,
+                              b_ctx_out->tracked_dp_bindings,
+@@ -1851,7 +1879,6 @@ consider_l3gw_lport(const struct sbrec_port_binding *pb,
+ 
+ static void
+ consider_localnet_lport(const struct sbrec_port_binding *pb,
+-                        struct binding_ctx_in *b_ctx_in,
+                         struct binding_ctx_out *b_ctx_out)
+ {
+     struct local_datapath *ld
+@@ -1860,14 +1887,23 @@ consider_localnet_lport(const struct sbrec_port_binding *pb,
+     if (!ld) {
+         return;
+     }
++
++    bool pb_localnet_learn_fdb = smap_get_bool(&pb->options,
++                                               "localnet_learn_fdb", false);
++    if (pb_localnet_learn_fdb != b_ctx_out->localnet_learn_fdb) {
++        b_ctx_out->localnet_learn_fdb = pb_localnet_learn_fdb;
++        if (b_ctx_out->tracked_dp_bindings) {
++            b_ctx_out->localnet_learn_fdb_changed = true;
++            tracked_datapath_lport_add(pb, TRACKED_RESOURCE_UPDATED,
++                                       b_ctx_out->tracked_dp_bindings);
++        }
++    }
++
+     /* Add all localnet ports to local_ifaces so that we allocate ct zones
+      * for them. */
+     update_local_lports(pb->logical_port, b_ctx_out);
+ 
+-    if (b_ctx_in->ovs_idl_txn) {
+-        configure_qos(pb, b_ctx_in, b_ctx_out);
+-    }
+-
++    add_or_del_qos_port(pb->logical_port, true);
+     update_related_lport(pb, b_ctx_out);
+ }
+ 
+@@ -1988,6 +2024,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
+         return;
+     }
+ 
++    shash_clear_free_data(&_qos_ports);
+     struct shash bridge_mappings = SHASH_INITIALIZER(&bridge_mappings);
+ 
+     if (b_ctx_in->br_int) {
+@@ -2103,7 +2140,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out)
+      * accordingly. */
+     struct lport *lnet_lport;
+     LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) {
+-        consider_localnet_lport(lnet_lport->pb, b_ctx_in, b_ctx_out);
++        consider_localnet_lport(lnet_lport->pb, b_ctx_out);
+         update_ld_localnet_port(lnet_lport->pb, &bridge_mappings,
+                                 b_ctx_out->local_datapaths);
+         free(lnet_lport);
+@@ -2345,7 +2382,7 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
+ 
+     lbinding = local_binding_find(local_bindings, iface_id);
+ 
+-   if (lbinding) {
++    if (lbinding) {
+         if (lbinding->multiple_bindings) {
+             VLOG_INFO("Multiple bindings for %s: force recompute to clean up",
+                       iface_id);
+@@ -2365,54 +2402,50 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
+                 return true;
+             }
+         }
+-    }
+ 
+-    struct binding_lport *b_lport =
+-        local_binding_get_primary_or_localport_lport(lbinding);
+-    if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) {
+-        struct local_datapath *ld =
+-            get_local_datapath(b_ctx_out->local_datapaths,
+-                               b_lport->pb->datapath->tunnel_key);
+-        if (ld) {
+-            remove_pb_from_local_datapath(b_lport->pb,
+-                                          b_ctx_out, ld);
+-        }
+-
+-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, b_lport->pb,
+-                               b_ctx_in->ovsrec_port_by_qos,
+-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
++        struct binding_lport *b_lport =
++            local_binding_get_primary_or_localport_lport(lbinding);
+ 
+-        /* Release the primary binding lport and other children lports if
+-         * any. */
+-        LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
+-            if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport,
+-                                       !b_ctx_in->ovnsb_idl_txn,
+-                                       b_ctx_out)) {
+-                return false;
++        if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) {
++            struct local_datapath *ld =
++                get_local_datapath(b_ctx_out->local_datapaths,
++                                   b_lport->pb->datapath->tunnel_key);
++            if (ld) {
++                remove_pb_from_local_datapath(b_lport->pb,
++                                              b_ctx_out, ld);
+             }
+-        }
+-        if (lbinding->iface && lbinding->iface->name) {
+-            if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr,
+-                                               lbinding->iface->name,
+-                                               &lbinding->iface->header_.uuid);
+-        }
++            add_or_del_qos_port(b_lport->pb->logical_port, false);
+ 
+-    } else if (lbinding && b_lport && b_lport->type == LP_LOCALPORT) {
+-        /* lbinding is associated with a localport.  Remove it from the
+-         * related lports. */
+-        remove_related_lport(b_lport->pb, b_ctx_out);
+-    }
++            /* Release the primary binding lport and other children lports if
++             * any. */
++            LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
++                if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport,
++                                           !b_ctx_in->ovnsb_idl_txn,
++                                           b_ctx_out)) {
++                    return false;
++                }
++            }
++            if (lbinding->iface && lbinding->iface->name) {
++                if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr,
++                                           lbinding->iface->name,
++                                           &lbinding->iface->header_.uuid);
++            }
+ 
+-    if (lbinding) {
+-        /* Clear the iface of the local binding. */
+-        lbinding->iface = NULL;
+-    }
++        } else if (b_lport && b_lport->type == LP_LOCALPORT) {
++            /* lbinding is associated with a localport.  Remove it from the
++             * related lports. */
++            remove_related_lport(b_lport->pb, b_ctx_out);
++        }
+ 
+-    /* Check if the lbinding has children of type PB_CONTAINER.
+-     * If so, don't delete the local_binding. */
+-    if (lbinding && !is_lbinding_container_parent(lbinding)) {
+-        local_binding_delete(lbinding, local_bindings, binding_lports,
+-                             b_ctx_out->if_mgr);
++        /* Check if the lbinding has children of type PB_CONTAINER.
++         * If so, don't delete the local_binding. */
++        if (!is_lbinding_container_parent(lbinding)) {
++            local_binding_delete(lbinding, local_bindings, binding_lports,
++                                 b_ctx_out->if_mgr);
++        } else {
++            /* Clear the iface of the local binding. */
++            lbinding->iface = NULL;
++        }
+     }
+ 
+     remove_local_lports(iface_id, b_ctx_out);
+@@ -2938,7 +2971,7 @@ handle_updated_port(struct binding_ctx_in *b_ctx_in,
+         break;
+ 
+     case LP_LOCALNET: {
+-        consider_localnet_lport(pb, b_ctx_in, b_ctx_out);
++        consider_localnet_lport(pb, b_ctx_out);
+ 
+         struct shash bridge_mappings =
+             SHASH_INITIALIZER(&bridge_mappings);
+@@ -3026,9 +3059,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in,
+             shash_add(&deleted_other_pbs, pb->logical_port, pb);
+         }
+ 
+-        remove_stale_qos_entry(b_ctx_in->ovs_idl_txn, pb,
+-                               b_ctx_in->ovsrec_port_by_qos,
+-                               b_ctx_in->qos_table, b_ctx_out->qos_map);
++        add_or_del_qos_port(pb->logical_port, false);
+     }
+ 
+     struct shash_node *node;
+@@ -3140,7 +3171,7 @@ delete_done:
+                 b_ctx_in->sbrec_port_binding_by_datapath) {
+                 enum en_lport_type lport_type = get_lport_type(pb);
+                 if (lport_type == LP_LOCALNET) {
+-                    consider_localnet_lport(pb, b_ctx_in, b_ctx_out);
++                    consider_localnet_lport(pb, b_ctx_out);
+                     update_ld_localnet_port(pb, &bridge_mappings,
+                                             b_ctx_out->local_datapaths);
+                 } else if (lport_type == LP_EXTERNAL) {
+@@ -3207,7 +3238,7 @@ local_binding_delete(struct local_binding *lbinding,
+                      struct if_status_mgr *if_mgr)
+ {
+     shash_find_and_delete(local_bindings, lbinding->name);
+-    if_status_mgr_delete_iface(if_mgr, lbinding->name);
++    if_status_mgr_delete_iface(if_mgr, lbinding->name, lbinding->iface);
+     local_binding_destroy(lbinding, binding_lports);
+ }
+ 
+@@ -3424,6 +3455,80 @@ port_binding_set_down(const struct sbrec_chassis *chassis_rec,
+         }
+ }
+ 
++void
++port_binding_set_up(const struct sbrec_chassis *chassis_rec,
++                    const struct sbrec_port_binding_table *pb_table,
++                    const char *iface_id,
++                    const struct uuid *pb_uuid)
++{
++    const struct sbrec_port_binding *pb =
++        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
++    if (!pb) {
++        VLOG_DBG("port_binding already deleted for %s", iface_id);
++        return;
++    }
++    if (pb->n_up && !pb->up[0]) {
++        bool up = true;
++        sbrec_port_binding_set_up(pb, &up, 1);
++        VLOG_INFO("Setting lport %s up in Southbound", pb->logical_port);
++        set_pb_chassis_in_sbrec(pb, chassis_rec, true);
++    }
++}
++
++void
++port_binding_set_pb(const struct sbrec_chassis *chassis_rec,
++                    const struct sbrec_port_binding_table *pb_table,
++                    const char *iface_id,
++                    const struct uuid *pb_uuid,
++                    enum can_bind bind_type)
++{
++    const struct sbrec_port_binding *pb =
++        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
++    if (!pb) {
++        VLOG_DBG("port_binding already deleted for %s", iface_id);
++        return;
++    }
++    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);
++    }
++}
++
++bool
++port_binding_pb_chassis_is_set(
++    const struct sbrec_chassis *chassis_rec,
++    const struct sbrec_port_binding_table *pb_table,
++    const struct uuid *pb_uuid)
++{
++    const struct sbrec_port_binding *pb =
++        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
++
++    if (pb && ((pb->chassis == chassis_rec) ||
++        is_additional_chassis(pb, chassis_rec))) {
++        return true;
++    }
++    return false;
++}
++
++bool
++port_binding_is_up(const struct sbrec_chassis *chassis_rec,
++                   const struct sbrec_port_binding_table *pb_table,
++                   const struct uuid *pb_uuid)
++{
++    const struct sbrec_port_binding *pb =
++        sbrec_port_binding_table_get_for_uuid(pb_table, pb_uuid);
++
++    if (!pb || pb->chassis != chassis_rec) {
++        return false;
++    }
++
++    if (pb->n_up && !pb->up[0]) {
++        return false;
++    }
++    return true;
++}
++
+ static void
+ binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly)
+ {
+@@ -3614,5 +3719,6 @@ void
+ binding_destroy(void)
+ {
+     shash_destroy_free_data(&_claimed_ports);
++    shash_destroy_free_data(&_qos_ports);
+     sset_clear(&_postponed_ports);
+ }
+diff --git a/controller/binding.h b/controller/binding.h
+index 24bc84079..75e7a2679 100644
+--- a/controller/binding.h
++++ b/controller/binding.h
+@@ -108,6 +108,9 @@ struct binding_ctx_out {
+     struct if_status_mgr *if_mgr;
+ 
+     struct sset *postponed_ports;
++
++    bool localnet_learn_fdb;
++    bool localnet_learn_fdb_changed;
+ };
+ 
+ /* Local bindings. binding.c module binds the logical port (represented by
+@@ -217,6 +220,18 @@ void port_binding_set_down(const struct sbrec_chassis *chassis_rec,
+                            const struct sbrec_port_binding_table *pb_table,
+                            const char *iface_id,
+                            const struct uuid *pb_uuid);
++void port_binding_set_up(const struct sbrec_chassis *chassis_rec,
++                         const struct sbrec_port_binding_table *,
++                         const char *iface_id,
++                         const struct uuid *pb_uuid);
++void port_binding_set_pb(const struct sbrec_chassis *chassis_rec,
++                         const struct sbrec_port_binding_table *,
++                         const char *iface_id,
++                         const struct uuid *pb_uuid,
++                         enum can_bind bind_type);
++bool port_binding_pb_chassis_is_set(const struct sbrec_chassis *chassis_rec,
++                                    const struct sbrec_port_binding_table *,
++                                    const struct uuid *pb_uuid);
+ 
+ /* This structure represents a logical port (or port binding)
+  * which is associated with 'struct local_binding'.
+@@ -250,4 +265,20 @@ void binding_destroy(void);
+ 
+ void destroy_qos_map(struct hmap *);
+ 
++void update_qos(struct ovsdb_idl_index * sbrec_port_binding_by_name,
++                struct ovsdb_idl_txn *ovs_idl_txn,
++                struct ovsdb_idl_index *ovsrec_port_by_qos,
++                const struct ovsrec_qos_table *qos_table,
++                struct hmap *qos_map,
++                const struct ovsrec_open_vswitch_table *ovs_table,
++                const struct ovsrec_bridge_table *bridge_table);
++
++bool lport_maybe_postpone(const char *port_name, long long int now,
++                          struct sset *postponed_ports);
++
++void claimed_lport_set_up(const struct sbrec_port_binding *pb,
++                          const struct sbrec_port_binding *parent_pb);
++bool port_binding_is_up(const struct sbrec_chassis *chassis_rec,
++                        const struct sbrec_port_binding_table *,
++                        const struct uuid *pb_uuid);
+ #endif /* controller/binding.h */
+diff --git a/controller/chassis.c b/controller/chassis.c
+index 031bd4463..a6f13ccc4 100644
+--- a/controller/chassis.c
++++ b/controller/chassis.c
+@@ -369,6 +369,7 @@ chassis_build_other_config(const struct ovs_chassis_cfg *ovs_cfg,
+     smap_replace(config, OVN_FEATURE_MAC_BINDING_TIMESTAMP, "true");
+     smap_replace(config, OVN_FEATURE_CT_LB_RELATED, "true");
+     smap_replace(config, OVN_FEATURE_FDB_TIMESTAMP, "true");
++    smap_replace(config, OVN_FEATURE_LS_DPG_COLUMN, "true");
+ }
+ 
+ /*
+@@ -502,6 +503,12 @@ chassis_other_config_changed(const struct ovs_chassis_cfg *ovs_cfg,
+         return true;
+     }
+ 
++    if (!smap_get_bool(&chassis_rec->other_config,
++                       OVN_FEATURE_LS_DPG_COLUMN,
++                       false)) {
++        return true;
++    }
++
+     return false;
+ }
+ 
+@@ -632,6 +639,7 @@ update_supported_sset(struct sset *supported)
+     sset_add(supported, OVN_FEATURE_MAC_BINDING_TIMESTAMP);
+     sset_add(supported, OVN_FEATURE_CT_LB_RELATED);
+     sset_add(supported, OVN_FEATURE_FDB_TIMESTAMP);
++    sset_add(supported, OVN_FEATURE_LS_DPG_COLUMN);
+ }
+ 
+ static void
+diff --git a/controller/if-status.c b/controller/if-status.c
+index 6c5afc866..6e8aa1f7e 100644
+--- a/controller/if-status.c
++++ b/controller/if-status.c
+@@ -177,7 +177,9 @@ static const char *if_state_names[] = {
+ 
+ struct ovs_iface {
+     char *id;               /* Extracted from OVS external_ids.iface_id. */
++    char *name;             /* OVS iface name. */
+     struct uuid pb_uuid;    /* Port_binding uuid */
++    struct uuid parent_pb_uuid; /* Parent port_binding uuid */
+     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
+@@ -185,6 +187,7 @@ struct ovs_iface {
+                              */
+     uint16_t mtu;           /* Extracted from OVS interface.mtu field. */
+     enum can_bind bind_type;/* CAN_BIND_AS_MAIN or CAN_BIND_AS_ADDITIONAL */
++    bool is_vif;            /* Vifs, container or virtual ports */
+ };
+ 
+ static uint64_t ifaces_usage;
+@@ -224,6 +227,7 @@ static void if_status_mgr_update_bindings(
+     struct if_status_mgr *mgr, struct local_binding_data *binding_data,
+     const struct sbrec_chassis *,
+     const struct ovsrec_interface_table *iface_table,
++    const struct sbrec_port_binding_table *pb_table,
+     bool sb_readonly, bool ovs_readonly);
+ 
+ static void ovn_uninstall_hash_account_mem(const char *name, bool erase);
+@@ -273,12 +277,23 @@ if_status_mgr_destroy(struct if_status_mgr *mgr)
+     free(mgr);
+ }
+ 
++/* is_vif controls whether we wait for flows to be updated
++ * before setting the interface up. It is true for VIF, CONTAINER
++ * and VIRTUAL ports.
++ * Non-VIF ports are reported up as soon as they are claimed
++ * to maintain compatibility with older versions.
++ * See aae25e6 binding: Correctly set Port_Binding.up for container/virtual
++ * ports.
++ */
++
+ void
+ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+                           const struct sbrec_port_binding *pb,
+                           const struct sbrec_chassis *chassis_rec,
+                           const struct ovsrec_interface *iface_rec,
+-                          bool sb_readonly, enum can_bind bind_type)
++                          bool sb_readonly, enum can_bind bind_type,
++                          bool is_vif,
++                          const struct sbrec_port_binding *parent_pb)
+ {
+     const char *iface_id = pb->logical_port;
+     struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
+@@ -287,8 +302,13 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+         iface = ovs_iface_create(mgr, iface_id, iface_rec, OIF_CLAIMED);
+     }
+     iface->bind_type = bind_type;
++    iface->is_vif = is_vif;
+ 
+     memcpy(&iface->pb_uuid, &pb->header_.uuid, sizeof(iface->pb_uuid));
++    if (parent_pb) {
++        memcpy(&iface->parent_pb_uuid, &parent_pb->header_.uuid,
++               sizeof(iface->pb_uuid));
++    }
+     if (!sb_readonly) {
+         if (bind_type == CAN_BIND_AS_MAIN) {
+             set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+@@ -357,7 +377,8 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
+ }
+ 
+ void
+-if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
++if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id,
++                           const struct ovsrec_interface *iface_rec)
+ {
+     struct ovs_iface *iface = shash_find_data(&mgr->ifaces, iface_id);
+ 
+@@ -365,6 +386,12 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+         return;
+     }
+ 
++    if (iface_rec && strcmp(iface->name, iface_rec->name)) {
++        VLOG_DBG("Interface %s not deleted as port %s bound to %s",
++                 iface_rec->name, iface_id, iface->name);
++        return;
++    }
++
+     switch (iface->state) {
+     case OIF_CLAIMED:
+     case OIF_INSTALL_FLOWS:
+@@ -394,6 +421,7 @@ if_status_handle_claims(struct if_status_mgr *mgr,
+                         struct local_binding_data *binding_data,
+                         const struct sbrec_chassis *chassis_rec,
+                         struct hmap *tracked_datapath,
++                        const struct sbrec_port_binding_table *pb_table,
+                         bool sb_readonly)
+ {
+     if (!binding_data || sb_readonly) {
+@@ -406,9 +434,15 @@ if_status_handle_claims(struct if_status_mgr *mgr,
+     bool rc = false;
+     HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_CLAIMED]) {
+         struct ovs_iface *iface = node->data;
+-        VLOG_INFO("if_status_handle_claims for %s", iface->id);
+-        local_binding_set_pb(bindings, iface->id, chassis_rec,
+-                             tracked_datapath, true, iface->bind_type);
++        VLOG_DBG("if_status_handle_claims for %s, is_vif = %d", iface->id,
++                  iface->is_vif);
++        if (iface->is_vif) {
++            local_binding_set_pb(bindings, iface->id, chassis_rec,
++                                 tracked_datapath, true, iface->bind_type);
++        } else {
++            port_binding_set_pb(chassis_rec, pb_table, iface->id,
++                                &iface->pb_uuid, iface->bind_type);
++        }
+         rc = true;
+     }
+     return rc;
+@@ -470,18 +504,37 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+      */
+     HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_MARK_UP]) {
+         struct ovs_iface *iface = node->data;
+-
+-        if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
+-            chassis_rec)) {
+-            if (!sb_readonly) {
+-                local_binding_set_pb(bindings, iface->id, chassis_rec,
+-                                     NULL, true, iface->bind_type);
+-            } else {
+-                continue;
++        if (iface->is_vif) {
++            if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
++                chassis_rec)) {
++                if (!sb_readonly) {
++                    long long int now = time_msec();
++                    if (lport_maybe_postpone(iface->id, now,
++                                             get_postponed_ports())) {
++                        continue;
++                    }
++                    local_binding_set_pb(bindings, iface->id, chassis_rec,
++                                         NULL, true, iface->bind_type);
++                } else {
++                    continue;
++                }
++            }
++            if (local_binding_is_up(bindings, iface->id, chassis_rec)) {
++                ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
++            }
++        } else {
++            if (!port_binding_pb_chassis_is_set(chassis_rec, pb_table,
++                                                &iface->pb_uuid)) {
++                if (!sb_readonly) {
++                    port_binding_set_pb(chassis_rec, pb_table, iface->id,
++                                        &iface->pb_uuid, iface->bind_type);
++                } else {
++                    continue;
++                }
++            }
++            if (port_binding_is_up(chassis_rec, pb_table, &iface->pb_uuid)) {
++                ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
+             }
+-        }
+-        if (local_binding_is_up(bindings, iface->id, chassis_rec)) {
+-            ovs_iface_set_state(mgr, iface, OIF_INSTALLED);
+         }
+     }
+ 
+@@ -510,9 +563,13 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+     if (!sb_readonly) {
+         HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_INSTALL_FLOWS]) {
+             struct ovs_iface *iface = node->data;
+-
+             if (!local_bindings_pb_chassis_is_set(bindings, iface->id,
+                 chassis_rec)) {
++                long long int now = time_msec();
++                if (lport_maybe_postpone(iface->id, now,
++                                         get_postponed_ports())) {
++                    continue;
++                }
+                 local_binding_set_pb(bindings, iface->id, chassis_rec,
+                                      NULL, true, iface->bind_type);
+             }
+@@ -528,9 +585,13 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+             /* No need to to update pb->chassis as already done
+              * in if_status_handle_claims or if_status_mgr_claim_iface
+              */
+-            ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS);
+-            iface->install_seqno = mgr->iface_seqno + 1;
+-            new_ifaces = true;
++            if (iface->is_vif) {
++                ovs_iface_set_state(mgr, iface, OIF_INSTALL_FLOWS);
++                iface->install_seqno = mgr->iface_seqno + 1;
++                new_ifaces = true;
++            } else {
++                ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
++            }
+         }
+     } else {
+         HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_CLAIMED]) {
+@@ -587,6 +648,7 @@ if_status_mgr_run(struct if_status_mgr *mgr,
+                   struct local_binding_data *binding_data,
+                   const struct sbrec_chassis *chassis_rec,
+                   const struct ovsrec_interface_table *iface_table,
++                  const struct sbrec_port_binding_table *pb_table,
+                   bool sb_readonly, bool ovs_readonly)
+ {
+     struct ofctrl_acked_seqnos *acked_seqnos =
+@@ -622,15 +684,18 @@ if_status_mgr_run(struct if_status_mgr *mgr,
+ 
+     /* Update binding states. */
+     if_status_mgr_update_bindings(mgr, binding_data, chassis_rec,
+-                                  iface_table,
++                                  iface_table, pb_table,
+                                   sb_readonly, ovs_readonly);
+ }
+ 
+ static void
+-ovs_iface_account_mem(const char *iface_id, bool erase)
++ovs_iface_account_mem(const char *iface_id, char *iface_name, bool erase)
+ {
+     uint32_t size = (strlen(iface_id) + sizeof(struct ovs_iface) +
+                      sizeof(struct shash_node));
++    if (iface_name) {
++        size += strlen(iface_name);
++    }
+     if (erase) {
+         ifaces_usage -= size;
+     } else {
+@@ -682,12 +747,18 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
+ {
+     struct ovs_iface *iface = xzalloc(sizeof *iface);
+ 
+-    VLOG_DBG("Interface %s create.", iface_id);
++    VLOG_DBG("Interface %s create for iface %s.", iface_id,
++             iface_rec ? iface_rec->name : "");
+     iface->id = xstrdup(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);
++    if (iface_rec) {
++        ovs_iface_account_mem(iface_id, iface_rec->name, false);
++        if_status_mgr_iface_update(mgr, iface_rec);
++        iface->name = xstrdup(iface_rec->name);
++    } else {
++        ovs_iface_account_mem(iface_id, NULL, false);
++    }
+     return iface;
+ }
+ 
+@@ -711,7 +782,8 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
+     if (node) {
+         shash_steal(&mgr->ifaces, node);
+     }
+-    ovs_iface_account_mem(iface->id, true);
++    ovs_iface_account_mem(iface->id, iface->name, true);
++    free(iface->name);
+     free(iface->id);
+     free(iface);
+ }
+@@ -752,6 +824,7 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
+                               struct local_binding_data *binding_data,
+                               const struct sbrec_chassis *chassis_rec,
+                               const struct ovsrec_interface_table *iface_table,
++                              const struct sbrec_port_binding_table *pb_table,
+                               bool sb_readonly, bool ovs_readonly)
+ {
+     if (!binding_data) {
+@@ -788,9 +861,20 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
+     char *ts_now_str = xasprintf("%lld", time_wall_msec());
+     HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_MARK_UP]) {
+         struct ovs_iface *iface = node->data;
+-
+-        local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str,
+-                             sb_readonly, ovs_readonly);
++        if (iface->is_vif) {
++            local_binding_set_up(bindings, iface->id, chassis_rec, ts_now_str,
++                                 sb_readonly, ovs_readonly);
++        } else if (!sb_readonly) {
++            const struct sbrec_port_binding *pb =
++                sbrec_port_binding_table_get_for_uuid(pb_table,
++                                                      &iface->pb_uuid);
++            if (pb) {
++                const struct sbrec_port_binding *parent_pb =
++                    sbrec_port_binding_table_get_for_uuid(pb_table,
++                                                  &iface->parent_pb_uuid);
++                claimed_lport_set_up(pb, parent_pb);
++            }
++        }
+     }
+     free(ts_now_str);
+ 
+diff --git a/controller/if-status.h b/controller/if-status.h
+index 9714f6d8d..4ae5ad481 100644
+--- a/controller/if-status.h
++++ b/controller/if-status.h
+@@ -32,9 +32,12 @@ void if_status_mgr_claim_iface(struct if_status_mgr *,
+                                const struct sbrec_port_binding *pb,
+                                const struct sbrec_chassis *chassis_rec,
+                                const struct ovsrec_interface *iface_rec,
+-                               bool sb_readonly, enum can_bind bind_type);
++                               bool sb_readonly, enum can_bind bind_type,
++                               bool notify_up,
++                               const struct sbrec_port_binding *parent_pb);
+ 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_delete_iface(struct if_status_mgr *, const char *iface_id,
++                                const struct ovsrec_interface *iface_rec);
+ 
+ void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
+                           const struct sbrec_chassis *chassis,
+@@ -45,6 +48,7 @@ void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
+ void if_status_mgr_run(struct if_status_mgr *mgr, struct local_binding_data *,
+                        const struct sbrec_chassis *,
+                        const struct ovsrec_interface_table *iface_table,
++                       const struct sbrec_port_binding_table *pb_table,
+                        bool sb_readonly, bool ovs_readonly);
+ void if_status_mgr_get_memory_usage(struct if_status_mgr *mgr,
+                                     struct simap *usage);
+@@ -54,6 +58,7 @@ bool if_status_handle_claims(struct if_status_mgr *mgr,
+                              struct local_binding_data *binding_data,
+                              const struct sbrec_chassis *chassis_rec,
+                              struct hmap *tracked_datapath,
++                             const struct sbrec_port_binding_table *pb_table,
+                              bool sb_readonly);
+ void if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr,
+                                         const char *name,
+diff --git a/controller/lflow.c b/controller/lflow.c
+index f70080e8e..b0cf4253c 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -1017,6 +1017,7 @@ convert_match_to_expr(const struct sbrec_logical_flow *lflow,
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+         VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s",
+                     lflow->match, error);
++        expr_destroy(e);
+         free(error);
+         return NULL;
+     }
+@@ -1892,6 +1893,7 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
+                                           local_datapaths, &match,
+                                           &ofpacts, flow_table);
+         }
++        /* datapath_group column is deprecated. */
+         if (lb->slb->datapath_group) {
+             for (size_t i = 0; i < lb->slb->datapath_group->n_datapaths; i++) {
+                 add_lb_ct_snat_hairpin_for_dp(
+@@ -1900,6 +1902,15 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
+                     local_datapaths, &match, &ofpacts, flow_table);
+             }
+         }
++        if (lb->slb->ls_datapath_group) {
++            for (size_t i = 0;
++                 i < lb->slb->ls_datapath_group->n_datapaths; i++) {
++                add_lb_ct_snat_hairpin_for_dp(
++                    lb, !!lb_vip->vip_port,
++                    lb->slb->ls_datapath_group->datapaths[i],
++                    local_datapaths, &match, &ofpacts, flow_table);
++            }
++        }
+     }
+ 
+     ofpbuf_uninit(&ofpacts);
+@@ -2055,11 +2066,17 @@ lflow_handle_changed_static_mac_bindings(
+ static void
+ consider_fdb_flows(const struct sbrec_fdb *fdb,
+                    const struct hmap *local_datapaths,
+-                   struct ovn_desired_flow_table *flow_table)
++                   struct ovn_desired_flow_table *flow_table,
++                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
++                   bool localnet_learn_fdb)
+ {
+-    if (!get_local_datapath(local_datapaths, fdb->dp_key)) {
++    struct local_datapath *ld = get_local_datapath(local_datapaths,
++                                                   fdb->dp_key);
++    if (!ld) {
+         return;
+     }
++    const struct sbrec_port_binding *pb = lport_lookup_by_key_with_dp(
++        sbrec_port_binding_by_key, ld->datapath, fdb->port_key);
+ 
+     struct eth_addr mac;
+     if (!eth_addr_from_string(fdb->mac, &mac)) {
+@@ -2081,6 +2098,7 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
+     ofpbuf_clear(&ofpacts);
+ 
+     uint8_t value = 1;
++    uint8_t is_vif =  pb ? !strcmp(pb->type, "") : 0;
+     put_load(&value, sizeof value, MFF_LOG_FLAGS,
+              MLF_LOOKUP_FDB_BIT, 1, &ofpacts);
+ 
+@@ -2091,6 +2109,18 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
+     ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
+                     fdb->header_.uuid.parts[0], &lookup_match, &ofpacts,
+                     &fdb->header_.uuid);
++
++    if (is_vif && localnet_learn_fdb) {
++        struct match lookup_match_vif = MATCH_CATCHALL_INITIALIZER;
++        match_set_metadata(&lookup_match_vif, htonll(fdb->dp_key));
++        match_set_dl_src(&lookup_match_vif, mac);
++        match_set_reg_masked(&lookup_match_vif, MFF_LOG_FLAGS - MFF_REG0,
++                             MLF_LOCALNET, MLF_LOCALNET);
++
++        ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
++                        fdb->header_.uuid.parts[0], &lookup_match_vif,
++                        &ofpacts, &fdb->header_.uuid);
++    }
+     ofpbuf_uninit(&ofpacts);
+ }
+ 
+@@ -2099,11 +2129,14 @@ consider_fdb_flows(const struct sbrec_fdb *fdb,
+ static void
+ add_fdb_flows(const struct sbrec_fdb_table *fdb_table,
+               const struct hmap *local_datapaths,
+-              struct ovn_desired_flow_table *flow_table)
++              struct ovn_desired_flow_table *flow_table,
++              struct ovsdb_idl_index *sbrec_port_binding_by_key,
++              bool localnet_learn_fdb)
+ {
+     const struct sbrec_fdb *fdb;
+     SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) {
+-        consider_fdb_flows(fdb, local_datapaths, flow_table);
++        consider_fdb_flows(fdb, local_datapaths, flow_table,
++                           sbrec_port_binding_by_key, localnet_learn_fdb);
+     }
+ }
+ 
+@@ -2126,7 +2159,9 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out *l_ctx_out)
+                          l_ctx_in->lb_hairpin_use_ct_mark,
+                          l_ctx_out->flow_table);
+     add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths,
+-                  l_ctx_out->flow_table);
++                  l_ctx_out->flow_table,
++                  l_ctx_in->sbrec_port_binding_by_key,
++                  l_ctx_in->localnet_learn_fdb);
+     add_port_sec_flows(l_ctx_in->binding_lports, l_ctx_in->chassis,
+                        l_ctx_out->flow_table);
+ }
+@@ -2216,7 +2251,9 @@ lflow_add_flows_for_datapath(const struct sbrec_datapath_binding *dp,
+     SBREC_FDB_FOR_EACH_EQUAL (fdb_row, fdb_index_row,
+                               l_ctx_in->sbrec_fdb_by_dp_key) {
+         consider_fdb_flows(fdb_row, l_ctx_in->local_datapaths,
+-                           l_ctx_out->flow_table);
++                           l_ctx_out->flow_table,
++                           l_ctx_in->sbrec_port_binding_by_key,
++                           l_ctx_in->localnet_learn_fdb);
+     }
+     sbrec_fdb_index_destroy_row(fdb_index_row);
+ 
+@@ -2280,6 +2317,15 @@ lflow_handle_flows_for_lport(const struct sbrec_port_binding *pb,
+                                           pb->logical_port)) {
+         consider_port_sec_flows(pb, l_ctx_out->flow_table);
+     }
++    if (l_ctx_in->localnet_learn_fdb_changed && l_ctx_in->localnet_learn_fdb) {
++        const struct sbrec_fdb *fdb;
++        SBREC_FDB_TABLE_FOR_EACH (fdb, l_ctx_in->fdb_table) {
++            consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
++                               l_ctx_out->flow_table,
++                               l_ctx_in->sbrec_port_binding_by_key,
++                               l_ctx_in->localnet_learn_fdb);
++        }
++    }
+     return true;
+ }
+ 
+@@ -2409,7 +2455,9 @@ lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in,
+         VLOG_DBG("Add fdb flows for fdb "UUID_FMT,
+                  UUID_ARGS(&fdb->header_.uuid));
+         consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
+-                           l_ctx_out->flow_table);
++                           l_ctx_out->flow_table,
++                           l_ctx_in->sbrec_port_binding_by_key,
++                           l_ctx_in->localnet_learn_fdb);
+     }
+ 
+     return true;
+diff --git a/controller/lflow.h b/controller/lflow.h
+index 5da4385e4..9b7ffa19c 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -100,6 +100,7 @@ struct lflow_ctx_in {
+     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_datapath;
+     struct ovsdb_idl_index *sbrec_logical_flow_by_logical_dp_group;
+     struct ovsdb_idl_index *sbrec_port_binding_by_name;
++    struct ovsdb_idl_index *sbrec_port_binding_by_key;
+     struct ovsdb_idl_index *sbrec_fdb_by_dp_key;
+     struct ovsdb_idl_index *sbrec_mac_binding_by_datapath;
+     struct ovsdb_idl_index *sbrec_static_mac_binding_by_datapath;
+@@ -127,6 +128,8 @@ struct lflow_ctx_in {
+     const struct flow_collector_ids *collector_ids;
+     const struct hmap *local_lbs;
+     bool lb_hairpin_use_ct_mark;
++    bool localnet_learn_fdb;
++    bool localnet_learn_fdb_changed;
+ };
+ 
+ struct lflow_ctx_out {
+diff --git a/controller/local_data.c b/controller/local_data.c
+index cf0b21bb1..ab18386c7 100644
+--- a/controller/local_data.c
++++ b/controller/local_data.c
+@@ -56,6 +56,20 @@ static bool datapath_is_transit_switch(const struct sbrec_datapath_binding *);
+ 
+ static uint64_t local_datapath_usage;
+ 
++/* To be used when hmap_node.hash might be wrong e.g. tunnel_key got updated */
++struct local_datapath *
++get_local_datapath_no_hash(const struct hmap *local_datapaths,
++                           uint32_t tunnel_key)
++{
++    struct local_datapath *ld;
++    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
++        if (ld->datapath->tunnel_key == tunnel_key) {
++            return ld;
++        }
++    }
++    return NULL;
++}
++
+ struct local_datapath *
+ get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key)
+ {
+@@ -661,8 +675,24 @@ lb_is_local(const struct sbrec_load_balancer *sbrec_lb,
+         }
+     }
+ 
++    /* datapath_group column is deprecated. */
+     struct sbrec_logical_dp_group *dp_group = sbrec_lb->datapath_group;
++    for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
++        if (get_local_datapath(local_datapaths,
++                               dp_group->datapaths[i]->tunnel_key)) {
++            return true;
++        }
++    }
++
++    dp_group = sbrec_lb->ls_datapath_group;
++    for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
++        if (get_local_datapath(local_datapaths,
++                               dp_group->datapaths[i]->tunnel_key)) {
++            return true;
++        }
++    }
+ 
++    dp_group = sbrec_lb->lr_datapath_group;
+     for (size_t i = 0; dp_group && i < dp_group->n_datapaths; i++) {
+         if (get_local_datapath(local_datapaths,
+                                dp_group->datapaths[i]->tunnel_key)) {
+diff --git a/controller/local_data.h b/controller/local_data.h
+index f6d8f725f..2a1a3c0f9 100644
+--- a/controller/local_data.h
++++ b/controller/local_data.h
+@@ -69,6 +69,10 @@ struct local_datapath *local_datapath_alloc(
+     const struct sbrec_datapath_binding *);
+ struct local_datapath *get_local_datapath(const struct hmap *,
+                                           uint32_t tunnel_key);
++struct local_datapath *get_local_datapath_no_hash(
++    const struct hmap *local_datapaths,
++    uint32_t tunnel_key);
++
+ bool
+ need_add_peer_to_local(
+     struct ovsdb_idl_index *sbrec_port_binding_by_name,
+diff --git a/controller/lport.c b/controller/lport.c
+index add7e91aa..b3721024b 100644
+--- a/controller/lport.c
++++ b/controller/lport.c
+@@ -44,13 +44,10 @@ lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ }
+ 
+ const struct sbrec_port_binding *
+-lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+-                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
+-                    uint64_t dp_key, uint64_t port_key)
++lport_lookup_by_key_with_dp(struct ovsdb_idl_index *sbrec_port_binding_by_key,
++                            const struct sbrec_datapath_binding *db,
++                            uint64_t port_key)
+ {
+-    /* Lookup datapath corresponding to dp_key. */
+-    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
+-        sbrec_datapath_binding_by_key, dp_key);
+     if (!db) {
+         return NULL;
+     }
+@@ -69,6 +66,19 @@ lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+     return retval;
+ }
+ 
++const struct sbrec_port_binding *
++lport_lookup_by_key(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
++                    struct ovsdb_idl_index *sbrec_port_binding_by_key,
++                    uint64_t dp_key, uint64_t port_key)
++{
++    /* Lookup datapath corresponding to dp_key. */
++    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
++        sbrec_datapath_binding_by_key, dp_key);
++
++    return lport_lookup_by_key_with_dp(sbrec_port_binding_by_key, db,
++                                       port_key);
++}
++
+ bool
+ lport_is_chassis_resident(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                           const struct sbrec_chassis *chassis,
+diff --git a/controller/lport.h b/controller/lport.h
+index 644c67255..2f72aef5e 100644
+--- a/controller/lport.h
++++ b/controller/lport.h
+@@ -43,6 +43,10 @@ const struct sbrec_port_binding *lport_lookup_by_key(
+     struct ovsdb_idl_index *sbrec_port_binding_by_key,
+     uint64_t dp_key, uint64_t port_key);
+ 
++const struct sbrec_port_binding *lport_lookup_by_key_with_dp(
++    struct ovsdb_idl_index *sbrec_port_binding_by_key,
++    const struct sbrec_datapath_binding *dp, uint64_t port_key);
++
+ enum can_bind {
+     CANNOT_BIND = 0,
+     CAN_BIND_AS_MAIN,
+diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
+index 7b4100592..0b9641045 100644
+--- a/controller/ovn-controller.8.xml
++++ b/controller/ovn-controller.8.xml
+@@ -363,7 +363,10 @@
+         The boolean flag indicates if <code>ovn-controller</code> when create
+         tunnel ports should set <code>local_ip</code> parameter.  Can be
+         heplful to pin source outer IP for the tunnel when multiple interfaces
+-        are used on the host for overlay traffic.
++        are used on the host for overlay traffic. This is also useful when
++        running multiple <code>ovn-controller</code> instances on the same
++        chassis, in which case this setting will guarantee that their tunnel
++        ports have unique configuration and can exist in parallel.
+       </dd>
+       <dt><code>external_ids:garp-max-timeout-sec</code></dt>
+       <dd>
+@@ -398,9 +401,11 @@
+         names on the same host using the same <code>vswitchd</code> instance.
+         This may be useful when running a hybrid setup with more than one CMS
+         managing ports on the host, or to use different datapath types on the
+-        same host. Note that this ability is highly experimental and has known
+-        limitations (for example, stateful ACLs are not supported). Use at your
+-        own risk.
++        same host. Make sure you also set
++        <code>external_ids:ovn-set-local-ip</code> when using such
++        configuration. Also note that this ability is highly experimental and
++        has known limitations (for example, stateful ACLs are not supported).
++        Use at your own risk.
+     </p>
+ 
+     <p>
+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
+index b3e4e0da8..6787dda80 100644
+--- a/controller/ovn-controller.c
++++ b/controller/ovn-controller.c
+@@ -88,7 +88,6 @@
+ 
+ VLOG_DEFINE_THIS_MODULE(main);
+ 
+-static unixctl_cb_func ovn_controller_exit;
+ static unixctl_cb_func ct_zone_list;
+ static unixctl_cb_func extend_table_list;
+ static unixctl_cb_func inject_pkt;
+@@ -211,8 +210,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+ {
+     /* Monitor Port_Bindings rows for local interfaces and local datapaths.
+      *
+-     * Monitor Logical_Flow, MAC_Binding, Multicast_Group, and DNS tables for
+-     * local datapaths.
++     * Monitor Logical_Flow, MAC_Binding, FDB, Multicast_Group, and DNS tables
++     * for local datapaths.
+      *
+      * Monitor Controller_Event rows for local chassis.
+      *
+@@ -230,6 +229,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+     struct ovsdb_idl_condition lf = OVSDB_IDL_CONDITION_INIT(&lf);
+     struct ovsdb_idl_condition ldpg = OVSDB_IDL_CONDITION_INIT(&ldpg);
+     struct ovsdb_idl_condition mb = OVSDB_IDL_CONDITION_INIT(&mb);
++    struct ovsdb_idl_condition fdb = OVSDB_IDL_CONDITION_INIT(&fdb);
+     struct ovsdb_idl_condition mg = OVSDB_IDL_CONDITION_INIT(&mg);
+     struct ovsdb_idl_condition dns = OVSDB_IDL_CONDITION_INIT(&dns);
+     struct ovsdb_idl_condition ce =  OVSDB_IDL_CONDITION_INIT(&ce);
+@@ -248,6 +248,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+         ovsdb_idl_condition_add_clause_true(&pb);
+         ovsdb_idl_condition_add_clause_true(&lf);
+         ovsdb_idl_condition_add_clause_true(&mb);
++        ovsdb_idl_condition_add_clause_true(&fdb);
+         ovsdb_idl_condition_add_clause_true(&mg);
+         ovsdb_idl_condition_add_clause_true(&dns);
+         ovsdb_idl_condition_add_clause_true(&ce);
+@@ -337,6 +338,8 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
+             sbrec_logical_flow_add_clause_logical_datapath(&lf, OVSDB_F_EQ,
+                                                            uuid);
+             sbrec_mac_binding_add_clause_datapath(&mb, OVSDB_F_EQ, uuid);
++            sbrec_fdb_add_clause_dp_key(&fdb, OVSDB_F_EQ,
++                                        ld->datapath->tunnel_key);
+             sbrec_multicast_group_add_clause_datapath(&mg, OVSDB_F_EQ, uuid);
+             sbrec_dns_add_clause_datapaths(&dns, OVSDB_F_INCLUDES, &uuid, 1);
+             sbrec_ip_multicast_add_clause_datapath(&ip_mcast, OVSDB_F_EQ,
+@@ -360,6 +363,7 @@ out:;
+         sb_table_set_req_mon_condition(ovnsb_idl, logical_flow, &lf),
+         sb_table_set_req_mon_condition(ovnsb_idl, logical_dp_group, &ldpg),
+         sb_table_set_req_mon_condition(ovnsb_idl, mac_binding, &mb),
++        sb_table_set_req_mon_condition(ovnsb_idl, fdb, &fdb),
+         sb_table_set_req_mon_condition(ovnsb_idl, multicast_group, &mg),
+         sb_table_set_req_mon_condition(ovnsb_idl, dns, &dns),
+         sb_table_set_req_mon_condition(ovnsb_idl, controller_event, &ce),
+@@ -378,6 +382,7 @@ out:;
+     ovsdb_idl_condition_destroy(&lf);
+     ovsdb_idl_condition_destroy(&ldpg);
+     ovsdb_idl_condition_destroy(&mb);
++    ovsdb_idl_condition_destroy(&fdb);
+     ovsdb_idl_condition_destroy(&mg);
+     ovsdb_idl_condition_destroy(&dns);
+     ovsdb_idl_condition_destroy(&ce);
+@@ -1499,6 +1504,8 @@ struct ed_type_runtime_data {
+     /* Tracked data. See below for more details and comments. */
+     bool tracked;
+     bool local_lports_changed;
++    bool localnet_learn_fdb;
++    bool localnet_learn_fdb_changed;
+     struct hmap tracked_dp_bindings;
+ 
+     struct shash local_active_ports_ipv6_pd;
+@@ -1709,6 +1716,8 @@ init_binding_ctx(struct engine_node *node,
+     b_ctx_out->postponed_ports = rt_data->postponed_ports;
+     b_ctx_out->tracked_dp_bindings = NULL;
+     b_ctx_out->if_mgr = ctrl_ctx->if_mgr;
++    b_ctx_out->localnet_learn_fdb = rt_data->localnet_learn_fdb;
++    b_ctx_out->localnet_learn_fdb_changed = false;
+ }
+ 
+ static void
+@@ -1766,6 +1775,7 @@ en_runtime_data_run(struct engine_node *node, void *data)
+     }
+ 
+     binding_run(&b_ctx_in, &b_ctx_out);
++    rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb;
+ 
+     engine_set_node_state(node, EN_UPDATED);
+ }
+@@ -1815,6 +1825,8 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data)
+         engine_ovsdb_node_get_index(
+                 engine_get_input("SB_chassis", node),
+                 "name");
++    const struct sbrec_port_binding_table *pb_table =
++        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
+ 
+     if (chassis_id) {
+         chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+@@ -1829,7 +1841,7 @@ runtime_data_sb_ro_handler(struct engine_node *node, void *data)
+                                     &rt_data->lbinding_data,
+                                     chassis,
+                                     &rt_data->tracked_dp_bindings,
+-                                    sb_readonly)) {
++                                    pb_table, sb_readonly)) {
+             engine_set_node_state(node, EN_UPDATED);
+             rt_data->tracked = true;
+         }
+@@ -1878,9 +1890,12 @@ runtime_data_sb_port_binding_handler(struct engine_node *node, void *data)
+     }
+ 
+     rt_data->local_lports_changed = b_ctx_out.local_lports_changed;
++    rt_data->localnet_learn_fdb = b_ctx_out.localnet_learn_fdb;
++    rt_data->localnet_learn_fdb_changed = b_ctx_out.localnet_learn_fdb_changed;
+     if (b_ctx_out.related_lports_changed ||
+             b_ctx_out.non_vif_ports_changed ||
+             b_ctx_out.local_lports_changed ||
++            b_ctx_out.localnet_learn_fdb_changed ||
+             !hmap_is_empty(b_ctx_out.tracked_dp_bindings)) {
+         engine_set_node_state(node, EN_UPDATED);
+     }
+@@ -1897,6 +1912,7 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED,
+             engine_get_input("SB_datapath_binding", node));
+     const struct sbrec_datapath_binding *dp;
+     struct ed_type_runtime_data *rt_data = data;
++    struct local_datapath *ld;
+ 
+     SBREC_DATAPATH_BINDING_TABLE_FOR_EACH_TRACKED (dp, dp_table) {
+         if (sbrec_datapath_binding_is_deleted(dp)) {
+@@ -1904,6 +1920,28 @@ runtime_data_sb_datapath_binding_handler(struct engine_node *node OVS_UNUSED,
+                                    dp->tunnel_key)) {
+                 return false;
+             }
++            /* If the tunnel key got updated, get_local_datapath will not find
++             * the ld. Use get_local_datapath_no_hash which does not
++             * rely on the hash.
++             */
++            if (sbrec_datapath_binding_is_updated(
++                    dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY)) {
++                if (get_local_datapath_no_hash(&rt_data->local_datapaths,
++                                               dp->tunnel_key)) {
++                    return false;
++                }
++            }
++        } else if (sbrec_datapath_binding_is_updated(
++                        dp, SBREC_DATAPATH_BINDING_COL_TUNNEL_KEY)
++                   && !sbrec_datapath_binding_is_new(dp)) {
++            /* If the tunnel key is updated, remove the entry (with a wrong
++             * hash) from the map. It will be (properly) added back later.
++             */
++            if ((ld = get_local_datapath_no_hash(&rt_data->local_datapaths,
++                                                 dp->tunnel_key))) {
++                hmap_remove(&rt_data->local_datapaths, &ld->hmap_node);
++                local_datapath_destroy(ld);
++            }
+         }
+     }
+ 
+@@ -2637,9 +2675,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data)
+             struct tracked_lport *t_lport = shash_node->data;
+             if (strcmp(t_lport->pb->type, "")
+                 && strcmp(t_lport->pb->type, "localport")
++                && strcmp(t_lport->pb->type, "l3gateway")
+                 && strcmp(t_lport->pb->type, "localnet")) {
+-                /* We allocate zone-id's only to VIF, localport, and localnet
+-                 * lports. */
++                /* We allocate zone-id's only to VIF, localport, l3gateway,
++                 * and localnet lports. */
+                 continue;
+             }
+ 
+@@ -2803,12 +2842,25 @@ load_balancers_by_dp_init(const struct hmap *local_datapaths,
+             load_balancers_by_dp_add_one(local_datapaths,
+                                          lb->datapaths[i], lb, lbs);
+         }
++        /* datapath_group column is deprecated. */
+         for (size_t i = 0; lb->datapath_group
+                            && i < lb->datapath_group->n_datapaths; i++) {
+             load_balancers_by_dp_add_one(local_datapaths,
+                                          lb->datapath_group->datapaths[i],
+                                          lb, lbs);
+         }
++        for (size_t i = 0; lb->ls_datapath_group
++                           && i < lb->ls_datapath_group->n_datapaths; i++) {
++            load_balancers_by_dp_add_one(local_datapaths,
++                                         lb->ls_datapath_group->datapaths[i],
++                                         lb, lbs);
++        }
++        for (size_t i = 0; lb->lr_datapath_group
++                           && i < lb->lr_datapath_group->n_datapaths; i++) {
++            load_balancers_by_dp_add_one(local_datapaths,
++                                         lb->lr_datapath_group->datapaths[i],
++                                         lb, lbs);
++        }
+     }
+     return lbs;
+ }
+@@ -3788,6 +3840,11 @@ init_lflow_ctx(struct engine_node *node,
+                 engine_get_input("SB_port_binding", node),
+                 "name");
+ 
++    struct ovsdb_idl_index *sbrec_port_binding_by_key =
++        engine_ovsdb_node_get_index(
++                engine_get_input("SB_port_binding", node),
++                "key");
++
+     struct ovsdb_idl_index *sbrec_logical_flow_by_dp =
+         engine_ovsdb_node_get_index(
+                 engine_get_input("SB_logical_flow", node),
+@@ -3887,6 +3944,7 @@ init_lflow_ctx(struct engine_node *node,
+     l_ctx_in->sbrec_logical_flow_by_logical_dp_group =
+         sbrec_logical_flow_by_dp_group;
+     l_ctx_in->sbrec_port_binding_by_name = sbrec_port_binding_by_name;
++    l_ctx_in->sbrec_port_binding_by_key = sbrec_port_binding_by_key;
+     l_ctx_in->sbrec_fdb_by_dp_key = sbrec_fdb_by_dp_key;
+     l_ctx_in->sbrec_mac_binding_by_datapath = sbrec_mac_binding_by_datapath;
+     l_ctx_in->sbrec_static_mac_binding_by_datapath =
+@@ -3905,6 +3963,8 @@ init_lflow_ctx(struct engine_node *node,
+     l_ctx_in->active_tunnels = &rt_data->active_tunnels;
+     l_ctx_in->related_lport_ids = &rt_data->related_lports.lport_ids;
+     l_ctx_in->binding_lports = &rt_data->lbinding_data.lports;
++    l_ctx_in->localnet_learn_fdb = rt_data->localnet_learn_fdb;
++    l_ctx_in->localnet_learn_fdb_changed = rt_data->localnet_learn_fdb_changed;
+     l_ctx_in->chassis_tunnels = &non_vif_data->chassis_tunnels;
+     l_ctx_in->lb_hairpin_use_ct_mark = n_opts->lb_hairpin_use_ct_mark;
+     l_ctx_in->nd_ra_opts = &fo->nd_ra_opts;
+@@ -3930,8 +3990,8 @@ en_lflow_output_init(struct engine_node *node OVS_UNUSED,
+ {
+     struct ed_type_lflow_output *data = xzalloc(sizeof *data);
+     ovn_desired_flow_table_init(&data->flow_table);
+-    ovn_extend_table_init(&data->group_table);
+-    ovn_extend_table_init(&data->meter_table);
++    ovn_extend_table_init(&data->group_table, "group-table", 0);
++    ovn_extend_table_init(&data->meter_table, "meter-table", 0);
+     objdep_mgr_init(&data->lflow_deps_mgr);
+     lflow_conj_ids_init(&data->conj_ids);
+     uuidset_init(&data->objs_processed);
+@@ -4895,11 +4955,6 @@ controller_output_mac_cache_handler(struct engine_node *node,
+     return true;
+ }
+ 
+-struct ovn_controller_exit_args {
+-    bool *exiting;
+-    bool *restart;
+-};
+-
+ /* Handles sbrec_chassis changes.
+  * If a new chassis is added or removed return false, so that
+  * flows are recomputed.  For any updates, there is no need for
+@@ -4974,9 +5029,7 @@ int
+ main(int argc, char *argv[])
+ {
+     struct unixctl_server *unixctl;
+-    bool exiting;
+-    bool restart;
+-    struct ovn_controller_exit_args exit_args = {&exiting, &restart};
++    struct ovn_exit_args exit_args = {};
+     int retval;
+ 
+     /* Read from system-id-override file once on startup. */
+@@ -4996,7 +5049,7 @@ main(int argc, char *argv[])
+     if (retval) {
+         exit(EXIT_FAILURE);
+     }
+-    unixctl_command_register("exit", "", 0, 1, ovn_controller_exit,
++    unixctl_command_register("exit", "", 0, 1, ovn_exit_command_callback,
+                              &exit_args);
+ 
+     daemonize_complete();
+@@ -5508,10 +5561,8 @@ main(int argc, char *argv[])
+     VLOG_INFO("OVN internal version is : [%s]", ovn_version);
+ 
+     /* Main loop. */
+-    exiting = false;
+-    restart = false;
+     bool sb_monitor_all = false;
+-    while (!exiting) {
++    while (!exit_args.exiting) {
+         memory_run();
+         if (memory_should_report()) {
+             struct simap usage = SIMAP_INITIALIZER(&usage);
+@@ -5645,9 +5696,20 @@ main(int argc, char *argv[])
+                                            br_int ? br_int->name : NULL)) {
+                 VLOG_INFO("OVS feature set changed, force recompute.");
+                 engine_set_force_recompute(true);
++                if (ovs_feature_set_discovered()) {
++                    uint32_t max_groups = ovs_feature_max_select_groups_get();
++                    uint32_t max_meters = ovs_feature_max_meters_get();
++                    struct ed_type_lflow_output *lflow_out_data =
++                        engine_get_internal_data(&en_lflow_output);
++
++                    ovn_extend_table_reinit(&lflow_out_data->group_table,
++                                            max_groups);
++                    ovn_extend_table_reinit(&lflow_out_data->meter_table,
++                                            max_meters);
++                }
+             }
+ 
+-            if (br_int) {
++            if (br_int && ovs_feature_set_discovered()) {
+                 ct_zones_data = engine_get_data(&en_ct_zones);
+                 if (ct_zones_data && ofctrl_run(br_int, ovs_table,
+                                                 &ct_zones_data->pending)) {
+@@ -5809,6 +5871,13 @@ main(int argc, char *argv[])
+                                     &runtime_data->local_datapaths,
+                                     sb_monitor_all);
+                         }
++                        if (ovs_idl_txn) {
++                            update_qos(sbrec_port_binding_by_name, ovs_idl_txn,
++                                       ovsrec_port_by_qos,
++                                       ovsrec_qos_table_get(ovs_idl_loop.idl),
++                                       &runtime_data->qos_map,
++                                       ovs_table, bridge_table);
++                        }
+                     }
+ 
+                     if (mac_cache_data) {
+@@ -5864,6 +5933,8 @@ main(int argc, char *argv[])
+                     if_status_mgr_run(if_mgr, binding_data, chassis,
+                                       ovsrec_interface_table_get(
+                                                   ovs_idl_loop.idl),
++                                      sbrec_port_binding_table_get(
++                                                 ovnsb_idl_loop.idl),
+                                       !ovnsb_idl_txn, !ovs_idl_txn);
+                     stopwatch_stop(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
+                                    time_msec());
+@@ -5941,7 +6012,7 @@ main(int argc, char *argv[])
+         unixctl_server_run(unixctl);
+ 
+         unixctl_server_wait(unixctl);
+-        if (exiting || pending_pkt.conn) {
++        if (exit_args.exiting || pending_pkt.conn) {
+             poll_immediate_wake();
+         }
+ 
+@@ -5992,7 +6063,7 @@ loop_done:
+         memory_wait();
+         poll_block();
+         if (should_service_stop()) {
+-            exiting = true;
++            exit_args.exiting = true;
+         }
+     }
+ 
+@@ -6000,7 +6071,7 @@ loop_done:
+     engine_cleanup();
+ 
+     /* It's time to exit.  Clean up the databases if we are not restarting */
+-    if (!restart) {
++    if (!exit_args.restart) {
+         bool done = !ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl);
+         while (!done) {
+             update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl,
+@@ -6052,7 +6123,6 @@ loop_done:
+     }
+ 
+     free(ovn_version);
+-    unixctl_server_destroy(unixctl);
+     lflow_destroy();
+     ofctrl_destroy();
+     pinctrl_destroy();
+@@ -6077,6 +6147,8 @@ loop_done:
+     if (cli_system_id) {
+         free(cli_system_id);
+     }
++    ovn_exit_args_finish(&exit_args);
++    unixctl_server_destroy(unixctl);
+     service_stop();
+     ovsrcu_exit();
+ 
+@@ -6156,6 +6228,7 @@ parse_options(int argc, char *argv[])
+             break;
+ 
+         case 'n':
++            free(cli_system_id);
+             cli_system_id = xstrdup(optarg);
+             break;
+ 
+@@ -6200,16 +6273,6 @@ usage(void)
+     exit(EXIT_SUCCESS);
+ }
+ 
+-static void
+-ovn_controller_exit(struct unixctl_conn *conn, int argc,
+-             const char *argv[], void *exit_args_)
+-{
+-    struct ovn_controller_exit_args *exit_args = exit_args_;
+-    *exit_args->exiting = true;
+-    *exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart");
+-    unixctl_command_reply(conn, NULL);
+-}
+-
+ static void
+ ct_zone_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
+              const char *argv[] OVS_UNUSED, void *ct_zones_)
+diff --git a/controller/physical.c b/controller/physical.c
+index 75257bc85..3b862ca34 100644
+--- a/controller/physical.c
++++ b/controller/physical.c
+@@ -341,7 +341,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding,
+             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+             VLOG_WARN_RL(
+                 &rl, "Failed to locate tunnel to reach main chassis %s "
+-                     "for port %s. Cloning packets disabled for the chassis.",
++                     "for port %s.",
+                 binding->chassis->name, binding->logical_port);
+         } else {
+             struct tunnel *tun_elem = xmalloc(sizeof *tun_elem);
+@@ -363,7 +363,7 @@ get_remote_tunnels(const struct sbrec_port_binding *binding,
+             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+             VLOG_WARN_RL(
+                 &rl, "Failed to locate tunnel to reach additional chassis %s "
+-                     "for port %s. Cloning packets disabled for the chassis.",
++                     "for port %s.",
+                 binding->additional_chassis[i]->name, binding->logical_port);
+             continue;
+         }
+@@ -703,22 +703,33 @@ put_replace_chassis_mac_flows(const struct simap *ct_zones,
+     }
+ }
+ 
+-#define VLAN_80211AD_ETHTYPE 0x88a8
+-#define VLAN_80211Q_ETHTYPE 0x8100
++#define VLAN_8021AD_ETHTYPE 0x88a8
++#define VLAN_8021Q_ETHTYPE 0x8100
+ 
+ static void
+ ofpact_put_push_vlan(struct ofpbuf *ofpacts, const struct smap *options, int tag)
+ {
+     const char *ethtype_opt = options ? smap_get(options, "ethtype") : NULL;
+ 
+-    int ethtype = VLAN_80211Q_ETHTYPE;
++    int ethtype = VLAN_8021Q_ETHTYPE;
+     if (ethtype_opt) {
+-        if (!strcasecmp(ethtype_opt, "802.11ad")) {
+-            ethtype = VLAN_80211AD_ETHTYPE;
+-        } else if (strcasecmp(ethtype_opt, "802.11q")) {
++      if (!strcasecmp(ethtype_opt, "802.11ad")
++          || !strcasecmp(ethtype_opt, "802.1ad")) {
++        ethtype = VLAN_8021AD_ETHTYPE;
++      } else if (!strcasecmp(ethtype_opt, "802.11q")
++                 || !strcasecmp(ethtype_opt, "802.1q")) {
++        ethtype = VLAN_8021Q_ETHTYPE;
++      } else {
+             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+             VLOG_WARN_RL(&rl, "Unknown port ethtype: %s", ethtype_opt);
+-        }
++      }
++      if (!strcasecmp(ethtype_opt, "802.11ad")
++          || !strcasecmp(ethtype_opt, "802.11q")) {
++        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
++        VLOG_WARN_RL(&rl, "Using incorrect value ethtype: %s for either "
++                          "802.1q or 802.1ad please correct this value",
++                          ethtype_opt);
++      }
+     }
+ 
+     struct ofpact_push_vlan *push_vlan;
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index ff5a3444c..ececbdb48 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -373,9 +373,13 @@ static const struct sbrec_fdb *fdb_lookup(
+     uint32_t dp_key, const char *mac);
+ static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
++            struct ovsdb_idl_index *sbrec_port_binding_by_key,
++            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                         const struct fdb_entry *fdb_e)
+                         OVS_REQUIRES(pinctrl_mutex);
+ static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
++            struct ovsdb_idl_index *sbrec_port_binding_by_key,
++            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+                         struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
+                         OVS_REQUIRES(pinctrl_mutex);
+ static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn);
+@@ -1280,11 +1284,25 @@ fill_ipv6_prefix_state(struct ovsdb_idl_txn *ovnsb_idl_txn,
+             continue;
+         }
+ 
++        /* To reach this point, the port binding must be a logical router
++         * port. LRPs are configured with a single MAC that is always non-NULL.
++         * Therefore, as long as we are working with a port_binding that was
++         * inserted into the southbound database by northd, we can always
++         * safely extract pb->mac[0] since it will be non-NULL.
++         *
++         * However, if a port_binding was inserted by someone else, then we
++         * need to double-check our assumption first.
++         */
++        if (pb->n_mac != 1) {
++            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++            VLOG_ERR_RL(&rl, "Port binding "UUID_FMT" has %"PRIuSIZE" MACs "
++                        "instead of 1", UUID_ARGS(&pb->header_.uuid),
++                        pb->n_mac);
++            continue;
++        }
+         struct lport_addresses c_addrs;
+-        for (size_t j = 0; j < pb->n_mac; j++) {
+-            if (extract_lsp_addresses(pb->mac[j], &c_addrs)) {
+-                    break;
+-            }
++        if (!extract_lsp_addresses(pb->mac[0], &c_addrs)) {
++            continue;
+         }
+ 
+         pfd = shash_find_data(&ipv6_prefixd, pb->logical_port);
+@@ -3577,7 +3595,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                       chassis);
+     bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
+                     chassis, active_tunnels);
+-    run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
++    run_put_fdbs(ovnsb_idl_txn, sbrec_port_binding_by_key,
++                 sbrec_datapath_binding_by_key, sbrec_fdb_by_dp_key_mac);
+     run_activated_ports(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+                         sbrec_port_binding_by_key, chassis);
+     ovs_mutex_unlock(&pinctrl_mutex);
+@@ -6226,6 +6245,12 @@ pinctrl_handle_put_nd_ra_opts(
+ 
+     /* Set the IPv6 payload length and calculate the ICMPv6 checksum. */
+     struct ovs_16aligned_ip6_hdr *nh = dp_packet_l3(&pkt_out);
++
++    /* Set the source to "ff02::1" if the original source is "::". */
++    if (!memcmp(&nh->ip6_src, &in6addr_any, sizeof in6addr_any)) {
++        memcpy(&nh->ip6_src, &in6addr_all_hosts, sizeof in6addr_all_hosts);
++    }
++
+     nh->ip6_plen = htons(userdata->size);
+     struct ovs_ra_msg *ra = dp_packet_l4(&pkt_out);
+     ra->icmph.icmp6_cksum = 0;
+@@ -6338,26 +6363,31 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
+     char *protocol = NULL;
+     char *load_balancer = NULL;
+ 
++    struct empty_lb_backends_event *event = NULL;
++
+     while (userdata->size) {
+         userdata_opt = ofpbuf_try_pull(userdata, sizeof opt_hdr);
+         if (!userdata_opt) {
+-            return false;
++            goto cleanup;
+         }
+         memcpy(&opt_hdr, userdata_opt, sizeof opt_hdr);
+ 
+         size_t size = ntohs(opt_hdr.size);
+         char *userdata_opt_data = ofpbuf_try_pull(userdata, size);
+         if (!userdata_opt_data) {
+-            return false;
++            goto cleanup;
+         }
+         switch (ntohs(opt_hdr.opt_code)) {
+         case EMPTY_LB_VIP:
++            free(vip);
+             vip = xmemdup0(userdata_opt_data, size);
+             break;
+         case EMPTY_LB_PROTOCOL:
++            free(protocol);
+             protocol = xmemdup0(userdata_opt_data, size);
+             break;
+         case EMPTY_LB_LOAD_BALANCER:
++            free(load_balancer);
+             load_balancer = xmemdup0(userdata_opt_data, size);
+             break;
+         default:
+@@ -6368,36 +6398,39 @@ pinctrl_handle_empty_lb_backends_opts(struct ofpbuf *userdata)
+     if (!vip || !protocol || !load_balancer) {
+         static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+         VLOG_WARN_RL(&rl, "missing lb parameters in userdata");
+-        free(vip);
+-        free(protocol);
+-        free(load_balancer);
+-        return false;
++        goto cleanup;
+     }
+ 
+-    struct empty_lb_backends_event *event;
+-
+     event = pinctrl_find_empty_lb_backends_event(vip, protocol,
+                                                  load_balancer, hash);
+-    if (!event) {
+-        if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
+-            COVERAGE_INC(pinctrl_drop_controller_event);
+-            return false;
+-        }
++    if (event) {
++        goto cleanup;
++    }
+ 
+-        event = xzalloc(sizeof *event);
+-        hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
+-                    &event->hmap_node, hash);
+-        event->vip = vip;
+-        event->protocol = protocol;
+-        event->load_balancer = load_balancer;
+-        event->timestamp = time_msec();
+-        notify_pinctrl_main();
+-    } else {
+-        free(vip);
+-        free(protocol);
+-        free(load_balancer);
++    if (hmap_count(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS]) >= 1000) {
++        COVERAGE_INC(pinctrl_drop_controller_event);
++        goto cleanup;
+     }
+-    return true;
++
++    event = xzalloc(sizeof *event);
++    hmap_insert(&event_table[OVN_EVENT_EMPTY_LB_BACKENDS],
++                &event->hmap_node, hash);
++    event->vip = vip;
++    event->protocol = protocol;
++    event->load_balancer = load_balancer;
++    event->timestamp = time_msec();
++    notify_pinctrl_main();
++
++    vip = NULL;
++    protocol = NULL;
++    load_balancer = NULL;
++
++cleanup:
++    free(vip);
++    free(protocol);
++    free(load_balancer);
++
++    return event != NULL;
+ }
+ 
+ static void
+@@ -7755,7 +7788,9 @@ svc_monitors_run(struct rconn *swconn,
+             if (svc_mon->n_success >= svc_mon->success_count) {
+                 svc_mon->status = SVC_MON_ST_ONLINE;
+                 svc_mon->n_success = 0;
++                svc_mon->n_failures = 0;
+             }
++
+             if (current_time >= svc_mon->next_send_time) {
+                 svc_monitor_send_health_check(swconn, svc_mon);
+                 next_run_time = svc_mon->wait_time;
+@@ -7767,6 +7802,7 @@ svc_monitors_run(struct rconn *swconn,
+         case SVC_MON_S_OFFLINE:
+             if (svc_mon->n_failures >= svc_mon->failure_count) {
+                 svc_mon->status = SVC_MON_ST_OFFLINE;
++                svc_mon->n_success = 0;
+                 svc_mon->n_failures = 0;
+             }
+ 
+@@ -7812,7 +7848,6 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn,
+         return false;
+     }
+ 
+-    uint32_t tcp_seq = ntohl(get_16aligned_be32(&th->tcp_seq));
+     uint32_t tcp_ack = ntohl(get_16aligned_be32(&th->tcp_ack));
+ 
+     if (th->tcp_dst != svc_mon->tp_src) {
+@@ -7829,10 +7864,10 @@ pinctrl_handle_tcp_svc_check(struct rconn *swconn,
+         svc_mon->n_success++;
+         svc_mon->state = SVC_MON_S_ONLINE;
+ 
+-        /* Send RST-ACK packet. */
+-        svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST | TCP_ACK,
+-                                            htonl(tcp_ack + 1),
+-                                            htonl(tcp_seq + 1), th->tcp_dst);
++        /* Send RST packet. */
++        svc_monitor_send_tcp_health_check__(swconn, svc_mon, TCP_RST,
++                                            htonl(tcp_ack),
++                                            htonl(0), th->tcp_dst);
+         /* Calculate next_send_time. */
+         svc_mon->next_send_time = time_msec() + svc_mon->interval;
+         return true;
+@@ -8184,6 +8219,8 @@ fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key,
+ static void
+ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+             struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
++            struct ovsdb_idl_index *sbrec_port_binding_by_key,
++            struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+             const struct fdb_entry *fdb_e)
+ {
+     /* Convert ethernet argument to string form for database. */
+@@ -8192,14 +8229,28 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+              ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
+ 
+     /* Update or add an FDB entry. */
++    const struct sbrec_port_binding *sb_entry_pb = NULL;
++    const struct sbrec_port_binding *new_entry_pb = NULL;
+     const struct sbrec_fdb *sb_fdb =
+         fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
+     if (!sb_fdb) {
+         sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
+         sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
+         sbrec_fdb_set_mac(sb_fdb, mac_string);
++    } else {
++        /* check whether sb_fdb->port_key is vif or localnet type */
++        sb_entry_pb = lport_lookup_by_key(
++            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
++            sb_fdb->dp_key, sb_fdb->port_key);
++        new_entry_pb = lport_lookup_by_key(
++            sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
++            fdb_e->dp_key, fdb_e->port_key);
++    }
++    /* Do not have localnet overwrite a previous vif entry */
++    if (!sb_entry_pb || !new_entry_pb || strcmp(sb_entry_pb->type, "") ||
++        strcmp(new_entry_pb->type, "localnet")) {
++        sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
+     }
+-    sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
+ 
+     /* For backward compatibility check if timestamp column is available
+      * in SB DB. */
+@@ -8210,6 +8261,8 @@ run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ 
+ static void
+ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
++             struct ovsdb_idl_index *sbrec_port_binding_by_key,
++             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+              struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
+              OVS_REQUIRES(pinctrl_mutex)
+ {
+@@ -8219,7 +8272,9 @@ run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ 
+     const struct fdb_entry *fdb_e;
+     HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
+-        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e);
++        run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac,
++                    sbrec_port_binding_by_key,
++                    sbrec_datapath_binding_by_key, fdb_e);
+     }
+     ovn_fdbs_flush(&put_fdbs);
+ }
+diff --git a/controller/statctrl.c b/controller/statctrl.c
+index cb1545cbb..8cce97df8 100644
+--- a/controller/statctrl.c
++++ b/controller/statctrl.c
+@@ -233,7 +233,7 @@ statctrl_thread_handler(void *arg)
+     struct statctrl_ctx *ctx = arg;
+ 
+     /* OpenFlow connection to the switch. */
+-    struct rconn *swconn = rconn_create(5, 0, DSCP_DEFAULT,
++    struct rconn *swconn = rconn_create(0, 0, DSCP_DEFAULT,
+                                         1 << OFP15_VERSION);
+ 
+     while (!latch_is_set(&ctx->exit_latch)) {
+diff --git a/debian/changelog b/debian/changelog
+index 96d132784..479d4c944 100644
+--- a/debian/changelog
++++ b/debian/changelog
+@@ -1,3 +1,15 @@
++OVN (23.09.2-1) unstable; urgency=low
++   [ OVN team ]
++   * New upstream version
++
++ -- OVN team <dev@openvswitch.org>  Fri, 01 Dec 2023 14:41:31 -0500
++
++OVN (23.09.1-1) unstable; urgency=low
++   [ OVN team ]
++   * New upstream version
++
++ -- OVN team <dev@openvswitch.org>  Fri, 01 Dec 2023 14:41:31 -0500
++
+ ovn (23.09.0-1) unstable; urgency=low
+ 
+    * New upstream version
+diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
+index e2023c2ba..020b1d60b 100644
+--- a/ic/ovn-ic.c
++++ b/ic/ovn-ic.c
+@@ -1630,13 +1630,18 @@ collect_lr_routes(struct ic_context *ctx,
+     const struct icnbrec_transit_switch *key;
+ 
+     struct hmap *routes_ad;
++    const struct icnbrec_transit_switch *t_sw;
+     for (int i = 0; i < ic_lr->n_isb_pbs; i++) {
+         isb_pb = ic_lr->isb_pbs[i];
+         key = icnbrec_transit_switch_index_init_row(
+             ctx->icnbrec_transit_switch_by_name);
+         icnbrec_transit_switch_index_set_name(key, isb_pb->transit_switch);
+-        ts_name = icnbrec_transit_switch_index_find(
+-            ctx->icnbrec_transit_switch_by_name, key)->name;
++        t_sw = icnbrec_transit_switch_index_find(
++             ctx->icnbrec_transit_switch_by_name, key);
++        if (!t_sw) {
++            continue;
++        }
++        ts_name = t_sw->name;
+         icnbrec_transit_switch_index_destroy_row(key);
+         routes_ad = shash_find_data(routes_ad_by_ts, ts_name);
+         if (!routes_ad) {
+@@ -2216,10 +2221,19 @@ main(int argc, char *argv[])
+                 ovn_db_run(&ctx);
+             }
+ 
+-            ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop);
+-            ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
+-            ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop);
+-            ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop);
++            int rc1 = ovsdb_idl_loop_commit_and_wait(&ovnnb_idl_loop);
++            int rc2 = ovsdb_idl_loop_commit_and_wait(&ovnsb_idl_loop);
++            int rc3 = ovsdb_idl_loop_commit_and_wait(&ovninb_idl_loop);
++            int rc4 = ovsdb_idl_loop_commit_and_wait(&ovnisb_idl_loop);
++            if (!rc1 || !rc2 || !rc3 || !rc4) {
++                VLOG_DBG(" a transaction failed in: %s %s %s %s",
++                         !rc1 ? "nb" : "", !rc2 ? "sb" : "",
++                         !rc3 ? "ic_nb" : "", rc4 ? "ic_sb" : "");
++                /* A transaction failed. Wake up immediately to give
++                 * opportunity to send the proper transaction
++                 */
++                poll_immediate_wake();
++            }
+         } else {
+             /* ovn-ic is paused
+              *    - we still want to handle any db updates and update the
+diff --git a/include/ovn/features.h b/include/ovn/features.h
+index 3bf536127..2c47ab766 100644
+--- a/include/ovn/features.h
++++ b/include/ovn/features.h
+@@ -26,6 +26,7 @@
+ #define OVN_FEATURE_MAC_BINDING_TIMESTAMP "mac-binding-timestamp"
+ #define OVN_FEATURE_CT_LB_RELATED "ovn-ct-lb-related"
+ #define OVN_FEATURE_FDB_TIMESTAMP "fdb-timestamp"
++#define OVN_FEATURE_LS_DPG_COLUMN "ls-dpg-column"
+ 
+ /* OVS datapath supported features.  Based on availability OVN might generate
+  * different types of openflows.
+@@ -48,5 +49,8 @@ void ovs_feature_support_destroy(void);
+ bool ovs_feature_is_supported(enum ovs_feature_value feature);
+ bool ovs_feature_support_run(const struct smap *ovs_capabilities,
+                              const char *br_name);
++bool ovs_feature_set_discovered(void);
++uint32_t ovs_feature_max_meters_get(void);
++uint32_t ovs_feature_max_select_groups_get(void);
+ 
+ #endif
+diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
+index a7b64ef67..272277ec4 100644
+--- a/include/ovn/logical-fields.h
++++ b/include/ovn/logical-fields.h
+@@ -77,6 +77,7 @@ enum mff_log_flags_bits {
+     MLF_CHECK_PORT_SEC_BIT = 12,
+     MLF_LOOKUP_COMMIT_ECMP_NH_BIT = 13,
+     MLF_USE_LB_AFF_SESSION_BIT = 14,
++    MLF_LOCALNET_BIT = 15,
+ };
+ 
+ /* MFF_LOG_FLAGS_REG flag assignments */
+@@ -124,6 +125,10 @@ enum mff_log_flags {
+     MLF_LOOKUP_COMMIT_ECMP_NH = (1 << MLF_LOOKUP_COMMIT_ECMP_NH_BIT),
+ 
+     MLF_USE_LB_AFF_SESSION = (1 << MLF_USE_LB_AFF_SESSION_BIT),
++
++    /* Indicate that the port is localnet. */
++    MLF_LOCALNET = (1 << MLF_LOCALNET_BIT),
++
+ };
+ 
+ /* OVN logical fields
+diff --git a/lib/actions.c b/lib/actions.c
+index b880927b6..4d408d82d 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -5004,6 +5004,7 @@ encode_COMMIT_LB_AFF(const struct ovnact_commit_lb_aff *lb_aff,
+     ol->hard_timeout = OFP_FLOW_PERMANENT;
+     ol->priority = OFP_DEFAULT_PRIORITY;
+     ol->table_id = OFTABLE_CHK_LB_AFFINITY;
++    ol->cookie = htonll(ep->lflow_uuid.parts[0]);
+ 
+     /* Match on metadata of the packet that created the new table. */
+     ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+diff --git a/lib/extend-table.c b/lib/extend-table.c
+index ebb1a054c..8e79d7177 100644
+--- a/lib/extend-table.c
++++ b/lib/extend-table.c
+@@ -17,9 +17,9 @@
+ #include <config.h>
+ #include <string.h>
+ 
+-#include "bitmap.h"
+ #include "extend-table.h"
+ #include "hash.h"
++#include "id-pool.h"
+ #include "lib/uuid.h"
+ #include "openvswitch/vlog.h"
+ 
+@@ -30,13 +30,29 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table,
+                                 struct ovn_extend_table_lflow_to_desired *l);
+ 
+ void
+-ovn_extend_table_init(struct ovn_extend_table *table)
++ovn_extend_table_init(struct ovn_extend_table *table, const char *table_name,
++                      uint32_t n_ids)
+ {
+-    table->table_ids = bitmap_allocate(MAX_EXT_TABLE_ID);
+-    bitmap_set1(table->table_ids, 0); /* table id 0 is invalid. */
+-    hmap_init(&table->desired);
+-    hmap_init(&table->lflow_to_desired);
+-    hmap_init(&table->existing);
++    *table = (struct ovn_extend_table) {
++        .name = xstrdup(table_name),
++        .n_ids = n_ids,
++        /* Table id 0 is invalid, set id-pool base to 1. */
++        .table_ids = id_pool_create(1, n_ids),
++        .desired = HMAP_INITIALIZER(&table->desired),
++        .lflow_to_desired = HMAP_INITIALIZER(&table->lflow_to_desired),
++        .existing = HMAP_INITIALIZER(&table->existing),
++    };
++}
++
++void
++ovn_extend_table_reinit(struct ovn_extend_table *table, uint32_t n_ids)
++{
++    if (n_ids != table->n_ids) {
++        ovn_extend_table_clear(table, true);
++        id_pool_destroy(table->table_ids);
++        table->table_ids = id_pool_create(1, n_ids);
++        table->n_ids = n_ids;
++    }
+ }
+ 
+ static struct ovn_extend_table_info *
+@@ -117,13 +133,13 @@ ovn_extend_table_add_desired_to_lflow(struct ovn_extend_table *table,
+         ovs_list_init(&l->desired);
+         hmap_insert(&table->lflow_to_desired, &l->hmap_node,
+                     uuid_hash(lflow_uuid));
+-        VLOG_DBG("%s: add new lflow_to_desired entry "UUID_FMT,
+-                 __func__, UUID_ARGS(lflow_uuid));
++        VLOG_DBG("%s: table %s: add new lflow_to_desired entry "UUID_FMT,
++                 __func__, table->name, UUID_ARGS(lflow_uuid));
+     }
+ 
+     ovs_list_insert(&l->desired, &r->list_node);
+-    VLOG_DBG("%s: lflow "UUID_FMT" use new item %s, id %"PRIu32,
+-             __func__, UUID_ARGS(lflow_uuid), r->desired->name,
++    VLOG_DBG("%s: table %s: lflow "UUID_FMT" use new item %s, id %"PRIu32,
++             __func__, table->name, UUID_ARGS(lflow_uuid), r->desired->name,
+              r->desired->table_id);
+ }
+ 
+@@ -160,10 +176,11 @@ ovn_extend_info_add_lflow_ref(struct ovn_extend_table *table,
+ }
+ 
+ static void
+-ovn_extend_info_del_lflow_ref(struct ovn_extend_table_lflow_ref *r)
++ovn_extend_info_del_lflow_ref(struct ovn_extend_table *table,
++                              struct ovn_extend_table_lflow_ref *r)
+ {
+-    VLOG_DBG("%s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__,
+-             r->desired->name, UUID_ARGS(&r->lflow_uuid),
++    VLOG_DBG("%s: table %s: name %s, lflow "UUID_FMT" n %"PRIuSIZE, __func__,
++             table->name, r->desired->name, UUID_ARGS(&r->lflow_uuid),
+              hmap_count(&r->desired->references));
+     hmap_remove(&r->desired->references, &r->hmap_node);
+     ovs_list_remove(&r->list_node);
+@@ -191,8 +208,8 @@ ovn_extend_table_clear(struct ovn_extend_table *table, bool existing)
+         if (g->peer) {
+             g->peer->peer = NULL;
+         } else {
+-            /* Unset the bitmap because the peer is deleted already. */
+-            bitmap_set0(table->table_ids, g->table_id);
++            /* Unset the id because the peer is deleted already. */
++            id_pool_free_id(table->table_ids, g->table_id);
+         }
+         ovn_extend_table_info_destroy(g);
+     }
+@@ -206,7 +223,8 @@ ovn_extend_table_destroy(struct ovn_extend_table *table)
+     hmap_destroy(&table->lflow_to_desired);
+     ovn_extend_table_clear(table, true);
+     hmap_destroy(&table->existing);
+-    bitmap_free(table->table_ids);
++    id_pool_destroy(table->table_ids);
++    free(table->name);
+ }
+ 
+ /* Remove an entry from existing table */
+@@ -221,7 +239,7 @@ ovn_extend_table_remove_existing(struct ovn_extend_table *table,
+         existing->peer->peer = NULL;
+     } else {
+         /* Dealloc the ID. */
+-        bitmap_set0(table->table_ids, existing->table_id);
++        id_pool_free_id(table->table_ids, existing->table_id);
+     }
+     ovn_extend_table_info_destroy(existing);
+ }
+@@ -234,15 +252,15 @@ ovn_extend_table_delete_desired(struct ovn_extend_table *table,
+     struct ovn_extend_table_lflow_ref *r;
+     LIST_FOR_EACH_SAFE (r, list_node, &l->desired) {
+         struct ovn_extend_table_info *e = r->desired;
+-        ovn_extend_info_del_lflow_ref(r);
++        ovn_extend_info_del_lflow_ref(table, r);
+         if (hmap_is_empty(&e->references)) {
+-            VLOG_DBG("%s: %s, "UUID_FMT, __func__,
+-                     e->name, UUID_ARGS(&l->lflow_uuid));
++            VLOG_DBG("%s: table %s: %s, "UUID_FMT, __func__,
++                     table->name, e->name, UUID_ARGS(&l->lflow_uuid));
+             hmap_remove(&table->desired, &e->hmap_node);
+             if (e->peer) {
+                 e->peer->peer = NULL;
+             } else {
+-                bitmap_set0(table->table_ids, e->table_id);
++                id_pool_free_id(table->table_ids, e->table_id);
+             }
+             ovn_extend_table_info_destroy(e);
+         }
+@@ -284,7 +302,7 @@ ovn_extend_table_sync(struct ovn_extend_table *table)
+     }
+ }
+ 
+-/* Assign a new table ID for the table information from the bitmap.
++/* Assign a new table ID for the table information from the ID pool.
+  * If it already exists, return the old ID. */
+ uint32_t
+ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
+@@ -298,9 +316,9 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
+     /* Check whether we have non installed but allocated group_id. */
+     HMAP_FOR_EACH_WITH_HASH (table_info, hmap_node, hash, &table->desired) {
+         if (!strcmp(table_info->name, name)) {
+-            VLOG_DBG("ovn_externd_table_assign_id: reuse old id %"PRIu32
+-                     " for %s, used by lflow "UUID_FMT,
+-                     table_info->table_id, table_info->name,
++            VLOG_DBG("ovn_extend_table_assign_id: table %s: "
++                     "reuse old id %"PRIu32" for %s, used by lflow "UUID_FMT,
++                     table->name, table_info->table_id, table_info->name,
+                      UUID_ARGS(&lflow_uuid));
+             ovn_extend_info_add_lflow_ref(table, table_info, &lflow_uuid);
+             return table_info->table_id;
+@@ -320,15 +338,13 @@ ovn_extend_table_assign_id(struct ovn_extend_table *table, const char *name,
+ 
+     if (!existing_info) {
+         /* Reserve a new id. */
+-        table_id = bitmap_scan(table->table_ids, 0, 1, MAX_EXT_TABLE_ID + 1);
+-    }
++        if (!id_pool_alloc_id(table->table_ids, &table_id)) {
++            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ 
+-    if (table_id == MAX_EXT_TABLE_ID + 1) {
+-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+-        VLOG_ERR_RL(&rl, "%"PRIu32" out of table ids.", table_id);
+-        return EXT_TABLE_ID_INVALID;
++            VLOG_ERR_RL(&rl, "table %s: out of table ids.", table->name);
++            return EXT_TABLE_ID_INVALID;
++        }
+     }
+-    bitmap_set1(table->table_ids, table_id);
+ 
+     table_info = ovn_extend_table_info_alloc(name, table_id, existing_info,
+                                              hash);
+diff --git a/lib/extend-table.h b/lib/extend-table.h
+index b43a146b4..90e6e470d 100644
+--- a/lib/extend-table.h
++++ b/lib/extend-table.h
+@@ -17,18 +17,21 @@
+ #ifndef EXTEND_TABLE_H
+ #define EXTEND_TABLE_H 1
+ 
+-#define MAX_EXT_TABLE_ID 65535
+ #define EXT_TABLE_ID_INVALID 0
+ 
+ #include "openvswitch/hmap.h"
+ #include "openvswitch/list.h"
+ #include "openvswitch/uuid.h"
+ 
++struct id_pool;
++
+ /* Used to manage expansion tables associated with Flow table,
+  * such as the Group Table or Meter Table. */
+ struct ovn_extend_table {
+-    unsigned long *table_ids;  /* Used as a bitmap with value set
+-                                * for allocated ids in either desired or
++    char *name; /* Used to identify this table in a user friendly way,
++                 * e.g., for logging. */
++    uint32_t n_ids;
++    struct id_pool *table_ids; /* Used to allocate ids in either desired or
+                                 * existing (or both).  If the same "name"
+                                 * exists in both desired and existing tables,
+                                 * they must share the same ID.  The "peer"
+@@ -81,7 +84,9 @@ struct ovn_extend_table_lflow_ref {
+     struct ovn_extend_table_info *desired;
+ };
+ 
+-void ovn_extend_table_init(struct ovn_extend_table *);
++void ovn_extend_table_init(struct ovn_extend_table *, const char *table_name,
++                           uint32_t n_ids);
++void ovn_extend_table_reinit(struct ovn_extend_table *, uint32_t n_ids);
+ 
+ void ovn_extend_table_destroy(struct ovn_extend_table *);
+ 
+diff --git a/lib/features.c b/lib/features.c
+index d24e8f6c5..b31b5f6dd 100644
+--- a/lib/features.c
++++ b/lib/features.c
+@@ -27,6 +27,7 @@
+ #include "openvswitch/rconn.h"
+ #include "openvswitch/ofp-msgs.h"
+ #include "openvswitch/ofp-meter.h"
++#include "openvswitch/ofp-group.h"
+ #include "openvswitch/ofp-util.h"
+ #include "ovn/features.h"
+ 
+@@ -81,6 +82,18 @@ static struct ovs_feature all_ovs_features[] = {
+ /* A bitmap of OVS features that have been detected as 'supported'. */
+ static uint32_t supported_ovs_features;
+ 
++/* Last set of received feature replies. */
++static struct ofputil_meter_features ovs_meter_features_reply;
++static struct ofputil_group_features ovs_group_features_reply;
++
++/* Currently discovered set of features. */
++static struct ofputil_meter_features ovs_meter_features;
++static struct ofputil_group_features ovs_group_features;
++
++/* Number of features replies still expected to receive for the requests
++ * we sent already. */
++static uint32_t n_features_reply_expected;
++
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+ 
+ /* ovs-vswitchd connection. */
+@@ -145,11 +158,19 @@ ovs_feature_get_openflow_cap(const char *br_name)
+ 
+     /* send new requests just after reconnect. */
+     if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
+-        /* dump datapath meter capabilities. */
++        n_features_reply_expected = 0;
++
++        /* Dump OpenFlow switch meter capabilities. */
+         msg = ofpraw_alloc(OFPRAW_OFPST13_METER_FEATURES_REQUEST,
+                            rconn_get_version(swconn), 0);
+         rconn_send(swconn, msg, NULL);
++        n_features_reply_expected++;
++        /* Dump OpenFlow switch group capabilities. */
++        msg = ofputil_encode_group_features_request(rconn_get_version(swconn));
++        rconn_send(swconn, msg, NULL);
++        n_features_reply_expected++;
+     }
++    conn_seq_no = rconn_get_connection_seqno(swconn);
+ 
+     bool ret = false;
+     for (int i = 0; i < 50; i++) {
+@@ -163,21 +184,13 @@ ovs_feature_get_openflow_cap(const char *br_name)
+         ofptype_decode(&type, oh);
+ 
+         if (type == OFPTYPE_METER_FEATURES_STATS_REPLY) {
+-            struct ofputil_meter_features mf;
+-            ofputil_decode_meter_features(oh, &mf);
+-
+-            bool old_state = supported_ovs_features & OVS_DP_METER_SUPPORT;
+-            bool new_state = mf.max_meters > 0;
+-
+-            if (old_state != new_state) {
+-                ret = true;
+-                if (new_state) {
+-                    supported_ovs_features |= OVS_DP_METER_SUPPORT;
+-                } else {
+-                    supported_ovs_features &= ~OVS_DP_METER_SUPPORT;
+-                }
+-            }
+-            conn_seq_no = rconn_get_connection_seqno(swconn);
++            ofputil_decode_meter_features(oh, &ovs_meter_features_reply);
++            ovs_assert(n_features_reply_expected);
++            n_features_reply_expected--;
++        } else if (type == OFPTYPE_GROUP_FEATURES_STATS_REPLY) {
++            ofputil_decode_group_features_reply(oh, &ovs_group_features_reply);
++            ovs_assert(n_features_reply_expected);
++            n_features_reply_expected--;
+         } else if (type == OFPTYPE_ECHO_REQUEST) {
+             rconn_send(swconn, ofputil_encode_echo_reply(oh), NULL);
+         }
+@@ -186,6 +199,26 @@ ovs_feature_get_openflow_cap(const char *br_name)
+     rconn_run_wait(swconn);
+     rconn_recv_wait(swconn);
+ 
++    /* If all feature replies were received, update the set of supported
++     * features. */
++    if (!n_features_reply_expected) {
++        if (memcmp(&ovs_meter_features, &ovs_meter_features_reply,
++                   sizeof ovs_meter_features_reply)) {
++            ovs_meter_features = ovs_meter_features_reply;
++            if (ovs_meter_features.max_meters) {
++                supported_ovs_features |= OVS_DP_METER_SUPPORT;
++            } else {
++                supported_ovs_features &= ~OVS_DP_METER_SUPPORT;
++            }
++            ret = true;
++        }
++        if (memcmp(&ovs_group_features, &ovs_group_features_reply,
++                   sizeof ovs_group_features_reply)) {
++            ovs_group_features = ovs_group_features_reply;
++            ret = true;
++        }
++    }
++
+     return ret;
+ }
+ 
+@@ -229,3 +262,26 @@ ovs_feature_support_run(const struct smap *ovs_capabilities,
+     }
+     return updated;
+ }
++
++bool
++ovs_feature_set_discovered(void)
++{
++    /* The supported feature set has been discovered if we're connected
++     * to OVS and it replied to all our feature request messages. */
++    return swconn && rconn_is_connected(swconn) &&
++           n_features_reply_expected == 0;
++}
++
++/* Returns the number of meters the OVS datapath supports. */
++uint32_t
++ovs_feature_max_meters_get(void)
++{
++    return ovs_meter_features.max_meters;
++}
++
++/* Returns the number of select groups the OVS datapath supports. */
++uint32_t
++ovs_feature_max_select_groups_get(void)
++{
++    return ovs_group_features.max_groups[OFPGT11_SELECT];
++}
+diff --git a/lib/logical-fields.c b/lib/logical-fields.c
+index fd509d9ee..7a1e66d0a 100644
+--- a/lib/logical-fields.c
++++ b/lib/logical-fields.c
+@@ -129,6 +129,10 @@ ovn_init_symtab(struct shash *symtab)
+              MLF_USE_SNAT_ZONE);
+     expr_symtab_add_subfield(symtab, "flags.use_snat_zone", NULL,
+                              flags_str);
++    snprintf(flags_str, sizeof flags_str, "flags[%d]",
++             MLF_LOCALNET_BIT);
++    expr_symtab_add_subfield(symtab, "flags.localnet", NULL,
++                             flags_str);
+ 
+     /* Connection tracking state. */
+     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
+diff --git a/lib/memory-trim.c b/lib/memory-trim.c
+index d8c6b4743..be8983fa7 100644
+--- a/lib/memory-trim.c
++++ b/lib/memory-trim.c
+@@ -71,13 +71,14 @@ memory_trimmer_can_run(struct memory_trimmer *mt)
+     }
+ 
+     long long int now = time_msec();
+-    if (now < mt->last_active_ms || now < mt->trim_timeout_ms) {
++    if (now < mt->last_active_ms) {
+         VLOG_WARN_RL(&rl, "Detected last active timestamp overflow");
+         mt->recently_active = false;
+         return true;
+     }
+ 
+-    if (now - mt->trim_timeout_ms >= mt->last_active_ms) {
++    if (now > mt->trim_timeout_ms
++        && now - mt->trim_timeout_ms >= mt->last_active_ms) {
+         VLOG_INFO_RL(&rl, "Detected inactivity "
+                     "(last active %lld ms ago): trimming memory",
+                     now - mt->last_active_ms);
+diff --git a/lib/ovn-util.c b/lib/ovn-util.c
+index ffe295696..33105202f 100644
+--- a/lib/ovn-util.c
++++ b/lib/ovn-util.c
+@@ -1302,3 +1302,34 @@ sorted_array_apply_diff(const struct sorted_array *a1,
+         apply_callback(arg, a2->arr[idx2], false);
+     }
+ }
++
++/* Call for the unixctl command that will store the connection and
++ * set the appropriate conditions. */
++void
++ovn_exit_command_callback(struct unixctl_conn *conn, int argc,
++                          const char *argv[], void *exit_args_)
++{
++    struct ovn_exit_args *exit_args = exit_args_;
++
++    exit_args->n_conns++;
++    exit_args->conns = xrealloc(exit_args->conns,
++                                exit_args->n_conns * sizeof *exit_args->conns);
++
++    exit_args->exiting = true;
++    exit_args->conns[exit_args->n_conns - 1] = conn;
++
++    if (!exit_args->restart) {
++        exit_args->restart = argc == 2 && !strcmp(argv[1], "--restart");
++    }
++}
++
++/* Reply to all waiting unixctl connections and free the connection array.
++ * This function should be called after the heaviest cleanup has finished. */
++void
++ovn_exit_args_finish(struct ovn_exit_args *exit_args)
++{
++    for (size_t i = 0; i < exit_args->n_conns; i++) {
++        unixctl_command_reply(exit_args->conns[i], NULL);
++    }
++    free(exit_args->conns);
++}
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index 16f18353d..bff50dbde 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -474,4 +474,17 @@ void sorted_array_apply_diff(const struct sorted_array *a1,
+                                                     const char *item,
+                                                     bool add),
+                              const void *arg);
++
++/* Utilities around properly handling exit command. */
++struct ovn_exit_args {
++    struct unixctl_conn **conns;
++    size_t n_conns;
++    bool exiting;
++    bool restart;
++};
++
++void ovn_exit_command_callback(struct unixctl_conn *conn, int argc,
++                               const char *argv[], void *exit_args_);
++void ovn_exit_args_finish(struct ovn_exit_args *exit_args);
++
+ #endif /* OVN_UTIL_H */
+diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
+index 250cec848..d06f46a54 100644
+--- a/northd/en-lb-data.c
++++ b/northd/en-lb-data.c
+@@ -144,6 +144,12 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data)
+ 
+     const struct nbrec_load_balancer *tracked_lb;
+     NBREC_LOAD_BALANCER_TABLE_FOR_EACH_TRACKED (tracked_lb, nb_lb_table) {
++        /* "New" + "Deleted" is a no-op. */
++        if (nbrec_load_balancer_is_new(tracked_lb)
++            && nbrec_load_balancer_is_deleted(tracked_lb)) {
++            continue;
++        }
++
+         struct ovn_northd_lb *lb;
+         if (nbrec_load_balancer_is_new(tracked_lb)) {
+             /* New load balancer. */
+@@ -153,19 +159,22 @@ lb_data_load_balancer_handler(struct engine_node *node, void *data)
+             add_crupdated_lb_to_tracked_data(lb, trk_lb_data,
+                                              lb->health_checks);
+             trk_lb_data->has_routable_lb |= lb->routable;
+-        } else if (nbrec_load_balancer_is_deleted(tracked_lb)) {
+-            lb = ovn_northd_lb_find(&lb_data->lbs,
+-                                    &tracked_lb->header_.uuid);
+-            ovs_assert(lb);
++            continue;
++        }
++
++        /* Protect against "spurious" deletes reported by the IDL. */
++        lb = ovn_northd_lb_find(&lb_data->lbs, &tracked_lb->header_.uuid);
++        if (!lb) {
++            continue;
++        }
++
++        if (nbrec_load_balancer_is_deleted(tracked_lb)) {
+             hmap_remove(&lb_data->lbs, &lb->hmap_node);
+             add_deleted_lb_to_tracked_data(lb, trk_lb_data,
+                                            lb->health_checks);
+             trk_lb_data->has_routable_lb |= lb->routable;
+         } else {
+             /* Load balancer updated. */
+-            lb = ovn_northd_lb_find(&lb_data->lbs,
+-                                    &tracked_lb->header_.uuid);
+-            ovs_assert(lb);
+             bool health_checks = lb->health_checks;
+             struct sset old_ips_v4 = SSET_INITIALIZER(&old_ips_v4);
+             struct sset old_ips_v6 = SSET_INITIALIZER(&old_ips_v6);
+@@ -217,6 +226,12 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data)
+     const struct nbrec_load_balancer_group *tracked_lb_group;
+     NBREC_LOAD_BALANCER_GROUP_TABLE_FOR_EACH_TRACKED (tracked_lb_group,
+                                                       nb_lbg_table) {
++        /* "New" + "Deleted" is a no-op. */
++        if (nbrec_load_balancer_group_is_new(tracked_lb_group)
++            && nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
++            continue;
++        }
++
+         if (nbrec_load_balancer_group_is_new(tracked_lb_group)) {
+             struct ovn_lb_group *lb_group =
+                 create_lb_group(tracked_lb_group, &lb_data->lbs,
+@@ -228,21 +243,22 @@ lb_data_load_balancer_group_handler(struct engine_node *node, void *data)
+             }
+ 
+             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
+-        } else if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
+-            struct ovn_lb_group *lb_group;
+-            lb_group = ovn_lb_group_find(&lb_data->lbgrps,
+-                                         &tracked_lb_group->header_.uuid);
+-            ovs_assert(lb_group);
++            continue;
++        }
++
++        /* Protect against "spurious" deletes reported by the IDL. */
++        struct ovn_lb_group *lb_group;
++        lb_group = ovn_lb_group_find(&lb_data->lbgrps,
++                                     &tracked_lb_group->header_.uuid);
++        if (!lb_group) {
++            continue;
++        }
++
++        if (nbrec_load_balancer_group_is_deleted(tracked_lb_group)) {
+             hmap_remove(&lb_data->lbgrps, &lb_group->hmap_node);
+             add_deleted_lbgrp_to_tracked_data(lb_group, trk_lb_data);
+             trk_lb_data->has_routable_lb |= lb_group->has_routable_lb;
+         } else {
+-
+-            struct ovn_lb_group *lb_group;
+-            lb_group = ovn_lb_group_find(&lb_data->lbgrps,
+-                                         &tracked_lb_group->header_.uuid);
+-            ovs_assert(lb_group);
+-
+             /* Determine the lbs which are added or deleted for this
+              * lb group and add them to tracked data.
+              * Eg.  If an lb group lbg1 before the update had [lb1, lb2, lb3]
+diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
+index aae396a43..2ec3bf54f 100644
+--- a/northd/en-sync-sb.c
++++ b/northd/en-sync-sb.c
+@@ -220,7 +220,8 @@ en_sync_to_sb_lb_run(struct engine_node *node, void *data OVS_UNUSED)
+     struct northd_data *northd_data = engine_get_input_data("northd", node);
+ 
+     sync_lbs(eng_ctx->ovnsb_idl_txn, sb_load_balancer_table,
+-             &northd_data->ls_datapaths, &northd_data->lb_datapaths_map);
++             &northd_data->ls_datapaths, &northd_data->lr_datapaths,
++             &northd_data->lb_datapaths_map, &northd_data->features);
+     engine_set_node_state(node, EN_UPDATED);
+ }
+ 
+@@ -248,6 +249,23 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
+     return true;
+ }
+ 
++bool
++sync_to_sb_lb_sb_load_balancer(struct engine_node *node, void *data OVS_UNUSED)
++{
++    const struct sbrec_load_balancer_table *sb_load_balancer_table =
++        EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
++
++    /* The only reason to handle SB.Load_Balancer updates is to detect
++     * spurious records being created in clustered databases due to
++     * lack of indexing on the SB.Load_Balancer table.  All other changes
++     * are valid and performed by northd, the only write-client for
++     * this table. */
++    if (check_sb_lb_duplicates(sb_load_balancer_table)) {
++        return false;
++    }
++    return true;
++}
++
+ /* sync_to_sb_pb engine node functions.
+  * This engine node syncs the SB Port Bindings (partly).
+  * en_northd engine create the SB Port binding rows and
+diff --git a/northd/en-sync-sb.h b/northd/en-sync-sb.h
+index f08565eee..3bcbb8259 100644
+--- a/northd/en-sync-sb.h
++++ b/northd/en-sync-sb.h
+@@ -21,6 +21,8 @@ void *en_sync_to_sb_lb_init(struct engine_node *, struct engine_arg *);
+ void en_sync_to_sb_lb_run(struct engine_node *, void *data);
+ void en_sync_to_sb_lb_cleanup(void *data);
+ bool sync_to_sb_lb_northd_handler(struct engine_node *, void *data OVS_UNUSED);
++bool sync_to_sb_lb_sb_load_balancer(struct engine_node *,
++                                    void *data OVS_UNUSED);
+ 
+ void *en_sync_to_sb_pb_init(struct engine_node *, struct engine_arg *);
+ void en_sync_to_sb_pb_run(struct engine_node *, void *data);
+diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
+index 8b0817117..04df0b06c 100644
+--- a/northd/inc-proc-northd.c
++++ b/northd/inc-proc-northd.c
+@@ -230,7 +230,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
+ 
+     engine_add_input(&en_sync_to_sb_lb, &en_northd,
+                      sync_to_sb_lb_northd_handler);
+-    engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer, NULL);
++    engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
++                     sync_to_sb_lb_sb_load_balancer);
+ 
+     engine_add_input(&en_sync_to_sb_pb, &en_northd,
+                      sync_to_sb_pb_northd_handler);
+diff --git a/northd/northd.c b/northd/northd.c
+index 04afe62a8..8aeca33f8 100644
+--- a/northd/northd.c
++++ b/northd/northd.c
+@@ -491,6 +491,15 @@ build_chassis_features(const struct sbrec_chassis_table *sbrec_chassis_table,
+             chassis_features->fdb_timestamp) {
+             chassis_features->fdb_timestamp = false;
+         }
++
++        bool ls_dpg_column =
++            smap_get_bool(&chassis->other_config,
++                          OVN_FEATURE_LS_DPG_COLUMN,
++                          false);
++        if (!ls_dpg_column &&
++            chassis_features->ls_dpg_column) {
++            chassis_features->ls_dpg_column = false;
++        }
+     }
+ }
+ 
+@@ -4511,6 +4520,7 @@ struct sb_lb {
+ 
+     const struct sbrec_load_balancer *slb;
+     struct ovn_dp_group *dpg;
++    struct ovn_dp_group *lr_dpg;
+     struct uuid lb_uuid;
+ };
+ 
+@@ -4533,10 +4543,13 @@ find_slb_in_sb_lbs(struct hmap *sb_lbs, const struct uuid *lb_uuid)
+ void
+ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+          const struct sbrec_load_balancer_table *sbrec_load_balancer_table,
+-         struct ovn_datapaths *ls_datapaths, struct hmap *lb_dps_map)
++         struct ovn_datapaths *ls_datapaths,
++         struct ovn_datapaths *lr_datapaths,
++         struct hmap *lb_dps_map,
++         struct chassis_features *chassis_features)
+ {
+-    struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups);
+-    size_t bitmap_len = ods_size(ls_datapaths);
++    struct hmap ls_dp_groups = HMAP_INITIALIZER(&ls_dp_groups);
++    struct hmap lr_dp_groups = HMAP_INITIALIZER(&lr_dp_groups);
+     struct ovn_lb_datapaths *lb_dps;
+     struct hmap sb_lbs = HMAP_INITIALIZER(&sb_lbs);
+ 
+@@ -4554,7 +4567,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+         }
+ 
+         /* Delete any SB load balancer entries that refer to NB load balancers
+-         * that don't exist anymore or are not applied to switches anymore.
++         * that don't exist anymore or are not applied to switches/routers
++         * anymore.
+          *
+          * There is also a special case in which duplicate LBs might be created
+          * in the SB, e.g., due to the fact that OVSDB only ensures
+@@ -4562,7 +4576,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+          * are not indexed in any way.
+          */
+         lb_dps = ovn_lb_datapaths_find(lb_dps_map, &lb_uuid);
+-        if (!lb_dps || !lb_dps->n_nb_ls || !hmapx_add(&existing_lbs, lb_dps)) {
++        if (!lb_dps || (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) ||
++            !hmapx_add(&existing_lbs, lb_dps)) {
+             sbrec_load_balancer_delete(sbrec_lb);
+             continue;
+         }
+@@ -4573,12 +4588,26 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+         hmap_insert(&sb_lbs, &sb_lb->hmap_node, uuid_hash(&lb_uuid));
+ 
+         /* Find or create datapath group for this load balancer. */
+-        sb_lb->dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups,
+-                                                sb_lb->slb->datapath_group,
+-                                                lb_dps->n_nb_ls,
+-                                                lb_dps->nb_ls_map,
+-                                                bitmap_len, true,
+-                                                ls_datapaths, NULL);
++        if (lb_dps->n_nb_ls) {
++            struct sbrec_logical_dp_group *ls_datapath_group
++                = chassis_features->ls_dpg_column
++                    ? sb_lb->slb->ls_datapath_group
++                    : sb_lb->slb->datapath_group; /* deprecated */
++            sb_lb->dpg = ovn_dp_group_get_or_create(
++                    ovnsb_txn, &ls_dp_groups,
++                    ls_datapath_group,
++                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
++                    ods_size(ls_datapaths), true,
++                    ls_datapaths, NULL);
++        }
++        if (lb_dps->n_nb_lr) {
++            sb_lb->lr_dpg = ovn_dp_group_get_or_create(
++                    ovnsb_txn, &lr_dp_groups,
++                    sb_lb->slb->lr_datapath_group,
++                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
++                    ods_size(lr_datapaths), false,
++                    NULL, lr_datapaths);
++        }
+     }
+     hmapx_destroy(&existing_lbs);
+ 
+@@ -4586,7 +4615,7 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+      * the SB load balancer columns. */
+     HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
+ 
+-        if (!lb_dps->n_nb_ls) {
++        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
+             continue;
+         }
+ 
+@@ -4599,8 +4628,8 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+ 
+         struct sb_lb *sb_lb = find_slb_in_sb_lbs(&sb_lbs,
+                                             &lb_dps->lb->nlb->header_.uuid);
+-        ovs_assert(!sb_lb || (sb_lb->slb && sb_lb->dpg));
+-        struct ovn_dp_group *lb_dpg = NULL;
++        ovs_assert(!sb_lb || (sb_lb->slb && (sb_lb->dpg || sb_lb->lr_dpg)));
++        struct ovn_dp_group *lb_dpg = NULL, *lb_lr_dpg = NULL;
+         if (!sb_lb) {
+             sbrec_lb = sbrec_load_balancer_insert(ovnsb_txn);
+             char *lb_id = xasprintf(
+@@ -4612,15 +4641,29 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+         } else {
+             sbrec_lb = sb_lb->slb;
+             lb_dpg = sb_lb->dpg;
++            lb_lr_dpg = sb_lb->lr_dpg;
+         }
+ 
+         /* Find or create datapath group for this load balancer. */
+-        if (!lb_dpg) {
+-            lb_dpg = ovn_dp_group_get_or_create(ovnsb_txn, &dp_groups,
+-                                                sbrec_lb->datapath_group,
+-                                                lb_dps->n_nb_ls,
+-                                                lb_dps->nb_ls_map, bitmap_len,
+-                                                true, ls_datapaths, NULL);
++        if (!lb_dpg && lb_dps->n_nb_ls) {
++            struct sbrec_logical_dp_group *ls_datapath_group
++                = chassis_features->ls_dpg_column
++                    ? sbrec_lb->ls_datapath_group
++                    : sbrec_lb->datapath_group; /* deprecated */
++            lb_dpg = ovn_dp_group_get_or_create(
++                    ovnsb_txn, &ls_dp_groups,
++                    ls_datapath_group,
++                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
++                    ods_size(ls_datapaths), true,
++                    ls_datapaths, NULL);
++        }
++        if (!lb_lr_dpg && lb_dps->n_nb_lr) {
++            lb_lr_dpg = ovn_dp_group_get_or_create(
++                    ovnsb_txn, &lr_dp_groups,
++                    sbrec_lb->lr_datapath_group,
++                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
++                    ods_size(lr_datapaths), false,
++                    NULL, lr_datapaths);
+         }
+ 
+         /* Update columns. */
+@@ -4628,7 +4671,23 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+         sbrec_load_balancer_set_vips(sbrec_lb,
+                                      ovn_northd_lb_get_vips(lb_dps->lb));
+         sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
+-        sbrec_load_balancer_set_datapath_group(sbrec_lb, lb_dpg->dp_group);
++
++        if (chassis_features->ls_dpg_column) {
++            sbrec_load_balancer_set_ls_datapath_group(
++                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
++            );
++            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
++        } else {
++            /* datapath_group column is deprecated. */
++            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
++            sbrec_load_balancer_set_datapath_group(
++                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
++            );
++        }
++
++        sbrec_load_balancer_set_lr_datapath_group(
++            sbrec_lb, lb_lr_dpg ? lb_lr_dpg->dp_group : NULL
++        );
+         sbrec_load_balancer_set_options(sbrec_lb, &options);
+         /* Clearing 'datapaths' column, since 'dp_group' is in use. */
+         sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
+@@ -4636,11 +4695,17 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+     }
+ 
+     struct ovn_dp_group *dpg;
+-    HMAP_FOR_EACH_POP (dpg, node, &dp_groups) {
++    HMAP_FOR_EACH_POP (dpg, node, &ls_dp_groups) {
++        bitmap_free(dpg->bitmap);
++        free(dpg);
++    }
++    hmap_destroy(&ls_dp_groups);
++
++    HMAP_FOR_EACH_POP (dpg, node, &lr_dp_groups) {
+         bitmap_free(dpg->bitmap);
+         free(dpg);
+     }
+-    hmap_destroy(&dp_groups);
++    hmap_destroy(&lr_dp_groups);
+ 
+     struct sb_lb *sb_lb;
+     HMAP_FOR_EACH_POP (sb_lb, hmap_node, &sb_lbs) {
+@@ -4659,6 +4724,33 @@ sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
+             sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
+         }
+     }
++    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
++        ovs_assert(od->nbr);
++
++        if (od->sb->n_load_balancers) {
++            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
++        }
++    }
++}
++
++bool
++check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
++{
++    struct sset existing_nb_lb_uuids =
++        SSET_INITIALIZER(&existing_nb_lb_uuids);
++    const struct sbrec_load_balancer *sbrec_lb;
++    bool duplicates = false;
++
++    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
++        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
++        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
++            duplicates = true;
++            break;
++        }
++    }
++
++    sset_destroy(&existing_nb_lb_uuids);
++    return duplicates;
+ }
+ 
+ /* Syncs the SB port binding for the ovn_port 'op'.  Caller should make sure
+@@ -5089,23 +5181,21 @@ lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *nbsp)
+ }
+ 
+ static bool
+-ls_port_has_changed(const struct nbrec_logical_switch_port *old,
+-                    const struct nbrec_logical_switch_port *new)
++ls_port_has_changed(const struct nbrec_logical_switch_port *new)
+ {
+-    if (old != new) {
+-        return true;
+-    }
+     /* XXX: Need a better OVSDB IDL interface for this check. */
+     return (nbrec_logical_switch_port_row_get_seqno(new,
+                                 OVSDB_IDL_CHANGE_MODIFY) > 0);
+ }
+ 
+ static struct ovn_port *
+-ovn_port_find_in_datapath(struct ovn_datapath *od, const char *name)
++ovn_port_find_in_datapath(struct ovn_datapath *od,
++                          const struct nbrec_logical_switch_port *nbsp)
+ {
+     struct ovn_port *op;
+-    HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(name, 0), &od->ports) {
+-        if (!strcmp(op->key, name)) {
++    HMAP_FOR_EACH_WITH_HASH (op, dp_node, hash_string(nbsp->name, 0),
++                             &od->ports) {
++        if (!strcmp(op->key, nbsp->name) && op->nbsp == nbsp) {
+             return op;
+         }
+     }
+@@ -5290,7 +5380,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
+     /* Compare the individual ports in the old and new Logical Switches */
+     for (size_t j = 0; j < changed_ls->n_ports; ++j) {
+         struct nbrec_logical_switch_port *new_nbsp = changed_ls->ports[j];
+-        op = ovn_port_find_in_datapath(od, new_nbsp->name);
++        op = ovn_port_find_in_datapath(od, new_nbsp);
+ 
+         if (!op) {
+             if (!lsp_can_be_inc_processed(new_nbsp)) {
+@@ -5307,7 +5397,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
+             }
+             ovs_list_push_back(&ls_change->added_ports,
+                                 &op->list);
+-        } else if (ls_port_has_changed(op->nbsp, new_nbsp)) {
++        } else if (ls_port_has_changed(new_nbsp)) {
+             /* Existing port updated */
+             bool temp = false;
+             if (lsp_is_type_changed(op->sb, new_nbsp, &temp) ||
+@@ -5580,9 +5670,9 @@ northd_handle_sb_port_binding_changes(
+              * notification of that transaction, and we can ignore in this
+              * case. Fallback to recompute otherwise, to avoid dangling
+              * sb idl pointers and other unexpected behavior. */
+-            if (op) {
+-                VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but the "
+-                            "LSP still exists.", pb->logical_port);
++            if (op && op->sb == pb) {
++                VLOG_WARN_RL(&rl, "A port-binding for %s is deleted but "
++                            "the LSP still exists.", pb->logical_port);
+                 return false;
+             }
+         } else {
+@@ -6954,6 +7044,9 @@ build_lswitch_learn_fdb_op(
+         ds_clear(match);
+         ds_clear(actions);
+         ds_put_format(match, "inport == %s", op->json_key);
++        if (lsp_is_localnet(op->nbsp)) {
++            ds_put_cstr(actions, "flags.localnet = 1; ");
++        }
+         ds_put_format(actions, REGBIT_LKUP_FDB
+                       " = lookup_fdb(inport, eth.src); next;");
+         ovn_lflow_add_with_lport_and_hint(lflows, op->od,
+@@ -9307,6 +9400,37 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
+         }
+     }
+ 
++    struct shash_node *snat_snode;
++    SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) {
++        struct ovn_snat_ip *snat_ip = snat_snode->data;
++
++        if (ovs_list_is_empty(&snat_ip->snat_entries)) {
++            continue;
++        }
++
++        struct ovn_nat *nat_entry =
++            CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries),
++                         struct ovn_nat, ext_addr_list_node);
++        const struct nbrec_nat *nat = nat_entry->nb;
++
++        /* Check if the ovn port has a network configured on which we could
++         * expect ARP requests/NS for the SNAT external_ip.
++         */
++        if (nat_entry_is_v6(nat_entry)) {
++            if (!sset_contains(&op->od->lb_ips->ips_v6, nat->external_ip)) {
++                build_lswitch_rport_arp_req_flow(
++                    nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
++                    stage_hint);
++            }
++        } else {
++            if (!sset_contains(&op->od->lb_ips->ips_v4, nat->external_ip)) {
++                build_lswitch_rport_arp_req_flow(
++                    nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
++                    stage_hint);
++            }
++        }
++    }
++
+     for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+         build_lswitch_rport_arp_req_flow(
+             op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
+@@ -13047,9 +13171,9 @@ build_neigh_learning_flows_for_lrouter(
+          *   address, the all-nodes multicast address. */
+         ds_clear(actions);
+         ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
+-                               " = lookup_nd(inport, ip6.src, nd.tll); "
++                               " = lookup_nd(inport, nd.target, nd.tll); "
+                                REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
+-                               " = lookup_nd_ip(inport, ip6.src); next;");
++                               " = lookup_nd_ip(inport, nd.target); 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));
+@@ -13895,7 +14019,15 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+                                               outport->json_key)
+                                   : NULL;
+ 
+-    if (op->lrp_networks.ipv4_addrs) {
++    char *ip4_src = NULL;
++
++    if (outport && outport->lrp_networks.ipv4_addrs) {
++        ip4_src = outport->lrp_networks.ipv4_addrs[0].addr_s;
++    } else if (op->lrp_networks.ipv4_addrs) {
++        ip4_src = op->lrp_networks.ipv4_addrs[0].addr_s;
++    }
++
++    if (ip4_src) {
+         ds_clear(match);
+         ds_put_format(match, "inport == %s && %sip4 && "REGBIT_PKT_LARGER
+                       " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key,
+@@ -13915,9 +14047,8 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+             "icmp4.code = 4; /* Frag Needed and DF was Set. */ "
+             "icmp4.frag_mtu = %d; "
+             "next(pipeline=ingress, table=%d); };",
+-            op->lrp_networks.ea_s,
+-            op->lrp_networks.ipv4_addrs[0].addr_s,
+-            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
++            op->lrp_networks.ea_s, ip4_src, mtu,
++            ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+         ovn_lflow_add_with_hint__(lflows, op->od, stage, 150,
+                                   ds_cstr(match), ds_cstr(actions),
+                                   NULL,
+@@ -13928,7 +14059,15 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+                                   &op->nbrp->header_);
+     }
+ 
+-    if (op->lrp_networks.ipv6_addrs) {
++    char *ip6_src = NULL;
++
++    if (outport && outport->lrp_networks.ipv6_addrs) {
++        ip6_src = outport->lrp_networks.ipv6_addrs[0].addr_s;
++    } else if (op->lrp_networks.ipv6_addrs) {
++        ip6_src = op->lrp_networks.ipv6_addrs[0].addr_s;
++    }
++
++    if (ip6_src) {
+         ds_clear(match);
+         ds_put_format(match, "inport == %s && %sip6 && "REGBIT_PKT_LARGER
+                       " && "REGBIT_EGRESS_LOOPBACK" == 0", op->json_key,
+@@ -13948,9 +14087,8 @@ build_icmperr_pkt_big_flows(struct ovn_port *op, int mtu, struct hmap *lflows,
+             "icmp6.code = 0; "
+             "icmp6.frag_mtu = %d; "
+             "next(pipeline=ingress, table=%d); };",
+-            op->lrp_networks.ea_s,
+-            op->lrp_networks.ipv6_addrs[0].addr_s,
+-            mtu, ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
++            op->lrp_networks.ea_s, ip6_src, mtu,
++            ovn_stage_get_table(S_ROUTER_IN_ADMISSION));
+         ovn_lflow_add_with_hint__(lflows, op->od, stage, 150,
+                                   ds_cstr(match), ds_cstr(actions),
+                                   NULL,
+@@ -15217,6 +15355,7 @@ build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
+         ds_put_format(&zone_actions, "eth.src = "ETH_ADDR_FMT"; ",
+                       ETH_ADDR_ARGS(mac));
+     }
++    ds_put_format(match, " && (!ct.trk || !ct.rpl)");
+ 
+     ds_put_cstr(&zone_actions, REGBIT_DST_NAT_IP_LOCAL" = 0; ");
+ 
+@@ -15275,10 +15414,8 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+             ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ",
+                           ETH_ADDR_ARGS(mac));
+         }
+-    } else {
+-        /* Gateway router. */
+-        ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
+     }
++    ds_put_cstr(match, " && (!ct.trk || !ct.rpl)");
+ 
+     ds_put_format(actions, "ct_snat(%s", nat->external_ip);
+     if (nat->external_port_range[0]) {
+@@ -17655,6 +17792,7 @@ northd_init(struct northd_data *data)
+         .mac_binding_timestamp = true,
+         .ct_lb_related = true,
+         .fdb_timestamp = true,
++        .ls_dpg_column = true,
+     };
+     data->ovn_internal_version_changed = false;
+     sset_init(&data->svc_monitor_lsps);
+@@ -18051,13 +18189,15 @@ static void
+ handle_cr_port_binding_changes(const struct sbrec_port_binding *sb,
+                 struct ovn_port *orp)
+ {
++    const struct nbrec_logical_router_port *nbrec_lrp = orp->l3dgw_port->nbrp;
++
+     if (sb->chassis) {
+-        nbrec_logical_router_port_update_status_setkey(
+-            orp->l3dgw_port->nbrp, "hosting-chassis",
+-            sb->chassis->name);
+-    } else {
+-        nbrec_logical_router_port_update_status_delkey(
+-            orp->l3dgw_port->nbrp, "hosting-chassis");
++        nbrec_logical_router_port_update_status_setkey(nbrec_lrp,
++                                                       "hosting-chassis",
++                                                       sb->chassis->name);
++    } else if (smap_get(&nbrec_lrp->status, "hosting-chassis")) {
++        nbrec_logical_router_port_update_status_delkey(nbrec_lrp,
++                                                       "hosting-chassis");
+     }
+ }
+ 
+diff --git a/northd/northd.h b/northd/northd.h
+index 4d030b10d..76dfc392d 100644
+--- a/northd/northd.h
++++ b/northd/northd.h
+@@ -70,6 +70,7 @@ struct chassis_features {
+     bool mac_binding_timestamp;
+     bool ct_lb_related;
+     bool fdb_timestamp;
++    bool ls_dpg_column;
+ };
+ 
+ /* A collection of datapaths. E.g. all logical switch datapaths, or all
+@@ -365,7 +366,11 @@ const char *northd_get_svc_monitor_mac(void);
+ const struct ovn_datapath *northd_get_datapath_for_port(
+     const struct hmap *ls_ports, const char *port_name);
+ void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
+-              struct ovn_datapaths *ls_datapaths, struct hmap *lbs);
++              struct ovn_datapaths *ls_datapaths,
++              struct ovn_datapaths *lr_datapaths,
++              struct hmap *lbs,
++              struct chassis_features *chassis_features);
++bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
+ 
+ void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports);
+ bool sync_pbs_for_northd_ls_changes(struct tracked_ls_changes *);
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index df8ed6750..97718821f 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -399,14 +399,16 @@
+     <p>
+       This table looks up the MAC learning table of the logical switch
+       datapath to check if the <code>port-mac</code> pair is present
+-      or not. MAC is learnt only for logical switch VIF ports whose
+-      port security is disabled and 'unknown' address set.
++      or not. MAC is learnt for logical switch VIF ports whose
++      port security is disabled and 'unknown' address setn as well as
++      for localnet ports with option localnet_learn_fdb. A localnet
++      port entry does not overwrite a VIF port entry.
+     </p>
+ 
+     <ul>
+       <li>
+         <p>
+-          For each such logical port <var>p</var> whose port security
++          For each such VIF logical port <var>p</var> whose port security
+           is disabled and 'unknown' address set following flow
+           is added.
+         </p>
+@@ -420,6 +422,22 @@
+         </ul>
+       </li>
+ 
++      <li>
++        <p>
++          For each such localnet logical port <var>p</var> following flow
++          is added.
++        </p>
++
++        <ul>
++          <li>
++            Priority 100 flow with the match
++            <code>inport == <var>p</var></code> and action
++            <code>flags.localnet = 1;</code>
++            <code>reg0[11] = lookup_fdb(inport, eth.src); next;</code>
++          </li>
++        </ul>
++      </li>
++
+       <li>
+         One priority-0 fallback flow that matches all packets and advances to
+         the next table.
+@@ -429,17 +447,20 @@
+     <h3>Ingress Table 3: Learn MAC of 'unknown' ports.</h3>
+ 
+     <p>
+-      This table learns the MAC addresses seen on the logical ports
+-      whose port security is disabled and 'unknown' address set
++      This table learns the MAC addresses seen on the VIF logical ports
++      whose port security is disabled and 'unknown' address set as well
++      as on localnet ports with localnet_learn_fdb option set
+       if the <code>lookup_fdb</code> action returned false in the
+-      previous table.
++      previous table. For localnet ports (with flags.localnet = 1),
++      lookup_fdb returns true if (port, mac) is found or if a mac
++      is found for a port of type vif.
+     </p>
+ 
+     <ul>
+       <li>
+         <p>
+-          For each such logical port <var>p</var> whose port security
+-          is disabled and 'unknown' address set following flow
++          For each such VIF logical port <var>p</var> whose port security is
++          disabled and 'unknown' address set and localnet port following flow
+           is added.
+         </p>
+ 
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 68fc8836e..b2c5552f6 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -47,7 +47,6 @@
+ 
+ VLOG_DEFINE_THIS_MODULE(ovn_northd);
+ 
+-static unixctl_cb_func ovn_northd_exit;
+ static unixctl_cb_func ovn_northd_pause;
+ static unixctl_cb_func ovn_northd_resume;
+ static unixctl_cb_func ovn_northd_is_paused;
+@@ -752,7 +751,7 @@ main(int argc, char *argv[])
+     int res = EXIT_SUCCESS;
+     struct unixctl_server *unixctl;
+     int retval;
+-    bool exiting;
++    struct ovn_exit_args exit_args = {};
+     int n_threads = 1;
+     struct northd_state state = {
+         .had_lock = false,
+@@ -774,7 +773,8 @@ main(int argc, char *argv[])
+     if (retval) {
+         exit(EXIT_FAILURE);
+     }
+-    unixctl_command_register("exit", "", 0, 0, ovn_northd_exit, &exiting);
++    unixctl_command_register("exit", "", 0, 0, ovn_exit_command_callback,
++                             &exit_args);
+     unixctl_command_register("pause", "", 0, 0, ovn_northd_pause, &state);
+     unixctl_command_register("resume", "", 0, 0, ovn_northd_resume, &state);
+     unixctl_command_register("is-paused", "", 0, 0, ovn_northd_is_paused,
+@@ -868,6 +868,8 @@ main(int argc, char *argv[])
+     stopwatch_create(LFLOWS_IGMP_STOPWATCH_NAME, SW_MS);
+     stopwatch_create(LFLOWS_DP_GROUPS_STOPWATCH_NAME, SW_MS);
+     stopwatch_create(LFLOWS_TO_SB_STOPWATCH_NAME, SW_MS);
++    stopwatch_create(PORT_GROUP_RUN_STOPWATCH_NAME, SW_MS);
++    stopwatch_create(SYNC_METERS_RUN_STOPWATCH_NAME, SW_MS);
+ 
+     /* Initialize incremental processing engine for ovn-northd */
+     inc_proc_northd_init(&ovnnb_idl_loop, &ovnsb_idl_loop);
+@@ -878,11 +880,9 @@ main(int argc, char *argv[])
+     run_update_worker_pool(n_threads);
+ 
+     /* Main loop. */
+-    exiting = false;
+-
+     struct northd_engine_context eng_ctx = {};
+ 
+-    while (!exiting) {
++    while (!exit_args.exiting) {
+         update_ssl_config();
+         memory_run();
+         if (memory_should_report()) {
+@@ -1024,7 +1024,7 @@ main(int argc, char *argv[])
+         unixctl_server_run(unixctl);
+         unixctl_server_wait(unixctl);
+         memory_wait();
+-        if (exiting) {
++        if (exit_args.exiting) {
+             poll_immediate_wake();
+         }
+ 
+@@ -1057,15 +1057,16 @@ main(int argc, char *argv[])
+         stopwatch_stop(NORTHD_LOOP_STOPWATCH_NAME, time_msec());
+         poll_block();
+         if (should_service_stop()) {
+-            exiting = true;
++            exit_args.exiting = true;
+         }
+         stopwatch_start(NORTHD_LOOP_STOPWATCH_NAME, time_msec());
+     }
+     inc_proc_northd_cleanup();
+ 
+-    unixctl_server_destroy(unixctl);
+     ovsdb_idl_loop_destroy(&ovnnb_idl_loop);
+     ovsdb_idl_loop_destroy(&ovnsb_idl_loop);
++    ovn_exit_args_finish(&exit_args);
++    unixctl_server_destroy(unixctl);
+     service_stop();
+     run_update_worker_pool(0);
+     ovsrcu_exit();
+@@ -1073,16 +1074,6 @@ main(int argc, char *argv[])
+     exit(res);
+ }
+ 
+-static void
+-ovn_northd_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
+-                const char *argv[] OVS_UNUSED, void *exiting_)
+-{
+-    bool *exiting = exiting_;
+-    *exiting = true;
+-
+-    unixctl_command_reply(conn, NULL);
+-}
+-
+ static void
+ ovn_northd_pause(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                 const char *argv[] OVS_UNUSED, void *state_)
+diff --git a/ovn-nb.xml b/ovn-nb.xml
+index 9131305ea..f84ad90c4 100644
+--- a/ovn-nb.xml
++++ b/ovn-nb.xml
+@@ -1101,7 +1101,7 @@
+ 
+         <column name="options" key="ethtype">
+           Optional. VLAN EtherType field value for encapsulating VLAN
+-          headers. Supported values: 802.11q (default), 802.11ad.
++          headers. Supported values: 802.1q (default), 802.1ad.
+         </column>
+ 
+         <column name="options" key="localnet_learn_fdb"
+diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
+index 7b20aa21b..a512e00e5 100644
+--- a/ovn-sb.ovsschema
++++ b/ovn-sb.ovsschema
+@@ -1,7 +1,7 @@
+ {
+     "name": "OVN_Southbound",
+-    "version": "20.27.4",
+-    "cksum": "3194181501 30531",
++    "version": "20.29.0",
++    "cksum": "3967183656 30959",
+     "tables": {
+         "SB_Global": {
+             "columns": {
+@@ -534,6 +534,14 @@
+                     {"type": {"key": {"type": "uuid",
+                                       "refTable": "Logical_DP_Group"},
+                               "min": 0, "max": 1}},
++                "ls_datapath_group":
++                    {"type": {"key": {"type": "uuid",
++                                      "refTable": "Logical_DP_Group"},
++                              "min": 0, "max": 1}},
++                "lr_datapath_group":
++                    {"type": {"key": {"type": "uuid",
++                                      "refTable": "Logical_DP_Group"},
++                              "min": 0, "max": 1}},
+                 "options": {
+                      "type": {"key": "string",
+                               "value": "string",
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index 46aedf973..519b9da8f 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -1721,9 +1721,12 @@
+ 
+           <p>
+             Looks up <var>A</var> in fdb table. If an entry is found
+-            and the the logical port key is <var>P</var>, <code>P</code>,
++            and the logical port key is <var>P</var>, <code>P</code>,
+             stores <code>1</code> in the 1-bit subfield
+-            <var>R</var>, else 0.
++            <var>R</var>, else 0. If <code>flags.localnet</code> is set
++            then <code>1</code> is stored if an entry is found and the
++            logical port key is <var>P</var> or if an entry is found and
++            the entry port type is VIF.
+           </p>
+ 
+           <p>
+@@ -4888,10 +4891,22 @@ tcp.flags = RST;
+     </column>
+ 
+     <column name="datapath_group">
++      Deprecated. The group of datapaths to which this load balancer applies
++      to. This means that the same load balancer applies to all datapaths in
++      a group.
++    </column>
++
++    <column name="ls_datapath_group">
+       The group of datapaths to which this load balancer applies to.  This
+       means that the same load balancer applies to all datapaths in a group.
+     </column>
+ 
++    <column name="lr_datapath_group">
++      The group of logical router datapaths to which this load balancer
++      applies to. This means that the same load balancer applies to all
++      datapaths in a group.
++    </column>
++
+     <group title="Load_Balancer options">
+     <column name="options" key="hairpin_snat_ip">
+       IP to be used as source IP for packets that have been hair-pinned after
+diff --git a/tests/automake.mk b/tests/automake.mk
+index eea0d00f4..f6f0f0e33 100644
+--- a/tests/automake.mk
++++ b/tests/automake.mk
+@@ -310,7 +310,8 @@ CHECK_PYFILES = \
+ 	tests/test-l7.py \
+ 	tests/uuidfilt.py \
+ 	tests/test-tcp-rst.py \
+-	tests/check_acl_log.py
++	tests/check_acl_log.py \
++	tests/scapy-server.py
+ 
+ EXTRA_DIST += $(CHECK_PYFILES)
+ PYCOV_CLEAN_FILES += $(CHECK_PYFILES:.py=.py,cover) .coverage
+diff --git a/tests/checkpatch.at b/tests/checkpatch.at
+index 26f319705..e7322fff4 100755
+--- a/tests/checkpatch.at
++++ b/tests/checkpatch.at
+@@ -8,7 +8,14 @@ OVS_START_SHELL_HELPERS
+ try_checkpatch() {
+     # Take the patch to test from $1.  Remove an initial four-space indent
+     # from it and, if it is just headers with no body, add a null body.
++    # If it does not have a 'Subject', add a valid one.
+     echo "$1" | sed 's/^    //' > test.patch
++    if grep 'Subject\:' test.patch >/dev/null 2>&1; then :
++    else
++        sed -i'' -e '1i\
++Subject: Patch this is.
++' test.patch
++    fi
+     if grep '---' expout >/dev/null 2>&1; then :
+     else
+         printf '\n---\n' >> test.patch
+@@ -236,6 +243,22 @@ done
+ AT_CLEANUP
+ 
+ 
++AT_SETUP([checkpatch - catastrophic backtracking])
++dnl Special case this rather than using the above construct because sometimes a
++dnl warning needs to be generated for line lengths (f.e. when the 'while'
++dnl keyword is used).
++try_checkpatch \
++   "COMMON_PATCH_HEADER
++    +     if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn)
++    " \
++    "ERROR: Inappropriate bracing around statement
++    #8 FILE: A.c:1:
++         if (!b_ctx_in->chassis_rec || !b_ctx_in->br_int || !b_ctx_in->ovs_idl_txn)
++"
++
++AT_CLEANUP
++
++
+ AT_SETUP([checkpatch - parenthesized constructs - for])
+ try_checkpatch \
+    "COMMON_PATCH_HEADER
+@@ -560,3 +583,25 @@ try_checkpatch \
+ "
+ 
+ AT_CLEANUP
++
++AT_SETUP([checkpatch - subject])
++try_checkpatch \
++   "Author: A
++    Commit: A
++    Subject: netdev: invalid case and dot ending
++
++    Signed-off-by: A" \
++    "WARNING: The subject summary should start with a capital.
++    WARNING: The subject summary should end with a dot.
++    Subject: netdev: invalid case and dot ending"
++
++try_checkpatch \
++   "Author: A
++    Commit: A
++    Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING!
++
++    Signed-off-by: A" \
++    "WARNING: The subject, '<area>: <summary>', is over 70 characters, i.e., 85.
++    Subject: netdev: This is a way to long commit summary and therefor it should report a WARNING!"
++
++AT_CLEANUP
+diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
+index 73971b3f4..50f31b22f 100644
+--- a/tests/ovn-controller-vtep.at
++++ b/tests/ovn-controller-vtep.at
+@@ -653,9 +653,6 @@ AT_CHECK([grep -c $northd_version vtep1/ovn-controller-vtep.log], [0], [1
+ as northd
+ OVS_APP_EXIT_AND_WAIT([ovn-northd])
+ 
+-as northd-backup
+-OVS_APP_EXIT_AND_WAIT([ovn-northd])
+-
+ check ovn-sbctl set SB_Global . options:northd_internal_version=foo
+ check ovn-sbctl set Chassis vtep1 vtep_logical_switches=foo
+ 
+diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
+index 4212d601a..6f496d142 100644
+--- a/tests/ovn-controller.at
++++ b/tests/ovn-controller.at
+@@ -60,7 +60,6 @@ check_bridge_mappings () {
+ # the RBAC rules programmed into the SB-DB. The test instruments the SB-DB
+ # directly and we need to stop northd to avoid overwriting the instrumentation.
+ kill `cat northd/ovn-northd.pid`
+-kill `cat northd-backup/ovn-northd.pid`
+ kill `cat ovn-nb/ovsdb-server.pid`
+ 
+ # Initially there should be no patch ports.
+@@ -526,6 +525,10 @@ OVS_WAIT_UNTIL([
+     test "$(ovn-sbctl get chassis hv1 other_config:port-up-notif)" = '"true"'
+ ])
+ 
++OVS_WAIT_UNTIL([
++    test "$(ovn-sbctl get chassis hv1 other_config:ls-dpg-column)" = '"true"'
++])
++
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
+@@ -575,10 +578,8 @@ localport lport : [[lsp1]]
+ 
+ # pause ovn-northd
+ check as northd ovn-appctl -t ovn-northd pause
+-check as northd-backup ovn-appctl -t ovn-northd pause
+ 
+ as northd ovn-appctl -t ovn-northd status
+-as northd-backup ovn-appctl -t ovn-northd status
+ 
+ pb_types=(patch chassisredirect l3gateway localnet localport l2gateway
+           virtual external remote vtep)
+@@ -2093,7 +2094,6 @@ AT_CHECK([ovs-ofctl dump-flows br-int table=46 | grep -c "priority=1100"], [0],
+ 
+ # pause ovn-northd
+ check as northd ovn-appctl -t ovn-northd pause
+-check as northd-backup ovn-appctl -t ovn-northd pause
+ 
+ # Simulate a SB address set "del and add" notification to ovn-controller in the
+ # same IDL iteration. The flows programmed by ovn-controller should reflect the
+@@ -2118,7 +2118,7 @@ AT_CLEANUP
+ 
+ AT_SETUP([ovn-controller - I-P handle lb_hairpin_use_ct_mark change])
+ 
+-ovn_start --backup-northd=none
++ovn_start
+ 
+ net_add n1
+ sim_add hv1
+@@ -2202,6 +2202,10 @@ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+ # The old OVS flows should remain (this is regardless of the configuration)
+ AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore])
+ 
++# We should have 2 flows with groups.
++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
++])
++
+ # Make a change to the ls1-lp1's IP
+ check ovn-nbctl --wait=sb lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.1.2.4"
+ 
+@@ -2214,10 +2218,18 @@ sleep 2
+ lflow_run_1=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
+ AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore])
+ 
++# We should have 2 flows with groups.
++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
++])
++
+ sleep 5
+ 
+ # Check after the wait
+ OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4])
++
++# We should have 2 flows with groups.
++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [2
++])
+ lflow_run_2=$(ovn-appctl -t ovn-controller coverage/read-counter lflow_run)
+ 
+ # Verify that the flow compute completed during the wait (after the wait it
+@@ -2230,7 +2242,7 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd])
+ start_daemon ovs-vswitchd --enable-dummy=system -vvconn -vofproto_dpif -vunixctl
+ OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4])
+ 
+-check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \
++check ovn-nbctl --wait=hv lb-add lb3 3.3.3.3 10.1.2.5 \
+ -- ls-lb-add ls1 lb3
+ 
+ # There should be 3 group IDs allocated (this is to ensure the group ID
+@@ -2238,6 +2250,10 @@ check ovn-nbctl --wait=hv lb-add lb3 2.2.2.2 10.1.2.5 \
+ AT_CHECK([ovn-appctl -t ovn-controller group-table-list | awk '{print $2}' | sort | uniq | wc -l], [0], [3
+ ])
+ 
++# We should have 3 flows with groups.
++AT_CHECK([ovs-ofctl dump-flows br-int | grep group -c], [0], [3
++])
++
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ 
+diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
+index d265bb90b..d4c436f84 100644
+--- a/tests/ovn-ic.at
++++ b/tests/ovn-ic.at
+@@ -92,6 +92,7 @@ check ovn-nbctl lr-add lr1
+ check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:01 10.0.0.1/24
+ check ovn-nbctl lrp-set-gateway-chassis lrp1 gw-az1
+ 
++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ check ovn-nbctl lsp-add ts1 lsp1 -- \
+     lsp-set-addresses lsp1 router -- \
+     lsp-set-type lsp1 router -- \
+@@ -156,6 +157,7 @@ create_ic_infra() {
+     ovn_as $az
+ 
+     check ovn-ic-nbctl ts-add $ts
++    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+     check ovn-nbctl lr-add $lr
+     check ovn-nbctl lrp-add $lr $lrp 00:00:00:00:00:0$az_id 10.0.$az_id.1/24
+     check ovn-nbctl lrp-set-gateway-chassis $lrp gw-$az
+@@ -227,6 +229,7 @@ for i in 1 2; do
+     check ovn-nbctl lrp-add lr1 lrp$i 00:00:00:00:0$i:01 10.0.$i.1/24
+     check ovn-nbctl lrp-set-gateway-chassis lrp$i gw-az$i
+ 
++    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+     check ovn-nbctl lsp-add ts1 lsp$i -- \
+         lsp-set-addresses lsp$i router -- \
+         lsp-set-type lsp$i router -- \
+@@ -290,7 +293,7 @@ ovs-vsctl set open . external-ids:ovn-is-interconn=true
+ OVS_WAIT_UNTIL([ovn_as az2 ovn-sbctl show | grep gw1])
+ 
+ OVN_CLEANUP_SBOX(gw1)
+-AT_CHECK([ovn_as az2 ovn-sbctl show], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl show], [0], [dnl
+ ])
+ 
+ # Test encap change
+@@ -335,16 +338,17 @@ ovn-nbctl lsp-add ts1 lsp-ts1-lr1
+ ovn-nbctl lsp-set-addresses lsp-ts1-lr1 router
+ ovn-nbctl lsp-set-type lsp-ts1-lr1 router
+ ovn-nbctl --wait=hv lsp-set-options lsp-ts1-lr1 router-port=lrp-lr1-ts1
+-
++OVS_WAIT_UNTIL([ovn_as az2 ovn-nbctl show | grep lsp-ts1-lr1])
+ ovn_as az2 ovn-nbctl lsp-set-options lsp-ts1-lr1 requested-chassis=gw1
+-AT_CHECK([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl
++
++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl show | uuidfilt], [0], [dnl
+ switch <0> (ts1)
+     port lsp-ts1-lr1
+         type: remote
+         addresses: [["aa:aa:aa:aa:aa:01 169.254.100.1/24"]]
+ ])
+ 
+-AT_CHECK([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-sbctl -f csv -d bare --no-headings --columns logical_port,type list port_binding], [0], [dnl
+ lsp-ts1-lr1,remote
+ ])
+ 
+@@ -529,6 +533,7 @@ for i in 1 2; do
+     for j in 1 2; do
+         ts=ts$j$j
+         ovn-ic-nbctl --may-exist ts-add $ts
++        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+ 
+         # Create LRP and connect to TS
+         lr=lr$j$i
+@@ -552,6 +557,9 @@ ovn_as az1 ovn-nbctl show
+ echo az2
+ ovn_as az2 ovn-nbctl show
+ 
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 192.168])
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep learned | grep 10.10.10])
++
+ # Test routes from lr12 were learned to lr11
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 |
+              grep learned | awk '{print $1, $2}' | sort], [0], [dnl
+@@ -584,6 +592,7 @@ for i in 1 2; do
+     # Create LRP and connect to TS
+     ovn-nbctl lr-add lr$i
+     ovn-nbctl lrp-add lr$i lrp-lr$i-ts1 aa:aa:aa:aa:aa:0$i 169.254.100.$i/24
++    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+     ovn-nbctl lsp-add ts1 lsp-ts1-lr$i \
+             -- lsp-set-addresses lsp-ts1-lr$i router \
+             -- lsp-set-type lsp-ts1-lr$i router \
+@@ -909,6 +918,7 @@ for i in 1 2; do
+     for j in 1 2 3; do
+         ts=ts1$j
+         ovn-ic-nbctl --may-exist ts-add $ts
++        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+ 
+         lrp=lrp-$lr-$ts
+         lsp=lsp-$ts-$lr
+@@ -934,6 +944,7 @@ for i in 1 2; do
+     for j in 1 2; do
+         ts=ts2$j
+         ovn-ic-nbctl --may-exist ts-add $ts
++        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+ 
+         lrp=lrp-$lr-$ts
+         lsp=lsp-$ts-$lr
+@@ -957,7 +968,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 10.10.10.0/24 192.168.
+ ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "192.168.0.1/24"
+ 
+ # Test direct routes from lr12 were learned to lr11
+-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+              grep learned | awk '{print $1, $2, $5}' | sort ], [0], [dnl
+ 192.168.0.0/24 169.254.101.2 ecmp
+ 192.168.0.0/24 169.254.102.2 ecmp
+@@ -965,24 +976,24 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+ ])
+ 
+ # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11
+-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
+ IPv4 Routes
+ Route Table rtb1:
+             10.10.10.0/24             169.254.101.2 dst-ip (learned)
+ ])
+-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
+ IPv4 Routes
+ Route Table rtb2:
+             10.10.10.0/24             169.254.102.2 dst-ip (learned)
+ ])
+-AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
+ IPv4 Routes
+ Route Table rtb3:
+             10.10.10.0/24             169.254.103.2 dst-ip (learned)
+ ])
+ 
+ # Test routes from lr12 didn't leak as learned to lr21
+-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 192.168 | sort], [0], [dnl
+            192.168.0.0/24             169.254.101.2 dst-ip (learned) ecmp
+            192.168.0.0/24             169.254.102.2 dst-ip (learned) ecmp
+ ])
+@@ -1035,6 +1046,7 @@ for i in 1 2; do
+     for j in 1 2 3; do
+         ts=ts1$j
+         ovn-ic-nbctl --may-exist ts-add $ts
++        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+ 
+         lrp=lrp-$lr-$ts
+         lsp=lsp-$ts-$lr
+@@ -1060,6 +1072,7 @@ for i in 1 2; do
+     for j in 1 2; do
+         ts=ts2$j
+         ovn-ic-nbctl --may-exist ts-add $ts
++        OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep $ts])
+ 
+         lrp=lrp-$lr-$ts
+         lsp=lsp-$ts-$lr
+@@ -1083,6 +1096,7 @@ ovn_as az2 ovn-nbctl --route-table=rtb3 lr-route-add lr12 2001:db8:aaaa::/64 200
+ ovn_as az2 ovn-nbctl --wait=sb lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bb:01 "2001:db8:200::1/64"
+ 
+ # Test direct routes from lr12 were learned to lr11
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:3::2])
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 |
+              grep learned | awk '{print $1, $2, $5}' | sort], [0], [dnl
+ 2001:db8:200::/64 2001:db8:1::2 ecmp
+@@ -1091,16 +1105,19 @@ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 2001:db8:200 |
+ ])
+ 
+ # Test static routes from lr12 rtbs rtb1,rtb2,rtb3 were learned to lr11
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11 | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb1 lr-route-list lr11], [0], [dnl
+ IPv6 Routes
+ Route Table rtb1:
+        2001:db8:aaaa::/64             2001:db8:1::2 dst-ip (learned)
+ ])
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11 | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb2 lr-route-list lr11], [0], [dnl
+ IPv6 Routes
+ Route Table rtb2:
+        2001:db8:aaaa::/64             2001:db8:2::2 dst-ip (learned)
+ ])
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11 | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl --route-table=rtb3 lr-route-list lr11], [0], [dnl
+ IPv6 Routes
+ Route Table rtb3:
+@@ -1108,6 +1125,7 @@ Route Table rtb3:
+ ])
+ 
+ # Test routes from lr12 didn't leak as learned to lr21
++OVS_WAIT_UNTIL([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep "2001:db8:2::2" | grep learned])
+ AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr21 | grep 2001 | sort], [0], [dnl
+         2001:db8:200::/64             2001:db8:1::2 dst-ip (learned) ecmp
+         2001:db8:200::/64             2001:db8:2::2 dst-ip (learned) ecmp
+@@ -1127,6 +1145,7 @@ ovn-ic-nbctl ts-add ts1
+ for i in 1 2; do
+     ovn_start az$i
+     ovn_as az$i
++    OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ 
+     # Enable route learning at AZ level
+     ovn-nbctl set nb_global . options:ic-route-learn=true
+@@ -1152,13 +1171,13 @@ for i in 1 2; do
+     check ovn-nbctl --wait=sb lr-route-add $lr 0.0.0.0/0 192.168.$i.11
+ done
+ 
+-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep dst-ip | sort] , [0], [dnl
+                 0.0.0.0/0              192.168.1.11 dst-ip
+               10.0.0.0/24              192.168.1.10 dst-ip
+            192.168.2.0/24             169.254.100.2 dst-ip (learned)
+ ])
+ 
+-AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([ovn_as az2 ovn-nbctl lr-route-list lr12 | grep dst-ip | sort], [0], [dnl
+                 0.0.0.0/0              192.168.2.11 dst-ip
+               10.0.0.0/24              192.168.2.10 dst-ip
+            192.168.1.0/24             169.254.100.1 dst-ip (learned)
+@@ -1191,10 +1210,10 @@ done
+ # each LR has one connected subnet except TS port
+ 
+ 
+-# create lr11, lr21, lr22, ts1 and connect them
+-ovn-ic-nbctl ts-add ts1
++# create lr11, lr21, lr22 and connect them
+ 
+ ovn_as az1
++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ 
+ lr=lr11
+ ovn-nbctl lr-add $lr
+@@ -1209,6 +1228,7 @@ ovn-nbctl lsp-add ts1 $lsp \
+         -- lsp-set-options $lsp router-port=$lrp
+ 
+ ovn_as az2
++OVS_WAIT_UNTIL([ovn-nbctl show | grep switch | grep ts1])
+ for i in 1 2; do
+     lr=lr2$i
+     ovn-nbctl lr-add $lr
+@@ -1230,7 +1250,7 @@ ovn_as az2 ovn-nbctl lrp-add lr21 lrp-lr21 aa:aa:aa:aa:bc:01 "192.168.1.1/24"
+ ovn_as az2 ovn-nbctl lrp-add lr22 lrp-lr22 aa:aa:aa:aa:bc:02 "192.168.2.1/24"
+ 
+ # Test direct routes from lr21 and lr22 were learned to lr11
+-AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
++OVS_WAIT_FOR_OUTPUT([ovn_as az1 ovn-nbctl lr-route-list lr11 | grep 192.168 |
+              grep learned | awk '{print $1, $2}' | sort ], [0], [dnl
+ 192.168.1.0/24 169.254.10.21
+ 192.168.2.0/24 169.254.10.22
+diff --git a/tests/ovn-ipsec.at b/tests/ovn-ipsec.at
+index 10ef97878..f8df8d60e 100644
+--- a/tests/ovn-ipsec.at
++++ b/tests/ovn-ipsec.at
+@@ -48,11 +48,11 @@ ovn-nbctl set nb_global . options:ipsec_encapsulation=true
+ 
+ check ovn-nbctl --wait=hv sync
+ 
+-AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.1])
++OVS_WAIT_UNTIL([test x`as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_ip | tr -d '"\n'` = x192.168.0.1])
+ AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.2])
+ AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:remote_name | tr -d '\n'], [0], [hv1])
+ AT_CHECK([as hv2 ovs-vsctl get Interface ovn-hv1-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes])
+-AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'], [0], [192.168.0.2])
++OVS_WAIT_UNTIL([test x`as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_ip | tr -d '"\n'` = x192.168.0.2])
+ AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:local_ip | tr -d '"\n'], [0], [192.168.0.1])
+ AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:remote_name | tr -d '\n'], [0], [hv2])
+ AT_CHECK([as hv1 ovs-vsctl get Interface ovn-hv2-0 options:ipsec_encapsulation | tr -d '\n'], [0], [yes])
+diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
+index 13d5dc3d4..bb93fa5f0 100644
+--- a/tests/ovn-macros.at
++++ b/tests/ovn-macros.at
+@@ -188,16 +188,16 @@ ovn_start_northd() {
+ # ovn-sbctl and ovn-nbctl use them by default, and starts ovn-northd running
+ # against them.
+ #
+-# Normally this starts an active northd and a backup northd.  The following
++# Normally this starts only an active northd and no backup northd.  The following
+ # options are accepted to adjust that:
+-#   --backup-northd=none    Don't start a backup northd.
++#   --backup-northd         Start a backup northd.
+ #   --backup-northd=paused  Start the backup northd in the paused state.
+ ovn_start () {
+-    local backup_northd=:
++    local backup_northd=false
+     local backup_northd_options=
+     case $1 in
+-        --backup-northd=none) backup_northd=false; shift ;;
+-        --backup-northd=paused) backup_northd_options=--paused; shift ;;
++        --backup-northd) backup_northd=true; shift ;;
++        --backup-northd=paused) backup_northd=true; backup_northd_options=--paused; shift ;;
+     esac
+     local AZ=$1
+     local msg_prefix=${AZ:+$AZ: }
+@@ -288,11 +288,44 @@ net_attach () {
+         || return 1
+ }
+ 
++ovn_wait_for_encaps() {
++    local systemid=$1
++
++    if [[ -f "${OVN_SYSCONFDIR}/system-id-override" ]]; then
++        systemid=$(cat ${OVN_SYSCONFDIR}/system-id-override)
++    fi
++
++    local encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type-$systemid)
++    if [[ -z "$encap" ]]; then
++        encap=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-type)
++    fi
++    encap=$(tr -d '"' <<< $encap)
++
++    local ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip-$systemid)
++    if [[ -z "$ip" ]]; then
++        ip=$(ovs-vsctl get Open_vSwitch . external_ids:ovn-encap-ip)
++    fi
++    ip=$(tr -d '"' <<< $ip)
++
++    IFS="," read -r -a encap_types <<< "$encap"
++    for e in "${encap_types[[@]]}"; do
++        wait_column "$ip" sb:Encap ip chassis_name="$systemid" type="$e"
++    done
++}
++
+ # ovn_az_attach AZ NETWORK BRIDGE IP [MASKLEN] [ENCAP]
+ ovn_az_attach() {
+-    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan} systemid=${7-$sandbox} cli_args=${@:8}
++    local az=$1 net=$2 bridge=$3 ip=$4 masklen=${5-24} encap=${6-geneve,vxlan}
++    local systemid=${7-$sandbox} systemid_override=$8
+     net_attach $net $bridge || return 1
+ 
++    local expected_encap_id=$systemid
++    local cli_args=""
++    if [[ -n "$systemid_override" ]]; then
++        cli_args="-n $systemid_override"
++        expected_encap_id=$systemid_override
++    fi
++
+     mac=`ovs-vsctl get Interface $bridge mac_in_use | sed s/\"//g`
+     arp_table="$arp_table $sandbox,$bridge,$ip,$mac"
+     if test -z $(echo $ip | sed '/:/d'); then
+@@ -332,6 +365,11 @@ ovn_az_attach() {
+     fi
+ 
+     start_daemon ovn-controller --enable-dummy-vif-plug ${cli_args} || return 1
++    if test X"$az" = XNONE; then
++        ovn_wait_for_encaps $expected_encap_id
++    else
++        ovn_as $az ovn_wait_for_encaps $expected_encap_id
++    fi
+ }
+ 
+ # ovn_attach NETWORK BRIDGE IP [MASKLEN] [ENCAP]
+@@ -372,6 +410,7 @@ start_virtual_controller() {
+         || return 1
+ 
+     ovn-controller --enable-dummy-vif-plug ${cli_args} -vconsole:off --detach --no-chdir
++    ovn_wait_for_encaps $systemid
+ }
+ 
+ # ovn_setenv AZ
+@@ -833,15 +872,40 @@ ovn_trace_client() {
+ # ovs-appctl netdev-dummy/receive $vif $packet
+ #
+ fmt_pkt() {
+-    echo "from scapy.all import *; \
+-          import binascii; \
+-          out = binascii.hexlify(raw($1)); \
+-          print(out.decode())" | $PYTHON3
++    ctlfile=$ovs_base/scapy.ctl
++    if [[ ! -S $ctlfile ]]; then
++        start_scapy_server
++    fi
++    while [[ ! -S $ctlfile ]]; do sleep 0.1; done
++    ovs-appctl -t $ctlfile payload "$1"
++}
++
++start_scapy_server() {
++    pidfile=$ovs_base/scapy.pid
++    ctlfile=$ovs_base/scapy.ctl
++    logfile=$ovs_base/scapy.log
++    lockfile=$ovs_base/scapy.lock
++
++    flock -n $lockfile "$top_srcdir"/tests/scapy-server.py \
++        --pidfile=$pidfile --unixctl=$ctlfile --log-file=$logfile --detach \
++    && on_exit "test -e \"$pidfile\" && ovs-appctl -t $ctlfile exit"
++}
++
++sleep_northd() {
++  echo Northd going to sleep
++  AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)])
++  on_exit "kill -CONT $(cat northd/ovn-northd.pid)"
++}
++
++wake_up_northd() {
++  echo Northd waking up
++  AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)])
+ }
+ 
+ sleep_sb() {
+   echo SB going to sleep
+   AT_CHECK([kill -STOP $(cat ovn-sb/ovsdb-server.pid)])
++  on_exit "kill -CONT $(cat ovn-sb/ovsdb-server.pid)"
+ }
+ wake_up_sb() {
+   echo SB waking up
+@@ -865,6 +929,7 @@ sleep_ovs() {
+   hv=$1
+   echo ovs $hv going to sleep
+   AT_CHECK([kill -STOP $(cat $hv/ovs-vswitchd.pid)])
++  on_exit "kill -CONT $(cat $hv/ovs-vswitchd.pid)"
+ }
+ 
+ wake_up_ovs() {
+@@ -876,12 +941,17 @@ wake_up_ovs() {
+ sleep_ovsdb() {
+   echo OVSDB $1 going to sleep
+   AT_CHECK([kill -STOP $(cat $1/ovsdb-server.pid)])
++  on_exit "kill -CONT $(cat $1/ovsdb-server.pid)"
+ }
+ wake_up_ovsdb() {
+   echo OVSDB $1 waking up
+   AT_CHECK([kill -CONT $(cat $1/ovsdb-server.pid)])
+ }
+ 
++trim_zeros() {
++    sed 's/\(00\)\{1,\}$//'
++}
++
+ OVS_END_SHELL_HELPERS
+ 
+ m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
+diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
+index fde3a28ee..94641f2f0 100644
+--- a/tests/ovn-nbctl.at
++++ b/tests/ovn-nbctl.at
+@@ -2695,7 +2695,9 @@ dnl ---------------------------------------------------------------------
+ 
+ AT_SETUP([ovn-nbctl - daemon retry connection])
+ OVN_NBCTL_TEST_START daemon
+-AT_CHECK([kill `cat ovsdb-server.pid`])
++pid=$(cat ovsdb-server.pid)
++AT_CHECK([kill $pid])
++OVS_WAIT_WHILE([kill -0 $pid 2>/dev/null])
+ AT_CHECK([ovsdb-server --detach --no-chdir --pidfile --log-file --remote=punix:$OVS_RUNDIR/ovnnb_db.sock ovn-nb.db], [0], [], [stderr])
+ AT_CHECK([ovn-nbctl show], [0], [ignore])
+ OVN_NBCTL_TEST_STOP "/terminating with signal 15/d"
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index 65d3f4b03..c32122025 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -20,6 +20,14 @@ m4_define([_DUMP_DB_TABLES], [
+ # sure nothing is changed by the recompute. It is used for ensuring the
+ # correctness of incremental processing.
+ m4_define([CHECK_NO_CHANGE_AFTER_RECOMPUTE], [
++    wait_port_up=$1
++    if test X$wait_port_up = X1; then
++        # Wait for hv to have received all previous commands
++        # Make sure ports are up as otherwise it might come as a difference after recompute
++        echo "waiting for ports up"
++        check ovn-nbctl --wait=hv sync
++        wait_for_ports_up
++    fi
+     _DUMP_DB_TABLES(before)
+     check as northd ovn-appctl -t NORTHD_TYPE inc-engine/recompute
+     check ovn-nbctl --wait=sb sync
+@@ -813,7 +821,7 @@ AT_CLEANUP
+ 
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([ovn-northd restart])
+-ovn_start --backup-northd=none
++ovn_start
+ 
+ # Check that ovn-northd is active, by verifying that it creates and
+ # destroys southbound datapaths as one would expect.
+@@ -832,7 +840,7 @@ sleep 5
+ check_row_count Datapath_Binding 1
+ 
+ # Now resume ovn-northd.  Changes should catch up.
+-ovn_start_northd primary
++ovn_start_northd
+ wait_row_count Datapath_Binding 2
+ 
+ AT_CLEANUP
+@@ -841,7 +849,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([northbound database reconnection])
+ 
+-ovn_start --backup-northd=none
++ovn_start
+ 
+ # Check that ovn-northd is active, by verifying that it creates and
+ # destroys southbound datapaths as one would expect.
+@@ -873,7 +881,7 @@ AT_CLEANUP
+ OVN_FOR_EACH_NORTHD_NO_HV([
+ AT_SETUP([southbound database reconnection])
+ 
+-ovn_start --backup-northd=none
++ovn_start
+ 
+ # Check that ovn-northd is active, by verifying that it creates and
+ # destroys southbound datapaths as one would expect.
+@@ -1100,7 +1108,7 @@ AT_CAPTURE_FILE([crflows])
+ AT_CHECK([grep -e "lr_out_snat" drflows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.1);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+ ])
+ 
+ AT_CHECK([grep -e "lr_out_snat" crflows | sed 's/table=../table=??/' | sort], [0], [dnl
+@@ -1130,7 +1138,7 @@ AT_CAPTURE_FILE([crflows2])
+ AT_CHECK([grep -e "lr_out_snat" drflows2 | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.1);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.1);)
+   table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
+ ])
+ 
+@@ -1159,7 +1167,7 @@ AT_CAPTURE_FILE([crflows2])
+ AT_CHECK([grep -e "lr_out_snat" drflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range), action=(ct_snat(172.16.1.2);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $allowed_range && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
+ ])
+ 
+ AT_CHECK([grep -e "lr_out_snat" crflows3 | sed 's/table=../table=??/' | sort], [0], [dnl
+@@ -1186,7 +1194,7 @@ AT_CAPTURE_FILE([crflows2])
+ AT_CHECK([grep -e "lr_out_snat" drflows4 | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=??(lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.2);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.2);)
+   table=??(lr_out_snat        ), priority=163  , match=(ip && ip4.src == 50.0.0.11 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && ip4.dst == $disallowed_range), action=(next;)
+ ])
+ 
+@@ -2860,7 +2868,7 @@ lbg0_uuid=$(fetch_column sb:load_balancer _uuid name=lbg0)
+ echo
+ echo "__file__:__line__: Check that SB lb0 has sw0 in datapaths column."
+ 
+-lb0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb0)
++lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
+ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                     | grep -A1 $lb0_dp_group | tail -1], [0], [dnl
+ $sw0_sb_uuid
+@@ -2871,7 +2879,7 @@ check_column "" sb:datapath_binding load_balancers external_ids:name=sw0
+ echo
+ echo "__file__:__line__: Check that SB lbg0 has sw0 in datapaths column."
+ 
+-lbg0_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg0)
++lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
+ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                     | grep -A1 $lbg0_dp_group | tail -1], [0], [dnl
+ $sw0_sb_uuid
+@@ -2897,6 +2905,15 @@ sb:load_balancer vips,protocol name=lbg0
+ 
+ check ovn-nbctl lr-add lr0 -- add logical_router lr0 load_balancer_group $lbg
+ check ovn-nbctl --wait=sb lr-lb-add lr0 lb0
++check_row_count sb:load_balancer 2
++
++lr0_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
++lb0_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb0)
++
++AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
++                    | grep -A1 $lb0_lr_dp_group | tail -1], [0], [dnl
++$lr0_sb_uuid
++])
+ 
+ echo
+ echo "__file__:__line__: Check that SB lb0 has only sw0 in datapaths column."
+@@ -2942,7 +2959,13 @@ check_row_count sb:load_balancer 2
+ lbg1=$(fetch_column nb:load_balancer _uuid name=lbg1)
+ check ovn-nbctl add load_balancer_group $lbg load_balancer $lbg1
+ check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+-check_row_count sb:load_balancer 3
++check_row_count sb:load_balancer 4
++
++lb1_lr_dp_group=$(fetch_column sb:load_balancer lr_datapath_group name=lb1)
++AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
++                    | grep -A1 $lb1_lr_dp_group | tail -1], [0], [dnl
++$lr0_sb_uuid
++])
+ 
+ echo
+ echo "__file__:__line__: Associate lb1 to sw1 and check that lb1 is created in SB DB."
+@@ -2959,7 +2982,7 @@ echo "__file__:__line__: Check that SB lbg1 has vips and protocol columns are se
+ check_column "20.0.0.30:80=20.0.0.50:8080 udp" sb:load_balancer vips,protocol name=lbg1
+ 
+ lb1_uuid=$(fetch_column sb:load_balancer _uuid name=lb1)
+-lb1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lb1)
++lb1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb1)
+ 
+ echo
+ echo "__file__:__line__: Check that SB lb1 has sw1 in datapaths column."
+@@ -2970,7 +2993,7 @@ $sw1_sb_uuid
+ ])
+ 
+ lbg1_uuid=$(fetch_column sb:load_balancer _uuid name=lbg1)
+-lbg1_dp_group=$(fetch_column sb:load_balancer datapath_group name=lbg1)
++lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
+ 
+ echo
+ echo "__file__:__line__: Check that SB lbg1 has sw0 and sw1 in datapaths column."
+@@ -4596,7 +4619,7 @@ AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+ PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+ AT_SKIP_IF([expr "$PKIDIR" : ".*[[ 	'\"
+ \\]]"])
+-ovn_start --backup-northd=none
++ovn_start
+ 
+ as northd
+ OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+@@ -5095,6 +5118,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+@@ -5109,6 +5133,7 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5129,8 +5154,10 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5144,7 +5171,9 @@ AT_CHECK([grep "ls_in_l2_lkup" ls2_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:02:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.2.1), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 20.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.100), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 40.0.0.200), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:201), action=(clone {outport = "ls2-ro2"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5162,9 +5191,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5180,9 +5211,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5204,9 +5237,11 @@ AT_CHECK([grep "ls_in_l2_lkup" ls1_lflows | sed 's/table=../table=??/' | sort],
+   table=??(ls_in_l2_lkup      ), priority=70   , match=(eth.mcast), action=(outport = "_MC_flood"; output;)
+   table=??(ls_in_l2_lkup      ), priority=75   , match=(eth.src == {00:00:00:00:01:01} && (arp.op == 1 || rarp.op == 3 || nd_ns)), action=(outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 10.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.1), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 192.168.1.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.100), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
++  table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && arp.op == 1 && arp.tpa == 30.0.0.200), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+   table=??(ls_in_l2_lkup      ), priority=80   , match=(flags[[1]] == 0 && nd_ns && nd.target == fe80::200:ff:fe00:101), action=(clone {outport = "ls1-ro1"; output; }; outport = "_MC_flood_l2"; output;)
+ ])
+ 
+@@ -5364,12 +5399,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
+ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
+-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
+ ])
+ 
+ # Separate zones for DGP
+@@ -5412,9 +5447,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
+ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
+ ])
+ 
+ # Associate load balancer to lr0
+@@ -5494,12 +5529,12 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
+ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat_in_czone(172.168.0.20);)
+-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=154  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat_in_czone(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=162  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl) && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.168.0.20);)
+ ])
+ 
+ # Separate zones for DGP
+@@ -5560,9 +5595,9 @@ AT_CHECK([grep "lr_out_post_undnat" lr0flows | sed 's/table=./table=?/' | sort],
+ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_snat        ), priority=0    , match=(1), action=(next;)
+   table=? (lr_out_snat        ), priority=120  , match=(nd_ns), action=(next;)
+-  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.10);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.30);)
+-  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_snat(172.168.0.20);)
++  table=? (lr_out_snat        ), priority=153  , match=(ip && ip4.src == 10.0.0.0/24 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.10);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.30);)
++  table=? (lr_out_snat        ), priority=161  , match=(ip && ip4.src == 10.0.0.3 && outport == "lr0-public" && is_chassis_resident("cr-lr0-public") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.168.0.20);)
+ ])
+ 
+ # Make the logical router as Gateway router
+@@ -6019,9 +6054,9 @@ ovn_start
+ check ovn-nbctl ls-add ls -- lb-add lb1 10.0.0.1:80 10.0.0.2:80 -- ls-lb-add ls lb1
+ check ovn-nbctl --wait=sb sync
+ 
+-dps=$(fetch_column Load_Balancer datapath_group)
++dps=$(fetch_column Load_Balancer ls_datapath_group)
+ nlb=$(fetch_column nb:Load_Balancer _uuid)
+-AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore])
++AT_CHECK([ovn-sbctl create Load_Balancer name=lb1 ls_datapath_group="$dps" external_ids="lb_id=$nlb"], [0], [ignore])
+ 
+ check ovn-nbctl --wait=sb sync
+ check_row_count Load_Balancer 1
+@@ -6105,10 +6140,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
+   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
+   table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
+@@ -6136,10 +6171,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
+   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
+   table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ AT_CHECK([grep -E "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
+@@ -6165,10 +6200,10 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
+   table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-public"), action=(reg9[[1]] = check_pkt_larger(1514); next;)
+   table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-public" && (tcp)), action=(next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl
+@@ -6190,14 +6225,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
+   table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1414); next;)
+   table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-public" && (tcp)), action=(next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ AT_CHECK([grep "lr_in_admission.*check_pkt_larger" lr0flows | sort], [0], [dnl
+@@ -6229,14 +6264,14 @@ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=.
+   table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-public" && (tcp)), action=(next;)
+   table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" && (tcp)), action=(next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw0" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:01; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-public" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1500; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ AT_CHECK([grep "lr_in_admission" lr0flows | grep -e "check_pkt_larger" -e "tcp" | sort], [0], [dnl
+@@ -6255,15 +6290,16 @@ check ovn-nbctl --wait=sb clear logical_router_port lr0-public options
+ ovn-sbctl dump-flows lr0 > lr0flows
+ AT_CAPTURE_FILE([lr0flows])
+ 
++grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=../table=??/' | sort
+ AT_CHECK([grep -e "chk_pkt_len" -e "lr_in_larger_pkts" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(lr_in_chk_pkt_len  ), priority=0    , match=(1), action=(next;)
+   table=??(lr_in_chk_pkt_len  ), priority=50   , match=(outport == "lr0-sw0"), action=(reg9[[1]] = check_pkt_larger(1414); next;)
+   table=??(lr_in_chk_pkt_len  ), priority=55   , match=(outport == "lr0-sw0" && (tcp)), action=(next;)
+   table=??(lr_in_larger_pkts  ), priority=0    , match=(1), action=(next;)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 172.168.0.100; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:20ff:fe20:1213; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 20.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+-  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff02; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-public" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:20:20:12:13; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip4 && reg9[[1]] && reg9[[0]] == 0), action=(icmp4_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip4.dst = ip4.src; ip4.src = 10.0.0.1; ip.ttl = 255; icmp4.type = 3; /* Destination Unreachable. */ icmp4.code = 4; /* Frag Needed and DF was Set. */ icmp4.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
++  table=??(lr_in_larger_pkts  ), priority=150  , match=(inport == "lr0-sw1" && outport == "lr0-sw0" && ip6 && reg9[[1]] && reg9[[0]] == 0), action=(icmp6_error {reg9[[0]] = 1; reg9[[1]] = 0; eth.dst = 00:00:00:00:ff:02; ip6.dst = ip6.src; ip6.src = fe80::200:ff:fe00:ff01; ip.ttl = 255; icmp6.type = 2; /* Packet Too Big. */ icmp6.code = 0; icmp6.frag_mtu = 1400; next(pipeline=ingress, table=0); };)
+ ])
+ 
+ check ovn-nbctl --wait=sb clear logical_router_port lr0-sw0 options
+@@ -7361,9 +7397,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/'
+ ])
+ 
+ AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);)
+ ])
+ 
+ check ovn-nbctl --wait=sb lr-nat-del DR snat 20.0.0.10
+@@ -7437,9 +7473,9 @@ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed 's/table=../table=??/'
+ ])
+ 
+ AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed 's/table=../table=??/' | sort], [0], [dnl
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat(172.16.1.10);)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat(10.0.0.10);)
+-  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat(192.168.0.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1" && is_chassis_resident("cr-DR-S1") && (!ct.trk || !ct.rpl)), action=(ct_snat(172.16.1.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2" && is_chassis_resident("cr-DR-S2") && (!ct.trk || !ct.rpl)), action=(ct_snat(10.0.0.10);)
++  table=??(lr_out_snat        ), priority=161  , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3" && is_chassis_resident("cr-DR-S3") && (!ct.trk || !ct.rpl)), action=(ct_snat(192.168.0.10);)
+ ])
+ 
+ AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed 's/table=../table=??/' | sort], [0], [dnl
+@@ -8618,7 +8654,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort
+ AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true])
+ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' | sort | sed 's/table=./table=?/'], [0], [dnl
+   table=? (ls_in_lookup_fdb   ), priority=0    , match=(1), action=(next;)
+-  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
++  table=? (ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+   table=? (ls_in_put_fdb      ), priority=0    , match=(1), action=(next;)
+   table=? (ls_in_put_fdb      ), priority=100  , match=(inport == "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
+ ])
+@@ -8701,7 +8737,7 @@ AT_CHECK([grep "ls_in_lb " S1flows | sed 's/table=../table=??/' | sort], [0], [d
+ 
+ ovn-sbctl get datapath S0 _uuid > dp_uuids
+ ovn-sbctl get datapath S1 _uuid >> dp_uuids
+-lb_dp_group=$(ovn-sbctl --bare --columns datapath_group find Load_Balancer name=lb0)
++lb_dp_group=$(ovn-sbctl --bare --columns ls_datapath_group find Load_Balancer name=lb0)
+ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                     | grep -A1 $lb_dp_group | tail -1 | tr ' ' '\n' | sort], [0], [dnl
+ $(cat dp_uuids | sort)
+@@ -8985,7 +9021,6 @@ grep -c mutate], [0], [2
+ # Pause ovn-northd and add/remove few addresses.  when it is resumed
+ # it should use mutate for updating the address sets.
+ check as northd ovn-appctl -t NORTHD_TYPE pause
+-check as northd-backup ovn-appctl -t NORTHD_TYPE pause
+ 
+ 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.5
+@@ -10008,14 +10043,21 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.11
+ 
+ check_recompute_counter() {
++    northd_recomp_min=$1
++    northd_recomp_max=$2
++    lflow_recomp_min=$3
++    lflow_recomp_max=$4
++    sync_sb_pb_recomp_min=$5
++    sync_sb_pb_recomp_max=$6
++
+     northd_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats northd recompute)
+-    AT_CHECK([test x$northd_recomp = x$1])
++    AT_CHECK([test $northd_recomp -ge $northd_recomp_min && test $northd_recomp -le $northd_recomp_max])
+ 
+     lflow_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats lflow recompute)
+-    AT_CHECK([test x$lflow_recomp = x$2])
++    AT_CHECK([test $lflow_recomp -ge $lflow_recomp_min && test $lflow_recomp -le $lflow_recomp_max])
+ 
+     sync_sb_pb_recomp=$(as northd ovn-appctl -t NORTHD_TYPE inc-engine/show-stats sync_to_sb_pb recompute)
+-    AT_CHECK([test x$sync_sb_pb_recomp = x$3])
++    AT_CHECK([test $sync_sb_pb_recomp -ge $sync_sb_pb_recomp_min && test $sync_sb_pb_recomp -le $sync_sb_pb_recomp_max])
+ }
+ 
+ check ovn-nbctl --wait=hv ls-add ls0
+@@ -10032,29 +10074,29 @@ check ovn-nbctl --wait=hv lsp-add ls0 lsp0-0 -- lsp-set-addresses lsp0-0 "unknow
+ ovs-vsctl add-port br-int lsp0-0 -- set interface lsp0-0 external_ids:iface-id=lsp0-0
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-check_recompute_counter 5 5 5
++check_recompute_counter 4 5 5 5 5 5
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=hv lsp-add ls0 lsp0-1 -- lsp-set-addresses lsp0-1 "aa:aa:aa:00:00:01 192.168.0.11"
+ ovs-vsctl add-port br-int lsp0-1 -- set interface lsp0-1 external_ids:iface-id=lsp0-1
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
+ ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=hv lsp-del lsp0-1
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=hv lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:88 192.168.0.88"
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+ # Delete and re-add a LSP for several times continuously, to ensure
+ # frequent operations do not trigger recompute when there are in-flight
+@@ -10068,14 +10110,14 @@ for i in $(seq 10); do
+     check ovn-nbctl lsp-del lsp0-2
+     check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
+ done
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+ # No change, no recompute
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=sb sync
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Associate DHCP for lsp0-2
+ ovn-nbctl dhcp-options-create 192.168.0.0/24
+@@ -10085,9 +10127,9 @@ ovn-nbctl dhcp-options-set-options $CIDR_UUID   lease_time=3600  router=192.168.
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ ovn-nbctl --wait=sb lsp-set-dhcpv4-options lsp0-2 $CIDR_UUID
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Add IPv6 address and associate DHCPv6 for lsp0-2
+ check ovn-nbctl lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:01 192.168.0.11 aef0::4"
+@@ -10096,9 +10138,9 @@ options="\"server_id\"=\"00:00:00:10:00:01\"")"
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ ovn-nbctl --wait=sb lsp-set-dhcpv6-options lsp0-2 ${d1}
+-check_recompute_counter 0 0 0
++check_recompute_counter 0 0 0 0 0 0
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ check ovn-nbctl --wait=hv ls-del ls0
+ 
+@@ -10125,11 +10167,11 @@ ovn-nbctl lsp-add ls0 ls0-lr0
+ ovn-nbctl lsp-set-type ls0-lr0 router
+ ovn-nbctl lsp-set-addresses ls0-lr0 router
+ check ovn-nbctl --wait=sb lsp-set-options ls0-lr0 router-port=lr0-ls0
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ ovn-nbctl lb-add lb0 192.168.0.10:80 10.0.0.10:8080
+ check ovn-nbctl --wait=sb ls-lb-add ls0 lb0
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ # Add a lsp.  northd and lflow engine shouldn't recompute even though this is
+@@ -10216,14 +10258,14 @@ check ovn-nbctl --wait=sb meter-add m drop 1 pktps
+ check ovn-nbctl --wait=sb acl-add ls from-lport 1 1 allow
+ dnl Only triggers recompute of the sync_meters and lflow nodes.
+ check_recompute_counter 0 2 2
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl --wait=sb meter-del m
+ check ovn-nbctl --wait=sb acl-del ls
+ dnl Only triggers recompute of the sync_meters and lflow nodes.
+ check_recompute_counter 0 2 2
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ AT_CLEANUP
+ ])
+@@ -10339,7 +10381,7 @@ wait_for_ports_up sw0-r1
+ check_column $remote_chassis_uuid Port_Binding chassis logical_port=sw0-r1
+ 
+ # Set the type to router and ovn-northd should not claim it.
+-check ovn-nbctl lsp-set-type sw0-r1 router
++check ovn-nbctl --wait=hv lsp-set-type sw0-r1 router
+ check_column '' Port_Binding chassis logical_port=sw0-r1
+ 
+ AT_CLEANUP
+@@ -10391,34 +10433,39 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ 
+ check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check ovn-nbctl --wait=sb set load_balancer . options:foo=bar
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check ovn-nbctl --wait=sb -- lb-add lb2 20.0.0.10:80 20.0.0.20:80 -- lb-add lb3 30.0.0.10:80 30.0.0.20:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ 
+ check ovn-nbctl --wait=sb -- lb-del lb2 -- lb-del lb3
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ 
+ AT_CHECK([ovn-nbctl --wait=sb \
+@@ -10429,6 +10476,7 @@ AT_CHECK([ovn-nbctl --wait=sb \
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ # Any change to load balancer health check should also result in full recompute
+ # of northd node (but not northd_lb_data node)
+@@ -10437,6 +10485,7 @@ check ovn-nbctl --wait=sb set load_balancer_health_check . options:foo=bar1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ # Delete the health check from the load balancer.  northd engine node should do a full recompute.
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10444,6 +10493,7 @@ check ovn-nbctl --wait=sb clear Load_Balancer . health_check
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl ls-add sw0
+@@ -10457,6 +10507,7 @@ ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ # Associate lb1 to sw0. There should be no recompute of northd engine node
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10464,7 +10515,11 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++# A LB applied to a switch/router triggers:
++# - a recompute in the first iteration (handling northd change)
++# - a compute in the second iteration (handling SB update)
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Modify the backend of the lb1 vip
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10472,7 +10527,8 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Cleanup the vip of lb1.
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10480,7 +10536,8 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Set the vips of lb1 back
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10488,7 +10545,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Add another vip to lb1
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10496,7 +10554,8 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Disassociate lb1 from sw0. There should be a full recompute of northd engine node.
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10504,7 +10563,8 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
+-CHECK_NO_CHANGE_AFTER_RECOMPUTE
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
+ 
+ # Associate lb1 to sw0 and also create a port sw0p1.  This should not result in
+ # full recompute of northd, but should rsult in full recompute of lflow node.
+@@ -10513,6 +10573,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -- lsp-add sw0 sw0p1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10523,6 +10584,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Add lb1 to lr0 and then disassociate
+@@ -10531,6 +10593,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Modify the backend of the lb1 vip
+@@ -10539,6 +10602,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Cleanup the vip of lb1.
+@@ -10547,6 +10611,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Set the vips of lb1 back
+@@ -10555,6 +10620,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Add another vip to lb1
+@@ -10563,6 +10629,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10570,14 +10637,16 @@ check ovn-nbctl --wait=sb lr-lb-del lr0 lb1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Test load balancer group now
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1)
++lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10586,31 +10655,35 @@ lb1_uuid=$(fetch_column nb:Load_Balancer _uuid)
+ 
+ # Add lb to the lbg1 group
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid
++check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl clear load_balancer_group . load_Balancer
++check ovn-nbctl --wait=sb clear load_balancer_group . load_Balancer
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ # Add back lb to the lbg1 group
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl add load_balancer_group . load_Balancer $lb1_uuid
++check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid
++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ # Update lb and this should not result in northd recompute
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10618,6 +10691,7 @@ check ovn-nbctl --wait=sb set load_balancer . options:bar=foo
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ # Modify the backend of the lb1 vip
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10625,6 +10699,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Cleanup the vip of lb1.
+@@ -10633,6 +10708,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Set the vips of lb1 back
+@@ -10641,6 +10717,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Add another vip to lb1
+@@ -10649,19 +10726,22 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl clear logical_switch sw0 load_balancer_group
++check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+ check ovn-nbctl add logical_router lr0 load_balancer_group $lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Modify the backend of the lb1 vip
+@@ -10670,6 +10750,7 @@ check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.1
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Cleanup the vip of lb1.
+@@ -10678,6 +10759,7 @@ check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Set the vips of lb1 back
+@@ -10686,6 +10768,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Add another vip to lb1
+@@ -10694,27 +10777,31 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl clear logical_router lr0 load_balancer_group
++check ovn-nbctl --wait=sb clear logical_router lr0 load_balancer_group
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ # Add back lb group to logical switch and then delete it.
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl add logical_switch sw0 load_balancer_group $lbg1_uuid
++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl clear logical_switch sw0 load_balancer_group -- \
++check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group -- \
+     destroy load_balancer_group $lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb compute compute
+ 
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+@@ -10733,29 +10820,33 @@ lb3_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb3)
+ lb4_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb4)
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-lbg1_uuid=$(ovn-nbctl create load_balancer_group name=lbg1)
++lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
++check ovn-nbctl --wait=sb set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl set logical_switch sw0 load_balancer_group=$lbg1_uuid
++check ovn-nbctl --wait=sb set logical_switch sw0 load_balancer_group=$lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+-check ovn-nbctl set logical_router lr1 load_balancer_group=$lbg1_uuid
++check ovn-nbctl --wait=sb set logical_router lr1 load_balancer_group=$lbg1_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10763,6 +10854,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10770,6 +10862,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb3
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10778,6 +10871,7 @@ check ovn-nbctl --wait=sb lr-lb-add lr1 lb2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10785,6 +10879,7 @@ check ovn-nbctl --wait=sb ls-lb-del sw0 lb2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10792,6 +10887,7 @@ check ovn-nbctl --wait=sb lr-lb-del lr1 lb2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute nocompute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Deleting lb4 should not result in lflow recompute as it is
+@@ -10801,6 +10897,7 @@ check ovn-nbctl --wait=sb lb-del lb4
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ # Deleting lb2 should result in lflow recompute as it is
+@@ -10810,6 +10907,7 @@ check ovn-nbctl --wait=sb lb-del lb2
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd norecompute compute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
+@@ -10817,7 +10915,61 @@ check ovn-nbctl --wait=sb remove load_balancer_group . load_balancer $lb3_uuid
+ check_engine_stats lb_data norecompute compute
+ check_engine_stats northd recompute nocompute
+ check_engine_stats lflow recompute nocompute
++check_engine_stats sync_to_sb_lb recompute compute
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([Load balancer incremental processing - batched updates])
++ovn_start
++
++# Check the scenario when a LB is created and quickly deleted (northd
++# processes this in a single iteration).
++
++check ovn-nbctl ls-add sw0
++lbg_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg)
++check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg_uuid
++
++# Pause ovn-northd.
++sleep_northd
++
++check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080
++lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp)
++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid
++check ovn-nbctl lb-del lb-temp
++
++# Let ovn-northd process all the updates that happened since it went to sleep.
++wake_up_northd
++
++# Add a new load balancer to make sure northd still functions properly.
++check ovn-nbctl lb-add lb1 50.0.0.10:80 50.0.0.30:8080
++lb1_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb1)
++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb1_uuid
++
++check ovn-nbctl --wait=sb sync
+ CHECK_NO_CHANGE_AFTER_RECOMPUTE
+ 
++# Re-check the same scenario but now also batch the additional LB creation.
++sleep_northd
++check ovn-nbctl lb-add lb-temp 50.0.0.10:80 50.0.0.20:8080
++lb_temp_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb-temp)
++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb_temp_uuid
++check ovn-nbctl lb-del lb-temp
++
++# Add a new load balancer to make sure northd still functions properly.
++check ovn-nbctl lb-add lb2 50.0.0.10:80 50.0.0.30:8080
++lb2_uuid=$(fetch_column nb:Load_Balancer _uuid name=lb2)
++check ovn-nbctl add load_balancer_group $lbg_uuid load_balancer $lb2_uuid
++
++wake_up_northd
++check ovn-nbctl --wait=sb sync
++CHECK_NO_CHANGE_AFTER_RECOMPUTE
++
++AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE status], [0], [dnl
++Status: active
++])
++
+ AT_CLEANUP
+ ])
+diff --git a/tests/ovn.at b/tests/ovn.at
+index e127530f6..13f5575be 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -23,8 +23,11 @@ m4_divert_text([PREPARE_TESTS],
+      diff -u $exp_text.sorted $rcv_text.sorted
+    }
+    ovn_check_packets__ () {
+-     echo
+-     echo "$3: checking packets in $1 against $2:"
++     if [[ -n "$4" ]]; then
++       echo "$3: checking packets in $1 against $2: using $4"
++     else
++       echo "$3: checking packets in $1 against $2:"
++     fi
+      rcv_pcap=$1
+      rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'`
+      exp_text=$2
+@@ -35,7 +38,13 @@ m4_divert_text([PREPARE_TESTS],
+         echo "rcv_n=$rcv_n exp_n=$exp_n"
+         test $rcv_n -ge $exp_n],
+        [dump_diff__ "$rcv_pcap" "$exp_text"])
+-     sort $exp_text > expout
++     if [[ -n "$4" ]]; then
++       sort $exp_text | $4 > expout
++       cat $rcv_text | $4 > rcv_tmp
++       mv rcv_tmp $rcv_text
++     else
++       sort $exp_text > expout
++     fi
+    }
+    ovn_check_packets_remove_broadcast__ () {
+      echo "$3: checking packets in $1 against $2:"
+@@ -54,14 +63,26 @@ m4_divert_text([PREPARE_TESTS],
+    }
+    ovn_wait_packets__ () {
+      echo "$3: waiting for packets from $2 at $1:"
++     if [[ -n "$4" ]]; then
++       echo "$3: checking packets from $2 at $1: using $4"
++     else
++       echo "$3: checking packets from $2 at $1:"
++     fi
+      rcv_pcap=$1
+      rcv_text=`echo "$rcv_pcap.packets" | sed 's/\.pcap//'`
+      exp_text=$2
++     if [[ -n "$4" ]]; then
++       cmd=$4
++     else
++       cmd=:
++     fi
+      OVS_WAIT_UNTIL(
+        [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $rcv_pcap > $rcv_text
+-        sort $exp_text > expout
+-        test x"$(sort $rcv_text | comm -2 -3 expout -)" = "x"],
++        sort $exp_text | $cmd > expout
++        test x"$(sort $rcv_text | $cmd | comm -2 -3 expout -)" = "x"],
+        [dump_diff__ "$rcv_pcap" "$exp_text"])
++     cat $rcv_text | $cmd > rcv_tmp
++     mv rcv_tmp $rcv_text
+    }
+    ovn_wait_packets_uniq__ () {
+      echo "$3: waiting for packets from $2 at $1:"
+@@ -140,7 +161,7 @@ m4_divert_text([PREPARE_TESTS],
+ 
+ m4_define([OVN_CHECK_PACKETS],
+   [AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $1 ], [0], [ignore])
+-   ovn_check_packets__ "$1" "$2" "__file__:__line__"
++   ovn_check_packets__ "$1" "$2" "__file__:__line__" $3
+    AT_CHECK([sort $rcv_text], [0], [expout], [ignore], [dump_diff__ "$1" "$2"])])
+ 
+ m4_define([OVN_CHECK_PACKETS_REMOVE_BROADCAST],
+@@ -2198,13 +2219,13 @@ reg9[5] = chk_ecmp_nh();
+ 
+ # commit_lb_aff
+ commit_lb_aff(vip = "172.16.0.123:8080", backend = "10.0.0.3:8080", proto = tcp, timeout = 30);
+-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15])
++    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[],load:0x1f90->NXM_NX_REG8[0..15])
+ 
+ commit_lb_aff(vip = "172.16.0.123", backend = "10.0.0.3", timeout = 30);
+-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[])
++    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.16.0.123,load:0x1->NXM_NX_REG10[14],load:0xa000003->NXM_NX_REG4[])
+ 
+ commit_lb_aff(vip = "[::1]:8080", backend = "[::2]:8080", proto = tcp, timeout = 30);
+-    encodes as learn(table=78,idle_timeout=30,delete_learned,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15])
++    encodes as learn(table=78,idle_timeout=30,delete_learned,cookie=0xaaaaaaaa,OXM_OF_METADATA[],eth_type=0x86dd,NXM_NX_IPV6_SRC[],ipv6_dst=::1,nw_proto=6,tcp_dst=8080,load:0x1->NXM_NX_REG10[14],load:0x2->NXM_NX_XXREG0[],load:0x1f90->NXM_NX_REG8[0..15])
+ 
+ # chk_lb_aff()
+ reg9[6] = chk_lb_aff();
+@@ -3849,7 +3870,7 @@ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([VLAN transparency, passthru=true, multiple hosts, custom ethtype])
+ ovn_start
+ 
+-ethtype=802.11ad
++ethtype=802.1ad
+ 
+ check ovn-nbctl ls-add ls
+ check ovn-nbctl --wait=sb add Logical-Switch ls other_config vlan-passthru=true
+@@ -4650,6 +4671,12 @@ OVN_POPULATE_ARP
+ 
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv_gw"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv1"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv_gw"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv_gw"],["hv2"])
+ 
+ # test_packet INPORT DST SRC ETHTYPE OUTPORT...
+ #
+@@ -4849,6 +4876,13 @@ check ovn-nbctl --wait=hv sync
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+ 
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv3"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv2"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv3"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv3"],["hv1"])
++
+ # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+ #
+ # This shell function causes a packet to be received on INPORT.  The packet's
+@@ -5320,10 +5354,11 @@ test_arp() {
+ }
+ 
+ test_na() {
+-    local inport=$1 sha=$2 spa=$3
++    local inport=$1 sha=$2 spa=$3 src=${4-$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}')")
++                             IPv6(dst='ff01::1', src='${src}')/ \
++                             ICMPv6ND_NA(tgt='${spa}')/ \
++                             ICMPv6NDOptDstLLAddr(lladdr='${sha}')")
+ 
+     hv=hv`vif_to_hv $inport`
+     as $hv ovs-appctl netdev-dummy/receive vif$inport $request
+@@ -5399,6 +5434,24 @@ for i in 1 2; do
+     done
+ done
+ 
++# Make sure that we can update existing entry with
++# "always_learn_from_arp_request=false" even when the source of NA src is LLA.
++check ovn-sbctl --all destroy mac_binding
++check ovn-nbctl --wait=hv set logical_router lr0 options:always_learn_from_arp_request=false
++
++sha="f0:00:00:00:00:11"
++spa6="fd00::abcd:1"
++
++test_na 11 $sha $spa6
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
++
++sha="f0:00:00:00:00:12"
++lla6="fe80::abcd:1"
++
++test_na 11 $sha $spa6 $lla6
++wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
++check_row_count MAC_Binding 0 ip=\"$lla6\"
++
+ # Gracefully terminate daemons
+ OVN_CLEANUP([hv1], [hv2])
+ 
+@@ -6758,13 +6811,7 @@ test_dhcp() {
+ }
+ 
+ compare_dhcp_packets() {
+-    received=$($PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$1-tx.pcap)
+-    expected=$(cat $1.expected)
+-
+-    if test "$received" != "$expected"; then
+-        AT_CHECK_UNQUOTED([echo "$received"; tcpdump_hex "$received"], [0],
+-            [$(echo "$expected"; tcpdump_hex "$expected")])
+-    fi
++    OVN_CHECK_PACKETS([hv1/vif$1-tx.pcap], [$1.expected])
+ }
+ 
+ AT_CAPTURE_FILE([sbflows])
+@@ -6962,8 +7009,9 @@ expected_dhcp_opts=0
+ test_dhcp 11 2 f00000000002 07 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001
+ 
+ # There is no reply for this. Check for the INFO log in ovn-controller.log
+-AT_CHECK([test 1 = $(cat hv1/ovn-controller.log | \
+-grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)])
++OVS_WAIT_UNTIL(
++    [test 1 = $(cat hv1/ovn-controller.log | grep "DHCPRELEASE f0:00:00:00:00:02 10.0.0.6" -c)
++])
+ 
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+ AT_CHECK([cat 2.packets], [0], [])
+@@ -7120,7 +7168,9 @@ ciaddr=`ip_to_hex 0 0 0 0`
+ request_ip=0
+ expected_dhcp_opts=""
+ test_dhcp 18 1 f00000000001 04 0 $ciaddr $offer_ip $request_ip 0 0 ff1000000001 $server_ip 02 $expected_dhcp_opts
+-AT_CHECK([grep -F -iq 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log], [0], [])
++OVS_WAIT_UNTIL(
++    [test 1 -le $(grep -F -i -c 'DHCPDECLINE from f0:00:00:00:00:01, 10.0.0.4 duplicated' hv1/ovn-controller.log)
++])
+ 
+ # Send Etherboot.
+ 
+@@ -7138,7 +7188,7 @@ ovn-nbctl dhcp-options-set-options $d3 \
+    lease_time=3600 router=10.0.0.1 bootfile_name_alt=\"bootfile_name_alt\" \
+    bootfile_name=\"bootfile\"
+ 
+-ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 $d3
++ovn-nbctl --wait=hv lsp-set-dhcpv4-options ls1-lp1 $d3
+ 
+ offer_ip=`ip_to_hex 10 0 0 4`
+ server_ip=`ip_to_hex 10 0 0 1`
+@@ -7156,9 +7206,6 @@ compare_dhcp_packets 1
+ as northd
+ OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+ 
+-as northd-backup
+-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+-
+ northd_version=$(ovn-sbctl get SB_Global . options:northd_internal_version | sed s/\"//g)
+ echo "northd version = $northd_version"
+ 
+@@ -7295,10 +7342,6 @@ check ovn-nbctl --wait=hv sync
+ # Start with 0 because the first request will not have NXT_RESUME
+ n_resume=0
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ # This shell function sends a DHCPv6 request packet
+ # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
+ # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
+@@ -7400,12 +7443,8 @@ test_dhcpv6_release() {
+ check_packets() {
+     local port=$1
+ 
+-    $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif$port-tx.pcap | trim_zeros > $port.packets
+     # Skipping UDP checksum
+-    cat $port.expected | cut -c 1-120,125- > expout
+-    AT_CHECK([cat $port.packets | cut -c 1-120,125- ], [0], [expout])
+-
+-    rm $port.packets
++    OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["trim_zeros | cut -c 1-120,125-"])
+     rm $port.expected
+ }
+ 
+@@ -7508,7 +7547,7 @@ ovn-nbctl dhcp-options-set-options $d1 \
+     server_id=00:00:00:10:00:01 \
+     bootfile_name_alt=\"bootfile_name_alt\" \
+     bootfile_name=\"bootfile_name\"
+-ovn-nbctl lsp-set-dhcpv6-options ls1-lp2 ${d1}
++ovn-nbctl --wait=hv lsp-set-dhcpv6-options ls1-lp2 ${d1}
+ 
+ reset_pcap_file hv1-vif2 hv1/vif2
+ 
+@@ -7645,6 +7684,9 @@ ovn-nbctl lsp-add alice alice1 \
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ 
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv1"],["hv2"])
++OVN_WAIT_REMOTE_OUTPUT_FLOWS(["hv2"],["hv1"])
++
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+ dst_mac="000001010203"
+@@ -8418,6 +8460,7 @@ check_dynamic_addresses() {
+     check_row_count nb:Logical_Switch_Port 1 name="$1" dynamic_addresses="$arg"
+ }
+ 
++check ovn-nbctl --wait=sb sync
+ # Add a port to a switch that does not have a subnet set, then set the
+ # subnet which should result in an address being allocated for the port.
+ ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
+@@ -8733,7 +8776,7 @@ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ipam connectivity])
+ ovn_start
+ 
+-ovn-nbctl lr-add R1
++ovn-nbctl --wait=sb lr-add R1
+ 
+ # Test for a ping using dynamically allocated addresses.
+ ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
+@@ -9019,10 +9062,6 @@ packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111
+ # Send IP packet destined to 8.8.8.8 from lsp1lp2
+ as hv1 ovs-appctl netdev-dummy/receive hv1-ls1lp2 $packet
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ # ARP packet should be received with Target IP Address set to 192.168.1.254 and
+ # not 8.8.8.8
+ 
+@@ -9078,9 +9117,6 @@ AT_CAPTURE_FILE([sbflows])
+ 
+ # Wait for packet to be received.
+ OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 140])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros |sort | uniq > packets
+ AT_CHECK([sort packets], [0], [dnl
+ fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001
+@@ -9277,9 +9313,6 @@ ovn-nbctl list logical_router_port lrp0
+ ovn-nbctl show
+ # Wait for packet to be received.
+ OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets
+ expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
+ echo $expected > expout
+@@ -9300,9 +9333,6 @@ ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router" exclud
+ 
+ # Wait for packets to be received.
+ OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+ 
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
+ g0="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
+@@ -10721,10 +10751,6 @@ OVN_POPULATE_ARP
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ # Send ip packets between foo1 and bar1
+ # (East-west traffic should flow normally)
+ src_mac="f00000010203"
+@@ -11004,12 +11030,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 1.
+ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+-cat 1.expected | cut -c -48 > expout
+-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 1.expected | cut -c 53- > expout
+-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11026,12 +11049,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 2.
+ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+-cat 2.expected | cut -c -48 > expout
+-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 2.expected | cut -c 53- > expout
+-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11049,12 +11069,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 3.
+ OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+-cat 2.expected | cut -c -48 > expout
+-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 2.expected | cut -c 53- > expout
+-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11131,12 +11148,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 5.
+ OVS_WAIT_UNTIL([test 5 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+-cat 2.expected | cut -c -48 > expout
+-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 2.expected | cut -c 53- > expout
+-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11154,12 +11168,9 @@ test_dns 2 f00000000002 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 6.
+ OVS_WAIT_UNTIL([test 6 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets
+-cat 2.expected | cut -c -48 > expout
+-AT_CHECK([cat 2.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 2.expected | cut -c 53- > expout
+-AT_CHECK([cat 2.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11221,12 +11232,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 9.
+ OVS_WAIT_UNTIL([test 9 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+-cat 1.expected | cut -c -48 > expout
+-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 1.expected | cut -c 53- > expout
+-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11244,10 +11252,8 @@ test_dns6 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $
+ # NXT_RESUMEs should be 10
+ OVS_WAIT_UNTIL([test 10 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+ # Skipping the UDP checksum.
+-cat 1.expected | cut -c 1-120,125- > expout
+-AT_CHECK([cat 1.packets | cut -c 1-120,125-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 1-120,125-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11273,12 +11279,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 11.
+ OVS_WAIT_UNTIL([test 11 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+-cat 1.expected | cut -c -48 > expout
+-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 1.expected | cut -c 53- > expout
+-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11296,12 +11299,9 @@ test_dns 1 f00000000001 f000000000f0 $src_ip $dst_ip $dns_reply $dns_req_data $d
+ # NXT_RESUMEs should be 12.
+ OVS_WAIT_UNTIL([test 12 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets
+-cat 1.expected | cut -c -48 > expout
+-AT_CHECK([cat 1.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat 1.expected | cut -c 53- > expout
+-AT_CHECK([cat 1.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"])
+ 
+ reset_pcap_file hv1-vif1 hv1/vif1
+ reset_pcap_file hv1-vif2 hv1/vif2
+@@ -11481,30 +11481,7 @@ test_ip_packet()
+     # Resend packet from foo1 to outside1
+     check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+ 
+-    AT_CAPTURE_FILE([exp])
+-    AT_CAPTURE_FILE([rcv])
+-    check_packets() {
+-        > exp
+-        > rcv
+-
+-        pcap=ext1/vif1-tx.pcap
+-        type=ext1-vif1.expected
+-        echo "--- $pcap" | tee -a exp >> rcv
+-        sort -u "$type" >> exp
+-        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort -u >> rcv
+-        echo | tee -a exp >> rcv
+-
+-        pcap=$active_gw/br-phys_n1-tx.pcap
+-        echo "--- $pcap" | tee -a exp >> rcv
+-        sort -u "$type" >> exp
+-        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap > packets
+-        (grep "$expected" packets; grep "$exp_gw_ip_garp" packets) | sort -u >> rcv
+-        echo | tee -a exp >> rcv
+-
+-        $at_diff exp rcv >/dev/null
+-    }
+-
+-    OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' exp rcv])
++    OVN_CHECK_PACKETS_CONTAIN([ext1/vif1-tx.pcap], [ext1-vif1.expected])
+ 
+     if test $backup_vswitchd_dead != 1; then
+         # Check for backup gw only if vswitchd is alive
+@@ -12206,9 +12183,6 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv2"])
+ 
+ # Wait for packets to be received.
+ OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
+ expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80001000000000000c0a80001"
+ echo $expected > expout
+@@ -12240,21 +12214,12 @@ OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv3"])
+ # Re-add nat-addresses option
+ ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
+ 
+-# Wait for packets to be received.
+-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 250])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | sort | uniq > packets
+ garp_1="fffffffffffff0000000000308060001080006040001f00000000003c0a80003000000000000c0a80003"
+-echo $garp_1 > expout
++echo $garp_1 > expected_out
+ garp_2="fffffffffffff0000000000408060001080006040001f00000000004c0a80004000000000000c0a80004"
+-echo $garp_2 >> expout
++echo $garp_2 >> expected_out
+ 
+-cat packets | grep $garp_1 | head -1 > exp
+-cat packets | grep $garp_2 | head -1 >> exp
+-AT_CHECK([cat exp], [0], [expout])
++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
+ 
+ OVN_CLEANUP([hv1],[hv2],[hv3])
+ 
+@@ -12577,6 +12542,7 @@ AT_CLEANUP
+ 
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv6 ND Router Solicitation responder])
++AT_SKIP_IF([test $HAVE_SCAPY = no])
+ AT_KEYWORDS([ovn-nd_ra])
+ ovn_start
+ 
+@@ -12643,76 +12609,120 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ 
++n_resume=1
++
+ # Make sure that ovn-controller has installed the corresponding OF Flow.
+ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+ 
+ # This shell function sends a Router Solicitation packet.
+-# test_ipv6_ra INPORT SRC_MAC SRC_LLA ADDR_MODE MTU RA_PREFIX_OPT RDNSS DNSSL ROUTE_INFO
++# test_ipv6_ra INPORT SRC_MAC SRC_LLA RA_OPT MTU PREFIX RDNSS DNSSL ROUTES
+ test_ipv6_ra() {
+-    local inport=$1 src_mac=$2 src_lla=$3 addr_mode=$4 mtu=$5 prefix_opt=$6
+-    local rdnss=$7 dnssl=$8 route_info=$9
+-    local request=333300000002${src_mac}86dd6000000000103aff${src_lla}ff02000000000000000000000000000285000efc000000000101${src_mac}
++    local inport=$1 src_mac=$2 src_ip=$3 ra=$4 mtu=$5 prefix=$6
++    local rdnss=$7 dnssl=$8 routes=$9
+ 
+-    local len=24
+-    local mtu_opt=""
+-    if test $mtu != 0; then
+-        len=`expr $len + 8`
+-        mtu_opt=05010000${mtu}
++    local request=$(fmt_pkt "Ether(dst='33:33:00:00:00:02', src='${src_mac}')/ \
++                             IPv6(dst='ff02::2', src='${src_ip}')/ \
++                             ICMPv6ND_RS()/ \
++                             ICMPv6NDOptSrcLLAddr(lladdr='${src_mac}')")
++
++    local reply_dst=$src_ip
++    if test "${reply_dst}" = "::"; then
++        reply_dst="ff02::1"
+     fi
+ 
+-    if test ${#rdnss} != 0; then
+-        len=`expr $len + ${#rdnss} / 2`
++    local rep_scapy="Ether(dst='${src_mac}', src='fa:16:3e:00:00:01')/ \
++                     IPv6(dst='${reply_dst}', src='fe80::f816:3eff:fe00:1')/ \
++                     ${ra}/ \
++                     ICMPv6NDOptSrcLLAddr(lladdr='fa:16:3e:00:00:01')"
++
++    if test "${mtu}" != "0"; then
++        rep_scapy="${rep_scapy}/ICMPv6NDOptMTU(mtu=${mtu})"
+     fi
+ 
+-    if test ${#dnssl} != 0; then
+-        len=`expr $len + ${#dnssl} / 2`
++    if test -n "${rdnss}"; then
++        rep_scapy="${rep_scapy}/ICMPv6NDOptRDNSS(dns=[['${rdnss}']])"
+     fi
+ 
+-    if test ${#route_info} != 0; then
+-        len=`expr $len + ${#route_info} / 2`
++    if test -n "${dnssl}"; then
++        rep_scapy="${rep_scapy}/ICMPv6NDOptDNSSL(searchlist=[['${dnssl}']])"
+     fi
+ 
+-    if test ${#prefix_opt} != 0; then
+-        prefix_opt=${prefix_opt}fdad1234567800000000000000000000
+-        len=`expr $len + ${#prefix_opt} / 2`
++    if test -n "${routes}"; then
++        rep_scapy="${rep_scapy}/${routes}"
+     fi
+ 
+-    len=$(printf "%x" $len)
+-    local lrp_mac=fa163e000001
+-    local lrp_lla=fe80000000000000f8163efffe000001
+-    local reply=${src_mac}${lrp_mac}86dd6000000000${len}3aff${lrp_lla}${src_lla}8600XXXXff${addr_mode}ffff00000000000000000101${lrp_mac}${mtu_opt}${rdnss}${dnssl}${route_info}${prefix_opt}
++    if test -n "${prefix}"; then
++        local a_flag=$(echo $ra | grep -vc "M=1")
++        rep_scapy="${rep_scapy}/ICMPv6NDOptPrefixInfo(prefix='${prefix}', A=${a_flag})"
++    fi
++
++    local reply=$(fmt_pkt "${rep_scapy}")
+     echo $reply >> $inport.expected
+ 
+     as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request
++
++    OVS_WAIT_UNTIL([test $n_resume = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
++    n_resume=$((n_resume + 1))
++}
++
++check_packets() {
++    local port=$1
++
++    # Skipping UDP checksum
++    OVN_CHECK_PACKETS([hv1/vif$port-tx.pcap], [$port.expected], ["cut -c 1-112,117-"])
++
++    rm $port.packets
++    rm $port.expected
++
++    reset_pcap_file hv1-vif1 hv1/vif1
++    reset_pcap_file hv1-vif2 hv1/vif2
++    reset_pcap_file hv1-vif3 hv1/vif3
++}
++
++prepare_ra_opt() {
++    local mode=$1 priority=$2
++
++    if test "${mode}" = "stateful"; then
++        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, M=1, prf=${priority})"
++    elif test "${mode}" = "stateless"; then
++        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, O=1, prf=${priority})"
++    else
++        echo "ICMPv6ND_RA(chlim=255, routerlifetime=65535, prf=${priority})"
++    fi
++}
++
++prepare_route_opt() {
++    local prefix=$1 priority=$2 plen=$3
++
++    local len=2
++    if test $plen -gt 64; then
++        len=3
++    fi
++
++    echo "ICMPv6NDOptRouteInfo(len=$len, prf=${priority}, prefix='${prefix}', plen=$plen)"
+ }
+ 
+ AT_CAPTURE_FILE([ofctl_monitor0.log])
+ as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
+ --pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log
+ 
++prefix="fdad:1234:5678::"
++
+ # MTU is not set and the address mode is set to slaac
+-addr_mode=00
+-default_prefix_option_config=030440c0ffffffffffffffff00000000
+-src_mac=fa163e000002
+-src_lla=fe80000000000000f8163efffe000002
+-test_ipv6_ra 1 $src_mac $src_lla $addr_mode 0 $default_prefix_option_config
++ra=$(prepare_ra_opt "" 0)
++src_mac="fa:16:3e:00:00:02"
++src_lla="fe80::f816:3eff:fe00:2"
+ 
+-# NXT_RESUME should be 1.
+-OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
++test_ipv6_ra 1 $src_mac $src_lla "$ra" 0 $prefix "" "" ""
++check_packets 1
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
++# Check with RA with src being "::".
++ovn-nbctl --wait=hv lsp-set-port-security lp1 ""
+ 
+-cat 1.expected | cut -c -112 > expout
+-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
++test_ipv6_ra 1 $src_mac "::" "$ra" 0 $prefix "" "" ""
++check_packets 1
+ 
+-# Skipping the ICMPv6 checksum.
+-cat 1.expected | cut -c 117- > expout
+-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+-
+-rm -f *.expected
+-reset_pcap_file hv1-vif1 hv1/vif1
+-reset_pcap_file hv1-vif2 hv1/vif2
+-reset_pcap_file hv1-vif3 hv1/vif3
++ovn-nbctl --wait=hv lsp-set-port-security lp1 "fa:16:3e:00:00:02 10.0.0.12 fdad:1234:5678:0:f816:3eff:fe:2"
+ 
+ # Set the MTU to 1500, send_periodic to false, preference to LOW
+ ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:mtu=1500
+@@ -12726,33 +12736,14 @@ ovn-nbctl --wait=hv set Logical_Router_port lrp0 ipv6_ra_configs:route_info=HIGH
+ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+ 
+ # addr_mode byte also includes router preference information
+-addr_mode=18
+-default_prefix_option_config=030440c0ffffffffffffffff00000000
+-src_mac=fa163e000003
+-src_lla=fe80000000000000f8163efffe000003
+-mtu=000005dc
+-rdnss=19030000ffffffff10000000000000000000000000000011
+-dnssl=1f030000ffffffff02616102626202636300000000000000
+-route_info=18023008ffffffff100100000000000018036018ffffffff10020000000000000000000000000000
+-
+-test_ipv6_ra 2 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config $rdnss $dnssl $route_info
+-
+-# NXT_RESUME should be 2.
+-OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap  > 2.packets
+-
+-cat 2.expected | cut -c -112 > expout
+-AT_CHECK([cat 2.packets | cut -c -112], [0], [expout])
+-
+-# Skipping the ICMPv6 checksum.
+-cat 2.expected | cut -c 117- > expout
+-AT_CHECK([cat 2.packets | cut -c 117-], [0], [expout])
++ra=$(prepare_ra_opt "" 3)
++routes=$(prepare_route_opt '1001::' 1 48)
++routes="${routes}/$(prepare_route_opt '1002::' 3 96)"
++src_mac="fa:16:3e:00:00:03"
++src_lla="fe80::f816:3eff:fe00:3"
+ 
+-rm -f *.expected
+-reset_pcap_file hv1-vif1 hv1/vif1
+-reset_pcap_file hv1-vif2 hv1/vif2
+-reset_pcap_file hv1-vif3 hv1/vif3
++test_ipv6_ra 2 $src_mac $src_lla "$ra" 1500 $prefix "1000::11" "aa.bb.cc" "$routes"
++check_packets 2
+ 
+ # Set the address mode to dhcpv6_stateful, router_preference to HIGH
+ ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateful
+@@ -12763,30 +12754,12 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs route_info
+ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+ 
+ # addr_mode byte also includes router preference information
+-addr_mode=88
+-default_prefix_option_config=03044080ffffffffffffffff00000000
+-src_mac=fa163e000004
+-src_lla=fe80000000000000f8163efffe000004
+-mtu=000005dc
+-
+-test_ipv6_ra 3 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config "" $dnssl
+-
+-# NXT_RESUME should be 3.
+-OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap  > 3.packets
+-
+-cat 3.expected | cut -c -112 > expout
+-AT_CHECK([cat 3.packets | cut -c -112], [0], [expout])
+-
+-# Skipping the ICMPv6 checksum.
+-cat 3.expected | cut -c 117- > expout
+-AT_CHECK([cat 3.packets | cut -c 117-], [0], [expout])
++ra=$(prepare_ra_opt "stateful" 1)
++src_mac="fa:16:3e:00:00:04"
++src_lla="fe80::f816:3eff:fe00:4"
+ 
+-rm -f *.expected
+-reset_pcap_file hv1-vif1 hv1/vif1
+-reset_pcap_file hv1-vif2 hv1/vif2
+-reset_pcap_file hv1-vif3 hv1/vif3
++test_ipv6_ra 3 $src_mac $src_lla "$ra" 1500 $prefix "" "aa.bb.cc" ""
++check_packets 3
+ 
+ # Set the address mode to dhcpv6_stateless, reset router preference to default
+ ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=dhcpv6_stateless
+@@ -12795,46 +12768,27 @@ ovn-nbctl --wait=hv remove Logical_Router_Port lrp0 ipv6_ra_configs dnssl
+ # Make sure that ovn-controller has installed the corresponding OF Flow.
+ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+ 
+-addr_mode=40
+-default_prefix_option_config=030440c0ffffffffffffffff00000000
+-src_mac=fa163e000002
+-src_lla=fe80000000000000f8163efffe000002
+-mtu=000005dc
+-
+-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
+-
+-# NXT_RESUME should be 4.
+-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
++ra=$(prepare_ra_opt "stateless" 0)
++src_mac="fa:16:3e:00:00:02"
++src_lla="fe80::f816:3eff:fe00:2"
+ 
+-cat 1.expected | cut -c -112 > expout
+-AT_CHECK([cat 1.packets | cut -c -112], [0], [expout])
+-
+-# Skipping the ICMPv6 checksum.
+-cat 1.expected | cut -c 117- > expout
+-AT_CHECK([cat 1.packets | cut -c 117-], [0], [expout])
+-
+-rm -f *.expected
+-reset_pcap_file hv1-vif1 hv1/vif1
+-reset_pcap_file hv1-vif2 hv1/vif2
+-reset_pcap_file hv1-vif3 hv1/vif3
++test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 $prefix "" "" ""
++check_packets 1
+ 
+ # Set the address mode to invalid.
+ ovn-nbctl --wait=hv set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode=invalid
+ # Make sure that ovn-controller has not installed any OF Flow for IPv6 ND RA.
+ OVS_WAIT_UNTIL([test 0 = `as hv1 ovs-ofctl dump-flows br-int | grep -c "ipv6_dst=ff02::2,nw_ttl=255,icmp_type=133,icmp_code=0"`])
+ 
+-addr_mode=40
+-default_prefix_option_config=""
+-src_mac=fa163e000002
+-src_lla=fe80000000000000f8163efffe000002
+-mtu=000005dc
++ra=$(prepare_ra_opt "stateless" 0)
+ 
+-test_ipv6_ra 1 $src_mac $src_lla $addr_mode $mtu $default_prefix_option_config
++src_mac="fa:16:3e:00:00:02"
++src_lla="fe80::f816:3eff:fe00:2"
+ 
+-# NXT_RESUME should be 4 only.
+-OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`])
++# This shouldn't produce any NXT_RESUME.
++n_resume=$((n_resume - 1))
++
++test_ipv6_ra 1 $src_mac $src_lla "$ra" 1500 "" "" "" ""
+ 
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap  > 1.packets
+ AT_CHECK([cat 1.packets], [0], [])
+@@ -13879,33 +13833,15 @@ as hv1 reset_pcap_file snoopvif hv1/snoopvif
+ # add nat-addresses option
+ ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="router"
+ 
+-# Wait for packets to be received through hv2.
+-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ only_broadcast_from_lrp1() {
+     grep "fffffffffffff00000000001"
+ }
+ 
+ garp="fffffffffffff0000000000108060001080006040001f00000000001c0a80064000000000000c0a80064"
+-echo $garp > expout
++echo $garp > expected_out
+ 
+-OVS_WAIT_UNTIL(
+-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
+-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
+-     echo "expected received = $exp_rcvd"
+-     test $exp_rcvd -ge 1])
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv1_snoop_tx
+-echo "packets on hv1-snoopvif:"
+-cat hv1_snoop_tx
+-AT_CHECK([sort hv1_snoop_tx], [0], [expout])
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
+-echo "packets on hv2 br-phys tx"
+-cat hv2_br_phys_tx
+-AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [expout])
++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
++OVN_CHECK_PACKETS_CONTAIN([hv2/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
+ echo "packets on hv3 br-phys tx"
+ cat hv3_br_phys_tx
+@@ -13914,9 +13850,6 @@ AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [])
+ 
+ # at this point, we invert the priority of the gw chassis between hv2 and hv3
+ 
+-# Reset hv2/br-phys_n1 before inverting gw chassis, as otherwise packets might
+-# be reset (while not resetting hv1/snoopvif because not yet sent).
+-as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
+ ovn-nbctl --wait=hv \
+           --id=@gc0 create Gateway_Chassis \
+                     name=outside_gw1 chassis_name=hv2 priority=1 -- \
+@@ -13924,24 +13857,17 @@ ovn-nbctl --wait=hv \
+                     name=outside_gw2 chassis_name=hv3 priority=10 -- \
+           set Logical_Router_Port lrp0 'gateway_chassis=[@gc0,@gc1]'
+ 
++# We expect not to receive garp on hv2 after inverting the priority.
++# Hence  reset hv2 after inverting priority as otherwise a garp might
++# be received on hv2 between the reset and the priority change.
++
++as hv2 reset_pcap_file br-phys_n1 hv2/br-phys_n1
+ as hv3 reset_pcap_file br-phys_n1 hv3/br-phys_n1
+ as hv1 reset_pcap_file snoopvif hv1/snoopvif
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+-# Wait for packets to be received.
+-OVS_WAIT_UNTIL(
+-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
+-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
+-     echo "expected received = $exp_rcvd"
+-     test $exp_rcvd -ge 1])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
+-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
+-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
++OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
+ AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
+ 
+@@ -13974,25 +13900,11 @@ ovn-nbctl --wait=hv lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="rout
+ OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns nat_addresses find port_binding \
+ logical_port=lrp0-rp | grep is_chassis | wc -l`])
+ 
+-# Wait for packets to be received.
+-OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 100])
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ garp="fffffffffffff00000000001810007de08060001080006040001f00000000001c0a80064000000000000c0a80064"
+-echo $garp > expout
++echo $garp > expected_out
+ 
+-OVS_WAIT_UNTIL(
+-    [$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap > rcv_text
+-     exp_rcvd=$(cat rcv_text | grep $garp | wc -l)
+-     echo "expected received = $exp_rcvd"
+-     test $exp_rcvd -ge 1])
+-
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq >  hv1_snoopvif_tx
+-AT_CHECK([sort hv1_snoopvif_tx], [0], [expout])
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv3_br_phys_tx
+-AT_CHECK([grep $garp hv3_br_phys_tx | sort], [0], [expout])
++OVN_CHECK_PACKETS_CONTAIN([hv1/snoopvif-tx.pcap], [expected_out], "trim_zeros")
++OVN_CHECK_PACKETS_CONTAIN([hv3/br-phys_n1-tx.pcap], [expected_out], "trim_zeros")
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap | trim_zeros | only_broadcast_from_lrp1 | uniq > hv2_br_phys_tx
+ AT_CHECK([grep $garp hv2_br_phys_tx | sort], [0], [])
+ 
+@@ -14333,10 +14245,6 @@ wc -l], [0], [4
+ chassis_uuid=$(fetch_column Chassis _uuid name=hv1)
+ wait_row_count Port_Binding 1 logical_port=cr-ip6_public chassis=$chassis_uuid
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ # Test the IPv6 Neighbor Solicitation (NS) - nd_ns action for unknown MAC
+ # addresses. ovn-controller should generate an IPv6 NS request for IPv6
+ # packets whose MAC is unknown (in the ARP_REQUEST router pipeline stage.
+@@ -14888,6 +14796,10 @@ wait_column "$hv2_uuid" Port_Binding requested_additional_chassis logical_port=m
+ # ovn-installed on hv2 should guarantee that.
+ OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"'])
+ 
++# Still, this does not guarantee that all flows are installed on hv3: hv3 (might) still need to receive and handle
++# additional_chassis for migrator port
++ovn-nbctl --wait=hv sync
++
+ # check that...
+ # unicast from First arrives to hv1:Migrator
+ # unicast from First arrives to hv2:Migrator
+@@ -14974,6 +14886,9 @@ wait_column "" Port_Binding requested_additional_chassis logical_port=migrator
+ # For instance, migrator port might still be up from prior to complete migration to hv2
+ OVS_WAIT_UNTIL([test `as hv2 ovs-vsctl get Interface migrator external_ids:ovn-installed` = '"true"'])
+ 
++# Give time for hv3 to handle the change of Port_Binding  for migrator port
++ovn-nbctl --wait=hv sync
++
+ # check that...
+ # unicast from Third doesn't arrive to hv1:Migrator
+ # unicast from Third arrives to hv2:Migrator
+@@ -16188,7 +16103,7 @@ echo "verifying that lsp0 binding moves when requested-chassis is changed"
+ 
+ ovn-nbctl lsp-set-options lsp0 requested-chassis=hv2
+ # We might see multiple "Releasing lport ...", when sb is read only
+-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
++OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)])
+ 
+ wait_column "$hv2_uuid" Port_Binding chassis logical_port=lsp0
+ 
+@@ -16276,7 +16191,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [0], [ig
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=65 | grep actions=output:1], [0], [ignore])
+ 
+ check ovn-nbctl --wait=hv lsp-set-options lsp0 requested-chassis=non-existant-chassis
+-OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0 from this chassis" hv1/ovn-controller.log)])
++OVS_WAIT_UNTIL([test 1 -le $(grep -c "Releasing lport lsp0" hv1/ovn-controller.log)])
+ check ovn-nbctl --wait=hv sync
+ wait_column '' Port_Binding chasssi logical_port=lsp0
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep in_port=1], [1], [])
+@@ -18989,6 +18904,46 @@ for sf in 0 1; do
+     done
+ done
+ 
++check_packets() {
++    n_allowed=$1
++    > expected
++    > received
++    for i in 1 2 3; do
++        echo "--- hv$i vif${i}1" | tee -a expected >> received
++        sort ${i}1.expected >> expected
++        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv$i/vif${i}1-tx.pcap | sort >> received
++        echo | tee -a expected >> received
++    done
++
++    # need to verify the log for ACL hit as well, since in the allow case
++    # (unlike the drop case) it is tricky to pass just with the expected;
++    # since with the stateful rule the packet will still get by (default
++    # rule) even if it doesn't hit the allow rule.
++    # The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2
++    # (with/without stateful rule) for hv1 and hv2, each.
++    cat >>expected <<EOF
++--- acl logging
++hv1_drop hit 6
++hv2_drop hit 6
++hv1_allow hit $n_allowed
++hv2_allow hit $n_allowed
++EOF
++
++cat >>received <<EOF
++--- acl logging
++hv1_drop hit `grep -c 'acl_log.*|INFO|name="drop-acl"' hv1/ovn-controller.log`
++hv2_drop hit `grep -c 'acl_log.*|INFO|name="drop-acl"' hv2/ovn-controller.log`
++hv1_allow hit `grep -c 'acl_log.*|INFO|name="allow-acl"' hv1/ovn-controller.log`
++hv2_allow hit `grep -c 'acl_log.*|INFO|name="allow-acl"' hv2/ovn-controller.log`
++EOF
++
++    $at_diff expected received >/dev/null
++}
++
++# We need to wait and check here that packets are received as they should as otherwise packets
++# which were just sent might by handled after setting next ACL (allow) rules.
++OVS_WAIT_UNTIL([check_packets 0], [$at_diff -F'^---' expected received])
++
+ # Test allow rule
+ #----------------
+ ovn-nbctl acl-del lsw0
+@@ -19037,41 +18992,7 @@ as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int > offlows3
+ # Now check the packets actually received against the ones expected.
+ AT_CAPTURE_FILE([expected])
+ AT_CAPTURE_FILE([received])
+-check_packets() {
+-    > expected
+-    > received
+-    for i in 1 2 3; do
+-        echo "--- hv$i vif${i}1" | tee -a expected >> received
+-        sort ${i}1.expected >> expected
+-        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv$i/vif${i}1-tx.pcap | sort >> received
+-        echo | tee -a expected >> received
+-    done
+-
+-    # need to verify the log for ACL hit as well, since in the allow case
+-    # (unlike the drop case) it is tricky to pass just with the expected;
+-    # since with the stateful rule the packet will still get by (default
+-    # rule) even if it doesn't hit the allow rule.
+-    # The hit count for the ACL is 6 (1 unicast + 2 non-unicast) * 2
+-    # (with/without stateful rule) for hv1 and hv2, each.
+-    cat >>expected <<EOF
+---- acl logging
+-hv1_drop hit 6
+-hv2_drop hit 6
+-hv1_allow hit 6
+-hv2_allow hit 6
+-EOF
+-
+-cat >>received <<EOF
+---- acl logging
+-hv1_drop hit `grep -c 'acl_log.*|INFO|name="drop-acl"' hv1/ovn-controller.log`
+-hv2_drop hit `grep -c 'acl_log.*|INFO|name="drop-acl"' hv2/ovn-controller.log`
+-hv1_allow hit `grep -c 'acl_log.*|INFO|name="allow-acl"' hv1/ovn-controller.log`
+-hv2_allow hit `grep -c 'acl_log.*|INFO|name="allow-acl"' hv2/ovn-controller.log`
+-EOF
+-
+-    $at_diff expected received >/dev/null
+-}
+-OVS_WAIT_UNTIL([check_packets], [$at_diff -F'^---' expected received])
++OVS_WAIT_UNTIL([check_packets 6], [$at_diff -F'^---' expected received])
+ 
+ OVN_CLEANUP([hv1],[hv2],[hv3])
+ 
+@@ -19790,11 +19711,6 @@ test_dhcp() {
+     as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport} $request
+ }
+ 
+-
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ # This shell function sends a DHCPv6 request packet
+ # test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP OUTPORT...
+ # The OUTPORTs (zero or more) list the VIFs on which the original DHCPv6
+@@ -19876,12 +19792,9 @@ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 0 in hv2.
+ OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+-cat ext1_v4.expected | cut -c -48 > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat ext1_v4.expected | cut -c 53- > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
+ 
+ # ovs-ofctl also resumes the packets and this causes other ports to receive
+ # the DHCP request packet. So reset the pcap files so that its easier to test.
+@@ -19903,13 +19816,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 0 in hv2.
+ OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+-sort > ext1_v6.packets
+-cat ext1_v6.expected | cut -c -120 > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
+ # Skipping the UDP checksum
+-cat ext1_v6.expected | cut -c 125- > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
+ 
+ rm -f ext1_v6.expected
+ rm -f ext1_v6.packets
+@@ -19969,12 +19878,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 1 in hv2.
+ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+-cat ext1_v4.expected | cut -c -48 > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat ext1_v4.expected | cut -c 53- > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
+ 
+ # ovs-ofctl also resumes the packets and this causes other ports to receive
+ # the DHCP request packet. So reset the pcap files so that its easier to test.
+@@ -19995,13 +19901,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 2 in hv2.
+ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+-sort > ext1_v6.packets
+-cat ext1_v6.expected | cut -c -120 > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
+ # Skipping the UDP checksum
+-cat ext1_v6.expected | cut -c 125- > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
+ 
+ rm -f ext1_v6.expected
+ rm -f ext1_v6.packets
+@@ -20077,12 +19979,9 @@ OVS_WAIT_UNTIL([test 3 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 2 in hv2.
+ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+-cat ext1_v4.expected | cut -c -48 > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat ext1_v4.expected | cut -c 53- > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
+ 
+ # ovs-ofctl also resumes the packets and this causes other ports to receive
+ # the DHCP request packet. So reset the pcap files so that its easier to test.
+@@ -20104,13 +20003,9 @@ OVS_WAIT_UNTIL([test 4 = `cat ofctl_monitor0_hv1.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 2 in hv2.
+ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+-sort > ext1_v6.packets
+-cat ext1_v6.expected | cut -c -120 > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
+ # Skipping the UDP checksum
+-cat ext1_v6.expected | cut -c 125- > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
+ 
+ rm -f ext1_v6.expected
+ rm -f ext1_v6.packets
+@@ -20158,12 +20053,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 1 in hv3.
+ OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap > ext1_v4.packets
+-cat ext1_v4.expected | cut -c -48 > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c -48"])
+ # Skipping the IPv4 checksum.
+-cat ext1_v4.expected | cut -c 53- > expout
+-AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v4.expected], ["cut -c 53-"])
+ 
+ # ovs-ofctl also resumes the packets and this causes other ports to receive
+ # the DHCP request packet. So reset the pcap files so that its easier to test.
+@@ -20188,13 +20080,9 @@ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c NXT_RESUME`])
+ # NXT_RESUMEs should be 2 in hv3.
+ OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv3.log | grep -c NXT_RESUME`])
+ 
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
+-sort > ext1_v6.packets
+-cat ext1_v6.expected | cut -c -120 > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c -120"])
+ # Skipping the UDP checksum
+-cat ext1_v6.expected | cut -c 125- > expout
+-AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
++OVN_CHECK_PACKETS([hv1/ext1-tx.pcap], [ext1_v6.expected], ["cut -c 125-"])
+ 
+ # disconnect hv3 from the network, hv1 should take over
+ as hv3
+@@ -20568,12 +20456,12 @@ test_ip_packet_larger() {
+         expected=${expected}0000000000000000000000000000
+         echo $expected > br_phys_n1.expected
+     else
+-        src_ip=`ip_to_hex 10 0 0 1`
++        src_ip=`ip_to_hex 172.168.0.100`
+         dst_ip=`ip_to_hex 10 0 0 3`
+         # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload))
+         reply_pkt_len=008e
+         ip_csum=fc97
+-        icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01686b
++        icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01c55f
+         icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu)
+         icmp_reply=${icmp_reply}4500${pkt_len}000000003f01c4dd
+         icmp_reply=${icmp_reply}${orig_packet_l3}
+@@ -20665,7 +20553,7 @@ test_ip6_packet_larger() {
+ 
+     local ipv6_src=10000000000000000000000000000003
+     local ipv6_dst=20000000000000000000000000000002
+-    local ipv6_rt=10000000000000000000000000000001
++    local ipv6_rt=20000000000000000000000000000001
+ 
+     local payload=0000000000000000000000000000000000000000
+     local payload=${payload}0000000000000000000000000000000000000000
+@@ -21223,6 +21111,7 @@ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([ipam to non-ipam])
+ ovn_start
+ 
++check ovn-nbctl --wait=sb sync
+ ovn-nbctl --wait=hv set NB_Global . options:mac_prefix="0a:00:00:00:00:00"
+ ovn-nbctl ls-add sw0
+ ovn-nbctl lsp-add sw0 p0 -- lsp-set-addresses p0 dynamic
+@@ -23426,7 +23315,7 @@ check ovn-nbctl set Logical_Switch sw2 \
+     other_config:mcast_querier="false"
+ 
+ # Enable multicast snooping on sw3.
+-check ovn-nbctl --wait=sb set Logical_Switch sw3       \
++check ovn-nbctl --wait=hv set Logical_Switch sw3       \
+     other_config:mcast_querier="false" \
+     other_config:mcast_snoop="true"
+ 
+@@ -23465,7 +23354,7 @@ OVS_WAIT_UNTIL(
+   [$at_diff -F'^---' exp rcv])
+ 
+ # Enable multicast relay on rtr
+-check ovn-nbctl --wait=sb set logical_router rtr options:mcast_relay="true"
++check ovn-nbctl --wait=hv set logical_router rtr options:mcast_relay="true"
+ 
+ AT_CAPTURE_FILE([sbflows6])
+ ovn-sbctl dump-flows > sbflows6
+@@ -26413,10 +26302,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \
+ wait_for_ports_up
+ 
+ ovn_as az1
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
+ check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
+ check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
+ 
+ ovn_as az2
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
+ check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
+ 
+ dnl Enable unregistered IP multicast flooding and IP multicast relay.
+@@ -26625,10 +26517,13 @@ check ovn-nbctl lsp-add ts ts-lr3 \
+ wait_for_ports_up
+ 
+ ovn_as az1
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr2])
+ check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr3])
+ check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
+ 
+ ovn_as az2
++OVS_WAIT_UNTIL([ovn-nbctl show | grep ts-lr1])
+ check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
+ 
+ dnl Enable IP multicast snooping and IP multicast relay.  Reports are
+@@ -27984,8 +27879,9 @@ ovn-nbctl ls-add ls1
+ ovn-nbctl --wait=sb lsp-add ls1 lsp1
+ 
+ # Simulate the fact that lsp1 had been previously bound on hv1.
+-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
+-    -- --id=@c create chassis name=hv1 encaps=@e \
++ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
++    --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \
++    -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \
+     -- set Port_Binding lsp1 chassis=@c
+ 
+ as hv1
+@@ -28011,8 +27907,9 @@ ovn-nbctl ls-add ls1
+ ovn-nbctl --wait=sb lsp-add ls1 lsp1
+ 
+ # Simulate the fact that lsp1 had been previously bound on hv1.
+-ovn-sbctl --id=@e create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
+-    -- --id=@c create chassis hostname=hv1 name=hv1 encaps=@e \
++ovn-sbctl --id=@e1 create encap chassis_name=hv1 ip="192.168.0.1" type="geneve" \
++    --id=@e2 create encap chassis_name=hv1 ip="192.168.0.1" type="vxlan" \
++    -- --id=@c create chassis name=hv1 encaps=@e1,@e2 \
+     -- set Port_Binding lsp1 chassis=@c
+ 
+ as hv1
+@@ -28137,9 +28034,10 @@ OVS_WAIT_UNTIL([
+ ])
+ 
+ as hv1 check ovs-ofctl del-flows br-phys
++
+ AT_DATA([flows.txt], [dnl
+-table=0, priority=0 actions=NORMAL
+ table=0, priority=200 arp,actions=drop
++table=0, priority=0 actions=NORMAL
+ table=0, priority=100, pkt_mark=0x64 actions=drop
+ table=0, priority=100, pkt_mark=0x2 actions=drop
+ table=0, priority=100, pkt_mark=0x3 actions=drop
+@@ -30785,7 +30683,6 @@ check ovn-nbctl --wait=hv ls-lb-del sw0 lb-ipv6
+ # original destination tuple.
+ #
+ # ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst().
+-as northd-backup ovn-appctl -t NORTHD_TYPE pause
+ as northd ovn-appctl -t NORTHD_TYPE pause
+ 
+ check ovn-sbctl \
+@@ -30836,7 +30733,6 @@ OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_a
+ 
+ # Resume ovn-northd.
+ as northd ovn-appctl -t NORTHD_TYPE resume
+-as northd-backup ovn-appctl -t NORTHD_TYPE resume
+ check ovn-nbctl --wait=hv sync
+ 
+ as hv2 ovs-vsctl del-port hv2-vif1
+@@ -30957,9 +30853,6 @@ AT_CHECK([grep -c $northd_version hv1/ovn-controller.log], [0], [1
+ as northd
+ OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+ 
+-as northd-backup
+-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+-
+ check ovn-sbctl set SB_Global . options:northd_internal_version=foo
+ 
+ as hv1
+@@ -31101,7 +30994,6 @@ AS_BOX([ovn-controller should not reset Port_Binding.up without northd])
+ # Pause northd and clear the "up" field to simulate older ovn-northd
+ # versions writing to the Southbound DB.
+ as northd ovn-appctl -t NORTHD_TYPE pause
+-as northd-backup ovn-appctl -t NORTHD_TYPE pause
+ 
+ as hv1 ovn-appctl -t ovn-controller debug/pause
+ check ovn-sbctl clear Port_Binding lsp1 up
+@@ -31117,7 +31009,6 @@ check_column "" Port_Binding up logical_port=lsp1
+ # Once northd should explicitly set the Port_Binding.up field to 'false' and
+ # ovn-controller sets it to 'true' as soon as the update is processed.
+ as northd ovn-appctl -t NORTHD_TYPE resume
+-as northd-backup ovn-appctl -t NORTHD_TYPE resume
+ wait_column "true" Port_Binding up logical_port=lsp1
+ wait_column "true" nb:Logical_Switch_Port up name=lsp1
+ 
+@@ -31272,7 +31163,6 @@ check ovn-nbctl lsp-set-addresses sw0-p4 "00:00:00:00:00:04 192.168.47.4"
+ # will be sent in one transaction.
+ 
+ check as northd ovn-appctl -t NORTHD_TYPE pause
+-check as northd-backup ovn-appctl -t NORTHD_TYPE pause
+ 
+ check ovn-nbctl lsp-add sw0 sw0-p1
+ check ovn-nbctl lsp-set-addresses sw0-p1 "00:00:00:00:00:01 192.168.47.1"
+@@ -31453,10 +31343,6 @@ send_icmp_packet() {
+     as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
+ }
+ 
+-trim_zeros() {
+-    sed 's/\(00\)\{1,\}$//'
+-}
+-
+ AS_BOX([Wait for all ports to be up])
+ wait_for_ports_up
+ 
+@@ -33727,7 +33613,6 @@ check as hv1 ovn-appctl -t ovn-controller exit
+ 
+ # Pause northd to guarantee that ovn-controller starts before requested_chassis
+ # column is filled.
+-check as northd-backup ovn-appctl -t ovn-northd pause
+ check as northd ovn-appctl -t ovn-northd pause
+ 
+ # Wait until requested_chassis is empty
+@@ -33741,7 +33626,6 @@ wait_column "hv1" Chassis name
+ 
+ # Start northd and wait for events to be processed
+ check as northd ovn-appctl -t ovn-northd resume
+-check as northd-backup ovn-appctl -t ovn-northd resume
+ 
+ wait_for_ports_up lsp1
+ 
+@@ -34471,7 +34355,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke
+ if test -n "$dnat_zone"; then
+   dnat_zone=${dnat_zone::-1}
+ fi
+-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2)
++snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2)
+ if test -n "$snat_zone"; then
+   snat_zone=${snat_zone::-1}
+ fi
+@@ -34488,7 +34372,7 @@ dnat_zone=$(ovs-ofctl dump-flows br-int table=$DNAT_TABLE,metadata=0x${lr0_dp_ke
+ if test -n "$dnat_zone"; then
+   dnat_zone=${dnat_zone::-1}
+ fi
+-snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep -o zone=.*, | cut -d '=' -f 2)
++snat_zone=$(ovs-ofctl dump-flows br-int table=$SNAT_TABLE,metadata=0x${lr0_dp_key} | grep priority=153 | grep ct_state=-trk | grep -o zone=.*, | cut -d '=' -f 2)
+ if test -n "$snat_zone"; then
+   snat_zone=${snat_zone::-1}
+ fi
+@@ -34572,6 +34456,7 @@ AT_CLEANUP
+ 
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([MAC binding aging])
++AT_SKIP_IF([test $HAVE_SCAPY = no])
+ ovn_start
+ 
+ net_add n1
+@@ -35389,37 +35274,6 @@ OVN_CLEANUP([hv1],[hv2])
+ AT_CLEANUP
+ ])
+ 
+-OVN_FOR_EACH_NORTHD([
+-AT_SETUP([feature inactivity probe])
+-ovn_start
+-net_add n1
+-
+-sim_add hv1
+-as hv1
+-check ovs-vsctl add-br br-phys
+-ovn_attach n1 br-phys 192.168.0.1
+-
+-dnl Ensure that there are 4 openflow connections.
+-OVS_WAIT_UNTIL([test "$(grep -c 'negotiated OpenFlow version' hv1/ovs-vswitchd.log)" -eq "4"])
+-
+-dnl "Wait" 3 times 60 seconds and ensure ovn-controller writes to the
+-dnl openflow connections in the meantime.  This should allow ovs-vswitchd
+-dnl to probe the openflow connections at least twice.
+-
+-as hv1 ovs-appctl time/warp 60000
+-check ovn-nbctl --wait=hv sync
+-
+-as hv1 ovs-appctl time/warp 60000
+-check ovn-nbctl --wait=hv sync
+-
+-as hv1 ovs-appctl time/warp 60000
+-check ovn-nbctl --wait=hv sync
+-
+-AT_CHECK([test -z "`grep disconnecting hv1/ovs-vswitchd.log`"])
+-OVN_CLEANUP([hv1])
+-AT_CLEANUP
+-])
+-
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([Logical flows with Chassis_Template_Var references])
+ AT_KEYWORDS([templates])
+@@ -35795,7 +35649,7 @@ as hv1 ovs-vsctl set-ssl \
+ echo hv3 > ${OVN_SYSCONFDIR}/test_hv
+ 
+ # the last argument is passed to ovn-controller through cli
+-ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 -n hv3
++ovn_attach n1 br-phys 192.168.0.1 24 vxlan hv1 hv3
+ 
+ sim_add hv2
+ as hv2
+@@ -36728,7 +36582,6 @@ check ovn-nbctl lsp-add ls2 public2
+ check ovn-nbctl lsp-set-addresses public2 unknown
+ check ovn-nbctl lsp-set-type public2 localnet
+ check ovn-nbctl --wait=sb set Logical_Switch_Port public2 options:qos_min_rate=6000000000 options:qos_max_rate=7000000000 options:qos_burst=8000000000 options:network_name=phys
+-check ovn-nbctl --wait=sb lsp-set-options public2 qos_min_rate=6000000000 qos_max_rate=7000000000 qos_burst=8000000000
+ 
+ # Let's now send ovn controller to sleep, so it will receive both ofport notification and ls deletion simultaneously
+ sleep_controller hv-1
+@@ -36861,14 +36714,14 @@ check ovn-nbctl ls-add sw0
+ check ovn-nbctl lsp-add sw0 sw0-port1
+ check ovn-nbctl lsp-set-addresses sw0-port1 "50:54:00:00:00:01 192.168.0.2"
+ 
++ovs-vsctl add-port br-int p1 -- \
++    set Interface p1 external_ids:iface-id=sw0-port1
++
+ check ovn-nbctl lsp-add sw0 sw0-lr0
+ check ovn-nbctl lsp-set-type sw0-lr0 router
+ check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
+ check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+ 
+-ovs-vsctl add-port br-int p1 -- \
+-    set Interface p1 external_ids:iface-id=sw0-port1
+-
+ check ovn-appctl -t ovn-controller vlog/set dbg:main
+ 
+ wait_for_ports_up
+@@ -37005,3 +36858,548 @@ AT_CHECK([grep -c "NXT_CT_FLUSH_ZONE" hv1/ovs-vswitchd.log], [0], [dnl
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([virtual port claim race condition])
++AT_KEYWORDS([virtual ports])
++ovn_start
++
++send_garp() {
++    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
++    local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
++    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
++}
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovn-appctl vlog/set dbg
++ovs-vsctl -- add-port br-int hv1-vif1 -- \
++    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
++    options:tx_pcap=hv1/vif1-tx.pcap \
++    options:rxq_pcap=hv1/vif1-rx.pcap \
++    ofport-request=1
++ovs-vsctl -- add-port br-int hv1-vif3 -- \
++    set interface hv1-vif3 \
++    options:tx_pcap=hv1/vif3-tx.pcap \
++    options:rxq_pcap=hv1/vif3-rx.pcap \
++    ofport-request=3
++
++ovs-appctl -t ovn-controller vlog/set dbg
++
++ovn-nbctl ls-add sw0
++
++check ovn-nbctl lsp-add sw0 sw0-vir
++check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10"
++check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10"
++check ovn-nbctl lsp-set-type sw0-vir virtual
++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
++check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1
++
++check ovn-nbctl lsp-add sw0 sw0-p1
++check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3 1000::3"
++check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10 1000::3"
++
++# Create a logical router and attach both logical switches
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 1000::a/64
++check ovn-nbctl lsp-add sw0 sw0-lr0
++check ovn-nbctl lsp-set-type sw0-lr0 router
++check ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
++check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
++
++wait_for_ports_up
++ovn-nbctl --wait=hv sync
++hv1_ch_uuid=`ovn-sbctl --bare --columns _uuid find chassis name="hv1"`
++
++# Try to bind sw0-vir directly to an OVS port. This should be ignored by
++# ovn-controller.
++as hv1 ovs-vsctl set interface hv1-vif3 external-ids:iface-id=sw0-vir
++
++# Make sb to sleep, so that claim of sw0-vir (through pinctrl) and hv1-vif3 can be handled within same idl by controller
++sleep_sb
++
++# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir
++# and sw0-p1 should be its virtual_parent.
++eth_src=505400000003
++eth_dst=ffffffffffff
++spa=$(ip_to_hex 10 0 0 10)
++tpa=$(ip_to_hex 10 0 0 10)
++send_garp 1 1 $eth_src $eth_dst $spa $tpa
++
++OVS_WAIT_UNTIL([test 1 = `cat hv1/ovn-controller.log | grep "pinctrl received  packet-in" | \
++grep opcode=BIND_VPORT | grep OF_Table_ID=29 | wc -l`])
++
++sleep_controller hv1
++
++# Cleanup hv1-vif3. This should not interfere with sw0-vir claim
++as hv1 ovs-vsctl del-port hv1-vif3
++
++wake_up_sb
++ovn-nbctl --wait=sb sync
++wake_up_controller hv1
++check ovn-nbctl --wait=hv sync
++
++wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid
++check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1
++wait_for_ports_up sw0-vir
++check ovn-nbctl --wait=hv sync
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([pod to pod with localnet_learn_fdb])
++AT_SKIP_IF([test $HAVE_SCAPY = no])
++
++# 6 VIFs, 3 per HV: vif11, vif12, vif13 on hv1.
++# vif11 will exchange packets with vif21, vif12 w/ vif22 and so on.
++#
++ovn_start
++net_add n1
++
++check ovn-nbctl ls-add ls0
++
++check ovn-nbctl lsp-add ls0 ln_port -- \
++      lsp-set-addresses ln_port unknown -- \
++      lsp-set-type ln_port localnet -- \
++      lsp-set-options ln_port network_name=physnet1
++
++for i in $(seq 1 3); do
++    check ovn-nbctl lsp-add ls0 vif1$i -- \
++          lsp-set-addresses vif1$i unknown
++    check ovn-nbctl lsp-add ls0 vif2$i -- \
++          lsp-set-addresses vif2$i unknown
++done
++
++n_pkt=(0 0 0 0 0 0)
++
++for hv in 1 2; do
++    sim_add hv${hv}
++    as hv${hv}
++    ovs-vsctl add-br br-phys
++    ovn_attach n1 br-phys 192.168.0.${hv}
++
++    for i in $(seq 1 3); do
++        ovs-vsctl -- add-port br-int vif${hv}${i} -- \
++            set interface vif${hv}${i} external-ids:iface-id=vif${hv}${i} \
++            options:tx_pcap=hv${hv}/vif${hv}${i}-tx.pcap \
++            options:rxq_pcap=hv${hv}/vif${hv}${i}-rx.pcap \
++            ofport-request=$i
++    done
++    ovs-vsctl -- add-port br-phys ext0 -- \
++        set interface ext0 \
++        options:tx_pcap=hv${hv}/ext0-tx.pcap \
++        options:rxq_pcap=hv${hv}/ext0-rx.pcap \
++        ofport-request=4
++    ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
++done
++
++OVN_POPULATE_ARP
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++ln_port_key=$(fetch_column port_binding tunnel_key logical_port=ln_port)
++vif11_key=$(fetch_column port_binding tunnel_key logical_port=vif11)
++vif12_key=$(fetch_column port_binding tunnel_key logical_port=vif12)
++vif13_key=$(fetch_column port_binding tunnel_key logical_port=vif13)
++vif21_key=$(fetch_column port_binding tunnel_key logical_port=vif21)
++vif22_key=$(fetch_column port_binding tunnel_key logical_port=vif22)
++vif23_key=$(fetch_column port_binding tunnel_key logical_port=vif23)
++
++ensure_controller_run() {
++# We want to make sure controller could run at least one full loop.
++# We can't use wait=hv as sb might be sleeping.
++# Use 2 ovn-appctl to guarentee that ovn-controller run the full loop, and not just the unixctl handling
++  hv=$1
++  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
++  OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
++}
++
++wait_for_packets() {
++    local hv=$1
++    local vif=$2
++    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
++    n_pkt[[$counter]]=$((${n_pkt[[$counter]]} + 1))
++    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
++    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
++}
++
++check_no_packets() {
++    local hv=$1
++    local vif=$2
++    counter=$(((${hv:2} - 1) * 3 + ${vif:4} - 1))
++    echo "waiting for ${n_pkt[[$counter]]} packets on ${hv}/${vif} using counter $counter"
++    OVS_WAIT_UNTIL([test $(tcpdump ip -nnner ${hv}/${vif}-tx.pcap | wc -l) -eq ${n_pkt[[$counter]]}])
++}
++
++send_packet() {
++    hv=$1
++    a_src=$2
++    a_dst=$3
++    dev=vif$2
++    AS_BOX([$(date +%H:%M:%S.%03N) sending packet from $dev in $hv $a_src to $a_dst])
++    packet=$(fmt_pkt "
++            Ether(dst='00:00:00:00:10:${a_dst}', src='00:00:00:00:10:${a_src}') /
++            IP(src='192.168.10.${a_src}', dst='192.168.10.${a_dst}') /
++            UDP(sport=1234, dport=1235)
++           ")
++    as $hv ovs-appctl netdev-dummy/receive $dev $packet
++}
++
++check_flow_count() {
++    hv=$1
++    count=$2
++    echo "Checking flow count for $hv is $count"
++    OVS_WAIT_UNTIL([test $count = $(as $hv ovs-ofctl dump-flows br-int table=72 | grep -v "NXST_FLOW reply" | wc -l)])
++}
++
++# Sending packet in both direction. Should create FDB entries for vifs
++# No localnet_learn_fdb yet
++AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21])
++send_packet hv1 11 21
++for i in 1 2 3; do
++    wait_for_packets hv2 vif2${i}
++done
++for i in 2 3; do
++    wait_for_packets hv1 vif1${i}
++done
++# vif11 should now own the mac
++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
++
++send_packet hv2 21 11
++wait_for_packets hv1 vif11
++for i in 2 3; do
++    check_no_packets hv1 vif1${i}
++done
++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
++
++check_flow_count hv1 2
++check_flow_count hv2 2
++
++# We now enable localnet_learn_fdb
++# We check how it behaves with existing vif entries in fdb
++check ovn-nbctl --wait=hv set logical_switch_port ln_port options:localnet_learn_fdb=true
++
++AS_BOX([$(date +%H:%M:%S.%03N) vif11 <=> vif21 after learn_fdb])
++send_packet hv1 11 21
++
++wait_for_packets hv2 vif21
++for i in 2 3; do
++    check_no_packets hv2 vif2${i}
++done
++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
++
++send_packet hv2 21 11
++wait_for_packets hv1 vif11
++for i in 2 3; do
++    check_no_packets hv1 vif1${i}
++done
++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
++
++# In both controllers,
++# - 1st packet (in both dir) should have reached controller for the vif, not for localnet (as learn_fdb disabled for localnet)
++# - 2nd packet should not cause any upcall to controller as vif already owns the mac.
++AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++
++check_flow_count hv1 4
++check_flow_count hv2 4
++
++# Send a few more packets
++send_packet hv1 11 21
++send_packet hv2 21 11
++
++for hv in 1 2; do
++    wait_for_packets hv${hv} vif${hv}1
++    for i in 2 3; do
++        echo CHECK
++        check_no_packets hv${hv} vif${hv}${i}
++    done
++done
++
++# The last packets should have gone through the fast path
++AT_CHECK([test 1 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 1 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++
++# Check that there are no bad surprises
++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
++
++# We will now create fdb entries AFTER enabing localnet_learn_fdb
++# We make ovn_controller (hv1 or hv2) to sleep to control who writes first to fdb
++# as otherwise no guarentee.
++AS_BOX([$(date +%H:%M:%S.%03N) vif12 <=> vif22])
++# We make sure that the fdb update by the vif is done after the localnet update
++sleep_controller hv1
++send_packet hv1 12 22
++for i in 1 3; do
++    wait_for_packets hv1 vif1${i}
++done
++for i in 1 2 3; do
++    wait_for_packets hv2 vif2${i}
++done
++
++# ln_port should own the mac as vif not written yet
++wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:12"'
++
++wake_up_controller hv1
++# vif1 should now own the mac
++wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
++
++sleep_controller hv2
++send_packet hv2 22 12
++wait_for_packets hv1 vif12
++for i in 1 3; do
++    check_no_packets hv1 vif1${i}
++done
++wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:22"'
++
++wake_up_controller hv2
++wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
++
++check_flow_count hv1 8
++check_flow_count hv2 8
++
++AS_BOX([$(date +%H:%M:%S.%03N) vif13 <=> vif23 ])
++# Now we do the other way around: we make sure that the localnet update is done after the vif update.
++# So, when packet is sent from vif1 to vif2, vif1 will be learnt (by hv1) and written in sb
++# Then, when we wake up ovn-controller on hv2, it will learn on localnet.
++# This used to cause localnet entry to overwrite vif entry in sb
++sleep_controller hv2
++send_packet hv1 13 23
++for i in 1 2; do
++    wait_for_packets hv1 vif1${i}
++done
++for i in 1 2 3; do
++    wait_for_packets hv2 vif2${i}
++done
++
++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
++
++
++# At this point, FDB contains vif1 entry for mac 00:00:00:00:10:13.
++# However, as hv2 controller is sleeping, the flows in hv2 do not
++# contain the flows related to that fdb entry.
++# Hence, the packet which went through still failed the lookup.
++wake_up_controller hv2
++
++# FDB shoud not have changed. Just make sure controller has run and check fdb
++ensure_controller_run hv2
++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
++
++sleep_controller hv1
++send_packet hv2 23 13
++wait_for_packets hv1 vif13
++for i in 1 2; do
++    check_no_packets hv2 vif2${i}
++done
++for i in 1 2; do
++    check_no_packets hv1 vif1${i}
++done
++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
++
++wake_up_controller hv1
++ensure_controller_run hv1
++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
++
++# In both controllers
++# - vif11 <=> vif21: 1 PACKET_IN
++# - vif12 <=> vif22: 2 PACKET_IN
++# - vif13 <=> vif23: 2 PACKET_IN
++# controller + .
++AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++
++# Send a few more packets
++send_packet hv1 13 23
++send_packet hv2 23 13
++wait_for_packets hv2 vif23
++wait_for_packets hv1 vif13
++for i in 1 2; do
++    check_no_packets hv1 vif1${i}
++    check_no_packets hv2 vif2${i}
++done
++send_packet hv1 13 23
++send_packet hv2 23 13
++wait_for_packets hv2 vif23
++wait_for_packets hv1 vif13
++for i in 1 2; do
++    check_no_packets hv1 vif1${i}
++    check_no_packets hv2 vif2${i}
++done
++
++# The last packets should have gone through the fast path
++AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 5 = `cat hv2/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc -l`])
++
++# Check that there are no bad surprises
++wait_column "$vif11_key" fdb port_key mac='"00:00:00:00:10:11"'
++wait_column "$vif12_key" fdb port_key mac='"00:00:00:00:10:12"'
++wait_column "$vif13_key" fdb port_key mac='"00:00:00:00:10:13"'
++wait_column "$vif21_key" fdb port_key mac='"00:00:00:00:10:21"'
++wait_column "$vif22_key" fdb port_key mac='"00:00:00:00:10:22"'
++wait_column "$vif23_key" fdb port_key mac='"00:00:00:00:10:23"'
++
++check_flow_count hv1 12
++check_flow_count hv2 12
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([port up with slow northd])
++ovn_start
++
++sleep_northd() {
++  echo northd going to sleep
++  AT_CHECK([kill -STOP $(cat northd/ovn-northd.pid)])
++}
++
++wake_up_northd() {
++  echo northd going to sleep
++  AT_CHECK([kill -CONT $(cat northd/ovn-northd.pid)])
++}
++
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.11
++
++check ovn-nbctl --wait=hv ls-add ls0
++# Create a pilot port and wait it up to make sure we are ready for the real
++# tests, so that the counters measured are accurate.
++check ovn-nbctl --wait=hv lsp-add ls0 lsp-pilot -- lsp-set-addresses lsp-pilot "unknown"
++ovs-vsctl add-port br-int lsp-pilot -- set interface lsp-pilot external_ids:iface-id=lsp-pilot
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++check ovn-nbctl --wait=hv lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
++ovs-vsctl add-port br-int lsp0-2 -- set interface lsp0-2 external_ids:iface-id=lsp0-2
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++sleep_northd
++check ovn-nbctl lsp-del lsp0-2
++check ovn-nbctl lsp-add ls0 lsp0-2 -- lsp-set-addresses lsp0-2 "aa:aa:aa:00:00:02 192.168.0.12"
++wake_up_northd
++
++check ovn-nbctl --wait=sb sync
++wait_for_ports_up
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([gratuitous arp max timeout])
++AT_KEYWORDS([slowtest])
++TAG_UNSTABLE
++AT_SKIP_IF([test $HAVE_TCPDUMP = no])
++ovn_start
++
++check ovn-nbctl ls-add ls0
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-ls0 f0:00:00:00:00:01 192.168.0.1/24
++check ovn-nbctl lsp-add ls0 ls0-lr0 -- set Logical_Switch_Port ls0-lr0 \
++    type=router options:router-port=lr0-ls0 addresses='"f0:00:00:00:00:01"'
++
++check ovn-nbctl lsp-add ls0 ln_port
++check ovn-nbctl lsp-set-addresses ln_port unknown
++check ovn-nbctl lsp-set-type ln_port localnet
++check ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1
++
++net_add n1
++sim_add hv1
++as hv1
++check ovs-vsctl \
++    -- add-br br-phys \
++    -- add-br br-eth0
++
++ovn_attach n1 br-phys 192.168.0.10
++
++check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0
++check ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap
++
++# set garp max timeout to 2s
++as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=2
++
++# Wait until the patch ports are created in hv1 to connect br-int to br-eth0
++check ovn-nbctl set logical_router lr0 options:chassis=hv1
++OVN_WAIT_PATCH_PORT_FLOWS(["ln_port"], ["hv1"])
++
++# sleep for 12s to get a garp every ~ 2s
++sleep 12
++
++n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l)
++AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10])
++
++# Temporarily remove lr0 chassis
++# Wait for hv confirmation to make sure chassis is removed before we reset pcap
++# Otherwise a garp might be sent after pcap have been reset but before chassis is removed
++check ovn-nbctl --wait=hv remove logical_router lr0 options chassis
++
++as hv1 reset_pcap_file snoopvif hv1/snoopvif
++# set garp max timeout to 1s
++as hv1 check ovs-vsctl set Open_vSwitch . external-ids:garp-max-timeout-sec=1
++check ovn-nbctl set logical_router lr0 options:chassis=hv1
++
++# sleep for 7s to get a garp every ~ 1s
++sleep 7
++
++n_arp=$(tcpdump -ner hv1/snoopvif-tx.pcap arp | wc -l)
++AT_CHECK([test $n_arp -ge 5 -a $n_arp -lt 10])
++
++OVN_CLEANUP([hv1])
++
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Changing tunnel_key])
++ovn_start
++
++net_add n1
++
++sim_add hv1
++as hv1
++check ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.11
++
++check ovn-nbctl --wait=hv ls-add ls \
++          -- lsp-add ls lsp1 \
++          -- lsp-add ls ls-lr \
++          -- lr-add lr \
++          -- lrp-add lr lr-ls f0:00:00:00:00:f1 192.168.1.1/24 \
++          -- set Logical_Switch_Port ls-lr \
++               type=router \
++               options:router-port=lr-ls \
++               addresses=router \
++          -- lrp-set-gateway-chassis lr-ls hv1
++
++sleep_controller hv1
++
++check ovn-nbctl --wait=sb set Logical_Switch ls other_config:requested-tnl-key=1000
++check ovn-nbctl --wait=sb ls-del ls
++wake_up_controller hv1
++
++check ovn-nbctl --wait=hv sync
++
++check ovn-nbctl --wait=hv ls-add ls1 \
++      -- lsp-add ls1 ls1-lr \
++      -- lrp-add lr lr-ls1 f0:00:00:00:00:f2 192.168.2.1/24 \
++      -- set Logical_Switch_Port ls1-lr type=router options:router-port=lr-ls1 addresses=router \
++      -- lrp-set-gateway-chassis lr-ls1 hv1
++
++check ovn-nbctl --wait=hv set Logical_Switch ls1 other_config:requested-tnl-key=1001
++
++OVN_CLEANUP([hv1])
++
++AT_CLEANUP
++])
+diff --git a/tests/scapy-server.py b/tests/scapy-server.py
+new file mode 100755
+index 000000000..1cc616f70
+--- /dev/null
++++ b/tests/scapy-server.py
+@@ -0,0 +1,81 @@
++#!/usr/bin/env python3
++
++import argparse
++import time
++
++import ovs.daemon
++import ovs.unixctl
++import ovs.unixctl.server
++
++import binascii
++from scapy.all import *  # noqa: F401,F403
++from scapy.all import raw
++
++
++vlog = ovs.vlog.Vlog("scapy-server")
++exiting = False
++
++
++def exit(conn, argv, aux):
++    global exiting
++
++    exiting = True
++    conn.reply(None)
++
++
++def process(data):
++    start_time = time.perf_counter()
++    vlog.info(f"received payload request: {data}")
++    try:
++        data = data.replace('\n', '')
++        return binascii.hexlify(raw(eval(data))).decode()
++    except Exception as e:
++        vlog.exception(f"failed to process payload request: {e}")
++        return ""
++    finally:
++        total_time = (time.perf_counter() - start_time) * 1000
++        vlog.info(f"took {total_time:.2f}ms to process payload request")
++
++
++def payload(conn, argv, aux):
++    try:
++        conn.reply(process(argv[0]))
++    except Exception as e:
++        vlog.exception(f"failed to reply to payload request: {e}")
++
++
++def main():
++    parser = argparse.ArgumentParser(
++        description="Scapy-based Frame Payload Generator")
++    parser.add_argument("--unixctl", help="UNIXCTL socket location or 'none'.")
++
++    ovs.daemon.add_args(parser)
++    ovs.vlog.add_args(parser)
++    args = parser.parse_args()
++    ovs.daemon.handle_args(args)
++    ovs.vlog.handle_args(args)
++
++    ovs.daemon.daemonize_start()
++    error, server = ovs.unixctl.server.UnixctlServer.create(args.unixctl)
++    if error:
++        ovs.util.ovs_fatal(error, "could not create unixctl server at %s"
++                           % args.unixctl, vlog)
++
++    ovs.unixctl.command_register("exit", "", 0, 0, exit, None)
++    ovs.unixctl.command_register("payload", "", 1, 1, payload, None)
++    ovs.daemon.daemonize_complete()
++
++    vlog.info("scapy server ready")
++
++    poller = ovs.poller.Poller()
++    while not exiting:
++        server.run()
++        server.wait(poller)
++        if exiting:
++            poller.immediate_wake()
++        poller.block()
++    server.close()
++
++
++if __name__ == '__main__':
++    main()
+diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
+index 97c6e433b..65c4884ea 100644
+--- a/tests/system-common-macros.at
++++ b/tests/system-common-macros.at
+@@ -372,7 +372,7 @@ ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
+          "192.168.2.1")
+ ADD_NAMESPACES(server)
+ ADD_VETH(s1, server, br-ext, "2001:1db8:3333::2/64", "f0:00:00:01:02:05", \
+-         "2001:1db8:3333::1")
++         "2001:1db8:3333::1", "nodad")
+ 
+ if test X"$1" = X"GR"; then
+    ovn-nbctl create Logical_Router name=R1 options:chassis=hv1
+@@ -409,9 +409,6 @@ ovn-nbctl lsp-add sw0 sw01 \
+ ovn-nbctl lsp-add sw1 sw11 \
+     -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep 2001:1db8:3333::2 | grep tentative)" = ""])
+-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
+-
+ AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
+ ovn-nbctl lsp-add public public1 \
+         -- lsp-set-addresses public1 unknown \
+diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
+index b29e6b55a..039d71170 100644
+--- a/tests/system-ovn-kmod.at
++++ b/tests/system-ovn-kmod.at
+@@ -1,224 +1,5 @@
+ AT_BANNER([system-ovn-kmod])
+ 
+-# SCTP and userspace conntrack do not mix. Therefore this
+-# test only can be run with kernel datapath. Otherwise,
+-# this is mostly a copy of existing load balancer tests
+-# in system-ovn.at
+-AT_SETUP([load balancing in gateway router - SCTP])
+-AT_SKIP_IF([test $HAVE_SCTP = no])
+-AT_SKIP_IF([test $HAVE_NC = no])
+-AT_KEYWORDS([ovnlb sctp])
+-
+-# Make sure the SCTP kernel module is loaded.
+-LOAD_MODULE([sctp])
+-
+-CHECK_CONNTRACK()
+-CHECK_CONNTRACK_NAT()
+-ovn_start
+-OVS_TRAFFIC_VSWITCHD_START()
+-ADD_BR([br-int])
+-
+-# 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 ovn-controller
+-start_daemon ovn-controller
+-
+-# Logical network:
+-# Two LRs - R1 and R2 that are connected to each other via LS "join"
+-# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
+-# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
+-# to it.  R2 is a gateway router on which we add load-balancing rules.
+-#
+-#    foo -- R1 -- join - R2 -- alice
+-#           |
+-#    bar ----
+-
+-ovn-nbctl create Logical_Router name=R1
+-ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
+-
+-ovn-nbctl ls-add foo
+-ovn-nbctl ls-add bar
+-ovn-nbctl ls-add alice
+-ovn-nbctl ls-add join
+-
+-# Connect foo to R1
+-ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
+-ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
+-    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
+-
+-# Connect bar to R1
+-ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
+-ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
+-    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
+-
+-# Connect alice to R2
+-ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
+-ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+-    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
+-
+-# Connect R1 to join
+-ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
+-ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
+-    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
+-
+-# Connect R2 to join
+-ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
+-ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
+-    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
+-
+-# Static routes.
+-ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
+-ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
+-
+-# Logical port 'foo1' in switch 'foo'.
+-ADD_NAMESPACES(foo1)
+-ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+-         "192.168.1.1")
+-ovn-nbctl lsp-add foo foo1 \
+--- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
+-
+-# Logical port 'alice1' in switch 'alice'.
+-ADD_NAMESPACES(alice1)
+-ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
+-         "172.16.1.1")
+-ovn-nbctl lsp-add alice alice1 \
+--- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
+-
+-# Logical port 'bar1' in switch 'bar'.
+-ADD_NAMESPACES(bar1)
+-ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
+-"192.168.2.1")
+-ovn-nbctl lsp-add bar bar1 \
+--- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
+-
+-# Config OVN load-balancer with a VIP.
+-uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
+-ovn-nbctl set logical_router R2 load_balancer=$uuid
+-
+-# Config OVN load-balancer with another VIP (this time with ports).
+-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
+-
+-# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule.
+-ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
+-    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
+-
+-# Wait for ovn-controller to catch up.
+-ovn-nbctl --wait=hv sync
+-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
+-grep 'nat(dst=192.168.2.2:12345)'])
+-
+-# Start webservers in 'foo1', 'bar1'.
+-OVS_START_L7([foo1], [sctp])
+-OVS_START_L7([bar1], [sctp])
+-
+-on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int"
+-
+-dnl Should work with the virtual IP address through NAT
+-for i in `seq 1 20`; do
+-    echo Request $i
+-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log])
+-done
+-
+-dnl Each server should have at least one connection.
+-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
+-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
+-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-])
+-
+-dnl Test load-balancing that includes L4 ports in NAT.
+-for i in `seq 1 20`; do
+-    echo Request $i
+-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+-done
+-
+-dnl Each server should have at least one connection.
+-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
+-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
+-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-])
+-
+-check_est_flows () {
+-    n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \
+-        | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p')
+-
+-    echo "n_packets=$n"
+-    test -n "$n" && test "$n" != "0"
+-}
+-
+-OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows])
+-
+-
+-ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2"
+-
+-# Destroy the load balancer and create again. ovn-controller will
+-# clear the OF flows and re add again and clears the n_packets
+-# for these flows.
+-ovn-nbctl destroy load_balancer $uuid
+-uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
+-ovn-nbctl set logical_router R2 load_balancer=$uuid
+-
+-check ovs-appctl dpctl/flush-conntrack
+-
+-# Config OVN load-balancer with another VIP (this time with ports).
+-ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
+-
+-ovn-nbctl list load_balancer
+-ovn-sbctl dump-flows R2
+-OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)'])
+-
+-dnl Test load-balancing that includes L4 ports in NAT.
+-for i in `seq 1 20`; do
+-    echo Request $i
+-    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
+-done
+-
+-dnl Each server should have at least one connection.
+-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
+-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
+-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-])
+-
+-AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
+-sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
+-sed -e 's/vtag_orig=[[0-9]]*/vtag_orig=<cleared>/' |
+-sed -e 's/vtag_reply=[[0-9]]*/vtag_reply=<cleared>/' | uniq], [0], [dnl
+-sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>,vtag_orig=<cleared>,vtag_reply=<cleared>)
+-])
+-
+-OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows])
+-
+-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([ovn-northd])
+-
+-as
+-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+-/connection dropped.*/d"])
+-AT_CLEANUP
+-
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([load balancing affinity sessions - IPv4])
+ AT_KEYWORDS([ovnlb])
+@@ -365,7 +146,7 @@ tcp,orig=(src=172.16.1.2,dst=172.16.1.100,sport=<cleared>,dport=<cleared>),reply
+ ])
+ 
+ dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2))
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a80<cleared>02/'], [0], [dnl
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xc0a80[[0-9]]02/load:0xc0a80<cleared>02/'], [0], [dnl
+  table=78, idle_timeout=60, tcp,metadata=$dp_key,nw_src=172.16.1.2,nw_dst=172.16.1.100,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0xc0a80<cleared>02->NXM_NX_REG4[[]],load:0x50->NXM_NX_REG8[[0..15]]
+ ])
+ 
+@@ -588,31 +369,27 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd20::1
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip -n foo1 a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd72::2/64", "f0:00:00:01:02:04", \
+-         "fd72::1")
+-OVS_WAIT_UNTIL([test "$(ip -n alice1 a | grep fd72::2 | grep tentative)" = ""])
++         "fd72::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd72::2"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
+-"fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip -n bar1 a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
+ 
+ ADD_NAMESPACES(bar2)
+ ADD_VETH(bar2, bar2, br-int, "fd12::3/64", "e0:00:00:01:02:05", \
+-"fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip -n bar2 a | grep fd12::3 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar2 \
+ -- lsp-set-addresses bar2 "e0:00:00:01:02:05 fd12::3"
+ 
+@@ -666,7 +443,7 @@ tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd
+ ])
+ 
+ dp_key=$(printf "0x%x" $(fetch_column datapath tunnel_key external_ids:name=R2))
+-AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1<cleared>000000000000/'], [0], [dnl
++AT_CHECK_UNQUOTED([ovs-ofctl dump-flows br-int table=78 --no-stats | strip_cookie | sed -e 's/load:0xfd1[[0-9]]000000000000/load:0xfd1<cleared>000000000000/'], [0], [dnl
+  table=78, idle_timeout=60, tcp6,metadata=$dp_key,ipv6_src=fd72::2,ipv6_dst=fd30::1,tp_dst=8080 actions=load:0x1->NXM_NX_REG10[[14]],load:0x2->NXM_NX_XXREG1[[0..63]],load:0xfd1<cleared>000000000000->NXM_NX_XXREG1[[64..127]],load:0x50->NXM_NX_REG8[[0..15]]
+ ])
+ 
+@@ -1115,3 +892,155 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ /connection dropped.*/d"])
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([LR with SNAT fragmentation needed for external server])
++AT_KEYWORDS([ovnlb])
++
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-ext])
++
++dnl Logical network:
++dnl 2 logical switches "public" (192.168.1.0/24) and "internal" (172.16.1.0/24)
++dnl connected to a router lr.
++dnl internal has a client.
++dnl server is connected through localnet.
++dnl
++dnl Server IP 192.168.1.2 MTU 900
++dnl Client IP  172.16.1.2 MTU 800
++dnl
++dnl SNAT for internal 172.16.1.2/24 with router ip 192.168.1.1.
++
++check ovs-ofctl add-flow br-ext action=normal
++# Set external-ids in br-int needed for ovn-controller
++check 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 \
++        -- set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext
++
++dnl Start ovn-controller
++start_daemon ovn-controller
++
++check ovn-nbctl lr-add lr
++check ovn-nbctl ls-add internal
++check ovn-nbctl ls-add public
++
++check ovn-nbctl lrp-add lr lr-pub 00:00:01:01:02:03 192.168.1.1/24
++check ovn-nbctl lsp-add  public pub-lr -- set Logical_Switch_Port pub-lr \
++    type=router options:router-port=lr-pub addresses=\"00:00:01:01:02:03\"
++
++check ovn-nbctl lrp-add lr lr-internal 00:00:01:01:02:04 172.16.1.1/24
++check ovn-nbctl lsp-add internal internal-lr -- set Logical_Switch_Port internal-lr \
++    type=router options:router-port=lr-internal addresses=\"00:00:01:01:02:04\"
++
++ovn-nbctl lsp-add public ln_port \
++                -- lsp-set-addresses ln_port unknown \
++                -- lsp-set-type ln_port localnet \
++                -- lsp-set-options ln_port network_name=phynet
++
++ADD_NAMESPACES(server)
++ADD_VETH([server], [server], [br-ext], ["192.168.1.2/24"],
++         ["f0:00:00:01:02:03"], ["192.168.1.1"])
++NS_EXEC([server], [ip l set dev server mtu 900])
++NS_EXEC([server], [ip l show dev server])
++
++ADD_NAMESPACES(client)
++ADD_VETH([client], [client], [br-int], ["172.16.1.2/24"],
++         ["f0:00:0f:01:02:03"], ["172.16.1.1"])
++NS_EXEC([client], [ip l set dev client mtu 800])
++NS_EXEC([client], [ip l show dev client])
++check ovn-nbctl lsp-add internal client \
++  -- lsp-set-addresses client "f0:00:0f:01:02:03 172.16.1.2"
++
++dnl Config OVN load-balancer with a VIP.  (not necessary, but if we do not
++dnl have a load balancer and comment out snat, we will receive a stray fragment
++dnl on the client side.)
++dnl check ovn-nbctl lb-add lb1 192.168.1.20:4242 172.16.1.2:4242 udp
++dnl check ovn-nbctl lr-lb-add lr lb1
++check ovn-nbctl set logical_router lr options:chassis=hv1
++check ovn-nbctl set logical_router_port lr-internal options:gateway_mtu=800
++
++check ovn-nbctl lr-nat-add lr snat 192.168.1.1 172.16.1.2/24
++
++check ovn-nbctl --wait=hv sync
++
++ovn-nbctl show
++ovs-vsctl show
++ovn-appctl -t ovn-controller vlog/set vconn:file:dbg pinctrl:file:dbg
++
++AT_DATA([server.py], [dnl
++import socket
++
++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
++
++server_address = '192.168.1.2'
++server_port = 4242
++
++server = (server_address, server_port)
++sock.bind(server)
++print("Listening on ", server_address, ":", str(server_port), flush=True)
++
++while True:
++  payload, client_address = sock.recvfrom(1000)
++  print("Received data from ", str(client_address), ": ", payload)
++  sent = sock.sendto(b"x" * 1017, client_address)
++  print("Sent back: ", str(sent), "bytes", flush=True)
++])
++NETNS_DAEMONIZE([server], [$PYTHON3 ./server.py > server.log], [server.pid])
++
++dnl Collect packets on server side.
++NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \
++          'ip and (icmp or udp)' > server.tcpdump 2>server_err], [tcpdump0.pid])
++OVS_WAIT_UNTIL([grep "listening" server_err])
++
++dnl Collect packets on client side.
++NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \
++          'ip and (icmp or udp)' > client.tcpdump 2>client_err], [tcpdump1.pid])
++OVS_WAIT_UNTIL([grep "listening" client_err])
++
++dnl Send two packets to the server with a short interval.
++dnl First packet should generate 'needs frag', the second should result in
++dnl corectly fragmented reply.
++AT_DATA([client.py], [dnl
++import socket
++import time
++
++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
++sock.sendto(b"x" * 7, ("192.168.1.2", 4242))
++time.sleep(1)
++sock.sendto(b"x" * 7, ("192.168.1.2", 4242))
++time.sleep(5)
++])
++NS_CHECK_EXEC([client], [$PYTHON3 ./client.py])
++
++dnl Expecting 2 outgoing packets and 2 fragments back - 8 lines total.
++OVS_WAIT_UNTIL([test "$(cat client.tcpdump | wc -l)" = "8"])
++
++ovn-appctl -t ovn-controller vlog/set info
++
++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/tests/system-ovn.at b/tests/system-ovn.at
+index 59d0cb2a0..3ecf1db42 100644
+--- a/tests/system-ovn.at
++++ b/tests/system-ovn.at
+@@ -251,24 +251,21 @@ ovn-nbctl lr-route-add R2 fd12::/64 fd00::1
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd21::2/64", "f0:00:00:01:02:04", \
+-         "fd21::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd21::2 | grep tentative)" = ""])
++         "fd21::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd21::2"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
+-         "fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
+ 
+@@ -538,16 +535,14 @@ ovn-nbctl lr-route-add R2 fd10::/64 fd20::1
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd10::2/64", "f0:00:00:01:02:03", \
+-         "fd10::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd10::2 | grep tentative)" = ""])
++         "fd10::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd10::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd30::2/64", "f0:00:00:01:02:04", \
+-         "fd30::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::2 | grep tentative)" = ""])
++         "fd30::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::2"
+ 
+@@ -908,32 +903,28 @@ ovn-nbctl set logical_router R3 options:dnat_force_snat_ip=fd20::3
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd30::3/64", "f0:00:00:01:02:04", \
+-         "fd30::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd30::3 | grep tentative)" = ""])
++         "fd30::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd30::3"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:05", \
+-         "fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:05 fd12::2"
+ 
+ # Logical port 'bob1' in switch 'bob'.
+ ADD_NAMESPACES(bob1)
+ ADD_VETH(bob1, bob1, br-int, "fd30::4/64", "f0:00:00:01:02:06", \
+-         "fd30::2")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd30::4 | grep tentative)" = ""])
++         "fd30::2", "nodad")
+ ovn-nbctl lsp-add bob bob1 \
+ -- lsp-set-addresses bob1 "f0:00:00:01:02:06 fd30::4"
+ 
+@@ -1149,8 +1140,7 @@ ovn-nbctl lsp-add foo foo1 \
+ 
+ ADD_NAMESPACES(foo16)
+ ADD_VETH(foo16, foo16, br-int, "fd11::2/64", "f0:00:00:02:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo16 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo16 \
+ -- lsp-set-addresses foo16 "f0:00:00:02:02:03 fd11::2"
+ 
+@@ -1163,8 +1153,7 @@ ovn-nbctl lsp-add alice alice1 \
+ 
+ ADD_NAMESPACES(alice16)
+ ADD_VETH(alice16, alice16, br-int, "fd30::3/64", "f0:00:00:02:02:04", \
+-         "fd30::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice16 ip a | grep fd30::3 | grep tentative)" = ""])
++         "fd30::1", "nodad")
+ ovn-nbctl lsp-add alice alice16 \
+ -- lsp-set-addresses alice16 "f0:00:00:02:02:04 fd30::3"
+ 
+@@ -1177,8 +1166,7 @@ ovn-nbctl lsp-add bar bar1 \
+ 
+ ADD_NAMESPACES(bar16)
+ ADD_VETH(bar16, bar16, br-int, "fd12::2/64", "f0:00:00:02:02:05", \
+-         "fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bar16 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar16 \
+ -- lsp-set-addresses bar16 "f0:00:00:02:02:05 fd12::2"
+ 
+@@ -1191,8 +1179,7 @@ ovn-nbctl lsp-add bob bob1 \
+ 
+ ADD_NAMESPACES(bob16)
+ ADD_VETH(bob16, bob16, br-int, "fd30::4/64", "f0:00:00:02:02:06", \
+-         "fd30::2")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bob16 ip a | grep fd30::4 | grep tentative)" = ""])
++         "fd30::2", "nodad")
+ ovn-nbctl lsp-add bob bob16 \
+ -- lsp-set-addresses bob16 "f0:00:00:02:02:06 fd30::4"
+ 
+@@ -2376,14 +2363,10 @@ ADD_NAMESPACES(server)
+ ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \
+          "172.16.1.1")
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
+-
+ ADD_NAMESPACES(client)
+ ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \
+          "172.16.1.1")
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""])
+-
+ # Start webservers in 'server'.
+ OVS_START_L7([server], [http])
+ 
+@@ -3670,32 +3653,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'foo2' in switch 'foo'.
+ ADD_NAMESPACES(foo2)
+ ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo2 \
+ -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \
+-         "fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \
+-         "fd20::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""])
++         "fd20::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2"
+ 
+@@ -4018,32 +3997,28 @@ ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'foo2' in switch 'foo'.
+ ADD_NAMESPACES(foo2)
+ ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:06", \
+-         "fd11::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
++         "fd11::1", "nodad")
+ ovn-nbctl lsp-add foo foo2 \
+ -- lsp-set-addresses foo2 "f0:00:00:01:02:06 fd11::3"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:04", \
+-         "fd12::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bar1 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd12::1", "nodad")
+ ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:04 fd12::2"
+ 
+ # Logical port 'alice1' in switch 'alice'.
+ ADD_NAMESPACES(alice1)
+ ADD_VETH(alice1, alice1, br-int, "fd20::2/64", "f0:00:00:01:02:05", \
+-         "fd20::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd20::2 | grep tentative)" = ""])
++         "fd20::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:05 fd20::2"
+ 
+@@ -4928,8 +4903,7 @@ ovn-nbctl lsp-add sw sw-rtr                       \
+     -- lsp-set-options sw-rtr router-port=rtr-sw
+ 
+ ADD_NAMESPACES(lsp)
+-ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff")
+-OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" = ""])
++ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", "4200::00ff", "nodad")
+ ovn-nbctl --wait=hv -t 3 sync
+ 
+ # Start IPv6 TCP server on lsp.
+@@ -5058,8 +5032,8 @@ ADD_NAMESPACES(sw0-p2-rej)
+ ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+          "10.0.0.1")
+ 
+-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
++NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0])
++NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0])
+ 
+ ADD_NAMESPACES(sw1-p1-rej)
+ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+@@ -5099,9 +5073,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r
+ #Wait for tcpdump to get started before generating first packets
+ OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""])
+-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""])
+-
+ OVS_WAIT_UNTIL([
+     ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+@@ -5308,8 +5279,8 @@ ADD_NAMESPACES(sw0-p2-rej)
+ ADD_VETH(sw0-p2-rej, sw0-p2-rej, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \
+          "10.0.0.1")
+ 
+-NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej], [0])
+-NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej], [0])
++NS_CHECK_EXEC([sw0-p1-rej], [ip a a aef0::3/64 dev sw0-p1-rej nodad], [0])
++NS_CHECK_EXEC([sw0-p2-rej], [ip a a aef0::4/64 dev sw0-p2-rej nodad], [0])
+ 
+ ADD_NAMESPACES(sw1-p1-rej)
+ ADD_VETH(sw1-p1-rej, sw1-p1-rej, br-int, "20.0.0.3/24", "40:54:00:00:00:03", \
+@@ -5349,9 +5320,6 @@ NS_CHECK_EXEC([sw0-p2-rej], [tcpdump -l -nn -i sw0-p2-rej tcp port 80 > sw0-p2-r
+ #Wait for tcpdump to get started before generating first packets
+ OVS_WAIT_UNTIL([test 1 = $(cat err | grep -c listening)])
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p1-rej ip a | grep aef0::3 | grep tentative)" = ""])
+-OVS_WAIT_UNTIL([test "$(ip netns exec sw0-p2-rej ip a | grep aef0::4 | grep tentative)" = ""])
+-
+ OVS_WAIT_UNTIL([
+     ip netns exec sw0-p2-rej nc -vz6 aef0::3 80 2>&1 | grep -i 'connection refused'
+ ])
+@@ -5878,12 +5846,10 @@ check ovn-nbctl                                                  \
+     -- ls-lb-add ls lb-test
+ 
+ ADD_NAMESPACES(vm1)
+-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""])
++ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad")
+ 
+ ADD_NAMESPACES(vm2)
+-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""])
++ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad")
+ 
+ # Wait for ovn-controller to catch up.
+ wait_for_ports_up
+@@ -6306,8 +6272,7 @@ NS_CHECK_EXEC([alice1], [sysctl -w net.ipv6.conf.default.router_solicitations=1]
+ net.ipv6.conf.default.router_solicitations = 1
+ ])
+ ADD_VETH(alice1, alice1, br-int, "fd01::2/64", "f0:00:00:01:02:04", \
+-         "fd01::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd01::2 | grep tentative)" = ""])
++         "fd01::1", "nodad")
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd01::2"
+ # Add neighbour MAC address to avoid sending IPv6 NS messages which could
+@@ -6322,8 +6287,7 @@ NS_CHECK_EXEC([bob1], [sysctl -w net.ipv6.conf.default.router_solicitations=1],
+ net.ipv6.conf.default.router_solicitations = 1
+ ])
+ ADD_VETH(bob1, bob1, br-int, "fd07::1/64", "f0:00:00:01:02:06", \
+-         "fd07::2")
+-OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd07::1 | grep tentative)" = ""])
++         "fd07::2", "nodad")
+ # Add neighbour MAC addresses to avoid sending IPv6 NS messages which could
+ # cause datapath flows to be evicted
+ NS_CHECK_EXEC([bob1], [ip -6 neigh add fd07::2 lladdr 00:00:02:01:02:03 dev bob1], [0])
+@@ -6513,8 +6477,6 @@ ADD_NAMESPACES(ext-foo)
+ ADD_VETH(ext-foo, ext-foo, br-ext, "172.16.1.100/24", "00:10:10:01:02:13", \
+          "172.16.1.1")
+ 
+-OVS_WAIT_UNTIL([test "$(ip netns exec ext-foo ip a | grep fe80 | grep tentative)" = ""])
+-
+ AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
+ ovn-nbctl lsp-add public public1 \
+         -- lsp-set-addresses public1 unknown \
+@@ -6630,6 +6592,28 @@ AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000])
+ AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000])
+ AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000])
+ 
++OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public'])
++OVS_WAIT_UNTIL([tc class show dev ovs-public | \
++                grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b'])
++
++OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-ext'])
++OVS_WAIT_UNTIL([tc class show dev ovs-ext | \
++                grep -q 'class htb .* rate 400Kbit ceil 600Kbit burst 750000b cburst 750000b'])
++
++# The same now with ovs db read only
++#
++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_min_rate=400000])
++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_max_rate=600000])
++AT_CHECK([ovn-nbctl remove Logical_Switch_Port ext options qos_burst=6000000])
++OVS_WAIT_UNTIL([test "$(tc class show dev ovs-ext | grep 'class htb')" == ""])
++
++sleep_ovsdb .
++
++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_min_rate=400000])
++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_max_rate=600000])
++AT_CHECK([ovn-nbctl set Logical_Switch_Port ext options:qos_burst=6000000])
++wake_up_ovsdb .
++
+ OVS_WAIT_UNTIL([tc qdisc show | grep -q 'htb 1: dev ovs-public'])
+ OVS_WAIT_UNTIL([tc class show dev ovs-public | \
+                 grep -q 'class htb .* rate 200Kbit ceil 300Kbit burst 375000b cburst 375000b'])
+@@ -7348,9 +7332,9 @@ check ovn-nbctl lsp-add public public1 \
+ 
+ NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
+ check ovn-nbctl meter-add acl-meter drop 1 pktps 0
+-check ovn-nbctl --wait=hv copp-add copp0 reject acl-meter
+-check ovn-nbctl --wait=hv ls-copp-add copp0 sw0
+-check ovn-nbctl acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject
++check ovn-nbctl copp-add copp0 reject acl-meter
++check ovn-nbctl ls-copp-add copp0 sw0
++check ovn-nbctl --wait=hv acl-add sw0 from-lport 1002 'inport == "sw01" && ip && udp' reject
+ 
+ AT_CHECK([ovn-nbctl copp-list copp0], [0], [dnl
+ reject: acl-meter
+@@ -7371,7 +7355,7 @@ rm -f reject.pcap
+ 
+ # Let's update the meter
+ 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
++check ovn-nbctl --may-exist --wait=hv meter-add acl-meter drop 10 pktps 0
+ ip netns exec sw01 scapy -H <<-EOF
+ p = IP(src="192.168.1.2", dst="192.168.1.1") / UDP(dport = 12345) / Raw(b"X"*64)
+ send (p, iface='sw01', loop = 0, verbose = 0, count = 40)
+@@ -7402,7 +7386,7 @@ kill $(pidof tcpdump)
+ 
+ NS_EXEC([server], [tcpdump -l -n -i s1 arp[[24:4]]=0xac100164 > arp.pcap &])
+ check ovn-nbctl meter-add arp-meter drop 1 pktps 0
+-check ovn-nbctl --wait=hv copp-add copp1 arp-resolve arp-meter
++check ovn-nbctl copp-add copp1 arp-resolve arp-meter
+ check ovn-nbctl --wait=hv lr-copp-add copp1 R1
+ AT_CHECK([ovn-nbctl copp-list copp1], [0], [dnl
+ arp-resolve: arp-meter
+@@ -7421,7 +7405,7 @@ OVS_WAIT_UNTIL([
+ kill $(pidof tcpdump)
+ 
+ check ovn-nbctl meter-add icmp-meter drop 1 pktps 0
+-check ovn-nbctl --wait=hv copp-add copp2 icmp4-error icmp-meter
++check ovn-nbctl copp-add copp2 icmp4-error icmp-meter
+ check ovn-nbctl --wait=hv lr-copp-add copp2 R1
+ AT_CHECK([ovn-nbctl copp-list copp2 |grep icmp4-error], [0], [dnl
+ icmp4-error: icmp-meter
+@@ -7441,7 +7425,7 @@ OVS_WAIT_UNTIL([
+ kill $(pidof tcpdump)
+ 
+ check ovn-nbctl meter-add bfd-meter drop 1 pktps 0
+-check ovn-nbctl --wait=hv copp-add copp3 bfd bfd-meter
++check ovn-nbctl copp-add copp3 bfd bfd-meter
+ check ovn-nbctl --wait=hv lr-copp-add copp3 R1
+ AT_CHECK([ovn-nbctl copp-list copp3 |grep bfd], [0], [dnl
+ bfd: bfd-meter
+@@ -7471,7 +7455,7 @@ kill $(pidof tcpdump)
+ 
+ check ovn-nbctl set nb_global . options:svc_monitor_mac="33:33:33:33:33:33"
+ check ovn-nbctl meter-add svc-meter drop 1 pktps 0
+-check ovn-nbctl --wait=hv copp-add copp4 svc-monitor svc-meter
++check ovn-nbctl copp-add copp4 svc-monitor svc-meter
+ check ovn-nbctl --wait=hv ls-copp-add copp4 sw0
+ check ovn-appctl -t ovn-controller vlog/set vconn:dbg
+ AT_CHECK([ovn-nbctl copp-list copp4], [0], [dnl
+@@ -8696,22 +8680,19 @@ check ovn-nbctl lr-nat-add lr1 snat 172.16.1.10 192.168.1.0/24
+ check ovn-nbctl lr-nat-add lr1 snat 1711::10 2001::/64
+ 
+ ADD_NAMESPACES(ls1p1)
+-ADD_VETH(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01", \
+-         "192.168.1.254", , "2001::1/64", "2001::a")
++ADD_VETH(ls1p1, ls1p1, br-int, "2001::1/64", "00:00:00:01:01:01", \
++         "2001::a", "nodad", "192.168.1.1/24", "192.168.1.254")
+ 
+ ADD_NAMESPACES(ls1p2)
+-ADD_VETH(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02", \
+-         "192.168.1.254", , "2001::2/64", "2001::a")
++ADD_VETH(ls1p2, ls1p2, br-int, "2001::2/64", "00:00:00:01:01:02", \
++         "2001::a", "nodad", "192.168.1.2/24", "192.168.1.254")
+ 
+ ADD_NAMESPACES(ext1)
+-ADD_VETH(ext1, ext1, br0, "172.16.1.1/24", "00:ee:00:01:01:01", \
+-         "172.16.1.254", , "1711::1/64", "1711::a")
++ADD_VETH(ext1, ext1, br0, "1711::1/64", "00:ee:00:01:01:01", \
++         "1711::a", "nodad", "172.16.1.1/24", "172.16.1.254")
+ 
+ check ovn-nbctl --wait=hv sync
+ wait_for_ports_up
+-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p1 ip a | grep 2001::1 | grep tentative)" = ""])
+-OVS_WAIT_UNTIL([test "$(ip netns exec ls1p2 ip a | grep 2002::2 | grep tentative)" = ""])
+-OVS_WAIT_UNTIL([test "$(ip netns exec ext1 ip a | grep 1711::1 | grep tentative)" = ""])
+ 
+ NS_CHECK_EXEC([ls1p1], [ping -q -c 3 -i 0.3 -w 2  172.16.1.1 | FORMAT_PING], \
+ [0], [dnl
+@@ -9231,8 +9212,9 @@ name: 'vport4' value: '999'
+ NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
+ 
+ NETNS_DAEMONIZE([vm1],
+-    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2>/dev/null],
++    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 42.42.42.2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
+     [tcpdump1.pid])
++OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
+ 
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+ NS_CHECK_EXEC([vm1], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+@@ -9355,16 +9337,13 @@ check ovn-nbctl --template lb-add lb-test-udp2 "^vip:^vport4" "[[4242::2]]:4343"
+     -- lr-lb-add rtr lb-test-udp2
+ 
+ ADD_NAMESPACES(vm1)
+-ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""])
++ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1", "nodad")
+ 
+ ADD_NAMESPACES(vm2)
+-ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""])
++ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1", "nodad")
+ 
+ ADD_NAMESPACES(vm3)
+-ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec vm3 ip a | grep 4343::2 | grep tentative)" = ""])
++ADD_VETH(vm3, vm3, br-int, "4343::2/64", "00:00:00:00:00:03", "4343::1", "nodad")
+ 
+ # Wait for ovn-controller to catch up.
+ wait_for_ports_up
+@@ -9385,8 +9364,9 @@ name: 'vport4' value: '999'
+ NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
+ 
+ NETNS_DAEMONIZE([vm1],
+-    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2>/dev/null],
++    [tcpdump -n -i vm1 -nnleX -c6 udp and dst 4242::2 and dst port 4343 > vm1.pcap 2> vm1.pcap.stderr],
+     [tcpdump1.pid])
++OVS_WAIT_UNTIL([grep "listening" vm1.pcap.stderr])
+ 
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+ NS_CHECK_EXEC([vm1], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+@@ -10825,32 +10805,28 @@ check ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
+ # Logical port 'foo1' in switch 'foo'.
+ ADD_NAMESPACES(foo1)
+ ADD_VETH(foo1, foo1, br-int, "fd11::2/64", "f0:00:00:01:02:03", \
+-         "fd7b:6b4d:7b25:d22f::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd11::2 | grep tentative)" = ""])
++         "fd7b:6b4d:7b25:d22f::1", "nodad")
+ check ovn-nbctl lsp-add foo foo1 \
+ -- lsp-set-addresses foo1 "f0:00:00:01:02:03 fd11::2"
+ 
+ # Logical port 'foo2' in switch 'foo'.
+ ADD_NAMESPACES(foo2)
+ ADD_VETH(foo2, foo2, br-int, "fd11::3/64", "f0:00:00:01:02:04", \
+-         "fd7b:6b4d:7b25:d22f::2")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo2 ip a | grep fd11::3 | grep tentative)" = ""])
++         "fd7b:6b4d:7b25:d22f::2", "nodad")
+ check ovn-nbctl lsp-add foo foo2 \
+ -- lsp-set-addresses foo2 "f0:00:00:01:02:04 fd11::3"
+ 
+ # Logical port 'foo3' in switch 'foo'.
+ ADD_NAMESPACES(foo3)
+ ADD_VETH(foo3, foo3, br-int, "fd11::4/64", "f0:00:00:01:02:05", \
+-         "fd7b:6b4d:7b25:d22d::1")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo3 ip a | grep fd11::4 | grep tentative)" = ""])
++         "fd7b:6b4d:7b25:d22d::1", "nodad")
+ check ovn-nbctl lsp-add foo foo3 \
+ -- lsp-set-addresses foo3 "f0:00:00:01:02:05 fd11::4"
+ 
+ # Logical port 'bar1' in switch 'bar'.
+ ADD_NAMESPACES(bar1)
+ ADD_VETH(bar1, bar1, br-int, "fd12::2/64", "f0:00:00:01:02:06", \
+-"fd7b:6b4d:7b25:d22f::3")
+-OVS_WAIT_UNTIL([test "$(ip netns exec foo1 ip a | grep fd12::2 | grep tentative)" = ""])
++         "fd7b:6b4d:7b25:d22f::3", "nodad")
+ check ovn-nbctl lsp-add bar bar1 \
+ -- lsp-set-addresses bar1 "f0:00:00:01:02:06 fd12::2"
+ 
+@@ -11686,7 +11662,7 @@ check ovn-nbctl lsp-add ls0 ls0-lr0 \
+     -- 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")
++ADD_VETH(vif0, vif0, br-int, "fd00::2/64", "00:00:00:00:00:02", "fd00::1", "nodad")
+ 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
+@@ -11729,3 +11705,417 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ 
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ct_flush on logical router load balancer])
++AT_KEYWORDS([ct-lr-flush])
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++
++# 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 R1
++
++check ovn-nbctl ls-add sw0
++check ovn-nbctl ls-add public
++
++check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
++check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24
++
++check ovn-nbctl set logical_router R1 options:chassis=hv1
++
++check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
++    type=router options:router-port=rp-sw0 \
++    -- lsp-set-addresses sw0-rp router
++
++check ovn-nbctl lsp-add sw0 sw0-vm \
++    -- lsp-set-addresses sw0-vm "00:00:01:01:02:04 192.168.1.2/24"
++
++check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
++    type=router options:router-port=rp-public \
++    -- lsp-set-addresses public-rp router
++
++check ovn-nbctl lsp-add public public-vm \
++   -- lsp-set-addresses public-vm "00:00:02:01:02:04 172.16.1.2/24"
++
++ADD_NAMESPACES(sw0-vm)
++ADD_VETH(sw0-vm, sw0-vm, br-int, "192.168.1.2/24", "00:00:01:01:02:04", \
++         "192.168.1.1")
++
++ADD_NAMESPACES(public-vm)
++ADD_VETH(public-vm, public-vm, br-int, "172.16.1.2/24", "00:00:02:01:02:04", \
++         "172.16.1.1")
++
++# Start webservers in 'server'.
++OVS_START_L7([sw0-vm], [http])
++
++# Create a load balancer and associate to R1
++check ovn-nbctl lb-add lb1 172.16.1.150:80 192.168.1.2:80 \
++    -- set load_balancer lb1 options:ct_flush="true"
++check ovn-nbctl lr-lb-add R1 lb1
++
++check ovn-nbctl --wait=hv sync
++
++for i in $(seq 1 5); do
++    echo Request $i
++    NS_CHECK_EXEC([public-vm], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++done
++
++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl
++1
++])
++
++check ovn-nbctl --wait=hv lb-del lb1
++
++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.150) | wc -l ], [0], [dnl
++0
++])
++
++check ovn-nbctl lb-add lb2 172.16.1.151:80 192.168.1.2:80
++check ovn-nbctl lr-lb-add R1 lb2
++
++check ovn-nbctl --wait=hv sync
++
++for i in $(seq 1 5); do
++    echo Request $i
++    NS_CHECK_EXEC([public-vm], [wget 172.16.1.151 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
++done
++
++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl
++1
++])
++
++check ovn-nbctl --wait=hv lb-del lb2
++
++OVS_WAIT_FOR_OUTPUT([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.151) | wc -l ], [0], [dnl
++1
++])
++
++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
++/Failed to acquire.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
++
++AT_SETUP([load balancing in gateway router - SCTP])
++AT_SKIP_IF([test $HAVE_SCTP = no])
++AT_SKIP_IF([test $HAVE_NC = no])
++AT_KEYWORDS([ovnlb sctp])
++
++# Make sure the SCTP kernel module is loaded.
++LOAD_MODULE([sctp])
++
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++
++# 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 the ovn-controller.
++start_daemon ovn-controller
++
++# Logical network:
++# Two LRs - R1 and R2 that are connected to each other via LS "join"
++# in 20.0.0.0/24 network. R1 has switchess foo (192.168.1.0/24) and
++# bar (192.168.2.0/24) connected to it. R2 has alice (172.16.1.0/24) connected
++# to it.  R2 is a gateway router on which we add load-balancing rules.
++#
++#    foo -- R1 -- join - R2 -- alice
++#           |
++#    bar ----
++
++ovn-nbctl create Logical_Router name=R1
++ovn-nbctl create Logical_Router name=R2 options:chassis=hv1
++
++ovn-nbctl ls-add foo
++ovn-nbctl ls-add bar
++ovn-nbctl ls-add alice
++ovn-nbctl ls-add join
++
++# Connect foo to R1
++ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
++ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
++    type=router options:router-port=foo addresses=\"00:00:01:01:02:03\"
++
++# Connect bar to R1
++ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
++ovn-nbctl lsp-add bar rp-bar -- set Logical_Switch_Port rp-bar \
++    type=router options:router-port=bar addresses=\"00:00:01:01:02:04\"
++
++# Connect alice to R2
++ovn-nbctl lrp-add R2 alice 00:00:02:01:02:03 172.16.1.1/24
++ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \
++    type=router options:router-port=alice addresses=\"00:00:02:01:02:03\"
++
++# Connect R1 to join
++ovn-nbctl lrp-add R1 R1_join 00:00:04:01:02:03 20.0.0.1/24
++ovn-nbctl lsp-add join r1-join -- set Logical_Switch_Port r1-join \
++    type=router options:router-port=R1_join addresses='"00:00:04:01:02:03"'
++
++# Connect R2 to join
++ovn-nbctl lrp-add R2 R2_join 00:00:04:01:02:04 20.0.0.2/24
++ovn-nbctl lsp-add join r2-join -- set Logical_Switch_Port r2-join \
++    type=router options:router-port=R2_join addresses='"00:00:04:01:02:04"'
++
++# Static routes.
++ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2
++ovn-nbctl lr-route-add R2 192.168.0.0/16 20.0.0.1
++
++# Logical port 'foo1' in switch 'foo'.
++ADD_NAMESPACES(foo1)
++ADD_VETH(foo1, foo1, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
++         "192.168.1.1")
++ovn-nbctl lsp-add foo foo1 \
++-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2"
++
++# Logical port 'alice1' in switch 'alice'.
++ADD_NAMESPACES(alice1)
++ADD_VETH(alice1, alice1, br-int, "172.16.1.2/24", "f0:00:00:01:02:04", \
++         "172.16.1.1")
++ovn-nbctl lsp-add alice alice1 \
++-- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
++
++# Logical port 'bar1' in switch 'bar'.
++ADD_NAMESPACES(bar1)
++ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:05", \
++"192.168.2.1")
++ovn-nbctl lsp-add bar bar1 \
++-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2"
++
++# Config OVN load-balancer with a VIP.
++uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
++ovn-nbctl set logical_router R2 load_balancer=$uuid
++
++# Config OVN load-balancer with another VIP (this time with ports).
++ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
++
++# Add SNAT rule to make sure that Load-balancing still works with a SNAT rule.
++ovn-nbctl -- --id=@nat create nat type="snat" logical_ip=192.168.2.2 \
++    external_ip=30.0.0.2 -- add logical_router R2 nat @nat
++
++# Wait for ovn-controller to catch up.
++ovn-nbctl --wait=hv sync
++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
++grep 'nat(dst=192.168.2.2:12345)'])
++
++# Start webservers in 'foo1', 'bar1'.
++OVS_START_L7([foo1], [sctp])
++OVS_START_L7([bar1], [sctp])
++
++on_exit "ovs-ofctl -O OpenFlow13 dump-flows br-int"
++
++dnl Should work with the virtual IP address through NAT
++for i in `seq 1 20`; do
++    echo Request $i
++    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.1 12345 > client$i.log])
++done
++
++dnl Each server should have at least one connection.
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
++sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
++sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
++sctp,orig=(src=172.16.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
++])
++
++dnl Test load-balancing that includes L4 ports in NAT.
++for i in `seq 1 20`; do
++    echo Request $i
++    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
++done
++
++dnl Each server should have at least one connection.
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
++sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2
++])
++
++check_est_flows () {
++    n=$(ovs-ofctl dump-flows br-int table=15 | grep "+est" \
++        | grep "ct_mark=$1" | sed -n 's/.*n_packets=\([[0-9]]\{1,\}\).*/\1/p')
++
++    echo "n_packets=$n"
++    test -n "$n" && test "$n" != "0"
++}
++
++OVS_WAIT_UNTIL([check_est_flows 0x2], [check established flows])
++
++
++ovn-nbctl set logical_router R2 options:lb_force_snat_ip="20.0.0.2"
++
++# Destroy the load balancer and create again. ovn-controller will
++# clear the OF flows and re add again and clears the n_packets
++# for these flows.
++ovn-nbctl destroy load_balancer $uuid
++uuid=`ovn-nbctl  create load_balancer protocol=sctp vips:30.0.0.1="192.168.1.2,192.168.2.2"`
++ovn-nbctl set logical_router R2 load_balancer=$uuid
++
++check ovs-appctl dpctl/flush-conntrack
++
++# Config OVN load-balancer with another VIP (this time with ports).
++ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:12345,192.168.2.2:12345"'
++
++ovn-nbctl list load_balancer
++ovn-sbctl dump-flows R2
++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=45 | grep 'nat(src=20.0.0.2)'])
++
++dnl Test load-balancing that includes L4 ports in NAT.
++for i in `seq 1 20`; do
++    echo Request $i
++    NS_CHECK_EXEC([alice1], [nc --sctp --recv-only 30.0.0.2 8000 > clients$i.log])
++done
++
++dnl Each server should have at least one connection.
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
++sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10
++sctp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10
++])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
++sed -e 's/zone=[[0-9]]*/zone=<cleared>/' |
++sed 's/,protoinfo=.*$//' | uniq], [0], [dnl
++sctp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>
++sctp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=20.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>
++])
++
++OVS_WAIT_UNTIL([check_est_flows 0xa], [check established flows])
++
++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([ovn-northd])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([load balancing affinity sessions - auto clear learnt flows])
++AT_SKIP_IF([test $HAVE_NC = no])
++AT_KEYWORDS([lb])
++
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++
++check 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 lr
++check ovn-nbctl lrp-add lr lr-ls 00:00:00:00:01:00 42.42.42.3/24
++check ovn-nbctl ls-add ls
++
++check ovn-nbctl lsp-add ls ls-lr
++check ovn-nbctl lsp-set-addresses ls-lr 00:00:00:00:01:00
++check ovn-nbctl lsp-set-type ls-lr router
++check ovn-nbctl lsp-set-options ls-lr router-port=lr-ls
++check ovn-nbctl lsp-add ls vm1
++check ovn-nbctl lsp-set-addresses vm1 00:00:00:00:00:01
++check ovn-nbctl lsp-add ls vm2
++check ovn-nbctl lsp-set-addresses vm2 00:00:00:00:00:02
++check ovn-nbctl lb-add lb-test 43.43.43.43:80 42.42.42.1:8080,42.42.42.2:8080 tcp \
++    -- set load_balancer lb-test options:affinity_timeout=65535 \
++    -- ls-lb-add ls lb-test
++
++dnl Start a server on vm1.
++ADD_NAMESPACES(vm1)
++ADD_VETH(vm1, vm1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", "42.42.42.3")
++NETNS_DAEMONIZE([vm1], [nc -l -k 42.42.42.1 8080], [vm1.pid])
++
++dnl Start a server on vm2.
++ADD_NAMESPACES(vm2)
++ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", "42.42.42.3")
++NETNS_DAEMONIZE([vm2], [nc -l -k 42.42.42.2 8080], [vm2.pid])
++
++dnl Wait for ovn-controller to catch up.
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
++dnl Test the connection.
++OVS_WAIT_UNTIL([
++    ip netns exec vm1 nc -z 43.43.43.43 80 &> /dev/null
++])
++
++OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c) -eq 1])
++
++dnl Find the backend that was hit.
++backend=$(ovs-ofctl dump-flows br-int table=78 | \
++    grep -oE 'load:0x2a2a2a0[[12]]' | sed -n 's/load:0x2a2a2a0\(.*\)/\1/p')
++
++dnl Remove the backend that was hit.
++if [[ "$backend" == "1" ]]; then
++    check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.2:8080\"
++else
++    check ovn-nbctl set load_balancer lb-test vip:\"43.43.43.43:80\"=\"42.42.42.1:8080\"
++fi
++check ovn-nbctl --wait=hv sync
++
++dnl The learnt flow should also be auto deleted.
++AT_CHECK([ovs-ofctl dump-flows br-int | grep 'table=78, n_packets' -c], [1], [dnl
++0
++])
++
++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([ovn-northd])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
+diff --git a/tests/test-ovn.c b/tests/test-ovn.c
+index 1f1e27b51..aaf2825ed 100644
+--- a/tests/test-ovn.c
++++ b/tests/test-ovn.c
+@@ -1300,11 +1300,11 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ 
+     /* Initialize group ids. */
+     struct ovn_extend_table group_table;
+-    ovn_extend_table_init(&group_table);
++    ovn_extend_table_init(&group_table, "group-table", OFPG_MAX);
+ 
+     /* Initialize meter ids for QoS. */
+     struct ovn_extend_table meter_table;
+-    ovn_extend_table_init(&meter_table);
++    ovn_extend_table_init(&meter_table, "meter-table", OFPM13_MAX);
+ 
+     /* Initialize collector sets. */
+     struct flow_collector_ids collector_ids;
+diff --git a/utilities/checkpatch.py b/utilities/checkpatch.py
+index 5467d604d..52d3fa845 100755
+--- a/utilities/checkpatch.py
++++ b/utilities/checkpatch.py
+@@ -40,6 +40,15 @@ MAX_LINE_LEN = 79
+ def open_spell_check_dict():
+     import enchant
+ 
++    try:
++        import codespell_lib
++        codespell_dir = os.path.dirname(codespell_lib.__file__)
++        codespell_file = os.path.join(codespell_dir, 'data', 'dictionary.txt')
++        if not os.path.exists(codespell_file):
++            codespell_file = ''
++    except:
++        codespell_file = ''
++
+     try:
+         extra_keywords = ['ovs', 'vswitch', 'vswitchd', 'ovs-vswitchd',
+                           'netdev', 'selinux', 'ovs-ctl', 'dpctl', 'ofctl',
+@@ -92,9 +101,18 @@ def open_spell_check_dict():
+                           'syscall', 'lacp', 'ipf', 'skb', 'valgrind']
+ 
+         global spell_check_dict
++
+         spell_check_dict = enchant.Dict("en_US")
++
++        if codespell_file:
++            with open(codespell_file) as f:
++                for line in f.readlines():
++                    words = line.strip().split('>')[1].strip(', ').split(',')
++                    for word in words:
++                        spell_check_dict.add_to_session(word.strip())
++
+         for kw in extra_keywords:
+-            spell_check_dict.add(kw)
++            spell_check_dict.add_to_session(kw)
+ 
+         return True
+     except:
+@@ -190,6 +208,7 @@ skip_trailing_whitespace_check = False
+ skip_gerrit_change_id_check = False
+ skip_block_whitespace_check = False
+ skip_signoff_check = False
++skip_committer_signoff_check = False
+ 
+ # Don't enforce character limit on files that include these characters in their
+ # name, as they may have legitimate reasons to have longer lines.
+@@ -282,9 +301,13 @@ def if_and_for_end_with_bracket_check(line):
+         if len(line) == MAX_LINE_LEN - 1 and line[-1] == ')':
+             return True
+ 
+-        if __regex_ends_with_bracket.search(line) is None and \
+-           __regex_if_macros.match(line) is None:
+-            return False
++        if __regex_ends_with_bracket.search(line) is None:
++            if line.endswith("\\") and \
++               __regex_if_macros.match(line) is not None:
++                return True
++            else:
++                return False
++
+     if __regex_conditional_else_bracing.match(line) is not None:
+         return False
+     if __regex_conditional_else_bracing2.match(line) is not None:
+@@ -410,9 +433,15 @@ def check_spelling(line, comment):
+     if not spell_check_dict or not spellcheck:
+         return False
+ 
++    if line.startswith('Fixes: '):
++        return False
++
+     words = filter_comments(line, True) if comment else line
+     words = words.replace(':', ' ').split(' ')
+ 
++    flagged_words = []
++    num_suggestions = 3
++
+     for word in words:
+         skip = False
+         strword = re.subn(r'\W+', '', word)[0].replace(',', '')
+@@ -437,9 +466,15 @@ def check_spelling(line, comment):
+                 skip = True
+ 
+             if not skip:
+-                print_warning("Check for spelling mistakes (e.g. \"%s\")"
+-                              % strword)
+-                return True
++                flagged_words.append(strword)
++
++    if len(flagged_words) > 0:
++        for mistake in flagged_words:
++            print_warning("Possible misspelled word: \"%s\"" % mistake)
++            print("Did you mean: ",
++                  spell_check_dict.suggest(mistake)[:num_suggestions])
++
++        return True
+ 
+     return False
+ 
+@@ -780,6 +815,36 @@ def run_file_checks(text):
+             check['check'](text)
+ 
+ 
++def run_subject_checks(subject, spellcheck=False):
++    warnings = False
++
++    if spellcheck and check_spelling(subject, False):
++        warnings = True
++
++    summary = subject[subject.rindex(': ') + 2:]
++    area_summary = subject[subject.index(': ') + 2:]
++    area_summary_len = len(area_summary)
++    if area_summary_len > 70:
++        print_warning("The subject, '<area>: <summary>', is over 70 "
++                      "characters, i.e., %u." % area_summary_len)
++        warnings = True
++
++    if summary[0].isalpha() and summary[0].islower():
++        print_warning(
++            "The subject summary should start with a capital.")
++        warnings = True
++
++    if subject[-1] not in [".", "?", "!"]:
++        print_warning(
++            "The subject summary should end with a dot.")
++        warnings = True
++
++    if warnings:
++        print(subject)
++
++    return warnings
++
++
+ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
+     global print_file_name, total_line, checking_file, \
+         empty_return_check_state
+@@ -800,6 +865,7 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
+         r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
+     is_author = re.compile(r'^(Author|From): (.*)$', re.I | re.M | re.S)
+     is_committer = re.compile(r'^(Commit: )(.*)$', re.I | re.M | re.S)
++    is_subject = re.compile(r'^(Subject: )(.*)$', re.I | re.M | re.S)
+     is_signature = re.compile(r'^(Signed-off-by: )(.*)$',
+                               re.I | re.M | re.S)
+     is_co_author = re.compile(r'^(Co-authored-by: )(.*)$',
+@@ -874,7 +940,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
+                             break
+                     if (committer
+                         and author != committer
+-                        and committer not in signatures):
++                        and committer not in signatures
++                        and not skip_committer_signoff_check):
+                         print_error("Committer %s needs to sign off."
+                                     % committer)
+ 
+@@ -899,6 +966,8 @@ def ovs_checkpatch_parse(text, filename, author=None, committer=None):
+                 committer = is_committer.match(line).group(2)
+             elif is_author.match(line):
+                 author = is_author.match(line).group(2)
++            elif is_subject.match(line):
++                run_subject_checks(line, spellcheck)
+             elif is_signature.match(line):
+                 m = is_signature.match(line)
+                 signatures.append(m.group(2))
+@@ -990,7 +1059,8 @@ Check options:
+ -S|--spellcheck                Check C comments and commit-message for possible
+                                spelling mistakes
+ -t|--skip-trailing-whitespace  Skips the trailing whitespace test
+-   --skip-gerrit-change-id     Skips the gerrit change id test"""
++   --skip-gerrit-change-id     Skips the gerrit change id test
++   --skip-committer-signoff    Skips the committer sign-off test"""
+           % sys.argv[0])
+ 
+ 
+@@ -1017,6 +1087,19 @@ def ovs_checkpatch_file(filename):
+     result = ovs_checkpatch_parse(part.get_payload(decode=False), filename,
+                                   mail.get('Author', mail['From']),
+                                   mail['Commit'])
++
++    if not mail['Subject'] or not mail['Subject'].strip():
++        if mail['Subject']:
++            mail.replace_header('Subject', sys.argv[-1])
++        else:
++            mail.add_header('Subject', sys.argv[-1])
++
++        print("Subject missing! Your provisional subject is",
++              mail['Subject'])
++
++    if run_subject_checks('Subject: ' + mail['Subject'], spellcheck):
++        result = True
++
+     ovs_checkpatch_print_result()
+     return result
+ 
+@@ -1048,6 +1131,7 @@ if __name__ == '__main__':
+                                        "skip-signoff-lines",
+                                        "skip-trailing-whitespace",
+                                        "skip-gerrit-change-id",
++                                       "skip-committer-signoff",
+                                        "spellcheck",
+                                        "quiet"])
+     except:
+@@ -1068,6 +1152,8 @@ if __name__ == '__main__':
+             skip_trailing_whitespace_check = True
+         elif o in ("--skip-gerrit-change-id"):
+             skip_gerrit_change_id_check = True
++        elif o in ("--skip-committer-signoff"):
++            skip_committer_signoff_check = True
+         elif o in ("-f", "--check-file"):
+             checking_file = True
+         elif o in ("-S", "--spellcheck"):
+diff --git a/utilities/containers/fedora/Dockerfile b/utilities/containers/fedora/Dockerfile
+index 4058d7f5b..c11ea37b7 100755
+--- a/utilities/containers/fedora/Dockerfile
++++ b/utilities/containers/fedora/Dockerfile
+@@ -1,4 +1,4 @@
+-FROM quay.io/fedora/fedora:latest
++FROM quay.io/fedora/fedora:38
+ 
+ ARG CONTAINERS_PATH
+ 
+diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt
+index 0d90765c9..aac98443b 100644
+--- a/utilities/containers/py-requirements.txt
++++ b/utilities/containers/py-requirements.txt
+@@ -1,5 +1,4 @@
+-flake8
+-hacking>=3.0
++flake8==5.0.4
+ scapy
+ sphinx
+ setuptools
+diff --git a/utilities/containers/ubuntu/Dockerfile b/utilities/containers/ubuntu/Dockerfile
+index 5d5bedbd9..3c7fe7775 100755
+--- a/utilities/containers/ubuntu/Dockerfile
++++ b/utilities/containers/ubuntu/Dockerfile
+@@ -1,4 +1,4 @@
+-FROM registry.hub.docker.com/library/ubuntu:latest
++FROM registry.hub.docker.com/library/ubuntu:22.04
+ 
+ ARG CONTAINERS_PATH
+ 
+diff --git a/utilities/ovn-ctl.8.xml b/utilities/ovn-ctl.8.xml
+index 82804096f..01f4aa26b 100644
+--- a/utilities/ovn-ctl.8.xml
++++ b/utilities/ovn-ctl.8.xml
+@@ -136,12 +136,14 @@
+     <p><code>--db-nb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
+     <p><code>--db-nb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
+     <p><code>--db-nb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
++    <p><code>--db-nb-election-timer=<var>Timeout in milliseconds</var></code></p>
+     <p><code>--db-sb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
+     <p><code>--db-sb-cluster-local-port=<var>PORT NUMBER</var></code></p>
+     <p><code>--db-sb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
+     <p><code>--db-sb-cluster-remote-addr=<var>IP ADDRESS</var></code></p>
+     <p><code>--db-sb-cluster-remote-port=<var>PORT NUMBER</var></code></p>
+     <p><code>--db-sb-cluster-remote-proto=<var>PROTO (tcp/ssl)</var></code></p>
++    <p><code>--db-sb-election-timer=<var>Timeout in milliseconds</var></code></p>
+     <p><code>--db-ic-nb-cluster-local-addr=<var>IP ADDRESS</var></code></p>
+     <p><code>--db-ic-nb-cluster-local-port=<var>PORT NUMBER</var></code></p>
+     <p><code>--db-ic-nb-cluster-local-proto=<var>PROTO (tcp/ssl)</var></code></p>
+diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
+index 444fbd2fe..821ad44dd 100644
+--- a/utilities/ovn-nbctl.c
++++ b/utilities/ovn-nbctl.c
+@@ -4693,6 +4693,7 @@ static void
+             nexthop = normalize_prefix_str(ctx->argv[3]);
+             if (!nexthop) {
+                 ctl_error(ctx, "bad nexthop argument: %s", ctx->argv[3]);
++                free(prefix);
+                 return;
+             }
+         }
+diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
+index 3948cae3f..f1f8c2b42 100644
+--- a/utilities/ovn-sbctl.c
++++ b/utilities/ovn-sbctl.c
+@@ -396,7 +396,9 @@ pre_get_info(struct ctl_context *ctx)
+     ovsdb_idl_add_column(ctx->idl, &sbrec_mac_binding_col_mac);
+ 
+     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapaths);
++    /* datapath_group column is deprecated. */
+     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_datapath_group);
++    ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_ls_datapath_group);
+     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_vips);
+     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_name);
+     ovsdb_idl_add_column(ctx->idl, &sbrec_load_balancer_col_protocol);
+@@ -932,10 +934,15 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn,
+                     break;
+                 }
+             }
++            /* datapath_group column is deprecated. */
+             if (lb->datapath_group && !dp_found) {
+                 dp_found = datapath_group_contains_datapath(lb->datapath_group,
+                                                             datapath);
+             }
++            if (lb->ls_datapath_group && !dp_found) {
++                dp_found = datapath_group_contains_datapath(
++                        lb->ls_datapath_group, datapath);
++            }
+             if (!dp_found) {
+                 continue;
+             }
+@@ -954,11 +961,17 @@ cmd_lflow_list_load_balancers(struct ctl_context *ctx, struct vconn *vconn,
+                 print_vflow_datapath_name(lb->datapaths[i], true,
+                                           &ctx->output);
+             }
++            /* datapath_group column is deprecated. */
+             for (size_t i = 0; lb->datapath_group
+                                && i < lb->datapath_group->n_datapaths; i++) {
+                 print_vflow_datapath_name(lb->datapath_group->datapaths[i],
+                                           true, &ctx->output);
+             }
++            for (size_t i = 0; lb->ls_datapath_group
++                    && i < lb->ls_datapath_group->n_datapaths; i++) {
++                print_vflow_datapath_name(lb->ls_datapath_group->datapaths[i],
++                                          true, &ctx->output);
++            }
+         }
+ 
+         ds_put_cstr(&ctx->output, "\n  vips:\n");
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index 0b86eae7b..13ae464ad 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -983,6 +983,7 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf,
+         if (error) {
+             VLOG_WARN("%s: parsing expression failed (%s)",
+                       sblf->match, error);
++            expr_destroy(match);
+             free(error);
+             return;
+         }
diff --git a/SPECS/ovn23.06.spec b/SPECS/ovn23.06.spec
deleted file mode 100644
index 9a9a793..0000000
--- a/SPECS/ovn23.06.spec
+++ /dev/null
@@ -1,560 +0,0 @@
-# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc.
-#
-# Copying and distribution of this file, with or without modification,
-# are permitted in any medium without royalty provided the copyright
-# notice and this notice are preserved.  This file is offered as-is,
-# without warranty of any kind.
-#
-# If tests have to be skipped while building, specify the '--without check'
-# option. For example:
-# rpmbuild -bb --without check rhel/openvswitch-fedora.spec
-
-# This defines the base package name's version.
-
-%define pkgver 2.13
-%define pkgname ovn23.06
-
-# If libcap-ng isn't available and there is no need for running OVS
-# as regular user, specify the '--without libcapng'
-%bcond_without libcapng
-
-# Enable PIE, bz#955181
-%global _hardened_build 1
-
-# RHEL-7 doesn't define _rundir macro yet
-# Fedora 15 onwards uses /run as _rundir
-%if 0%{!?_rundir:1}
-%define _rundir /run
-%endif
-
-# Build python2 (that provides python) and python3 subpackages on Fedora
-# Build only python3 (that provides python) subpackage on RHEL8
-# Build only python subpackage on RHEL7
-%if 0%{?rhel} > 7 || 0%{?fedora}
-# On RHEL8 Sphinx is included in buildroot
-%global external_sphinx 1
-%else
-# Don't use external sphinx (RHV doesn't have optional repositories enabled)
-%global external_sphinx 0
-%endif
-
-# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'.
-# But there is no solution to fix this. Using {_lib} macro will solve the
-# rpmlink error, but will install the files in /usr/lib64/.
-# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/
-# and we are not sure if pacemaker looks into this path to find the
-# OVN resource agent script.
-%global ovnlibdir %{_prefix}/lib
-
-Name: %{pkgname}
-Summary: Open Virtual Network support
-Group: System Environment/Daemons
-URL: http://www.ovn.org/
-Version: 23.06.1
-Release: 11%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
-Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
-
-# Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
-# lib/sflow*.[ch] files are SISSL
-License: ASL 2.0 and LGPLv2+ and SISSL
-
-%define ovncommit a20f880efdba9dcf19c1df77b31a3b8b9dffa345
-
-# Always pull an upstream release, since this is what we rebase to.
-Source: https://github.com/ovn-org/ovn/archive/%{ovncommit}.tar.gz#/ovn-%{version}.tar.gz
-
-%define ovscommit 0187eadfce4505d502e57c0e688b830f0a1ec728
-%define ovsshortcommit 0187ead
-
-Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
-%define ovsdir ovs-%{ovscommit}
-
-%define docutilsver 0.12
-%define pygmentsver 1.4
-%define sphinxver   1.1.3
-Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz
-Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz
-Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz
-
-Source500: configlib.sh
-Source501: gen_config_group.sh
-Source502: set_config.sh
-
-# Important: source503 is used as the actual copy file
-# @TODO: this causes a warning - fix it?
-Source504: arm64-armv8a-linuxapp-gcc-config
-Source505: ppc_64-power8-linuxapp-gcc-config
-Source506: x86_64-native-linuxapp-gcc-config
-
-Patch:     %{pkgname}.patch
-
-# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
-# in the -optional repository and so we can't require it directly since RHV
-# doesn't have the -optional repository enabled and so TPS fails
-%if %{external_sphinx}
-BuildRequires: python3-sphinx
-%else
-# Sphinx dependencies
-BuildRequires: python-devel
-BuildRequires: python-setuptools
-#BuildRequires: python2-docutils
-BuildRequires: python-jinja2
-BuildRequires: python-nose
-#BuildRequires: python2-pygments
-# docutils dependencies
-BuildRequires: python-imaging
-# pygments dependencies
-BuildRequires: python-nose
-%endif
-
-BuildRequires: gcc gcc-c++ make
-BuildRequires: autoconf automake libtool
-BuildRequires: systemd-units openssl openssl-devel
-BuildRequires: python3-devel python3-setuptools
-BuildRequires: desktop-file-utils
-BuildRequires: groff-base graphviz
-BuildRequires: unbound-devel
-
-# make check dependencies
-BuildRequires: procps-ng
-%if 0%{?rhel} == 8 || 0%{?fedora}
-BuildRequires: python3-pyOpenSSL
-%endif
-BuildRequires: tcpdump
-
-%if %{with libcapng}
-BuildRequires: libcap-ng libcap-ng-devel
-%endif
-
-Requires: hostname openssl iproute module-init-tools
-
-Requires(post): systemd-units
-Requires(preun): systemd-units
-Requires(postun): systemd-units
-
-# to skip running checks, pass --without check
-%bcond_without check
-
-%description
-OVN, the Open Virtual Network, is a system to support virtual network
-abstraction.  OVN complements the existing capabilities of OVS to add
-native support for virtual network abstractions, such as virtual L2 and L3
-overlays and security groups.
-
-%package central
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Requires: firewalld-filesystem
-Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1
-
-%description central
-OVN DB servers and ovn-northd running on a central node.
-
-%package host
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Requires: firewalld-filesystem
-Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1
-
-%description host
-OVN controller running on each host.
-
-%package vtep
-Summary: Open Virtual Network support
-License: ASL 2.0
-Requires: %{pkgname}
-Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release}
-Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1
-
-%description vtep
-OVN vtep controller
-
-%prep
-%autosetup -n ovn-%{ovncommit} -a 10 -p 1
-
-%build
-%if 0%{?commit0:1}
-# fix the snapshot unreleased version to be the released one.
-sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac
-%endif
-./boot.sh
-
-# OVN source code is now separate.
-# Build openvswitch first.
-# XXX Current openvswitch2.13 doesn't
-# use "2.13.0" for version. It's a commit hash
-pushd %{ovsdir}
-./boot.sh
-%configure \
-%if %{with libcapng}
-        --enable-libcapng \
-%else
-        --disable-libcapng \
-%endif
-        --enable-ssl \
-        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
-
-make %{?_smp_mflags}
-popd
-
-# Build OVN.
-# XXX OVS version needs to be updated when ovs2.13 is updated.
-%configure \
-        --with-ovs-source=$PWD/%{ovsdir} \
-%if %{with libcapng}
-        --enable-libcapng \
-%else
-        --disable-libcapng \
-%endif
-        --enable-ssl \
-        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
-
-make %{?_smp_mflags}
-
-%install
-%make_install
-install -p -D -m 0644 \
-        rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
-        $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn
-
-for service in ovn-controller ovn-controller-vtep ovn-northd; do
-        install -p -D -m 0644 \
-                        rhel/usr_lib_systemd_system_${service}.service \
-                        $RPM_BUILD_ROOT%{_unitdir}/${service}.service
-done
-
-install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn
-
-install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
-        $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
-install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
-        $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
-
-install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn
-ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \
-      $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
-
-install -p -D -m 0644 rhel/etc_logrotate.d_ovn \
-        $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn
-
-# remove unneeded files.
-rm -f $RPM_BUILD_ROOT%{_bindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl
-rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs*
-rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep*
-rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python
-rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs*
-rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
-rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
-rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc
-rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
-rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
-rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch
-rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool*
-rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \
-        $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver
-
-%check
-%if %{with check}
-    touch resolv.conf
-    export OVS_RESOLV_CONF=$(pwd)/resolv.conf
-    if ! make check TESTSUITEFLAGS='%{_smp_mflags}'; then
-        cat tests/testsuite.log
-        if ! make check TESTSUITEFLAGS='--recheck'; then
-            cat tests/testsuite.log
-            # Presently a test case - "2796: ovn -- ovn-controller incremental processing"
-            # is failing on aarch64 arch. Let's not exit for this arch
-            # until we figure out why it is failing.
-            # Test case 93: ovn.at:12105       ovn -- ACLs on Port Groups is failing
-            # repeatedly on s390x. This needs to be investigated.
-            %ifnarch aarch64
-            %ifnarch ppc64le
-            %ifnarch s390x
-                exit 1
-            %endif
-            %endif
-            %endif
-        fi
-    fi
-%endif
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-%pre central
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-northd.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-central > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-northd service is running which means old openvswitch-ovn-central
-        # is already installed and it will be cleaned up. So start ovn-northd
-        # service when posttrans central is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-northd
-    fi
-fi
-
-%pre host
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-controller.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-host > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-controller service is running which means old
-        # openvswitch-ovn-host is installed and it will be cleaned up. So
-        # start ovn-controller service when posttrans host is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-controller
-    fi
-fi
-
-%pre vtep
-if [ $1 -eq 1 ] ; then
-    # Package install.
-    /bin/systemctl status ovn-controller-vtep.service >/dev/null
-    ovn_status=$?
-    rpm -ql openvswitch-ovn-vtep > /dev/null
-    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
-        # ovn-controller-vtep service is running which means old
-        # openvswitch-ovn-vtep is installed and it will be cleaned up. So
-        # start ovn-controller-vtep service when posttrans host is called.
-        touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
-    fi
-fi
-
-%preun central
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-northd.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%preun host
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-controller.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%preun vtep
-%if 0%{?systemd_preun:1}
-    %systemd_preun ovn-controller-vtep.service
-%else
-    if [ $1 -eq 0 ] ; then
-        # Package removal, not upgrade
-        /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || :
-        /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%post
-%if %{with libcapng}
-if [ $1 -eq 1 ]; then
-    sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn
-    sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn
-fi
-%endif
-
-%post central
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-northd.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%post host
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-controller.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%post vtep
-%if 0%{?systemd_post:1}
-    %systemd_post ovn-controller-vtep.service
-%else
-    # Package install, not upgrade
-    if [ $1 -eq 1 ]; then
-        /bin/systemctl daemon-reload >dev/null || :
-    fi
-%endif
-
-%postun
-
-%postun central
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-northd.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-    # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%postun host
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-controller.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-        # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%postun vtep
-%if 0%{?systemd_postun_with_restart:1}
-    %systemd_postun_with_restart ovn-controller-vtep.service
-%else
-    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
-    if [ "$1" -ge "1" ] ; then
-        # Package upgrade, not uninstall
-        /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-%endif
-
-%posttrans central
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-northd
-        /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || :
-    fi
-fi
-
-
-%posttrans host
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-controller
-        /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || :
-    fi
-fi
-
-%posttrans vtep
-if [ $1 -eq 1 ]; then
-    # Package install, not upgrade
-    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then
-        rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
-        /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || :
-    fi
-fi
-
-%files
-%{_bindir}/ovn-nbctl
-%{_bindir}/ovn-sbctl
-%{_bindir}/ovn-trace
-%{_bindir}/ovn-detrace
-%{_bindir}/ovn_detrace.py
-%{_bindir}/ovn-appctl
-%{_bindir}/ovn-ic-nbctl
-%{_bindir}/ovn-ic-sbctl
-%dir %{_datadir}/ovn/
-%dir %{_datadir}/ovn/scripts/
-%{_datadir}/ovn/scripts/ovn-ctl
-%{_datadir}/ovn/scripts/ovn-lib
-%{_datadir}/ovn/scripts/ovndb-servers.ocf
-%{_mandir}/man8/ovn-ctl.8*
-%{_mandir}/man8/ovn-appctl.8*
-%{_mandir}/man8/ovn-nbctl.8*
-%{_mandir}/man8/ovn-ic-nbctl.8*
-%{_mandir}/man8/ovn-trace.8*
-%{_mandir}/man1/ovn-detrace.1*
-%{_mandir}/man7/ovn-architecture.7*
-%{_mandir}/man8/ovn-sbctl.8*
-%{_mandir}/man8/ovn-ic-sbctl.8*
-%{_mandir}/man5/ovn-nb.5*
-%{_mandir}/man5/ovn-ic-nb.5*
-%{_mandir}/man5/ovn-sb.5*
-%{_mandir}/man5/ovn-ic-sb.5*
-%dir %{ovnlibdir}/ocf/resource.d/ovn/
-%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
-%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn
-%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn
-
-%files central
-%{_bindir}/ovn-northd
-%{_bindir}/ovn-ic
-%{_mandir}/man8/ovn-northd.8*
-%{_mandir}/man8/ovn-ic.8*
-%{_datadir}/ovn/ovn-nb.ovsschema
-%{_datadir}/ovn/ovn-ic-nb.ovsschema
-%{_datadir}/ovn/ovn-sb.ovsschema
-%{_datadir}/ovn/ovn-ic-sb.ovsschema
-%{_unitdir}/ovn-northd.service
-%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
-
-%files host
-%{_bindir}/ovn-controller
-%{_mandir}/man8/ovn-controller.8*
-%{_unitdir}/ovn-controller.service
-%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
-
-%files vtep
-%{_bindir}/ovn-controller-vtep
-%{_mandir}/man8/ovn-controller-vtep.8*
-%{_unitdir}/ovn-controller-vtep.service
-
-%changelog
-* Thu Sep 14 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.06.1-11
-- northd: check if parent_name is set for tag_request 0
-[Upstream: c869db90e2b18515caad8ad95555d989d3379e3f]
-
-* Thu Sep 14 2023 Ales Musil <amusil@redhat.com> - 23.06.1-10
-- ofctrl: Prevent conjunction duplication (#2175928)
-[Upstream: 4281178a8882d0194ce8edf35018227ab20fa80e]
-
-* Thu Sep 14 2023 Ales Musil <amusil@redhat.com> - 23.06.1-9
-- ofctrl: Do not try to program long flows (#1955167)
-[Upstream: f18bbbbc1ec0110cde8146ea4e2b34b1ec488ba7]
-
-* Mon Sep 11 2023 Dumitru Ceara <dceara@redhat.com> - 23.06.1-8
-- northd: Always ct commit ECMP symmetric traffic in the original direction.
-[Upstream: 6de90ba4c43e1798a63167fd7f790126cf240e9c]
-
-* Wed Sep 06 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.06.1-7
-- Use correct nw_ttl=255 to match against legit NAs
-[Upstream: 059d1337af1be97b8f89fcf65e2ba6c9ae217d76]
-
-* Wed Sep 06 2023 Dumitru Ceara <dceara@redhat.com> - 23.06.1-6
-- checkpatch: Ignore yml files when checking line lengths.
-[Upstream: 18b9ca0630c537b142d6ac849a6a2092d4f5bc0f]
-
-* Wed Aug 30 2023 Ales Musil <amusil@redhat.com> - 23.06.1-5
-- northd: Make sure that skip_snat=true is evaluated before force_snat (#2224260)
-[Upstream: 04cf3fd8c2de752ac6ca538f6570547a69dcaac3]
-
-* Tue Aug 29 2023 Mark Michelson <mmichels@redhat.com> - 23.06.1-4
-- Prepare for 23.06.2.
-[Upstream: c215b5237d46e7aa3b7e095f1e955db5b646e4eb]
-
diff --git a/SPECS/ovn23.09.spec b/SPECS/ovn23.09.spec
new file mode 100644
index 0000000..6299c18
--- /dev/null
+++ b/SPECS/ovn23.09.spec
@@ -0,0 +1,930 @@
+# Copyright (C) 2009, 2010, 2013, 2014 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.  This file is offered as-is,
+# without warranty of any kind.
+#
+# If tests have to be skipped while building, specify the '--without check'
+# option. For example:
+# rpmbuild -bb --without check rhel/openvswitch-fedora.spec
+
+# This defines the base package name's version.
+
+%define pkgver 2.13
+%define pkgname ovn23.09
+
+# If libcap-ng isn't available and there is no need for running OVS
+# as regular user, specify the '--without libcapng'
+%bcond_without libcapng
+
+# Enable PIE, bz#955181
+%global _hardened_build 1
+
+# RHEL-7 doesn't define _rundir macro yet
+# Fedora 15 onwards uses /run as _rundir
+%if 0%{!?_rundir:1}
+%define _rundir /run
+%endif
+
+# Build python2 (that provides python) and python3 subpackages on Fedora
+# Build only python3 (that provides python) subpackage on RHEL8
+# Build only python subpackage on RHEL7
+%if 0%{?rhel} > 7 || 0%{?fedora}
+# On RHEL8 Sphinx is included in buildroot
+%global external_sphinx 1
+%else
+# Don't use external sphinx (RHV doesn't have optional repositories enabled)
+%global external_sphinx 0
+%endif
+
+# We would see rpmlinit error - E: hardcoded-library-path in '% {_prefix}/lib'.
+# But there is no solution to fix this. Using {_lib} macro will solve the
+# rpmlink error, but will install the files in /usr/lib64/.
+# OVN pacemaker ocf script file is copied in /usr/lib/ocf/resource.d/ovn/
+# and we are not sure if pacemaker looks into this path to find the
+# OVN resource agent script.
+%global ovnlibdir %{_prefix}/lib
+
+Name: %{pkgname}
+Summary: Open Virtual Network support
+Group: System Environment/Daemons
+URL: http://www.ovn.org/
+Version: 23.09.0
+Release: 103%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1
+
+# Nearly all of openvswitch is ASL 2.0.  The bugtool is LGPLv2+, and the
+# lib/sflow*.[ch] files are SISSL
+License: ASL 2.0 and LGPLv2+ and SISSL
+
+%define ovncommit 56d5cf6ccf4a7b034b3d0c009fbca7c509f2f42a
+
+# Always pull an upstream release, since this is what we rebase to.
+Source: https://github.com/ovn-org/ovn/archive/%{ovncommit}.tar.gz#/ovn-%{version}.tar.gz
+
+%define ovscommit ec1d730163d984934c467e050ebf6d39f8c09384
+%define ovsshortcommit ec1d730
+
+Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz
+%define ovsdir ovs-%{ovscommit}
+
+%define docutilsver 0.12
+%define pygmentsver 1.4
+%define sphinxver   1.1.3
+Source100: https://pypi.io/packages/source/d/docutils/docutils-%{docutilsver}.tar.gz
+Source101: https://pypi.io/packages/source/P/Pygments/Pygments-%{pygmentsver}.tar.gz
+Source102: https://pypi.io/packages/source/S/Sphinx/Sphinx-%{sphinxver}.tar.gz
+
+Source500: configlib.sh
+Source501: gen_config_group.sh
+Source502: set_config.sh
+
+# Important: source503 is used as the actual copy file
+# @TODO: this causes a warning - fix it?
+Source504: arm64-armv8a-linuxapp-gcc-config
+Source505: ppc_64-power8-linuxapp-gcc-config
+Source506: x86_64-native-linuxapp-gcc-config
+
+Patch:     %{pkgname}.patch
+
+# FIXME Sphinx is used to generate some manpages, unfortunately, on RHEL, it's
+# in the -optional repository and so we can't require it directly since RHV
+# doesn't have the -optional repository enabled and so TPS fails
+%if %{external_sphinx}
+BuildRequires: python3-sphinx
+%else
+# Sphinx dependencies
+BuildRequires: python-devel
+BuildRequires: python-setuptools
+#BuildRequires: python2-docutils
+BuildRequires: python-jinja2
+BuildRequires: python-nose
+#BuildRequires: python2-pygments
+# docutils dependencies
+BuildRequires: python-imaging
+# pygments dependencies
+BuildRequires: python-nose
+%endif
+
+BuildRequires: gcc gcc-c++ make
+BuildRequires: autoconf automake libtool
+BuildRequires: systemd-units openssl openssl-devel
+BuildRequires: python3-devel python3-setuptools
+BuildRequires: desktop-file-utils
+BuildRequires: groff-base graphviz
+BuildRequires: unbound-devel
+
+# make check dependencies
+BuildRequires: procps-ng
+%if 0%{?rhel} == 8 || 0%{?fedora}
+BuildRequires: python3-pyOpenSSL
+%endif
+BuildRequires: tcpdump
+
+%if %{with libcapng}
+BuildRequires: libcap-ng libcap-ng-devel
+%endif
+
+BuildRequires: python3-scapy
+
+Requires: hostname openssl iproute module-init-tools
+
+Requires(post): systemd-units
+Requires(preun): systemd-units
+Requires(postun): systemd-units
+
+# to skip running checks, pass --without check
+%bcond_without check
+
+%description
+OVN, the Open Virtual Network, is a system to support virtual network
+abstraction.  OVN complements the existing capabilities of OVS to add
+native support for virtual network abstractions, such as virtual L2 and L3
+overlays and security groups.
+
+%package central
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Requires: firewalld-filesystem
+Provides: openvswitch%{pkgver}-ovn-central = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-central < 2.11.0-1
+
+%description central
+OVN DB servers and ovn-northd running on a central node.
+
+%package host
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Requires: firewalld-filesystem
+Provides: openvswitch%{pkgver}-ovn-host = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-host < 2.11.0-1
+
+%description host
+OVN controller running on each host.
+
+%package vtep
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: %{pkgname}
+Provides: openvswitch%{pkgver}-ovn-vtep = %{?epoch:%{epoch}:}%{version}-%{release}
+Obsoletes: openvswitch%{pkgver}-ovn-vtep < 2.11.0-1
+
+%description vtep
+OVN vtep controller
+
+%prep
+%autosetup -n ovn-%{ovncommit} -a 10 -p 1
+
+%build
+%if 0%{?commit0:1}
+# fix the snapshot unreleased version to be the released one.
+sed -i.old -e "s/^AC_INIT(openvswitch,.*,/AC_INIT(openvswitch, %{version},/" configure.ac
+%endif
+./boot.sh
+
+# OVN source code is now separate.
+# Build openvswitch first.
+# XXX Current openvswitch2.13 doesn't
+# use "2.13.0" for version. It's a commit hash
+pushd %{ovsdir}
+./boot.sh
+%configure \
+%if %{with libcapng}
+        --enable-libcapng \
+%else
+        --disable-libcapng \
+%endif
+        --enable-ssl \
+        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+
+make %{?_smp_mflags}
+popd
+
+# Build OVN.
+# XXX OVS version needs to be updated when ovs2.13 is updated.
+%configure \
+        --with-ovs-source=$PWD/%{ovsdir} \
+%if %{with libcapng}
+        --enable-libcapng \
+%else
+        --disable-libcapng \
+%endif
+        --enable-ssl \
+        --with-pkidir=%{_sharedstatedir}/openvswitch/pki
+
+make %{?_smp_mflags}
+
+%install
+%make_install
+install -p -D -m 0644 \
+        rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn
+
+for service in ovn-controller ovn-controller-vtep ovn-northd; do
+        install -p -D -m 0644 \
+                        rhel/usr_lib_systemd_system_${service}.service \
+                        $RPM_BUILD_ROOT%{_unitdir}/${service}.service
+done
+
+install -d -m 0755 $RPM_BUILD_ROOT/%{_sharedstatedir}/ovn
+
+install -d $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/
+install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
+        $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
+install -p -m 0644 rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
+        $RPM_BUILD_ROOT%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
+
+install -d -m 0755 $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn
+ln -s %{_datadir}/ovn/scripts/ovndb-servers.ocf \
+      $RPM_BUILD_ROOT%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
+
+install -p -D -m 0644 rhel/etc_logrotate.d_ovn \
+        $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d/ovn
+
+# remove unneeded files.
+rm -f $RPM_BUILD_ROOT%{_bindir}/ovs*
+rm -f $RPM_BUILD_ROOT%{_bindir}/vtep-ctl
+rm -f $RPM_BUILD_ROOT%{_sbindir}/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man1/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man5/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man5/vtep*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man7/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man8/ovs*
+rm -f $RPM_BUILD_ROOT%{_mandir}/man8/vtep*
+rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/python
+rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovs*
+rm -rf $RPM_BUILD_ROOT%{_datadir}/ovn/bugtool-plugins
+rm -f $RPM_BUILD_ROOT%{_libdir}/*.a
+rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
+rm -f $RPM_BUILD_ROOT%{_libdir}/pkgconfig/*.pc
+rm -f $RPM_BUILD_ROOT%{_includedir}/ovn/*
+rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-appctl-bashcomp.bash
+rm -f $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/ovs-vsctl-bashcomp.bash
+rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/openvswitch
+rm -f $RPM_BUILD_ROOT%{_datadir}/ovn/scripts/ovn-bugtool*
+rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \
+        $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-underlay-driver
+
+%check
+%if %{with check}
+    touch resolv.conf
+    export OVS_RESOLV_CONF=$(pwd)/resolv.conf
+    if ! make check TESTSUITEFLAGS='%{_smp_mflags}'; then
+        cat tests/testsuite.log
+        if ! make check TESTSUITEFLAGS='--recheck'; then
+            cat tests/testsuite.log
+            # Presently a test case - "2796: ovn -- ovn-controller incremental processing"
+            # is failing on aarch64 arch. Let's not exit for this arch
+            # until we figure out why it is failing.
+            # Test case 93: ovn.at:12105       ovn -- ACLs on Port Groups is failing
+            # repeatedly on s390x. This needs to be investigated.
+            %ifnarch aarch64
+            %ifnarch ppc64le
+            %ifnarch s390x
+                exit 1
+            %endif
+            %endif
+            %endif
+        fi
+    fi
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%pre central
+if [ $1 -eq 1 ] ; then
+    # Package install.
+    /bin/systemctl status ovn-northd.service >/dev/null
+    ovn_status=$?
+    rpm -ql openvswitch-ovn-central > /dev/null
+    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+        # ovn-northd service is running which means old openvswitch-ovn-central
+        # is already installed and it will be cleaned up. So start ovn-northd
+        # service when posttrans central is called.
+        touch %{_localstatedir}/lib/rpm-state/ovn-northd
+    fi
+fi
+
+%pre host
+if [ $1 -eq 1 ] ; then
+    # Package install.
+    /bin/systemctl status ovn-controller.service >/dev/null
+    ovn_status=$?
+    rpm -ql openvswitch-ovn-host > /dev/null
+    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+        # ovn-controller service is running which means old
+        # openvswitch-ovn-host is installed and it will be cleaned up. So
+        # start ovn-controller service when posttrans host is called.
+        touch %{_localstatedir}/lib/rpm-state/ovn-controller
+    fi
+fi
+
+%pre vtep
+if [ $1 -eq 1 ] ; then
+    # Package install.
+    /bin/systemctl status ovn-controller-vtep.service >/dev/null
+    ovn_status=$?
+    rpm -ql openvswitch-ovn-vtep > /dev/null
+    if [[ "$?" = "0" && "$ovn_status" = "0" ]]; then
+        # ovn-controller-vtep service is running which means old
+        # openvswitch-ovn-vtep is installed and it will be cleaned up. So
+        # start ovn-controller-vtep service when posttrans host is called.
+        touch %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
+    fi
+fi
+
+%preun central
+%if 0%{?systemd_preun:1}
+    %systemd_preun ovn-northd.service
+%else
+    if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        /bin/systemctl --no-reload disable ovn-northd.service >/dev/null 2>&1 || :
+        /bin/systemctl stop ovn-northd.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%preun host
+%if 0%{?systemd_preun:1}
+    %systemd_preun ovn-controller.service
+%else
+    if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        /bin/systemctl --no-reload disable ovn-controller.service >/dev/null 2>&1 || :
+        /bin/systemctl stop ovn-controller.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%preun vtep
+%if 0%{?systemd_preun:1}
+    %systemd_preun ovn-controller-vtep.service
+%else
+    if [ $1 -eq 0 ] ; then
+        # Package removal, not upgrade
+        /bin/systemctl --no-reload disable ovn-controller-vtep.service >/dev/null 2>&1 || :
+        /bin/systemctl stop ovn-controller-vtep.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%post
+%if %{with libcapng}
+if [ $1 -eq 1 ]; then
+    sed -i 's:^#OVN_USER_ID=:OVN_USER_ID=:' %{_sysconfdir}/sysconfig/ovn
+    sed -i 's:\(.*su\).*:\1 openvswitch openvswitch:' %{_sysconfdir}/logrotate.d/ovn
+fi
+%endif
+
+%post central
+%if 0%{?systemd_post:1}
+    %systemd_post ovn-northd.service
+%else
+    # Package install, not upgrade
+    if [ $1 -eq 1 ]; then
+        /bin/systemctl daemon-reload >dev/null || :
+    fi
+%endif
+
+%post host
+%if 0%{?systemd_post:1}
+    %systemd_post ovn-controller.service
+%else
+    # Package install, not upgrade
+    if [ $1 -eq 1 ]; then
+        /bin/systemctl daemon-reload >dev/null || :
+    fi
+%endif
+
+%post vtep
+%if 0%{?systemd_post:1}
+    %systemd_post ovn-controller-vtep.service
+%else
+    # Package install, not upgrade
+    if [ $1 -eq 1 ]; then
+        /bin/systemctl daemon-reload >dev/null || :
+    fi
+%endif
+
+%postun
+
+%postun central
+%if 0%{?systemd_postun_with_restart:1}
+    %systemd_postun_with_restart ovn-northd.service
+%else
+    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+    if [ "$1" -ge "1" ] ; then
+    # Package upgrade, not uninstall
+        /bin/systemctl try-restart ovn-northd.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%postun host
+%if 0%{?systemd_postun_with_restart:1}
+    %systemd_postun_with_restart ovn-controller.service
+%else
+    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+    if [ "$1" -ge "1" ] ; then
+        # Package upgrade, not uninstall
+        /bin/systemctl try-restart ovn-controller.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%postun vtep
+%if 0%{?systemd_postun_with_restart:1}
+    %systemd_postun_with_restart ovn-controller-vtep.service
+%else
+    /bin/systemctl daemon-reload >/dev/null 2>&1 || :
+    if [ "$1" -ge "1" ] ; then
+        # Package upgrade, not uninstall
+        /bin/systemctl try-restart ovn-controller-vtep.service >/dev/null 2>&1 || :
+    fi
+%endif
+
+%posttrans central
+if [ $1 -eq 1 ]; then
+    # Package install, not upgrade
+    if [ -e %{_localstatedir}/lib/rpm-state/ovn-northd ]; then
+        rm %{_localstatedir}/lib/rpm-state/ovn-northd
+        /bin/systemctl start ovn-northd.service >/dev/null 2>&1 || :
+    fi
+fi
+
+
+%posttrans host
+if [ $1 -eq 1 ]; then
+    # Package install, not upgrade
+    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller ]; then
+        rm %{_localstatedir}/lib/rpm-state/ovn-controller
+        /bin/systemctl start ovn-controller.service >/dev/null 2>&1 || :
+    fi
+fi
+
+%posttrans vtep
+if [ $1 -eq 1 ]; then
+    # Package install, not upgrade
+    if [ -e %{_localstatedir}/lib/rpm-state/ovn-controller-vtep ]; then
+        rm %{_localstatedir}/lib/rpm-state/ovn-controller-vtep
+        /bin/systemctl start ovn-controller-vtep.service >/dev/null 2>&1 || :
+    fi
+fi
+
+%files
+%{_bindir}/ovn-nbctl
+%{_bindir}/ovn-sbctl
+%{_bindir}/ovn-trace
+%{_bindir}/ovn-detrace
+%{_bindir}/ovn_detrace.py
+%{_bindir}/ovn-appctl
+%{_bindir}/ovn-ic-nbctl
+%{_bindir}/ovn-ic-sbctl
+%dir %{_datadir}/ovn/
+%dir %{_datadir}/ovn/scripts/
+%{_datadir}/ovn/scripts/ovn-ctl
+%{_datadir}/ovn/scripts/ovn-lib
+%{_datadir}/ovn/scripts/ovndb-servers.ocf
+%{_mandir}/man8/ovn-ctl.8*
+%{_mandir}/man8/ovn-appctl.8*
+%{_mandir}/man8/ovn-nbctl.8*
+%{_mandir}/man8/ovn-ic-nbctl.8*
+%{_mandir}/man8/ovn-trace.8*
+%{_mandir}/man1/ovn-detrace.1*
+%{_mandir}/man7/ovn-architecture.7*
+%{_mandir}/man8/ovn-sbctl.8*
+%{_mandir}/man8/ovn-ic-sbctl.8*
+%{_mandir}/man5/ovn-nb.5*
+%{_mandir}/man5/ovn-ic-nb.5*
+%{_mandir}/man5/ovn-sb.5*
+%{_mandir}/man5/ovn-ic-sb.5*
+%dir %{ovnlibdir}/ocf/resource.d/ovn/
+%{ovnlibdir}/ocf/resource.d/ovn/ovndb-servers
+%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/logrotate.d/ovn
+%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/ovn
+
+%files central
+%{_bindir}/ovn-northd
+%{_bindir}/ovn-ic
+%{_mandir}/man8/ovn-northd.8*
+%{_mandir}/man8/ovn-ic.8*
+%{_datadir}/ovn/ovn-nb.ovsschema
+%{_datadir}/ovn/ovn-ic-nb.ovsschema
+%{_datadir}/ovn/ovn-sb.ovsschema
+%{_datadir}/ovn/ovn-ic-sb.ovsschema
+%{_unitdir}/ovn-northd.service
+%{ovnlibdir}/firewalld/services/ovn-central-firewall-service.xml
+
+%files host
+%{_bindir}/ovn-controller
+%{_mandir}/man8/ovn-controller.8*
+%{_unitdir}/ovn-controller.service
+%{ovnlibdir}/firewalld/services/ovn-host-firewall-service.xml
+
+%files vtep
+%{_bindir}/ovn-controller-vtep
+%{_mandir}/man8/ovn-controller-vtep.8*
+%{_unitdir}/ovn-controller-vtep.service
+
+%changelog
+* Tue Jan 23 2024 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-103
+- controller: fixed potential segfault when changing tunnel_key and deleting ls.
+[Upstream: 120075357a624293d52a1905c47a1bd249d2157c]
+
+* Fri Jan 19 2024 Ales Musil <amusil@redhat.com> - 23.09.0-102
+- northd: Use proper field for lookup_nd
+[Upstream: 8e25c1c37aa3301f69bc89ee49ffaef5aa2f76fd]
+
+* Wed Jan 17 2024 Dumitru Ceara <dceara@redhat.com> - 23.09.0-101
+- checkpatch.py: Port checkpatch related changes from the OVS repo.
+[Upstream: bf334c65e1ead50013880049564d445919aee61f]
+
+* Fri Jan 12 2024 Dumitru Ceara <dceara@redhat.com> - 23.09.0-100
+- actions: Make sure affinity learnt flows are auto deleted.
+[Upstream: 6ce267af7124a93306d8b5bf4944379536ecd264]
+
+* Thu Jan 11 2024 Mark Michelson <mmichels@redhat.com> - 23.09.0-99
+- pinctrl: Directly retrieve desired port_binding MAC.
+[Upstream: f85f5e3929c916985c7dfc0fe0f0433347d8bfae]
+
+* Tue Jan 09 2024 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.09.0-98
+- test: add dedicated test for garp-max-timeout
+[Upstream: 28fef02db946ba8113a2752e1abf61d8df5797e3]
+
+* Tue Jan 09 2024 Ales Musil <amusil@redhat.com> - 23.09.0-97
+- treewide: Fix small memory leaks reported by static analysis
+[Upstream: 0d5e6d65db19845aede9198d8e164d934a5f189e]
+
+* Tue Jan 09 2024 Ales Musil <amusil@redhat.com> - 23.09.0-96
+- Documentation: Add note about pinning the container after release
+[Upstream: 1a70f3f171c032c2329bb66f2e62d233ce19a494]
+
+* Tue Jan 09 2024 Ales Musil <amusil@redhat.com> - 23.09.0-95
+- ci: Cover more container posibilities
+[Upstream: 639aff0896527f9c48c56d6dfb3fdce84403b6dd]
+
+* Tue Jan 09 2024 Ales Musil <amusil@redhat.com> - 23.09.0-94
+- ci: Build container image before very job
+[Upstream: ca0f17758559ed836dfa0220e472ea99438cefb8]
+
+* Tue Jan 09 2024 Dumitru Ceara <dceara@redhat.com> - 23.09.0-93
+- ovs: Bump submodule to include IDL "spurious delete" fix.
+[Upstream: 9c97cdcd757ce356a85b3e6dde7eb19776fe4c38]
+
+* Wed Jan 03 2024 Jacob Tanenbaum <jtanenba@redhat.com> - 23.09.0-92
+- Correct ethtype referencing incorrect values
+[Upstream: e9863e57320d24f8fb0d02436834f795ba58ce48]
+
+* Mon Dec 18 2023 Numan Siddique <numans@ovn.org> - 23.09.0-91
+- Revert "ovn: add geneve PMTUD support"
+[Upstream: ed4e4a94ba44f5d5be5148ee82f336cab3adc7ec]
+
+* Mon Dec 18 2023 Daniel Ding <danieldin186@gmail.com> - 23.09.0-90
+- northd: forward arp request to lrp snat on.
+[Upstream: 20ea3b63fb3a2fce2c9e273bfbdcb4d8399b8091]
+
+* Wed Dec 13 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-89
+- northd: fix missing port up when deleting and adding back an lsp
+[Upstream: 8cabb443ae88dded5cd1800bdcea5c5760954d25]
+
+* Fri Dec 08 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-88
+- ovn-macros: Make sure stopped daemons continue before the test ends.
+[Upstream: e54ec661ef67cd93d1a72de907b37fab522bc2f9]
+
+* Thu Dec 07 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-87
+- system-test: Fix tcpdump usage in LB template tests.
+[Upstream: 5141c9d4c7c861f6a65a711e59a4e64ae7d2fcdb]
+
+* Thu Dec 07 2023 Eelco Chaudron <echaudro@redhat.com> - 23.09.0-86
+- tests: Move SCTP test from kernel only to general OVN system tests.
+[Upstream: 49d33629595a9c7fc44d7ac86926c83e475b322d]
+
+* Thu Dec 07 2023 Eelco Chaudron <echaudro@redhat.com> - 23.09.0-85
+- tests: Remove 'protoinfo' from the conntrack entries for SCTP tests.
+[Upstream: d87ffbe44d5b5c3f143c1e38e868f9db636b4565]
+
+* Thu Dec 07 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-84
+- northd: Skip transient IDL records.
+[Upstream: 44a40011f0b7f465c1eb60c9016bd56e09d7e538]
+
+* Thu Dec 07 2023 Ales Musil <amusil@redhat.com> - 23.09.0-83
+- system-tests: Consolidate wait condition in CoPP test
+[Upstream: ba7a45bde1de25868e0b16d8e58e6d523e2034ab]
+
+* Thu Dec 07 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-82
+- pinctrl: Fix up comments about sending RST packets for healthcheck.
+[Upstream: 4ef375edc8bee094f24b9e649dc01ce3edd2034b]
+
+* Mon Dec 04 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.09.0-81
+- ovn: add geneve PMTUD support (#2241711)
+[Upstream: e42ca82fb92cd69bbfd4da72b3c22bc57fc1ecd0]
+
+* Mon Dec 04 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-80
+- fmt_pkt: make sure scapy-server is started once
+[Upstream: b788911812171ee5d9c51806b1e287be910164c9]
+
+* Mon Dec 04 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-79
+- fmt_pkt: improve scapy-server logging
+[Upstream: 820e11754bff7b7029abf8bd8f166c169bdd8d04]
+
+* Mon Dec 04 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-78
+- fmt_pkt: use -S check to wait for scapy sock file
+[Upstream: 2d710c3b1d9444a49f80db6058462e7d33253644]
+
+* Mon Dec 04 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-77
+- fmt_pkt: don't subshell when calling ovs-appctl
+[Upstream: fc311bb6d6108d49b356d9c785f6d47e7dc8faff]
+
+* Mon Dec 04 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-76
+- controller: fix group_table and meter_table allocation
+[Upstream: acc63727d14ff7e9f447ed90115f74235f968499]
+
+* Fri Dec 01 2023 Mark Michelson <mmichels@redhat.com> - 23.09.0-75
+- Prepare for 23.09.2.
+[Upstream: 8a000cc863773030828a4cda2167840f08c4a65c]
+
+* Fri Dec 01 2023 Mark Michelson <mmichels@redhat.com> - 23.09.0-74
+- Set release date for 23.09.1.
+[Upstream: 0afd4e59e95b5f8c7b56760e91269786b0e0e52a]
+
+* Mon Nov 27 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-73
+- northd: Add missing stopwatch initialization.
+[Upstream: 7fd87c5d0b1492c14d90faec4af4069496ae3609]
+
+* Fri Nov 24 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-72
+- controller: avoid extra flows if localnet_learn_fdb is disabled
+[Upstream: b2f839849c36c058f940c417dc29e26165a1d30e]
+
+* Fri Nov 24 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-71
+- controller: FDB entries for localnet should not overwrite entries for vifs (#2242830)
+[Upstream: 33b0117598b23b8c0877e482ee350283a147bb5f]
+
+* Mon Nov 20 2023 Ales Musil <amusil@redhat.com> - 23.09.0-70
+- controller: Disable inactivity probe for statctrl
+[Upstream: bbd07439b9a8cd6db901bffcac7ac17f58e33a07]
+
+* Mon Nov 20 2023 Evgenii Kovalev <ekovalev.off@gmail.com> - 23.09.0-69
+- pinctrl: reset success and failures n_count regardless of svc state
+[Upstream: 617b84d7dd2ce3501b49e988e1ba06e86889c9bd]
+
+* Mon Nov 20 2023 Evgenii Kovalev <ekovalev.off@gmail.com> - 23.09.0-68
+- pinctrl: send RST instead of RST_ACK bit for lb hc
+[Upstream: beb26027cf26271c7cd780869b540737c7916e99]
+
+* Mon Nov 20 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-67
+- controller: Don't artificially limit group and meter IDs to 16bit.
+[Upstream: e9e716ad531e34766d2f02783ac08955096bf636]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-66
+- tests: fixed race_condition with max_prefix
+[Upstream: d257d800e41388bd2a387e0b6d5a0e41c2e8d8f1]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-65
+- tests: have CHECK_NO_CHANGE_AFTER_RECOMPUTE potentially wait for ports up
+[Upstream: dab54b81c7ee767943163f2aaaa27b2c4b367964]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-64
+- tests: fixed "ovn-nbctl - daemon retry connection"
+[Upstream: d2e0acb2a6aa510282da5e04036ec5258454c351]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-63
+- tests: fixed system test "LR with SNAT fragmentation needed for external server".
+[Upstream: 0bb6ba908421825428ec904d5316ae13090adbbf]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-62
+- tests: fixed "interconnection - static multicast" and "- IGMP/MLD multicast"
+[Upstream: 810d83e77ce3398bc94469404d85a01eb63e40bd]
+
+* Fri Nov 17 2023 martin.kalcok@canonical.com <martin.kalcok@canonical.com> - 23.09.0-61
+- ovn-ctl man: Add election timer config to manpage
+[Upstream: 25d4b6855f6ce3795314e9439716f775994c7f4d]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-60
+- Fix flows not removed in ha migration
+[Upstream: 5375cdd96eaf8e527e5afea402f279990398710c]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-59
+- binding: handle pb->chassis and pb->up from if-status module
+[Upstream: 619abe5c5e18f417fe20b252ca41b70e644466e0]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-58
+- binding: slight refactor if no local binding in consider_iface_release
+[Upstream: d039b4332b9ea739bdf6b2efc9f5f3e422fe9a42]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-57
+- controller: have I+P assigning ct_zones for l3gateway ports
+[Upstream: f5d01be7f1337bdc7885dd45592aa3b376467790]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-56
+- tests: fixed another set of flaky ovn-ic tests
+[Upstream: 650bffdbe0562dc364faaef51f51f99e82cccc56]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-55
+- tests: wait for all flows to be installed before sending packets
+[Upstream: b3d03b94178bee2479d6f66ffa34255a7feb79eb]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-54
+- tests: fixed "ipsec -- basic configuration"
+[Upstream: ac3ece28ca04cb74b21c80e2bd73767e29cca9a3]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-53
+- tests: fixed "LSP incremental processing"
+[Upstream: fcbc0ae1c66e31c38ad9d5e099237e7446958035]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-52
+- tests: do not start backup-northd by default
+[Upstream: 54fae8cbb5db827da95a2a52ff28f29e6c7740fe]
+
+* Fri Nov 17 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-51
+- tests: fixed multiple tests not properly waiting for packets to be received
+[Upstream: a1422144228bb9924dbc75782734a09c6ecfa534]
+
+* Fri Nov 17 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-50
+- ci: Pin Python, Fedora and Ubuntu runner versions.
+[Upstream: 627955eb79c2cd374853319c1d271c2fd1aeac37]
+
+* Thu Nov 16 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-49
+- ovs: Bump submodule to include E721 fixes.
+[Upstream: 1fa7628db4155d3a39d55fe61d8d19fa7d3030af]
+
+* Thu Nov 16 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-48
+- tests: Remove broken "feature inactivity probe" test.
+[Upstream: 5044376da0a1c14d1ccc4b41dfdbae14e74746b2]
+
+* Wed Nov 15 2023 Ilya Maximets <i.maximets@ovn.org> - 23.09.0-47
+- readthedocs: Add the configuration file.
+[Upstream: 84c93511ce9a612b9a815cc1403b4841cc2e4c58]
+
+* Wed Nov 15 2023 Ilya Maximets <i.maximets@ovn.org> - 23.09.0-46
+- Documentation: Use theme from Read The Docs.
+[Upstream: 39236dc3151baa3ace58c3ecd62ba0384b4c7a05]
+
+* Wed Nov 15 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-45
+- ovs: Bump submodule to v3.2.1.
+[Upstream: 74172ed481f7c239d9258845eb493f17d731df99]
+
+* Wed Nov 15 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-44
+- py-requirements: Remove hacking dependency and use recent flake8.
+[Upstream: c6a631f066eea105c57c265dc68257d1b5ee18e4]
+
+* Fri Nov 03 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-43
+- ovn-ic: wakeup on ovsdb transaction failures
+[Upstream: be4364e62ac739744c1ef5bdd74a85fe39d6e37d]
+
+* Fri Nov 03 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-42
+- ovn-ic: fix potential segmentation violation when ts is deleted
+[Upstream: 5f84ff658bfd1c26bd9749c3d2e09a7e3567a8bd]
+
+* Fri Oct 20 2023 Ales Musil <amusil@redhat.com> - 23.09.0-41
+- controller, northd: Wait for cleanup before replying to exit
+[Upstream: aae5b2ec8ec9f4f9f7c9738d23818c2c4967627c]
+
+* Fri Oct 20 2023 Mark Michelson <mmichels@redhat.com> - 23.09.0-40
+- tests: Add missing check for scapy.
+[Upstream: ea9310a5f1e37b373abffd85f7a8dd4fefc30c4e]
+
+* Wed Oct 18 2023 Ales Musil <amusil@redhat.com> - 23.09.0-39
+- ci: Apply the ASAN workaround only for Clang <16
+[Upstream: 15bf24b889b178d4cdbb6166d3bc5434ec59f9fc]
+
+* Wed Oct 18 2023 Ales Musil <amusil@redhat.com> - 23.09.0-38
+- ci: Use proper uname argument to get the HW type
+[Upstream: 2efc23f3edf0293ec81a167e1c4bf99fe5601ca2]
+
+* Thu Oct 12 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-37
+- tests: Wait for new ovn-controllers to connect to Southbound.
+[Upstream: 349266aac20f229b10ef0313c9f4e6b5f1af4ede]
+
+* Wed Oct 11 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-36
+- northd: Reset ls_datapath_group if not all chassis support it.
+[Upstream: df7656fbf6a4ec1175b8f464a1aa6ed6e74fde29]
+
+* Wed Oct 11 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.09.0-35
+- northd: introduce ls_datapath_group column in lb sb db table
+[Upstream: 276b9d47183ebd31c382742025e562fda8d14d11]
+
+* Wed Oct 11 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.09.0-34
+- northd: sync lb applied to logical routers in sb db lb table (#2193323)
+[Upstream: c33398e32b2753dd6c0cecf35ba48ad8faa69bfc]
+
+* Tue Oct 10 2023 shylou <liuxie_11@163.com> - 23.09.0-33
+- northd: Avoid snat on reply packets for dgw
+[Upstream: e8c79cecef9d6e15673be1a604baaaca083f0016]
+
+* Tue Oct 10 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-32
+- northd: Incrementally process SB.Load_balancer updates.
+[Upstream: a9788ef39e003b04ec426761833d85bbec1f3b84]
+
+* Tue Oct 10 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-31
+- tests: Add missing --wait=sb to the LB I-P test.
+[Upstream: cadfefdf1c6457d25b6d1f93e217493739418365]
+
+* Tue Oct 10 2023 Ales Musil <amusil@redhat.com> - 23.09.0-30
+- system-tests: Make sure that IPv6 address is available right away
+[Upstream: dc9eb3a1cc95accc37165902006db6eeab25fba6]
+
+* Tue Oct 10 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-29
+- Don't mention packet cloning when failing to find tunnel
+[Upstream: 44ee1a6cb40395617f5dbab5829c9f436c16a783]
+
+* Tue Oct 10 2023 Ales Musil <amusil@redhat.com> - 23.09.0-28
+- northd: Allow need frag to be SNATed
+[Upstream: 94c8f952bb848806e04a857a84718d2744cfcb9f]
+
+* Tue Oct 10 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-27
+- docs: require ovn-set-local-ip for co-located ovn-controllers
+[Upstream: 16bdac7965ae805040a107fc3cdade5bf4db63a2]
+
+* Tue Oct 10 2023 Ilya Maximets <i.maximets@ovn.org> - 23.09.0-26
+- memory-trim: Fix timestamp overflow warning right after reboot.
+[Upstream: 32ab7d94f9258ad6e938c715380a567b4a363a62]
+
+* Mon Oct 09 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-25
+- Fix missing flows in ls_in_dhcp_options table
+[Upstream: bb8fe6add97ab5fed5e4618b32c16e174faf44c8]
+
+* Mon Oct 09 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-24
+- controller: throttle port claim attempts from if-status
+[Upstream: bd32a6646d21c766497494c7a1a4add05a40cd22]
+
+* Fri Oct 06 2023 Ales Musil <amusil@redhat.com> - 23.09.0-23
+- ci: Free up additional space for ovn-k jobs.
+[Upstream: d30fe25c45620017ceea4f06e6e3ebd316ba734f]
+
+* Fri Oct 06 2023 Ales Musil <amusil@redhat.com> - 23.09.0-22
+- ci: Handle google-cloud-sdk -> google-cloud-cli package name change.
+[Upstream: 42e81bdcebc8cd744deb8034d2fb89ec3b85bf4a]
+
+* Fri Oct 06 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-21
+- ci: Free up disk space in a more robust way.
+[Upstream: cf99264e252c20edf93ab5735e18aa3225c98398]
+
+* Fri Oct 06 2023 Dumitru Ceara <dceara@redhat.com> - 23.09.0-20
+- ci: Update apt cache before installing gcc-multilib.
+[Upstream: fd79876c2757f9074d38bd41cc36f59f3ba26138]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-19
+- tests: fixed "send gratuitous ARP for NAT rules on HA distributed router"
+[Upstream: 94b671cf89b27f54d1d03149de900994c79df415]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-18
+- tests: move trim_zeros() to ovn-macros
+[Upstream: 56b0435d8431518f4299c622a6ec9fc8770b8b0c]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-17
+- tests: skip test "MAC binding aging" if scapy not available.
+[Upstream: 148431080738bdec5e625a9ce8d470e365ee14f2]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-16
+- tests: fixed "L2 Drop and Allow ACL w/ Stateful ACL"
+[Upstream: 6f8719c60b8a578d564d3a6147f963fddeeacaa1]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-15
+- tests: fixed multiple tests missing ovn-nbctl "wait"
+[Upstream: f8cdfedacf212d9f103c2adba0c6805c01c68ff4]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-14
+- tests: fixed "options:requested-chassis for logical port"
+[Upstream: cd74dda22b255890a120988e8737c22a25c49957]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-13
+- tests: fixed "Logical router policy packet marking"
+[Upstream: e5a794dc30b087e0c78764326c86a3258f97bcc0]
+
+* Fri Oct 06 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-12
+- tests: fixed multiple ovn-ic tests
+[Upstream: 0575b97dc676d8c225bc8f63befec1bf1390ebe1]
+
+* Thu Oct 05 2023 Ales Musil <amusil@redhat.com> - 23.09.0-11
+- pinctrl: Reply with correct destination for ICMPv6 RA packets
+[Upstream: b93f36a248f7df3eb71b5141c5deadec7c18ee24]
+
+* Mon Oct 02 2023 Han Zhou <hzhou@ovn.org> - 23.09.0-10
+- ovn-controller: Add monitor condition for FDB.
+[Upstream: c4008ae520af2561cfd68749227a8a468277e2e5]
+
+* Thu Sep 21 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-9
+- Rename scapy-server into scapy-server.py
+[Upstream: d16ec6f9a063a0cb2d7bac56e23dd60d0c856b76]
+
+* Wed Sep 20 2023 Patryk Diak <pdiak@redhat.com> - 23.09.0-8
+- Add ovnkube-identity binary to the ovn-kubernetes Dockerfile
+[Upstream: 35d9e42bc3e60629701743ca7e9d6890511cf0f5]
+
+* Tue Sep 19 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.09.0-7
+- tests: offload scapy transformations to a separate unixctl daemon
+[Upstream: 4a82a49363a591d429d86d60f9120166ea04cb91]
+
+* Tue Sep 19 2023 Ales Musil <amusil@redhat.com> - 23.09.0-6
+- northd: Remove hosting-chassis only if it's specified
+[Upstream: 0b45a1a1cc6f081184d599ba139847ff03d90912]
+
+* Fri Sep 15 2023 Xavier Simonart <xsimonar@redhat.com> - 23.09.0-5
+- QoS: Properly set qos when ovs db is read only (#2234349)
+[Upstream: 9c56ac4b74f6b964f102b94404b350417b1cd772]
+
+* Fri Sep 15 2023 Mark Michelson <mmichels@redhat.com> - 23.09.0-4
+- Prepare for 23.09.1.
+[Upstream: 36f37341d32589dcd8d4bfeb023046b07dea1a44]
+