diff --git a/.ovn.metadata b/.ovn.metadata
index 5b5b237..e8741e5 100644
--- a/.ovn.metadata
+++ b/.ovn.metadata
@@ -1,5 +1,5 @@
 002450621b33c5690060345b0aac25bc2426d675  SOURCES/docutils-0.12.tar.gz
-f9cfb42d90c2c5417825087573feb01117be91f3  SOURCES/openvswitch-a787fbb.tar.gz
-ba9c555a492827e129a040e0fa95bfe7e95792a0  SOURCES/ovn-22.12.0.tar.gz
+c39c740cd246836bcb125432d2205d0f76504bc0  SOURCES/openvswitch-8986d4d.tar.gz
+4b5217eba8aa5b020946395afa94e48ae81957e8  SOURCES/ovn-23.03.0.tar.gz
 d34f96421a86004aa5d26ecf975edefd09f948b1  SOURCES/Pygments-1.4.tar.gz
 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a  SOURCES/Sphinx-1.1.3.tar.gz
diff --git a/SOURCES/ovn22.12.patch b/SOURCES/ovn22.12.patch
deleted file mode 100644
index 6277eee..0000000
--- a/SOURCES/ovn22.12.patch
+++ /dev/null
@@ -1,3404 +0,0 @@
-diff --git a/.ci/ovn-kubernetes/Dockerfile b/.ci/ovn-kubernetes/Dockerfile
-index e74b620be..1884724ec 100644
---- a/.ci/ovn-kubernetes/Dockerfile
-+++ b/.ci/ovn-kubernetes/Dockerfile
-@@ -1,5 +1,5 @@
- ARG OVNKUBE_COMMIT=master
--ARG LIBOVSDB_COMMIT=8081fe24e48f
-+ARG LIBOVSDB_COMMIT=a6a173993830
- 
- FROM fedora:35 AS ovnbuilder
- 
-@@ -47,9 +47,17 @@ RUN GO111MODULE=on go install github.com/ovn-org/libovsdb/cmd/modelgen@${LIBOVSD
- # Clone OVN Kubernetes and build the binary based on the commit passed as argument
- WORKDIR /root
- RUN git clone https://github.com/ovn-org/ovn-kubernetes.git
--WORKDIR /root/ovn-kubernetes/go-controller
-+WORKDIR /root/ovn-kubernetes
- RUN git checkout ${OVNKUBE_COMMIT} && git log -n 1
- 
-+# Copy the ovn-kubernetes scripts from the OVN sources and apply any
-+# custom changes if needed.
-+RUN mkdir -p /tmp/ovn/.ci/ovn-kubernetes
-+COPY .ci/ovn-kubernetes /tmp/ovn/.ci/ovn-kubernetes
-+WORKDIR /tmp/ovn
-+RUN .ci/ovn-kubernetes/prepare.sh /root/ovn-kubernetes
-+
-+WORKDIR /root/ovn-kubernetes/go-controller
- # Make sure we use the OVN NB/SB schema from the local code.
- COPY --from=ovnbuilder /tmp/ovn/ovn-nb.ovsschema pkg/nbdb/ovn-nb.ovsschema
- COPY --from=ovnbuilder /tmp/ovn/ovn-sb.ovsschema pkg/sbdb/ovn-sb.ovsschema
-diff --git a/.ci/ovn-kubernetes/custom.patch b/.ci/ovn-kubernetes/custom.patch
-new file mode 100644
-index 000000000..e69de29bb
-diff --git a/.ci/ovn-kubernetes/prepare.sh b/.ci/ovn-kubernetes/prepare.sh
-new file mode 100755
-index 000000000..8fc9652af
---- /dev/null
-+++ b/.ci/ovn-kubernetes/prepare.sh
-@@ -0,0 +1,20 @@
-+#!/bin/bash
-+
-+set -ev
-+
-+ovnk8s_path=$1
-+topdir=$PWD
-+
-+pushd ${ovnk8s_path}
-+
-+# Add here any custom operations that need to performed on the
-+# ovn-kubernetes cloned repo, e.g., custom patches.
-+
-+# git apply --allow-empty is too new so not all git versions from major
-+# distros support it, just check if the custom patch file is not empty
-+# before applying it.
-+[ -s ${topdir}/.ci/ovn-kubernetes/custom.patch ] && \
-+    git apply -v ${topdir}/.ci/ovn-kubernetes/custom.patch
-+
-+popd # ${ovnk8s_path}
-+exit 0
-diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml
-index 344937e53..840ecd1e0 100644
---- a/.github/workflows/ovn-kubernetes.yml
-+++ b/.github/workflows/ovn-kubernetes.yml
-@@ -16,7 +16,7 @@ env:
-   GO_VERSION: "1.18.4"
-   K8S_VERSION: v1.24.0
-   OVNKUBE_COMMIT: "master"
--  LIBOVSDB_COMMIT: "98c0bad3cff1"
-+  LIBOVSDB_COMMIT: "a6a173993830"
-   KIND_CLUSTER_NAME: ovn
-   KIND_INSTALL_INGRESS: true
-   KIND_ALLOW_SYSTEM_WRITES: true
-@@ -73,6 +73,7 @@ jobs:
-     env:
-       JOB_NAME: "${{ matrix.target }}-${{ matrix.ha }}-${{ matrix.gateway-mode }}-${{ matrix.ipfamily }}-${{ matrix.disable-snat-multiple-gws }}-${{ matrix.second-bridge }}"
-       OVN_HYBRID_OVERLAY_ENABLE: "${{ matrix.target == 'control-plane' }}"
-+      KIND_INSTALL_METALLB: "${{ matrix.target == 'control-plane' }}"
-       OVN_MULTICAST_ENABLE:  "${{ matrix.target == 'control-plane' }}"
-       OVN_EMPTY_LB_EVENTS: "${{ matrix.target == 'control-plane' }}"
-       OVN_HA: "true"
-@@ -91,12 +92,19 @@ jobs:
-         go-version: ${{ env.GO_VERSION }}
-       id: go
- 
-+    - name: Check out ovn
-+      uses: actions/checkout@v3
-+
-     - name: Check out ovn-kubernetes
-       uses: actions/checkout@v3
-       with:
-           path: src/github.com/ovn-org/ovn-kubernetes
-           repository: ovn-org/ovn-kubernetes
- 
-+    - name: Prepare
-+      run: |
-+        .ci/ovn-kubernetes/prepare.sh src/github.com/ovn-org/ovn-kubernetes
-+
-     - name: Set up environment
-       run: |
-         export GOPATH=$(go env GOPATH)
-diff --git a/Makefile.am b/Makefile.am
-index c8f770146..f7758d114 100644
---- a/Makefile.am
-+++ b/Makefile.am
-@@ -90,6 +90,8 @@ EXTRA_DIST = \
- 	.ci/osx-build.sh \
- 	.ci/osx-prepare.sh \
- 	.ci/ovn-kubernetes/Dockerfile \
-+	.ci/ovn-kubernetes/prepare.sh \
-+	.ci/ovn-kubernetes/custom.patch \
- 	.github/workflows/test.yml \
- 	.github/workflows/ovn-kubernetes.yml \
- 	boot.sh \
-diff --git a/NEWS b/NEWS
-index 0920b44e2..acb8065bc 100644
---- a/NEWS
-+++ b/NEWS
-@@ -1,3 +1,6 @@
-+OVN v22.12.1 - xx xxx xxxx
-+--------------------------
-+
- OVN v22.12.0 - 16 Dec 2022
- --------------------------
-   - Add load balancer "affinity_timeout" option to configure load balancing
-diff --git a/build-aux/sodepends.py b/build-aux/sodepends.py
-index 343fda1af..7b1f9c840 100755
---- a/build-aux/sodepends.py
-+++ b/build-aux/sodepends.py
-@@ -63,7 +63,8 @@ def sodepends(include_info, filenames, dst):
-             continue
- 
-         # Open file.
--        include_dirs = [info[0] for info in include_info]
-+        include_dirs = [info[1] if len(info) == 2 else info[0]
-+                        for info in include_info]
-         fn = soutil.find_file(include_dirs, toplevel)
-         if not fn:
-             ok = False
-diff --git a/configure.ac b/configure.ac
-index 101467253..357758e0c 100644
---- a/configure.ac
-+++ b/configure.ac
-@@ -13,7 +13,7 @@
- # limitations under the License.
- 
- AC_PREREQ(2.63)
--AC_INIT(ovn, 22.12.0, bugs@openvswitch.org)
-+AC_INIT(ovn, 22.12.1, bugs@openvswitch.org)
- AC_CONFIG_MACRO_DIR([m4])
- AC_CONFIG_AUX_DIR([build-aux])
- AC_CONFIG_HEADERS([config.h])
-diff --git a/controller/chassis.c b/controller/chassis.c
-index 685d9b2ae..98f8da2be 100644
---- a/controller/chassis.c
-+++ b/controller/chassis.c
-@@ -352,6 +352,7 @@ chassis_build_other_config(const struct ovs_chassis_cfg *ovs_cfg,
-     smap_replace(config, OVN_FEATURE_PORT_UP_NOTIF, "true");
-     smap_replace(config, OVN_FEATURE_CT_NO_MASKED_LABEL, "true");
-     smap_replace(config, OVN_FEATURE_MAC_BINDING_TIMESTAMP, "true");
-+    smap_replace(config, OVN_FEATURE_CT_LB_RELATED, "true");
- }
- 
- /*
-@@ -469,6 +470,12 @@ chassis_other_config_changed(const struct ovs_chassis_cfg *ovs_cfg,
-         return true;
-     }
- 
-+    if (!smap_get_bool(&chassis_rec->other_config,
-+                       OVN_FEATURE_CT_LB_RELATED,
-+                       false)) {
-+        return true;
-+    }
-+
-     return false;
- }
- 
-diff --git a/controller/lflow.c b/controller/lflow.c
-index bb47bb0c7..4b1cfe318 100644
---- a/controller/lflow.c
-+++ b/controller/lflow.c
-@@ -1567,9 +1567,6 @@ add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip,
-     /* Hairpin replies have the same nw_proto as packets that created the
-      * session.
-      */
--    union mf_value imm_proto = {
--        .u8 = lb_proto,
--    };
-     ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-     ol_spec->dst.field = mf_from_id(MFF_IP_PROTO);
-     ol_spec->src.field = mf_from_id(MFF_IP_PROTO);
-@@ -1577,16 +1574,21 @@ add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip,
-     ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
-     ol_spec->n_bits = ol_spec->dst.n_bits;
-     ol_spec->dst_type = NX_LEARN_DST_MATCH;
--    ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
--    mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match);
--
--    /* Push value last, as this may reallocate 'ol_spec' */
--    imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
--    src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
--    memcpy(src_imm, &imm_proto, imm_bytes);
- 
-     /* Hairpin replies have source port == <backend-port>. */
-     if (has_l4_port) {
-+        union mf_value imm_proto = {
-+            .u8 = lb_proto,
-+        };
-+
-+        ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
-+        mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match);
-+
-+        /* Push value last, as this may reallocate 'ol_spec' */
-+        imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
-+        src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
-+        memcpy(src_imm, &imm_proto, imm_bytes);
-+
-         ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
-         switch (lb_proto) {
-         case IPPROTO_TCP:
-@@ -1610,6 +1612,8 @@ add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip,
-         ol_spec->n_bits = ol_spec->dst.n_bits;
-         ol_spec->dst_type = NX_LEARN_DST_MATCH;
-         ol_spec->src_type = NX_LEARN_SRC_FIELD;
-+    } else {
-+        ol_spec->src_type = NX_LEARN_SRC_FIELD;
-     }
- 
-     /* Set MLF_LOOKUP_LB_HAIRPIN_BIT for hairpin replies. */
-diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
-index 73c33a6bf..c899283dc 100644
---- a/controller/ovn-controller.c
-+++ b/controller/ovn-controller.c
-@@ -755,6 +755,11 @@ update_ct_zones(const struct shash *binding_lports,
-             }
-             bitmap_set1(ct_zone_bitmap, snat_req_node->data);
-             node->data = snat_req_node->data;
-+        } else {
-+            add_pending_ct_zone_entry(pending_ct_zones, CT_ZONE_OF_QUEUED,
-+                                      snat_req_node->data, true, snat_req_node->name);
-+            bitmap_set1(ct_zone_bitmap, snat_req_node->data);
-+            simap_put(ct_zones, snat_req_node->name, snat_req_node->data);
-         }
-     }
- 
-diff --git a/controller/pinctrl.c b/controller/pinctrl.c
-index 82da6ae73..e4d530138 100644
---- a/controller/pinctrl.c
-+++ b/controller/pinctrl.c
-@@ -1419,7 +1419,6 @@ prepare_ipv6_prefixd(struct ovsdb_idl_txn *ovnsb_idl_txn,
- 
- struct buffer_info {
-     struct ofpbuf ofpacts;
--    ofp_port_t ofp_port;
-     struct dp_packet *p;
- };
- 
-@@ -1495,7 +1494,6 @@ buffered_push_packet(struct buffered_packets *bp,
-     union mf_value pkt_mark_value;
-     mf_get_value(pkt_mark_field, &md->flow, &pkt_mark_value);
-     ofpact_put_set_field(&bi->ofpacts, pkt_mark_field, &pkt_mark_value, NULL);
--    bi->ofp_port = md->flow.in_port.ofp_port;
- 
-     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&bi->ofpacts);
-     resubmit->in_port = OFPP_CONTROLLER;
-@@ -1531,7 +1529,7 @@ buffered_send_packets(struct rconn *swconn, struct buffered_packets *bp,
-             .ofpacts = bi->ofpacts.data,
-             .ofpacts_len = bi->ofpacts.size,
-         };
--        match_set_in_port(&po.flow_metadata, bi->ofp_port);
-+        match_set_in_port(&po.flow_metadata, OFPP_CONTROLLER);
-         queue_msg(swconn, ofputil_encode_packet_out(&po, proto));
- 
-         ofpbuf_uninit(&bi->ofpacts);
-diff --git a/debian/changelog b/debian/changelog
-index 6f5a9ac2a..d658774f6 100644
---- a/debian/changelog
-+++ b/debian/changelog
-@@ -1,3 +1,9 @@
-+OVN (22.12.1-1) unstable; urgency=low
-+   [ OVN team ]
-+   * New upstream version
-+
-+ -- OVN team <dev@openvswitch.org>  Fri, 16 Dec 2022 09:52:44 -0500
-+
- ovn (22.12.0-1) unstable; urgency=low
- 
-    * New upstream version
-diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
-index 73ce77e5c..9a80a7f68 100644
---- a/ic/ovn-ic.c
-+++ b/ic/ovn-ic.c
-@@ -1911,13 +1911,112 @@ main(int argc, char *argv[])
-     struct ovsdb_idl_loop ovnisb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
-         ovsdb_idl_create(ovn_ic_sb_db, &icsbrec_idl_class, true, true));
- 
--    /* ovn-nb db. XXX: add only needed tables and columns */
-+    /* ovn-nb db. */
-     struct ovsdb_idl_loop ovnnb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
--        ovsdb_idl_create(ovnnb_db, &nbrec_idl_class, true, true));
--
--    /* ovn-sb db. XXX: add only needed tables and columns */
-+        ovsdb_idl_create(ovnnb_db, &nbrec_idl_class, false, true));
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_nb_global);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl, &nbrec_nb_global_col_name);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl, &nbrec_nb_global_col_options);
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl,
-+                        &nbrec_table_logical_router_static_route);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_route_table);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_ip_prefix);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_nexthop);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_external_ids);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_options);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_static_route_col_policy);
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_logical_router);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_col_name);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_col_static_routes);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_col_ports);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_col_options);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_col_external_ids);
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_logical_router_port);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_port_col_name);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_port_col_networks);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_port_col_external_ids);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_router_port_col_options);
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_logical_switch);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_col_name);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_col_ports);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_col_other_config);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_col_external_ids);
-+
-+    ovsdb_idl_add_table(ovnnb_idl_loop.idl, &nbrec_table_logical_switch_port);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_name);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_addresses);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_options);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_type);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_up);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_addresses);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_enabled);
-+    ovsdb_idl_add_column(ovnnb_idl_loop.idl,
-+                         &nbrec_logical_switch_port_col_external_ids);
-+
-+    /* ovn-sb db. */
-     struct ovsdb_idl_loop ovnsb_idl_loop = OVSDB_IDL_LOOP_INITIALIZER(
--        ovsdb_idl_create(ovnsb_db, &sbrec_idl_class, true, true));
-+        ovsdb_idl_create(ovnsb_db, &sbrec_idl_class, false, true));
-+
-+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_encaps);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_name);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_hostname);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_other_config);
-+
-+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_encap);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_encap_col_chassis_name);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_encap_col_type);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_encap_col_ip);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_encap_col_options);
-+
-+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_datapath_binding);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_datapath_binding_col_external_ids);
-+
-+    ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_port_binding);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_datapath);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_mac);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_options);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_logical_port);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_external_ids);
-+    ovsdb_idl_add_column(ovnsb_idl_loop.idl,
-+                         &sbrec_port_binding_col_chassis);
- 
-     /* Create IDL indexes */
-     struct ovsdb_idl_index *nbrec_ls_by_name
-diff --git a/include/ovn/features.h b/include/ovn/features.h
-index 679f67457..5bcd68739 100644
---- a/include/ovn/features.h
-+++ b/include/ovn/features.h
-@@ -24,6 +24,7 @@
- #define OVN_FEATURE_PORT_UP_NOTIF      "port-up-notif"
- #define OVN_FEATURE_CT_NO_MASKED_LABEL "ct-no-masked-label"
- #define OVN_FEATURE_MAC_BINDING_TIMESTAMP "mac-binding-timestamp"
-+#define OVN_FEATURE_CT_LB_RELATED "ovn-ct-lb-related"
- 
- /* OVS datapath supported features.  Based on availability OVN might generate
-  * different types of openflows.
-diff --git a/include/ovn/lex.h b/include/ovn/lex.h
-index 9159b7a26..64d33361f 100644
---- a/include/ovn/lex.h
-+++ b/include/ovn/lex.h
-@@ -29,6 +29,8 @@
- 
- struct ds;
- 
-+#define LEX_TEMPLATE_PREFIX '^'
-+
- /* Token type. */
- enum lex_type {
-     LEX_T_END,                  /* end of input */
-diff --git a/lib/lb.c b/lib/lb.c
-index 43628bba7..c13d07b99 100644
---- a/lib/lb.c
-+++ b/lib/lb.c
-@@ -314,11 +314,10 @@ ovn_lb_vip_destroy(struct ovn_lb_vip *vip)
-     free(vip->backends);
- }
- 
--void
--ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
-+static void
-+ovn_lb_vip_format__(const struct ovn_lb_vip *vip, struct ds *s,
-+                    bool needs_brackets)
- {
--    bool needs_brackets = vip->address_family == AF_INET6 && vip->port_str
--                          && !template;
-     if (needs_brackets) {
-         ds_put_char(s, '[');
-     }
-@@ -331,6 +330,30 @@ ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
-     }
- }
- 
-+/* Formats the VIP in the way the ovn-controller expects it, that is,
-+ * template IPv6 variables need to be between brackets too.
-+ */
-+static char *
-+ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
-+{
-+    struct ds s = DS_EMPTY_INITIALIZER;
-+
-+    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
-+        ovn_lb_vip_format__(vip, &s, true);
-+    } else {
-+        ovn_lb_vip_format(vip, &s, !!vip->port_str);
-+    }
-+    return ds_steal_cstr(&s);
-+}
-+
-+void
-+ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
-+{
-+    bool needs_brackets = vip->address_family == AF_INET6 && vip->port_str
-+                          && !template;
-+    ovn_lb_vip_format__(vip, s, needs_brackets);
-+}
-+
- void
- ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s,
-                            bool template)
-@@ -512,6 +535,7 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
-     lb->n_vips = smap_count(&nbrec_lb->vips);
-     lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
-     lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
-+    smap_init(&lb->template_vips);
-     lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
- 
-     bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
-@@ -560,6 +584,12 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
-         } else {
-             sset_add(&lb->ips_v6, lb_vip->vip_str);
-         }
-+
-+        if (lb->template && address_family == AF_INET6) {
-+            smap_add_nocopy(&lb->template_vips,
-+                            ovn_lb_vip6_template_format_internal(lb_vip),
-+                            xstrdup(node->value));
-+        }
-         n_vips++;
-     }
- 
-@@ -604,6 +634,15 @@ ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
-     return NULL;
- }
- 
-+const struct smap *
-+ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
-+{
-+    if (!smap_is_empty(&lb->template_vips)) {
-+        return &lb->template_vips;
-+    }
-+    return &lb->nlb->vips;
-+}
-+
- void
- ovn_northd_lb_add_lr(struct ovn_northd_lb *lb, size_t n,
-                      struct ovn_datapath **ods)
-@@ -637,6 +676,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
-     }
-     free(lb->vips);
-     free(lb->vips_nb);
-+    smap_destroy(&lb->template_vips);
-     sset_destroy(&lb->ips_v4);
-     sset_destroy(&lb->ips_v6);
-     free(lb->selection_fields);
-diff --git a/lib/lb.h b/lib/lb.h
-index 55a41ae0b..55becc1bf 100644
---- a/lib/lb.h
-+++ b/lib/lb.h
-@@ -19,6 +19,7 @@
- 
- #include <sys/types.h>
- #include <netinet/in.h>
-+#include "lib/smap.h"
- #include "openvswitch/hmap.h"
- #include "ovn-util.h"
- #include "sset.h"
-@@ -62,6 +63,9 @@ struct ovn_northd_lb {
-     char *selection_fields;
-     struct ovn_lb_vip *vips;
-     struct ovn_northd_lb_vip *vips_nb;
-+    struct smap template_vips; /* Slightly changed template VIPs, populated
-+                                * if needed.  Until now it's only required
-+                                * for IPv6 template load balancers. */
-     size_t n_vips;
- 
-     enum lb_neighbor_responder_mode neigh_mode;
-@@ -130,6 +134,7 @@ struct ovn_northd_lb_backend {
- struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
- struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
-                                          const struct uuid *);
-+const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
- void ovn_northd_lb_destroy(struct ovn_northd_lb *);
- void ovn_northd_lb_add_lr(struct ovn_northd_lb *lb, size_t n,
-                           struct ovn_datapath **ods);
-diff --git a/lib/lex.c b/lib/lex.c
-index 5251868b5..a8b9812bb 100644
---- a/lib/lex.c
-+++ b/lib/lex.c
-@@ -782,7 +782,7 @@ next:
-         p = lex_parse_port_group(p, token);
-         break;
- 
--    case '^':
-+    case LEX_TEMPLATE_PREFIX:
-         p = lex_parse_template(p, token);
-         break;
- 
-@@ -1061,7 +1061,7 @@ lexer_parse_template_string(const char *s, const struct smap *template_vars,
-                             struct sset *template_vars_ref)
- {
-     /* No '^' means no templates. */
--    if (!strchr(s, '^')) {
-+    if (!strchr(s, LEX_TEMPLATE_PREFIX)) {
-         return lex_str_use(s);
-     }
- 
-diff --git a/lib/ovn-util.c b/lib/ovn-util.c
-index 86b98acf7..69ab56423 100644
---- a/lib/ovn-util.c
-+++ b/lib/ovn-util.c
-@@ -825,24 +825,6 @@ ovn_get_internal_version(void)
-                      N_OVNACTS, OVN_INTERNAL_MINOR_VER);
- }
- 
--unsigned int
--ovn_parse_internal_version_minor(const char *ver)
--{
--    const char *p = ver + strlen(ver);
--    for (int i = 0; i < strlen(ver); i++) {
--        if (*p == '.') {
--            break;
--        }
--        p--;
--    }
--
--    unsigned int minor;
--    if (ovs_scan(p, ".%u", &minor)) {
--        return minor;
--    }
--    return 0;
--}
--
- #ifdef DDLOG
- /* Callbacks used by the ddlog northd code to print warnings and errors. */
- void
-diff --git a/lib/ovn-util.h b/lib/ovn-util.h
-index 809ff1d36..48dc846ad 100644
---- a/lib/ovn-util.h
-+++ b/lib/ovn-util.h
-@@ -70,6 +70,23 @@ struct lport_addresses {
-     struct ipv6_netaddr *ipv6_addrs;
- };
- 
-+static inline bool
-+ipv6_is_all_router(const struct in6_addr *addr)
-+{
-+    return ipv6_addr_equals(addr, &in6addr_all_routers);
-+}
-+
-+static const struct in6_addr in6addr_all_site_routers = {{{
-+    0xff,0x05,0x00,0x00,0x00,0x00,0x00,0x00,
-+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02
-+}}};
-+
-+static inline bool
-+ipv6_is_all_site_router(const struct in6_addr *addr)
-+{
-+    return ipv6_addr_equals(addr, &in6addr_all_site_routers);
-+}
-+
- bool is_dynamic_lsp_address(const char *address);
- bool extract_addresses(const char *address, struct lport_addresses *,
-                        int *ofs);
-@@ -248,11 +265,6 @@ bool ip_address_and_port_from_lb_key(const char *key, char **ip_address,
-  * value. */
- char *ovn_get_internal_version(void);
- 
--/* Parse the provided internal version string and return the "minor" part which
-- * is expected to be an unsigned integer followed by the last "." in the
-- * string. Returns 0 if the string can't be parsed. */
--unsigned int ovn_parse_internal_version_minor(const char *ver);
--
- /* OVN Packet definitions. These may eventually find a home in OVS's
-  * packets.h file. For the time being, they live here because OVN uses them
-  * and OVS does not.
-diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
-index 363e384bd..a7b735333 100644
---- a/northd/inc-proc-northd.c
-+++ b/northd/inc-proc-northd.c
-@@ -34,10 +34,13 @@
- #include "en-lflow.h"
- #include "en-northd-output.h"
- #include "en-sync-sb.h"
-+#include "unixctl.h"
- #include "util.h"
- 
- VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
- 
-+static unixctl_cb_func chassis_features_list;
-+
- #define NB_NODES \
-     NB_NODE(nb_global, "nb_global") \
-     NB_NODE(copp, "copp") \
-@@ -306,6 +309,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
-     engine_ovsdb_node_add_index(&en_sb_address_set,
-                                 "sbrec_address_set_by_name",
-                                 sbrec_address_set_by_name);
-+
-+    struct northd_data *northd_data =
-+        engine_get_internal_data(&en_northd);
-+    unixctl_command_register("debug/chassis-features-list", "", 0, 0,
-+                             chassis_features_list,
-+                             &northd_data->features);
- }
- 
- void inc_proc_northd_run(struct ovsdb_idl_txn *ovnnb_txn,
-@@ -354,3 +363,20 @@ void inc_proc_northd_cleanup(void)
-     engine_cleanup();
-     engine_set_context(NULL);
- }
-+
-+static void
-+chassis_features_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
-+                      const char *argv[] OVS_UNUSED, void *features_)
-+{
-+    struct chassis_features *features = features_;
-+    struct ds ds = DS_EMPTY_INITIALIZER;
-+
-+    ds_put_format(&ds, "ct_no_masked_label:    %s\n",
-+                  features->ct_no_masked_label ? "true" : "false");
-+    ds_put_format(&ds, "ct_lb_related:         %s\n",
-+                  features->ct_lb_related ? "true" : "false");
-+    ds_put_format(&ds, "mac_binding_timestamp: %s\n",
-+                  features->mac_binding_timestamp ? "true" : "false");
-+    unixctl_command_reply(conn, ds_cstr(&ds));
-+    ds_destroy(&ds);
-+}
-diff --git a/northd/northd.c b/northd/northd.c
-index 841ae9cc5..9cedec909 100644
---- a/northd/northd.c
-+++ b/northd/northd.c
-@@ -125,11 +125,11 @@ enum ovn_stage {
-     PIPELINE_STAGE(SWITCH, IN,  LB_AFF_CHECK,  11, "ls_in_lb_aff_check")  \
-     PIPELINE_STAGE(SWITCH, IN,  LB,            12, "ls_in_lb")            \
-     PIPELINE_STAGE(SWITCH, IN,  LB_AFF_LEARN,  13, "ls_in_lb_aff_learn")  \
--    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB,  14, "ls_in_acl_after_lb")  \
--    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      15, "ls_in_stateful")      \
--    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   16, "ls_in_pre_hairpin")   \
--    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   17, "ls_in_nat_hairpin")   \
--    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       18, "ls_in_hairpin")       \
-+    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   14, "ls_in_pre_hairpin")   \
-+    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   15, "ls_in_nat_hairpin")   \
-+    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       16, "ls_in_hairpin")       \
-+    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB,  17, "ls_in_acl_after_lb")  \
-+    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      18, "ls_in_stateful")      \
-     PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    19, "ls_in_arp_rsp")       \
-     PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  20, "ls_in_dhcp_options")  \
-     PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 21, "ls_in_dhcp_response") \
-@@ -215,6 +215,7 @@ enum ovn_stage {
- #define REGBIT_ACL_LABEL          "reg0[13]"
- #define REGBIT_FROM_RAMP          "reg0[14]"
- #define REGBIT_PORT_SEC_DROP      "reg0[15]"
-+#define REGBIT_ACL_HINT_ALLOW_REL "reg0[17]"
- 
- #define REG_ORIG_DIP_IPV4         "reg1"
- #define REG_ORIG_DIP_IPV6         "xxreg1"
-@@ -430,6 +431,13 @@ build_chassis_features(const struct northd_input *input_data,
-     const struct sbrec_chassis *chassis;
- 
-     SBREC_CHASSIS_TABLE_FOR_EACH (chassis, input_data->sbrec_chassis) {
-+        /* Only consider local AZ chassis.  Remote ones don't install
-+         * flows generated by the local northd.
-+         */
-+        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
-+            continue;
-+        }
-+
-         bool ct_no_masked_label =
-             smap_get_bool(&chassis->other_config,
-                           OVN_FEATURE_CT_NO_MASKED_LABEL,
-@@ -446,6 +454,15 @@ build_chassis_features(const struct northd_input *input_data,
-             chassis_features->mac_binding_timestamp) {
-             chassis_features->mac_binding_timestamp = false;
-         }
-+
-+        bool ct_lb_related =
-+            smap_get_bool(&chassis->other_config,
-+                          OVN_FEATURE_CT_LB_RELATED,
-+                          false);
-+        if (!ct_lb_related &&
-+            chassis_features->ct_lb_related) {
-+            chassis_features->ct_lb_related = false;
-+        }
-     }
- }
- 
-@@ -4410,7 +4427,7 @@ sync_lbs(struct northd_input *input_data, struct ovsdb_idl_txn *ovnsb_txn,
- 
-         /* Update columns. */
-         sbrec_load_balancer_set_name(lb->slb, lb->nlb->name);
--        sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips);
-+        sbrec_load_balancer_set_vips(lb->slb, ovn_northd_lb_get_vips(lb));
-         sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol);
-         sbrec_load_balancer_set_datapath_group(lb->slb, dpg->dp_group);
-         sbrec_load_balancer_set_options(lb->slb, &options);
-@@ -4849,7 +4866,7 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
-         struct ovn_port *port =
-             ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
- 
--        if (!port) {
-+        if (!port || !port->nbsp) {
-             continue;
-         }
- 
-@@ -6758,7 +6775,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
-                       ct_blocked_match);
-         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
-                       ds_cstr(&match), REGBIT_ACL_HINT_DROP" = 0; "
--                      REGBIT_ACL_HINT_BLOCK" = 0; next;");
-+                      REGBIT_ACL_HINT_BLOCK" = 0; "
-+                      REGBIT_ACL_HINT_ALLOW_REL" = 1; next;");
-         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-                       ds_cstr(&match), "next;");
- 
-@@ -6774,14 +6792,21 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
-          * a dynamically negotiated FTP data channel), but will allow
-          * related traffic such as an ICMP Port Unreachable through
-          * that's generated from a non-listening UDP port.  */
-+        const char *ct_in_acl_action =
-+            features->ct_lb_related
-+            ? REGBIT_ACL_HINT_ALLOW_REL" = 1; ct_commit_nat;"
-+            : REGBIT_ACL_HINT_ALLOW_REL" = 1; next;";
-+        const char *ct_out_acl_action = features->ct_lb_related
-+                                        ? "ct_commit_nat;"
-+                                        : "next;";
-         ds_clear(&match);
-         ds_put_format(&match, "!ct.est && ct.rel && !ct.new%s && %s == 0",
-                       use_ct_inv_match ? " && !ct.inv" : "",
-                       ct_blocked_match);
-         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
--                      ds_cstr(&match), "ct_commit_nat;");
-+                      ds_cstr(&match), ct_in_acl_action);
-         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
--                      ds_cstr(&match), "ct_commit_nat;");
-+                      ds_cstr(&match), ct_out_acl_action);
- 
-         /* Ingress and Egress ACL Table (Priority 65532).
-          *
-@@ -6790,6 +6815,11 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
-                       "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
-         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
-                       "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
-+
-+        /* Reply and related traffic matched by an "allow-related" ACL
-+         * should be allowed in the ls_in_acl_after_lb stage too. */
-+        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_AFTER_LB, UINT16_MAX - 3,
-+                      REGBIT_ACL_HINT_ALLOW_REL" == 1", "next;");
-     }
- 
-     /* Ingress or Egress ACL Table (Various priorities). */
-@@ -7838,7 +7868,7 @@ build_lrouter_groups(struct hmap *ports, struct ovs_list *lr_list)
- }
- 
- /*
-- * Ingress table 24: Flows that flood self originated ARP/RARP/ND packets in
-+ * Ingress table 25: Flows that flood self originated ARP/RARP/ND packets in
-  * the switching domain.
-  */
- static void
-@@ -7952,7 +7982,7 @@ lrouter_port_ipv6_reachable(const struct ovn_port *op,
- }
- 
- /*
-- * Ingress table 24: Flows that forward ARP/ND requests only to the routers
-+ * Ingress table 25: Flows that forward ARP/ND requests only to the routers
-  * that own the addresses. Other ARP/ND packets are still flooded in the
-  * switching domain as regular broadcast.
-  */
-@@ -7989,7 +8019,7 @@ build_lswitch_rport_arp_req_flow(const char *ips,
- }
- 
- /*
-- * Ingress table 24: Flows that forward ARP/ND requests only to the routers
-+ * Ingress table 25: Flows that forward ARP/ND requests only to the routers
-  * that own the addresses.
-  * Priorities:
-  * - 80: self originated GARPs that need to follow regular processing.
-@@ -8318,7 +8348,8 @@ build_lswitch_flows(const struct hmap *datapaths,
- 
-     struct ovn_datapath *od;
- 
--    /* Ingress table 25: Destination lookup for unknown MACs (priority 0). */
-+    /* Ingress table 25/26: Destination lookup for unknown MACs
-+     * (priority 0). */
-     HMAP_FOR_EACH (od, key_node, datapaths) {
-         if (!od->nbs) {
-             continue;
-@@ -8393,7 +8424,7 @@ build_lswitch_lflows_admission_control(struct ovn_datapath *od,
-     }
- }
- 
--/* Ingress table 18: ARP/ND responder, skip requests coming from localnet
-+/* Ingress table 19: ARP/ND responder, skip requests coming from localnet
-  * ports. (priority 100); see ovn-northd.8.xml for the rationale. */
- 
- static void
-@@ -8411,7 +8442,7 @@ build_lswitch_arp_nd_responder_skip_local(struct ovn_port *op,
-     }
- }
- 
--/* Ingress table 18: ARP/ND responder, reply for known IPs.
-+/* Ingress table 19: ARP/ND responder, reply for known IPs.
-  * (priority 50). */
- static void
- build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
-@@ -8671,7 +8702,7 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
-     }
- }
- 
--/* Ingress table 18: ARP/ND responder, by default goto next.
-+/* Ingress table 19: ARP/ND responder, by default goto next.
-  * (priority 0)*/
- static void
- build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
-@@ -8682,7 +8713,7 @@ build_lswitch_arp_nd_responder_default(struct ovn_datapath *od,
-     }
- }
- 
--/* Ingress table 18: ARP/ND responder for service monitor source ip.
-+/* Ingress table 19: ARP/ND responder for service monitor source ip.
-  * (priority 110)*/
- static void
- build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
-@@ -8730,7 +8761,7 @@ build_lswitch_arp_nd_service_monitor(struct ovn_northd_lb *lb,
- }
- 
- 
--/* Logical switch ingress table 19 and 20: DHCP options and response
-+/* Logical switch ingress table 20 and 21: DHCP options and response
-  * priority 100 flows. */
- static void
- build_lswitch_dhcp_options_and_response(struct ovn_port *op,
-@@ -8782,11 +8813,11 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op,
-     }
- }
- 
--/* Ingress table 19 and 20: DHCP options and response, by default goto
-+/* Ingress table 20 and 21: DHCP options and response, by default goto
-  * next. (priority 0).
-- * Ingress table 21 and 22: DNS lookup and response, by default goto next.
-+ * Ingress table 22 and 23: DNS lookup and response, by default goto next.
-  * (priority 0).
-- * Ingress table 23 - External port handling, by default goto next.
-+ * Ingress table 24 - External port handling, by default goto next.
-  * (priority 0). */
- static void
- build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
-@@ -8801,7 +8832,7 @@ build_lswitch_dhcp_and_dns_defaults(struct ovn_datapath *od,
-     }
- }
- 
--/* Logical switch ingress table 21 and 22: DNS lookup and response
-+/* Logical switch ingress table 22 and 23: DNS lookup and response
- * priority 100 flows.
- */
- static void
-@@ -8829,7 +8860,7 @@ build_lswitch_dns_lookup_and_response(struct ovn_datapath *od,
-     }
- }
- 
--/* Table 23: External port. Drop ARP request for router ips from
-+/* Table 24: External port. Drop ARP request for router ips from
-  * external ports  on chassis not binding those ports.
-  * This makes the router pipeline to be run only on the chassis
-  * binding the external ports. */
-@@ -8846,7 +8877,7 @@ build_lswitch_external_port(struct ovn_port *op,
-     }
- }
- 
--/* Ingress table 24: Destination lookup, broadcast and multicast handling
-+/* Ingress table 25: Destination lookup, broadcast and multicast handling
-  * (priority 70 - 100). */
- static void
- build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
-@@ -8931,7 +8962,7 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
- }
- 
- 
--/* Ingress table 24: Add IP multicast flows learnt from IGMP/MLD
-+/* Ingress table 25: Add IP multicast flows learnt from IGMP/MLD
-  * (priority 90). */
- static void
- build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
-@@ -8973,9 +9004,11 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
-                           igmp_group->mcgroup.name);
-         } else {
-             /* RFC 4291, section 2.7.1: Skip groups that correspond to all
--             * hosts.
-+             * hosts, all link-local routers and all site routers.
-              */
--            if (ipv6_is_all_hosts(&igmp_group->address)) {
-+            if (ipv6_is_all_hosts(&igmp_group->address) ||
-+                ipv6_is_all_router(&igmp_group->address) ||
-+                ipv6_is_all_site_router(&igmp_group->address)) {
-                 return;
-             }
-             if (atomic_compare_exchange_strong(
-@@ -9013,7 +9046,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
- 
- static struct ovs_mutex mcgroup_mutex = OVS_MUTEX_INITIALIZER;
- 
--/* Ingress table 24: Destination lookup, unicast handling (priority 50), */
-+/* Ingress table 25: Destination lookup, unicast handling (priority 50), */
- static void
- build_lswitch_ip_unicast_lookup(struct ovn_port *op,
-                                 struct hmap *lflows,
-@@ -10471,9 +10504,11 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-                                struct hmap *lflows,
-                                struct ds *match, struct ds *action,
-                                const struct shash *meter_groups,
--                               bool ct_lb_mark)
-+                               const struct chassis_features *features)
- {
--    const char *ct_natted = ct_lb_mark ? "ct_mark.natted" : "ct_label.natted";
-+    const char *ct_natted = features->ct_no_masked_label
-+                            ? "ct_mark.natted"
-+                            : "ct_label.natted";
-     char *skip_snat_new_action = NULL;
-     char *skip_snat_est_action = NULL;
-     char *new_match;
-@@ -10484,7 +10519,7 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
- 
-     bool reject = build_lb_vip_actions(lb_vip, vips_nb, action,
-                                        lb->selection_fields, false,
--                                       ct_lb_mark);
-+                                       features->ct_no_masked_label);
-     bool drop = !!strncmp(ds_cstr(action), "ct_lb", strlen("ct_lb"));
-     if (!drop) {
-         /* Remove the trailing ");". */
-@@ -10506,9 +10541,11 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-     }
- 
-     if (lb->skip_snat) {
--        skip_snat_new_action = xasprintf("flags.skip_snat_for_lb = 1; %s%s",
--                                         ds_cstr(action),
--                                         drop ? "" : "; skip_snat);");
-+        const char *skip_snat = features->ct_lb_related && !drop
-+                                ? "; skip_snat"
-+                                : "";
-+        skip_snat_new_action = xasprintf("flags.skip_snat_for_lb = 1; %s%s);",
-+                                         ds_cstr(action), skip_snat);
-         skip_snat_est_action = xasprintf("flags.skip_snat_for_lb = 1; "
-                                          "next;");
-     }
-@@ -10641,9 +10678,11 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-             skip_snat_new_action, est_match,
-             skip_snat_est_action, lflows, prio, meter_groups);
- 
--    char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s%s",
--                                  ds_cstr(action),
--                                  drop ? "" : "; force_snat);");
-+    const char *force_snat = features->ct_lb_related && !drop
-+                             ? "; force_snat"
-+                             : "";
-+    char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s%s);",
-+                                  ds_cstr(action), force_snat);
-     build_gw_lrouter_nat_flows_for_lb(lb, gw_router_force_snat,
-             n_gw_router_force_snat, reject, new_match,
-             new_actions, est_match,
-@@ -10898,7 +10937,7 @@ build_lrouter_flows_for_lb(struct ovn_northd_lb *lb, struct hmap *lflows,
- 
-         build_lrouter_nat_flows_for_lb(lb_vip, lb, &lb->vips_nb[i],
-                                        lflows, match, action, meter_groups,
--                                       features->ct_no_masked_label);
-+                                       features);
- 
-         if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
-             continue;
-@@ -14208,7 +14247,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
-                                 const struct hmap *ports, struct ds *match,
-                                 struct ds *actions,
-                                 const struct shash *meter_groups,
--                                bool ct_lb_mark)
-+                                const struct chassis_features *features)
- {
-     if (!od->nbr) {
-         return;
-@@ -14239,9 +14278,11 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
-      * a dynamically negotiated FTP data channel), but will allow
-      * related traffic such as an ICMP Port Unreachable through
-      * that's generated from a non-listening UDP port.  */
--    if (od->has_lb_vip) {
-+    if (od->has_lb_vip && features->ct_lb_related) {
-         ds_clear(match);
--        const char *ct_flag_reg = ct_lb_mark ? "ct_mark" : "ct_label";
-+        const char *ct_flag_reg = features->ct_no_masked_label
-+                                  ? "ct_mark"
-+                                  : "ct_label";
- 
-         ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
-         size_t match_len = match->length;
-@@ -14328,6 +14369,23 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
-             sset_add(&nat_entries, nat->external_ip);
-         } else {
-             if (!sset_contains(&nat_entries, nat->external_ip)) {
-+                /* Drop packets coming in from external that still has
-+                 * destination IP equals to the NAT external IP, to avoid loop.
-+                 * The packets must have gone through DNAT/unSNAT stage but
-+                 * failed to convert the destination. */
-+                ds_clear(match);
-+                ds_put_format(
-+                    match, "inport == %s && outport == %s && ip%s.dst == %s",
-+                    l3dgw_port->json_key, l3dgw_port->json_key,
-+                    is_v6 ? "6" : "4", nat->external_ip);
-+                ovn_lflow_add_with_hint(lflows, od,
-+                                        S_ROUTER_IN_ARP_RESOLVE,
-+                                        150, ds_cstr(match),
-+                                        debug_drop_action(),
-+                                        &nat->header_);
-+                /* Now for packets coming from other (downlink) LRPs, allow ARP
-+                 * resolve for the NAT IP, so that such packets can be
-+                 * forwarded for E/W NAT. */
-                 ds_clear(match);
-                 ds_put_format(
-                     match, "outport == %s && %s == %s",
-@@ -14464,7 +14522,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
- 
-     if (od->nbr->n_nat) {
-         ds_clear(match);
--        const char *ct_natted = ct_lb_mark ?
-+        const char *ct_natted = features->ct_no_masked_label ?
-                                 "ct_mark.natted" :
-                                 "ct_label.natted";
-         ds_put_format(match, "ip && %s == 1", ct_natted);
-@@ -14581,7 +14639,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
-     build_lrouter_arp_nd_for_datapath(od, lsi->lflows, lsi->meter_groups);
-     build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports, &lsi->match,
-                                     &lsi->actions, lsi->meter_groups,
--                                    lsi->features->ct_no_masked_label);
-+                                    lsi->features);
-     build_lb_affinity_default_flows(od, lsi->lflows);
- }
- 
-@@ -16073,6 +16131,7 @@ northd_init(struct northd_data *data)
-     data->features = (struct chassis_features) {
-         .ct_no_masked_label = true,
-         .mac_binding_timestamp = true,
-+        .ct_lb_related = true,
-     };
-     data->ovn_internal_version_changed = false;
- }
-diff --git a/northd/northd.h b/northd/northd.h
-index ff8727cb7..4d9055296 100644
---- a/northd/northd.h
-+++ b/northd/northd.h
-@@ -71,6 +71,7 @@ struct northd_input {
- struct chassis_features {
-     bool ct_no_masked_label;
-     bool mac_binding_timestamp;
-+    bool ct_lb_related;
- };
- 
- struct northd_data {
-diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
-index 058cbf71a..4de015e40 100644
---- a/northd/ovn-northd.8.xml
-+++ b/northd/ovn-northd.8.xml
-@@ -790,8 +790,9 @@
-         policy, <code>ct_mark.blocked</code> will get set and packets in the
-         reply direction will no longer be allowed, either. This flow also
-         clears the register bits <code>reg0[9]</code> and
--        <code>reg0[10]</code>.  If ACL logging and logging of related packets
--        is enabled, then a companion priority-65533 flow will be installed that
-+        <code>reg0[10]</code> and sets register bit <code>reg0[17]</code>.
-+        If ACL logging and logging of related packets is enabled, then a
-+        companion priority-65533 flow will be installed that
-         accomplishes the same thing but also logs the traffic.
-       </li>
- 
-@@ -1028,92 +1029,7 @@
-       </li>
-     </ul>
- 
--    <h3>Ingress table 14: <code>from-lport</code> ACLs after LB</h3>
--
--    <p>
--      Logical flows in this table closely reproduce those in the
--      <code>ACL</code> table in the <code>OVN_Northbound</code> database
--      for the <code>from-lport</code> direction with the option
--      <code>apply-after-lb</code> set to <code>true</code>.
--      The <code>priority</code> values from the <code>ACL</code> table have a
--      limited range and have 1000 added to them to leave room for OVN default
--      flows at both higher and lower priorities.
--    </p>
--
--    <ul>
--      <li>
--        <code>allow</code> apply-after-lb ACLs translate into logical flows
--        with the <code>next;</code> action.  If there are any stateful ACLs
--        (including both before-lb and after-lb ACLs)
--        on this datapath, then <code>allow</code> ACLs translate to
--        <code>ct_commit; next;</code> (which acts as a hint for the next tables
--        to commit the connection to conntrack). In case the <code>ACL</code>
--        has a label then <code>reg3</code> is loaded with the label value and
--        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
--        next tables to commit the label to conntrack).
--      </li>
--      <li>
--        <code>allow-related</code> apply-after-lb ACLs translate into logical
--        flows with the <code>ct_commit(ct_label=0/1); next;</code> actions
--        for new connections and <code>reg0[1] = 1; next;</code> for existing
--        connections.  In case the <code>ACL</code> has a label then
--        <code>reg3</code> is loaded with the label value and
--        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
--        next tables to commit the label to conntrack).
--      </li>
--      <li>
--        <code>allow-stateless</code> apply-after-lb ACLs translate into logical
--        flows with the <code>next;</code> action.
--      </li>
--      <li>
--        <code>reject</code> apply-after-lb ACLs translate into logical
--        flows with the
--        <code>tcp_reset { output &lt;-&gt; inport;
--        next(pipeline=egress,table=5);}</code>
--        action for TCP connections,<code>icmp4/icmp6</code> action
--        for UDP connections, and <code>sctp_abort {output &lt;-%gt; inport;
--        next(pipeline=egress,table=5);}</code> action for SCTP associations.
--      </li>
--      <li>
--        Other apply-after-lb ACLs translate to <code>drop;</code> for new
--        or untracked connections and <code>ct_commit(ct_label=1/1);</code> for
--        known connections.  Setting <code>ct_label</code> marks a connection
--        as one that was previously allowed, but should no longer be
--        allowed due to a policy change.
--      </li>
--    </ul>
--
--    <ul>
--      <li>
--        One priority-0 fallback flow that matches all packets and advances to
--        the next table.
--      </li>
--    </ul>
--
--    <h3>Ingress Table 15: Stateful</h3>
--
--    <ul>
--      <li>
--        A priority 100 flow is added which commits the packet to the conntrack
--        and sets the most significant 32-bits of <code>ct_label</code> with the
--        <code>reg3</code> value based on the hint provided by previous tables
--        (with a match for <code>reg0[1] == 1 &amp;&amp; reg0[13] == 1</code>).
--        This is used by the <code>ACLs</code> with label to commit the label
--        value to conntrack.
--      </li>
--
--      <li>
--        For <code>ACLs</code> without label, a second priority-100 flow commits
--        packets to connection tracker using <code>ct_commit; next;</code>
--        action based on a hint provided by the previous tables (with a match
--        for <code>reg0[1] == 1 &amp;&amp; reg0[13] == 0</code>).
--      </li>
--      <li>
--        A priority-0 flow that simply moves traffic to the next table.
--      </li>
--    </ul>
--
--    <h3>Ingress Table 16: Pre-Hairpin</h3>
-+    <h3>Ingress Table 14: Pre-Hairpin</h3>
-     <ul>
-       <li>
-         If the logical switch has load balancer(s) configured, then a
-@@ -1131,7 +1047,7 @@
-       </li>
-     </ul>
- 
--    <h3>Ingress Table 17: Nat-Hairpin</h3>
-+    <h3>Ingress Table 15: Nat-Hairpin</h3>
-     <ul>
-       <li>
-          If the logical switch has load balancer(s) configured, then a
-@@ -1166,7 +1082,7 @@
-       </li>
-     </ul>
- 
--    <h3>Ingress Table 18: Hairpin</h3>
-+    <h3>Ingress Table 16: Hairpin</h3>
-     <ul>
-       <li>
-         <p>
-@@ -1200,6 +1116,100 @@
-       </li>
-     </ul>
- 
-+    <h3>Ingress table 17: <code>from-lport</code> ACLs after LB</h3>
-+
-+    <p>
-+      Logical flows in this table closely reproduce those in the
-+      <code>ACL</code> table in the <code>OVN_Northbound</code> database
-+      for the <code>from-lport</code> direction with the option
-+      <code>apply-after-lb</code> set to <code>true</code>.
-+      The <code>priority</code> values from the <code>ACL</code> table have a
-+      limited range and have 1000 added to them to leave room for OVN default
-+      flows at both higher and lower priorities.
-+    </p>
-+
-+    <ul>
-+      <li>
-+        <code>allow</code> apply-after-lb ACLs translate into logical flows
-+        with the <code>next;</code> action.  If there are any stateful ACLs
-+        (including both before-lb and after-lb ACLs)
-+        on this datapath, then <code>allow</code> ACLs translate to
-+        <code>ct_commit; next;</code> (which acts as a hint for the next tables
-+        to commit the connection to conntrack). In case the <code>ACL</code>
-+        has a label then <code>reg3</code> is loaded with the label value and
-+        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
-+        next tables to commit the label to conntrack).
-+      </li>
-+      <li>
-+        <code>allow-related</code> apply-after-lb ACLs translate into logical
-+        flows with the <code>ct_commit(ct_label=0/1); next;</code> actions
-+        for new connections and <code>reg0[1] = 1; next;</code> for existing
-+        connections.  In case the <code>ACL</code> has a label then
-+        <code>reg3</code> is loaded with the label value and
-+        <code>reg0[13]</code> bit is set to 1 (which acts as a hint for the
-+        next tables to commit the label to conntrack).
-+      </li>
-+      <li>
-+        <code>allow-stateless</code> apply-after-lb ACLs translate into logical
-+        flows with the <code>next;</code> action.
-+      </li>
-+      <li>
-+        <code>reject</code> apply-after-lb ACLs translate into logical
-+        flows with the
-+        <code>tcp_reset { output &lt;-&gt; inport;
-+        next(pipeline=egress,table=5);}</code>
-+        action for TCP connections,<code>icmp4/icmp6</code> action
-+        for UDP connections, and <code>sctp_abort {output &lt;-%gt; inport;
-+        next(pipeline=egress,table=5);}</code> action for SCTP associations.
-+      </li>
-+      <li>
-+        Other apply-after-lb ACLs translate to <code>drop;</code> for new
-+        or untracked connections and <code>ct_commit(ct_label=1/1);</code> for
-+        known connections.  Setting <code>ct_label</code> marks a connection
-+        as one that was previously allowed, but should no longer be
-+        allowed due to a policy change.
-+      </li>
-+    </ul>
-+
-+    <ul>
-+      <li>
-+        One priority-65532 flow matching packets with <code>reg0[17]</code>
-+        set (either replies to existing sessions or traffic related to
-+        existing sessions) and allows these by advancing to the next
-+        table.
-+      </li>
-+    </ul>
-+
-+    <ul>
-+      <li>
-+        One priority-0 fallback flow that matches all packets and advances to
-+        the next table.
-+      </li>
-+    </ul>
-+
-+    <h3>Ingress Table 18: Stateful</h3>
-+
-+    <ul>
-+      <li>
-+        A priority 100 flow is added which commits the packet to the conntrack
-+        and sets the most significant 32-bits of <code>ct_label</code> with the
-+        <code>reg3</code> value based on the hint provided by previous tables
-+        (with a match for <code>reg0[1] == 1 &amp;&amp; reg0[13] == 1</code>).
-+        This is used by the <code>ACLs</code> with label to commit the label
-+        value to conntrack.
-+      </li>
-+
-+      <li>
-+        For <code>ACLs</code> without label, a second priority-100 flow commits
-+        packets to connection tracker using <code>ct_commit; next;</code>
-+        action based on a hint provided by the previous tables (with a match
-+        for <code>reg0[1] == 1 &amp;&amp; reg0[13] == 0</code>).
-+      </li>
-+      <li>
-+        A priority-0 flow that simply moves traffic to the next table.
-+      </li>
-+    </ul>
-+
-     <h3>Ingress Table 19: ARP/ND responder</h3>
- 
-     <p>
-@@ -4257,13 +4267,17 @@ outport = <var>P</var>
-           For each row in the <code>NAT</code> table with IPv4 address
-           <var>A</var> in the <ref column="external_ip"
-           table="NAT" db="OVN_Northbound"/> column of
--          <ref table="NAT" db="OVN_Northbound"/> table, a priority-100
--          flow with the match <code>outport === <var>P</var> &amp;&amp;
--          reg0 == <var>A</var></code> has actions <code>eth.dst = <var>E</var>;
--          next;</code>, where <code>P</code> is the distributed logical router
--          port, <var>E</var> is the Ethernet address if set in the
--          <ref column="external_mac" table="NAT" db="OVN_Northbound"/> column
--          of <ref table="NAT" db="OVN_Northbound"/> table for of type
-+          <ref table="NAT" db="OVN_Northbound"/> table, below two flows are
-+          programmed:
-+        </p>
-+
-+        <p>
-+          A priority-100 flow with the match <code>outport == <var>P</var>
-+          &amp;&amp; reg0 == <var>A</var></code> has actions <code>eth.dst =
-+          <var>E</var>; next;</code>, where <code>P</code> is the distributed
-+          logical router port, <var>E</var> is the Ethernet address if set in
-+          the <ref column="external_mac" table="NAT" db="OVN_Northbound"/>
-+          column of <ref table="NAT" db="OVN_Northbound"/> table for of type
-           <code>dnat_and_snat</code>, otherwise the Ethernet address of the
-           distributed logical router port. Note that if the
-           <ref column="external_ip" table="NAT" db="OVN_Northbound"/> is not
-@@ -4273,9 +4287,18 @@ outport = <var>P</var>
-           will be added.
-         </p>
- 
-+        <p>
-+          Corresponding to the above flow, a priority-150 flow with the match
-+          <code>inport == <var>P</var> &amp;&amp; outport == <var>P</var>
-+          &amp;&amp; ip4.dst == <var>A</var></code> has actions
-+          <code>drop;</code> to exclude packets that have gone through
-+          DNAT/unSNAT stage but failed to convert the destination, to avoid
-+          loop.
-+        </p>
-+
-         <p>
-           For IPv6 NAT entries, same flows are added, but using the register
--          <code>xxreg0</code> for the match.
-+          <code>xxreg0</code> and field <code>ip6</code> for the match.
-         </p>
-       </li>
- 
-diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
-index b2e00d6e4..cb1064f71 100644
---- a/ovn-architecture.7.xml
-+++ b/ovn-architecture.7.xml
-@@ -2832,8 +2832,7 @@
-       The maximum number of networks is reduced to 4096.
-     </li>
-     <li>
--      The maximum number of ports per network is reduced to 4096. (Including
--      multicast group ports.)
-+      The maximum number of ports per network is reduced to 2048.
-     </li>
-     <li>
-       ACLs matching against logical ingress port identifiers are not supported.
-diff --git a/tests/atlocal.in b/tests/atlocal.in
-index 0b9a31276..02e9ce9bb 100644
---- a/tests/atlocal.in
-+++ b/tests/atlocal.in
-@@ -166,6 +166,9 @@ fi
- # Set HAVE_TCPDUMP
- find_command tcpdump
- 
-+# Set HAVE_XXD
-+find_command xxd
-+
- # Set HAVE_LFTP
- find_command lftp
- 
-diff --git a/tests/network-functions.at b/tests/network-functions.at
-index c583bc31e..a2481c55c 100644
---- a/tests/network-functions.at
-+++ b/tests/network-functions.at
-@@ -128,12 +128,18 @@ OVS_START_SHELL_HELPERS
- # hex_to_binary HEXDIGITS
- #
- # Converts the pairs of HEXDIGITS into bytes and prints them on stdout.
--hex_to_binary() {
--    printf $(while test -n "$1"; do
--                 printf '\\%03o' 0x$(expr "$1" : '\(..\)')
--                 set -- "${1##??}"
--             done)
--}
-+if test x$HAVE_XXD = xno; then
-+    hex_to_binary() {
-+        printf $(while test -n "$1"; do
-+                     printf '\\%03o' 0x$(expr "$1" : '\(..\)')
-+                     set -- "${1##??}"
-+                 done)
-+    }
-+else
-+    hex_to_binary() {
-+        echo $1 | xxd -r -p
-+    }
-+fi
- 
- # tcpdump_hex TITLE PACKET
- #
-diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
-index 6bc9ba75d..e2f4fc85c 100644
---- a/tests/ovn-controller.at
-+++ b/tests/ovn-controller.at
-@@ -2499,3 +2499,30 @@ AT_CHECK([GET_LOCAL_TEMPLATE_VARS], [1], [])
- 
- AT_CLEANUP
- ])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ovn-controller - Requested SNAT Zone in router creation transaction])
-+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 This is key. Add the snat-ct-zone when creating the logical router and then
-+dnl do not make any further changes to the logical router settings.
-+check ovn-nbctl lr-add lr0 -- set Logical_Router lr0 options:snat-ct-zone=666
-+check ovn-nbctl lrp-add lr0 lrp-gw 01:00:00:00:00:01 172.16.0.1
-+check ovn-nbctl lrp-set-gateway-chassis lrp-gw hv1
-+
-+check ovn-nbctl --wait=hv sync
-+
-+lr_uuid=$(fetch_column Datapath_Binding _uuid external_ids:name=lr0)
-+ct_zones=$(ovn-appctl -t ovn-controller ct-zone-list)
-+zone_num=$(printf "$ct_zones" | grep ${lr_uuid}_snat | cut -d ' ' -f 2)
-+
-+check test "$zone_num" -eq 666
-+
-+AT_CLEANUP
-+])
-diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
-index c25d1122c..072102f36 100644
---- a/tests/ovn-northd.at
-+++ b/tests/ovn-northd.at
-@@ -2232,9 +2232,9 @@ check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.
- check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p1" && ip4.src == 10.0.0.13' allow
- check ovn-nbctl acl-add pg0 to-lport 1002 'outport == "pg0" && ip4.src == 10.0.0.11' drop
- 
--acl1=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.12' | head -1)
--acl2=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.13' | head -1)
--acl3=$(ovn-nbctl --bare --column _uuid,match find acl | grep -B1 '10.0.0.11' | head -1)
-+acl1=$(ovn-nbctl --bare --column _uuid,match find acl | grep -F -B1 '10.0.0.12' | head -1)
-+acl2=$(ovn-nbctl --bare --column _uuid,match find acl | grep -F -B1 '10.0.0.13' | head -1)
-+acl3=$(ovn-nbctl --bare --column _uuid,match find acl | grep -F -B1 '10.0.0.11' | head -1)
- check ovn-nbctl set acl $acl1 log=true severity=alert meter=meter_me name=acl_one
- check ovn-nbctl set acl $acl2 log=true severity=info  meter=meter_me name=acl_two
- check ovn-nbctl set acl $acl3 log=true severity=info  meter=meter_me name=acl_three
-@@ -2472,8 +2472,8 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e
-   table=7 (ls_in_acl_hint     ), priority=7    , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=1    , match=(ip && !ct.est), action=(reg0[[1]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
--  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
- ])
- 
-@@ -2485,7 +2485,8 @@ check ovn-nbctl --wait=sb \
-     -- ls-lb-add ls lb
- 
- AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
--  table=14(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-+  table=17(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-+  table=17(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=3 (ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
-   table=3 (ls_out_acl_hint    ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -2518,8 +2519,8 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e
-   table=8 (ls_in_acl          ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;)
-   table=8 (ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=8 (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
- ])
-@@ -2528,7 +2529,7 @@ ovn-nbctl --wait=sb clear logical_switch ls acls
- ovn-nbctl --wait=sb clear logical_switch ls load_balancer
- 
- AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
--  table=14(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-+  table=17(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-   table=3 (ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
-   table=4 (ls_out_acl         ), priority=65535, match=(1), action=(next;)
-   table=7 (ls_in_acl_hint     ), priority=65535, match=(1), action=(next;)
-@@ -4360,8 +4361,8 @@ ovn-sbctl dump-flows sw0 > sw0flows
- AT_CAPTURE_FILE([sw0flows])
- 
- AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort | sed 's/table=./table=?/'], [0], [dnl
--  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=? (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
- ])
-@@ -4380,9 +4381,9 @@ ovn-sbctl dump-flows sw0 > sw0flows
- AT_CAPTURE_FILE([sw0flows])
- 
- AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort | sed 's/table=./table=?/'], [0], [dnl
--  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_mark.blocked == 0), action=(ct_commit_nat;)
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-   table=? (ls_in_acl          ), priority=65532, match=((ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
--  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
- ])
- 
-@@ -4404,8 +4405,8 @@ ovn-sbctl dump-flows sw0 > sw0flows
- AT_CAPTURE_FILE([sw0flows])
- 
- AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort | sed 's/table=./table=?/'], [0], [dnl
--  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=? (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
- ])
-@@ -5139,7 +5140,8 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
- ])
- 
- check ovn-sbctl chassis-add gw1 geneve 127.0.0.1 \
--  -- set chassis gw1 other_config:ct-no-masked-label="true"
-+  -- set chassis gw1 other_config:ct-no-masked-label="true" \
-+  -- set chassis gw1 other_config:ovn-ct-lb-related="true"
- 
- # Create a distributed gw port on lr0
- check ovn-nbctl ls-add public
-@@ -6685,11 +6687,12 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
-   table=??(ls_in_acl          ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
-   table=??(ls_in_acl          ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -6730,8 +6733,8 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
-   table=??(ls_in_acl          ), priority=1    , match=(ip && !ct.est), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-@@ -6743,6 +6746,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
-   table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
-   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -6787,8 +6791,8 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
-   table=??(ls_in_acl          ), priority=2003 , match=(reg0[[7]] == 1 && (ip4 && icmp)), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
-@@ -6796,6 +6800,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
-   table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
-   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
-   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -7219,11 +7224,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
-   table=??(ls_in_acl          ), priority=1001 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=1001 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -7342,13 +7348,14 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
-   table=??(ls_in_acl          ), priority=1    , match=(ip && !ct.est), action=(drop;)
-   table=??(ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
-   table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -7467,11 +7474,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
-   table=??(ls_in_acl          ), priority=1    , match=(ip && !ct.est), action=(drop;)
-   table=??(ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
--  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=??(ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=??(ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
-+  table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
-   table=??(ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-@@ -7775,7 +7783,7 @@ sort | sed 's/table=../table=??/' ], [0], [dnl
-   table=??(ls_in_check_port_sec), priority=100  , match=(vlan.present), action=(drop;)
-   table=??(ls_in_check_port_sec), priority=50   , match=(1), action=(reg0[[15]] = check_in_port_sec(); next;)
-   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "localnetport"), action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
--  table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p1"), action=(reg0[[14]] = 1; next(pipeline=ingress, table=18);)
-+  table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p1"), action=(reg0[[14]] = 1; next(pipeline=ingress, table=16);)
-   table=??(ls_in_check_port_sec), priority=70   , match=(inport == "sw0p2"), action=(set_queue(10); reg0[[15]] = check_in_port_sec(); next;)
-   table=??(ls_in_apply_port_sec), priority=0    , match=(1), action=(next;)
-   table=??(ls_in_apply_port_sec), priority=50   , match=(reg0[[15]] == 1), action=(drop;)
-@@ -7832,6 +7840,22 @@ AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
-   table=2 (ls_out_pre_stateful), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb;)
- ])
- 
-+check ovn-nbctl --wait=sb set logical_router lr options:lb_force_snat_ip="42.42.42.1"
-+AT_CHECK([ovn-sbctl lflow-list | grep lr_in_dnat], [0], [dnl
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 66.66.66.66 && ct_label.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 66.66.66.66), action=(flags.force_snat_for_lb = 1; ct_lb(backends=42.42.42.2);)
-+  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+])
-+check ovn-nbctl remove logical_router lr options lb_force_snat_ip
-+
-+check ovn-nbctl --wait=sb set load_balancer lb-test options:skip_snat="true"
-+AT_CHECK([ovn-sbctl lflow-list | grep lr_in_dnat], [0], [dnl
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 66.66.66.66 && ct_label.natted == 1), action=(flags.skip_snat_for_lb = 1; next;)
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 66.66.66.66), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=42.42.42.2);)
-+  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+])
-+check ovn-nbctl remove load_balancer lb-test options skip_snat
-+
- AS_BOX([Chassis upgrades and supports ct_lb_mark - use ct_lb_mark and ct_mark.natted])
- check ovn-sbctl set chassis hv other_config:ct-no-masked-label=true
- check ovn-nbctl --wait=sb sync
-@@ -7865,8 +7889,8 @@ AT_CHECK([ovn-sbctl lflow-list | grep 'ls.*acl.*blocked' ], [0], [dnl
-   table=7 (ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_mark.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
--  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=8 (ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=6    , match=(!ct.new && ct.est && !ct.rpl && ct_mark.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-@@ -7887,15 +7911,15 @@ AT_CHECK([ovn-sbctl lflow-list | grep 'ls.*acl.*blocked' ], [0], [dnl
-   table=7 (ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
--  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(ct_commit_nat;)
--  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(reg0[[17]] = 1; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-   table=8 (ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_label.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=6    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=2    , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=1    , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
--  table=4 (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(ct_commit_nat;)
-+  table=4 (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-   table=4 (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-   table=4 (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-   table=4 (ls_out_acl         ), priority=1    , match=(ip && ct.est && ct_label.blocked == 1), action=(reg0[[1]] = 1; next;)
-@@ -7909,15 +7933,15 @@ AT_CHECK([ovn-sbctl lflow-list | grep 'ls.*acl.*blocked' ], [0], [dnl
-   table=7 (ls_in_acl_hint     ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_mark.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-   table=7 (ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
--  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
--  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; next;)
-+  table=8 (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-   table=8 (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=8 (ls_in_acl          ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=6    , match=(!ct.new && ct.est && !ct.rpl && ct_mark.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=4    , match=(!ct.new && ct.est && !ct.rpl && ct_mark.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=2    , match=(ct.est && ct_mark.blocked == 1), action=(reg0[[9]] = 1; next;)
-   table=3 (ls_out_acl_hint    ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
--  table=4 (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
-+  table=4 (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(next;)
-   table=4 (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
-   table=4 (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-   table=4 (ls_out_acl         ), priority=1    , match=(ip && ct.est && ct_mark.blocked == 1), action=(reg0[[1]] = 1; next;)
-@@ -8437,3 +8461,156 @@ check_row_count sb:Chassis_Template_Var 0
- 
- AT_CLEANUP
- ])
-+
-+OVN_FOR_EACH_NORTHD_NO_HV([
-+AT_SETUP([Load balancer CT related backwards compatibility])
-+AT_KEYWORDS([lb])
-+ovn_start
-+
-+check ovn-nbctl                                               \
-+  -- ls-add ls                                                \
-+  -- lr-add lr -- set logical_router lr options:chassis=local \
-+  -- lb-add lb-test 192.168.0.1 192.168.1.10                  \
-+  -- ls-lb-add ls lb-test                                     \
-+  -- lr-lb-add lr lb-test
-+
-+m4_define([DUMP_FLOWS_SORTED], [sed 's/table=[[0-9]]\{1,2\}/table=?/' | sort])
-+
-+AS_BOX([No chassis registered - CT related flows should be installed])
-+check ovn-nbctl --wait=sb sync
-+ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows0
-+
-+AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows0], [0], [dnl
-+  table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
-+  table=? (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
-+  table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_mark.natted == 1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
-+  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.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_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows0 | grep "priority=65532"], [0], [dnl
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-+  table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-+  table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-+])
-+
-+
-+AS_BOX([Chassis registered that doesn't support CT related])
-+check ovn-sbctl chassis-add hv geneve 127.0.0.1
-+check ovn-nbctl --wait=sb sync
-+ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows1
-+
-+AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows1], [0], [dnl
-+  table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
-+  table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_label.natted == 1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb(backends=192.168.1.10);)
-+])
-+
-+check ovn-nbctl --wait=sb set logical_router lr options:lb_force_snat_ip="192.168.1.1"
-+AT_CHECK([ovn-sbctl lflow-list | grep lr_in_dnat], [0], [dnl
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_label.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(flags.force_snat_for_lb = 1; ct_lb(backends=192.168.1.10);)
-+  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+])
-+check ovn-nbctl remove logical_router lr options lb_force_snat_ip
-+
-+check ovn-nbctl --wait=sb set load_balancer lb-test options:skip_snat="true"
-+AT_CHECK([ovn-sbctl lflow-list | grep lr_in_dnat], [0], [dnl
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_label.natted == 1), action=(flags.skip_snat_for_lb = 1; next;)
-+  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=192.168.1.10);)
-+  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+])
-+check ovn-nbctl remove load_balancer lb-test options skip_snat
-+
-+AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"], [0], [dnl
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(reg0[[17]] = 1; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+  table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
-+  table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-+])
-+
-+AS_BOX([Chassis upgrades and supports CT related])
-+check ovn-sbctl set chassis hv other_config:ct-no-masked-label=true
-+check ovn-sbctl set chassis hv other_config:ovn-ct-lb-related=true
-+check ovn-nbctl --wait=sb sync
-+ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows2
-+
-+AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows2], [0], [dnl
-+  table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
-+  table=? (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
-+  table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_mark.natted == 1), action=(next;)
-+  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
-+  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.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_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows2 | grep "priority=65532"], [0], [dnl
-+  table=? (ls_in_acl          ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg0[[17]] = 1; ct_commit_nat;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; reg0[[10]] = 0; reg0[[17]] = 1; next;)
-+  table=? (ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-+  table=? (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(ct_commit_nat;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
-+  table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
-+  table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
-+  table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
-+])
-+
-+AT_CLEANUP
-+])
-+
-+OVN_FOR_EACH_NORTHD_NO_HV([
-+AT_SETUP([Chassis-feature compatibitility - remote chassis])
-+ovn_start
-+
-+AS_BOX([Local chassis])
-+check ovn-sbctl chassis-add hv1 geneve 127.0.0.1 \
-+  -- set chassis hv1 other_config:ct-no-masked-label=true \
-+  -- set chassis hv1 other_config:ovn-ct-lb-related=true \
-+  -- set chassis hv1 other_config:mac-binding-timestamp=true
-+
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE debug/chassis-features-list], [0], [dnl
-+ct_no_masked_label:    true
-+ct_lb_related:         true
-+mac_binding_timestamp: true
-+])
-+
-+AS_BOX([Remote chassis])
-+check ovn-sbctl chassis-add hv2 geneve 127.0.0.2 \
-+  -- set chassis hv2 other_config:is-remote=true \
-+  -- set chassis hv2 other_config:ct-no-masked-label=false \
-+  -- set chassis hv2 other_config:ovn-ct-lb-related=false \
-+  -- set chassis hv2 other_config:mac-binding-timestamp=false
-+
-+check ovn-nbctl --wait=sb sync
-+
-+AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE debug/chassis-features-list], [0], [dnl
-+ct_no_masked_label:    true
-+ct_lb_related:         true
-+mac_binding_timestamp: true
-+])
-+
-+AT_CLEANUP
-+])
-diff --git a/tests/ovn.at b/tests/ovn.at
-index ad2014de6..f77a4983d 100644
---- a/tests/ovn.at
-+++ b/tests/ovn.at
-@@ -4461,7 +4461,12 @@ for i in 1 2 3; do
- done
- 
- # Gracefully terminate daemons
--OVN_CLEANUP([hv1],[hv2],[vtep])
-+
-+OVN_CLEANUP_SBOX([hv1])
-+OVN_CLEANUP_SBOX([hv2])
-+OVS_WAIT_UNTIL([test `as vtep ovs-vsctl list-ports vtep_bfd | wc -l` -eq 0])
-+OVN_CLEANUP([vtep])
-+
- OVN_CLEANUP_VSWITCH([hv3])
- 
- AT_CLEANUP
-@@ -25064,8 +25069,10 @@ OVN_FOR_EACH_NORTHD([
- AT_SETUP([interconnection])
- 
- ovn_init_ic_db
--n_az=5
--n_ts=5
-+# The number needs to stay relatively low due to high memory consumption
-+# with address sanitizers enabled.
-+n_az=3
-+n_ts=3
- for i in `seq 1 $n_az`; do
-     ovn_start az$i
- done
-@@ -28416,24 +28423,39 @@ wait_row_count Port_Binding 1 logical_port=lsp-cont1 chassis=$ch
- OVN_CLEANUP([hv1])
- AT_CLEANUP
- 
-+# TEST_LR_DROP_TRAFFIC_FOR_OWN_IPS [ DGP | GR ]
- # Test dropping traffic destined to router owned IPs.
--OVN_FOR_EACH_NORTHD([
--AT_SETUP([gateway router drop traffic for own IPs])
-+m4_define([TEST_LR_DROP_TRAFFIC_FOR_OWN_IPS], [
- ovn_start
- 
--ovn-nbctl lr-add r1 -- set logical_router r1 options:chassis=hv1
--ovn-nbctl ls-add s1
--
--# Connnect r1 to s1.
--ovn-nbctl lrp-add r1 lrp-r1-s1 00:00:00:00:01:01 10.0.1.1/24
--ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router \
--    options:router-port=lrp-r1-s1 addresses=router
--
--# Create logical port p1 in s1
--ovn-nbctl lsp-add s1 p1 \
-+ovn-nbctl lr-add r1 # Gateway router or LR with DGP on the ext side
-+ovn-nbctl ls-add ext # simulate external LS
-+ovn-nbctl ls-add s2 # simulate internal LS
-+
-+# Connnect r1 to ext.
-+ovn-nbctl lrp-add r1 lrp-r1-ext 00:00:00:00:01:01 10.0.1.1/24
-+if test X"$1" = X"DGP"; then
-+    ovn-nbctl lrp-set-gateway-chassis lrp-r1-ext hv1 1
-+else
-+    ovn-nbctl set logical_router r1 options:chassis=hv1
-+fi
-+ovn-nbctl lsp-add ext lsp-ext-r1 -- set Logical_Switch_Port lsp-ext-r1 type=router \
-+    options:router-port=lrp-r1-ext addresses=router
-+
-+# Connnect r1 to s2.
-+ovn-nbctl lrp-add r1 lrp-r1-s2 00:00:00:00:02:01 10.0.2.1/24
-+ovn-nbctl lsp-add s2 lsp-s2-r1 -- set Logical_Switch_Port lsp-s2-r1 type=router \
-+    options:router-port=lrp-r1-s2 addresses=router
-+
-+# Create logical port p1 in ext
-+ovn-nbctl lsp-add ext p1 \
- -- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" \
- -- lsp-set-port-security p1 "f0:00:00:00:01:02 10.0.1.2"
- 
-+# Create logical port p2 in s2
-+ovn-nbctl lsp-add s2 p2 \
-+-- lsp-set-addresses p2 "f0:00:00:00:02:02 10.0.2.2"
-+
- # Create two hypervisor and create OVS ports corresponding to logical ports.
- net_add n1
- 
-@@ -28447,6 +28469,12 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
-     options:rxq_pcap=hv1/vif1-rx.pcap \
-     ofport-request=1
- 
-+ovs-vsctl -- add-port br-int hv1-vif2 -- \
-+    set interface hv1-vif2 external-ids:iface-id=p2 \
-+    options:tx_pcap=hv1/vif2-tx.pcap \
-+    options:rxq_pcap=hv1/vif2-rx.pcap \
-+    ofport-request=2
-+
- # Pre-populate the hypervisors' ARP tables so that we don't lose any
- # packets for ARP resolution (native tunneling doesn't queue packets
- # for ARP resolution).
-@@ -28457,9 +28485,10 @@ ovn-nbctl --wait=hv sync
- 
- sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
- 
-+echo sw_key: $sw_key
- AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.1.1], [1], [])
- 
--# Send ip packets from p1 to lrp-r1-s1
-+# Send ip packets from p1 to lrp-r1-ext
- src_mac="f00000000102"
- dst_mac="000000000101"
- src_ip=`ip_to_hex 10 0 1 2`
-@@ -28478,10 +28507,10 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=11, n_packets=1,.*
- ])
- 
- # Use the router IP as SNAT IP.
--ovn-nbctl set logical_router r1 options:lb_force_snat_ip=10.0.1.1
-+ovn-nbctl lr-nat-add r1 snat 10.0.1.1 10.8.8.0/24
- ovn-nbctl --wait=hv sync
- 
--# Send ip packets from p1 to lrp-r1-s1
-+# Send ip packets from p1 to lrp-r1-ext
- src_mac="f00000000102"
- dst_mac="000000000101"
- src_ip=`ip_to_hex 10 0 1 2`
-@@ -28496,11 +28525,53 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep
- ])
- 
- # The packet should've been dropped in the lr_in_arp_resolve stage.
--AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=1,.* priority=2,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
-+if test X"$1" = X"DGP"; then
-+    prio=150
-+    inport=reg14
-+    outport=reg15
-+else
-+    prio=2
-+fi
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=1,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
- 1
- ])
- 
-+# Send ip packets from p2 to lrp-r1-ext
-+src_mac="f00000000202"
-+dst_mac="000000000201"
-+src_ip=`ip_to_hex 10 0 2 2`
-+dst_ip=`ip_to_hex 10 0 1 1`
-+packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
-+as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
-+
-+# Still no packet-ins should reach ovn-controller.
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep -v n_packets=0 -c], [1], [dnl
-+0
-+])
-+
-+if test X"$1" = X"DGP"; then
-+    # The packet dst should be resolved once for E/W centralized NAT purpose.
-+    AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=1,.* priority=100,reg0=0xa000101,reg15=.*metadata=0x${sw_key} actions=mod_dl_dst:00:00:00:00:01:01,resubmit" -c], [0], [dnl
-+1
-+])
-+fi
-+
-+# The packet should've been finally dropped in the lr_in_arp_resolve stage.
-+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=25, n_packets=2,.* priority=$prio,ip,$inport.*$outport.*metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl
-+1
-+])
- OVN_CLEANUP([hv1])
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([gateway router drop traffic for own IPs])
-+TEST_LR_DROP_TRAFFIC_FOR_OWN_IPS(GR)
-+AT_CLEANUP
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([distributed gateway port drop traffic for own IPs])
-+TEST_LR_DROP_TRAFFIC_FOR_OWN_IPS(DGP)
- AT_CLEANUP
- ])
- 
-diff --git a/tests/system-ovn.at b/tests/system-ovn.at
-index 99ad14aa5..1e6767846 100644
---- a/tests/system-ovn.at
-+++ b/tests/system-ovn.at
-@@ -1618,8 +1618,8 @@ OVS_WAIT_UNTIL([
- ovn-nbctl --reject lb-add lb3 30.0.0.10:80 ""
- ovn-nbctl ls-lb-add foo lb3
- # Filter reset segments
--NS_CHECK_EXEC([foo1], [tcpdump -c 1 -neei foo1 ip[[33:1]]=0x14 > rst.pcap 2>/dev/null &])
--sleep 1
-+NS_CHECK_EXEC([foo1], [tcpdump -l -c 1 -neei foo1 ip[[33:1]]=0x14 > rst.pcap 2>tcpdump_err &])
-+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
- NS_CHECK_EXEC([foo1], [wget -q 30.0.0.10],[4])
- 
- OVS_WAIT_UNTIL([
-@@ -1734,13 +1734,11 @@ OVS_START_L7([bar2], [http6])
- OVS_START_L7([bar3], [http6])
- 
- dnl Should work with the virtual IP fd03::1 address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log || (ovs-ofctl -O OpenFlow13 dump-flows br-int && false)])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log || (ovs-ofctl -O OpenFlow13 dump-flows br-int && false)])
- done
--
--dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::1) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::1) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -1748,27 +1746,25 @@ tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd
- ])
- 
- dnl Should work with the virtual IP fd03::3 address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::3]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::3]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
--
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::3) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::3) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::3,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::3,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::3,sport=<cleared>,dport=<cleared>),reply=(src=fd02::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- ])
- 
-+OVS_WAIT_FOR_OUTPUT([
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
--
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -1784,14 +1780,14 @@ OVS_WAIT_UNTIL([
- 
- AT_CHECK([ovs-appctl dpctl/flush-conntrack])
- 
-+OVS_WAIT_FOR_OUTPUT([
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd02::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -1933,13 +1929,13 @@ OVS_START_L7([foo3], [http])
- OVS_START_L7([foo4], [http])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$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) | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -1947,20 +1943,19 @@ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(s
- ])
- 
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$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) | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=192.168.1.2,dst=30.0.0.2,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.5,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- ])
- 
--
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -2044,13 +2039,13 @@ OVS_START_L7([foo3], [http6])
- OVS_START_L7([foo4], [http6])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::1) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::1) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd01::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd01::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -2058,20 +2053,19 @@ tcp,orig=(src=fd01::2,dst=fd03::1,sport=<cleared>,dport=<cleared>),reply=(src=fd
- ])
- 
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([foo1], [wget http://[[fd03::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd03::2) | grep -v fe80 | \
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd01::3,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd01::4,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd01::2,dst=fd03::2,sport=<cleared>,dport=<cleared>),reply=(src=fd01::5,dst=fd01::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- ])
- 
--
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -2199,27 +2193,27 @@ OVS_START_L7([bar1], [http])
- 
- check ovs-appctl dpctl/flush-conntrack
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$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) |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,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>)
- tcp,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>)
- ])
- 
- check ovs-appctl dpctl/flush-conntrack
-+OVS_WAIT_FOR_OUTPUT([
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$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) |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,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>)
- tcp,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>)
-@@ -2256,23 +2250,23 @@ OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=43 | \
- grep 'nat(src=20.0.0.2)'])
- 
- check ovs-appctl dpctl/flush-conntrack
-+exp_ct1="tcp,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>)
-+tcp,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>)"
-+exp_ct2="tcp,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>)
-+tcp,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>)"
-+
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$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>/'], [0], [dnl
--tcp,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>)
--tcp,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>)
--])
-+ct1=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+ct2=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
- 
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,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>)
--tcp,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>)
-+test "x$ct1 = x$exp_ct1" && test "x$ct2 = x$exp_ct2"
-+], [0], [dnl
- ])
- 
- OVS_WAIT_UNTIL([check_est_flows], [check established flows])
-@@ -2298,22 +2292,21 @@ rm -f wget*.log
- 
- check ovs-appctl dpctl/flush-conntrack
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+exp_ct1="tcp,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>)
-+tcp,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>)"
-+exp_ct2="tcp,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>)
-+tcp,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>)"
-+
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$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>/'], [0], [dnl
--tcp,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>)
--tcp,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>)
--])
--
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,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>)
--tcp,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>)
-+ct1=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+ct2=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+test "x$ct1 = x$exp_ct1" && test "x$ct2 = x$exp_ct2"
-+], [0], [dnl
- ])
- 
- OVS_WAIT_UNTIL([check_est_flows], [check established flows])
-@@ -2549,26 +2542,26 @@ OVS_START_L7([foo1], [http6])
- OVS_START_L7([bar1], [http6])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd72::2,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<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], [wget http://[[fd30::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget http://[[fd30::2]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::2) | grep -v fe80 |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::2) | grep -v fe80 |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd72::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd72::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -2727,24 +2720,24 @@ OVS_START_L7([foo1], [http])
- OVS_START_L7([bar1], [http])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+exp_ct1="tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
-+tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)"
-+exp_ct2="tcp,orig=(src=172.16.1.3,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>)
-+tcp,orig=(src=172.16.1.3,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>)"
-+
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$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>/'], [0], [dnl
--tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--])
--
-+ct1=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
- dnl Force SNAT should have worked.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=172.16.1.3,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>)
--tcp,orig=(src=172.16.1.3,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>)
-+ct2=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+test "x$ct1 = x$exp_ct1" && test "x$ct2 = x$exp_ct2"
-+], [0], [dnl
- ])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -2900,24 +2893,24 @@ OVS_START_L7([foo1], [http6])
- OVS_START_L7([bar1], [http6])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+exp_ct1="tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
-+tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
-+exp_ct2=tcp,orig=(src=fd72::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-+tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)"
-+
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--])
--
-+ct1=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
- dnl Force SNAT should have worked.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | grep -v fe80 |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=fd72::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
--tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-+ct2=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | grep -v fe80 | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+test "x$ct1 = x$exp_ct1" && test "x$ct2 = x$exp_ct2"
-+], [0], [dnl
- ])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -3111,39 +3104,32 @@ OVS_START_L7([foo16], [http6])
- OVS_START_L7([bar16], [http6])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
--done
--
--for i in `seq 1 20`; do
--    echo Request ${i}_6
--    NS_CHECK_EXEC([alice16], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget${i}_6.log])
-+exp_ct1="tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
-+tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)"
-+exp_ct2="tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
-+tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)"
-+exp_ct3="tcp,orig=(src=172.16.1.3,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>)
-+tcp,orig=(src=172.16.1.3,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>)"
-+exp_ct4="tcp,orig=(src=fd72::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-+tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)"
-+
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+    NS_EXEC([alice16], [wget http://[[fd30::1]] -t 5 -T 1 --retry-connrefused -v -o wget${i}_6.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>/'], [0], [dnl
--tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.1.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--tcp,orig=(src=172.16.1.3,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=172.16.1.3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--])
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--tcp,orig=(src=fd72::3,dst=fd30::1,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd72::3,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=10,protoinfo=(state=<cleared>)
--])
-+ct1=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+ct2=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd30::1) | grep -v fe80 | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
- 
- dnl Force SNAT should have worked.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=172.16.1.3,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>)
--tcp,orig=(src=172.16.1.3,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>)
--])
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | grep -v fe80 |
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
--tcp,orig=(src=fd72::3,dst=fd11::2,sport=<cleared>,dport=<cleared>),reply=(src=fd11::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
--tcp,orig=(src=fd72::3,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd20::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,protoinfo=(state=<cleared>)
-+ct3=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+ct4=$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd20::2) | grep -v fe80 | sed -e 's/zone=[[0-9]]*/zone=<cleared>/')
-+test "x$ct1 = x$exp_ct1" && test "x$ct2 = x$exp_ct2" && test "x$ct3 = x$exp_ct3" && test "x$ct4 = x$exp_ct4"
-+], [0], [dnl
- ])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -3262,26 +3248,26 @@ OVS_START_L7([foo1], [http])
- OVS_START_L7([bar1], [http])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 172.16.1.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.10) |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.10) |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=172.16.1.2,dst=172.16.1.10,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>)
- tcp,orig=(src=172.16.1.2,dst=172.16.1.10,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>)
- ])
- 
- dnl Test load-balancing that includes L4 ports in NAT.
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget 172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget 172.16.1.11:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.11) |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.1.11) |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=172.16.1.2,dst=172.16.1.11,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>)
- tcp,orig=(src=172.16.1.2,dst=172.16.1.11,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>)
-@@ -3405,26 +3391,26 @@ OVS_START_L7([foo1], [http6])
- OVS_START_L7([bar1], [http6])
- 
- dnl Should work with the virtual IP address through NAT
--for i in `seq 1 20`; do
--    echo Request $i
--    NS_CHECK_EXEC([alice1], [wget http://[[fd72::10]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget http://[[fd72::10]] -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd72::10) | grep -v fe80 |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd72::10) | grep -v fe80 |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd72::2,dst=fd72::10,sport=<cleared>,dport=<cleared>),reply=(src=fd01::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd72::2,dst=fd72::10,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<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], [wget http://[[fd72::11]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+OVS_WAIT_FOR_OUTPUT_UNQUOTED([
-+for i in `seq 1 10`; do
-+    NS_EXEC([alice1], [wget http://[[fd72::11]]:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
- done
- 
- dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd72::11) | grep -v fe80 |
-+ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd72::11) | grep -v fe80 |
- sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=fd72::2,dst=fd72::11,sport=<cleared>,dport=<cleared>),reply=(src=fd01::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=fd72::2,dst=fd72::11,sport=<cleared>,dport=<cleared>),reply=(src=fd02::2,dst=fd72::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
-@@ -3598,8 +3584,8 @@ icmp,orig=(src=192.168.2.2,dst=172.16.1.2,id=<cleared>,type=8,code=0),reply=(src
- ])
- 
- # Try to ping external network
--NS_CHECK_EXEC([ext-net], [tcpdump -n -c 3 -i ext-veth dst 172.16.1.3 and icmp > ext-net.pcap &])
--sleep 1
-+NS_CHECK_EXEC([ext-net], [tcpdump -l -n -c 3 -i ext-veth dst 172.16.1.3 and icmp > ext-net.pcap 2>tcpdump_err &])
-+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
- AT_CHECK([ovn-nbctl lr-nat-del R1 snat])
- NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.1 | FORMAT_PING], \
- [0], [dnl
-@@ -4507,17 +4493,15 @@ OVS_WAIT_UNTIL(
-     [ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | grep "ip4.dst == 10.0.0.10" > lflows.txt
-      test 1 = `cat lflows.txt | grep "ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80)" | wc -l`]
- )
--
- # From sw0-p2 send traffic to vip - 10.0.0.10
--for i in `seq 1 20`; do
--    echo Request $i
--    ovn-sbctl list service_monitor
--    NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
--done
-+#dnl Each server should have at least one connection.
-+OVS_WAIT_FOR_OUTPUT([
-+    for i in `seq 1 10`; do
-+        NS_EXEC([sw0-p2], [wget 10.0.0.10 -t 5 -T 1 --retry-connrefused -v -o wget$i.log])
-+    done
- 
--dnl Each server should have at least one connection.
--AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
--sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
-+    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.0.10) | \
-+      sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
- tcp,orig=(src=10.0.0.4,dst=10.0.0.10,sport=<cleared>,dport=<cleared>),reply=(src=10.0.0.3,dst=10.0.0.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- tcp,orig=(src=10.0.0.4,dst=10.0.0.10,sport=<cleared>,dport=<cleared>),reply=(src=20.0.0.3,dst=10.0.0.4,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
- ])
-@@ -4649,10 +4633,12 @@ ovn-nbctl lb-add lb-ipv4-tcp     88.88.88.88:8080 42.42.42.1:4041 tcp
- ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp
- ovn-nbctl lb-add lb-ipv4-udp     88.88.88.88:4040 42.42.42.1:2021 udp
- ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp
-+ovn-nbctl lb-add lb-ipv4 88.88.88.90 42.42.42.1
- ovn-nbctl ls-lb-add sw lb-ipv4-tcp
- ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv4-udp
- ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup
-+ovn-nbctl ls-lb-add sw lb-ipv4
- 
- ovn-nbctl lr-add rtr
- ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24
-@@ -4668,28 +4654,39 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
- ovn-nbctl --wait=hv -t 3 sync
- 
- # Start IPv4 TCP server on lsp.
--NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0])
-+NETNS_DAEMONIZE([lsp], [nc -l -k 42.42.42.1 4041], [lsp0.pid])
- 
- # Check that IPv4 TCP hairpin connection succeeds on both VIPs.
- NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0], [ignore], [ignore])
- NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0], [ignore], [ignore])
-+NS_CHECK_EXEC([lsp], [nc 88.88.88.90 4041 -z], [0], [ignore], [ignore])
- 
- # Capture IPv4 UDP hairpinned packets.
- filter="dst 42.42.42.1 and dst port 2021 and udp"
--NS_CHECK_EXEC([lsp], [tcpdump -nn -c 2 -i lsp ${filter} > lsp.pcap &])
--
--sleep 1
-+NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp ${filter} > lsp.pcap 2>tcpdump_err &])
-+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
- 
- # Generate IPv4 UDP hairpin traffic.
- NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.88 4040 &], [0])
- NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.89 4040 &], [0])
-+NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.90 2021 &], [0])
- 
- # Check hairpin traffic.
- OVS_WAIT_UNTIL([
-     total_pkts=$(cat lsp.pcap | wc -l)
--    test "${total_pkts}" = "2"
-+    test "${total_pkts}" = "3"
- ])
- 
-+ovn-nbctl pg-add pg0 lsp
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1004 "ip4 && ip4.dst == 10.0.0.2" drop
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip4 && tcp" allow-related
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip4 && udp" allow
-+ovn-nbctl --wait=hv sync
-+
-+## Check that IPv4 TCP hairpin connection succeeds on both VIPs.
-+NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0], [ignore], [ignore])
-+NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0], [ignore], [ignore])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -4736,10 +4733,12 @@ ovn-nbctl lb-add lb-ipv6-tcp     [[8800::0088]]:8080 [[4200::1]]:4041 tcp
- ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp
- ovn-nbctl lb-add lb-ipv6-udp     [[8800::0088]]:4040 [[4200::1]]:2021 udp
- ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp
-+ovn-nbctl lb-add lb-ipv6 8800::0090 4200::1
- ovn-nbctl ls-lb-add sw lb-ipv6-tcp
- ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup
- ovn-nbctl ls-lb-add sw lb-ipv6-udp
- ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup
-+ovn-nbctl ls-lb-add sw lb-ipv6
- 
- ovn-nbctl lr-add rtr
- ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64
-@@ -4754,28 +4753,39 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)"
- ovn-nbctl --wait=hv -t 3 sync
- 
- # Start IPv6 TCP server on lsp.
--NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0])
-+NETNS_DAEMONIZE([lsp], [nc -l -k 4200::1 4041], [lsp0.pid])
- 
- # Check that IPv6 TCP hairpin connection succeeds on both VIPs.
- NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0], [ignore], [ignore])
- NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0], [ignore], [ignore])
-+NS_CHECK_EXEC([lsp], [nc 8800::0090 4041 -z], [0], [ignore], [ignore])
- 
- # Capture IPv6 UDP hairpinned packets.
- filter="dst 4200::1 and dst port 2021 and udp"
--NS_CHECK_EXEC([lsp], [tcpdump -nn -c 2 -i lsp $filter > lsp.pcap &])
--
--sleep 1
-+NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp $filter > lsp.pcap 2>tcpdump_err &])
-+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
- 
- # Generate IPv6 UDP hairpin traffic.
- NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0088 4040 &], [0])
- NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0089 4040 &], [0])
-+NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0090 2021 &], [0])
- 
- # Check hairpin traffic.
- OVS_WAIT_UNTIL([
-     total_pkts=$(cat lsp.pcap | wc -l)
--    test "${total_pkts}" = "2"
-+    test "${total_pkts}" = "3"
- ])
- 
-+ovn-nbctl pg-add pg0 lsp
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip6 && tcp" allow-related
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1002 "ip6 && udp" allow
-+ovn-nbctl --apply-after-lb acl-add pg0 from-lport 1000 "ip6" drop
-+ovn-nbctl --wait=hv sync
-+
-+# Check that IPv6 TCP hairpin connection succeeds on both VIPs.
-+NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0], [ignore], [ignore])
-+NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0], [ignore], [ignore])
-+
- OVS_APP_EXIT_AND_WAIT([ovn-controller])
- 
- as ovn-sb
-@@ -4938,7 +4948,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -4950,7 +4960,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- # Now test for IPv6 UDP.
-@@ -4962,7 +4972,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 90" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -4975,7 +4985,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 94" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- # Delete all the ACLs of pg0 and add the ACL with a generic match with reject action.
-@@ -5000,7 +5010,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -5012,7 +5022,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 90" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- 
-@@ -5179,7 +5189,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -5191,7 +5201,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 94 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 94 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- # Now test for IPv6 UDP.
-@@ -5203,7 +5213,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 90" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -5216,7 +5226,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 94" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- # Delete all the ACLs of pg0 and add the ACL with a generic match with reject action.
-@@ -5241,7 +5251,7 @@ OVS_WAIT_UNTIL([
-     ip netns exec sw0-p1-rej nc -u 10.0.0.4 90 < foo
-     c=$(cat sw0-p1-rej-icmp.pcap | grep \
- "10.0.0.4 > 10.0.0.3: ICMP 10.0.0.4 udp port 90 unreachable" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- rm -f *.pcap
-@@ -5253,7 +5263,7 @@ OVS_WAIT_UNTIL([
-     c=$(cat sw0-p2-rej-icmp6.pcap | grep \
- "IP6 aef0::3 > aef0::4: ICMP6, destination unreachable, unreachable port, \
- aef0::3 udp port 90" | uniq | wc -l)
--    test $c -eq 1
-+    test $c -ge 1
- ])
- 
- 
-@@ -9277,13 +9287,15 @@ test_related_traffic() {
- 
-     check ovs-appctl dpctl/flush-conntrack
- 
--    NETNS_DAEMONIZE([client], [tcpdump -U -i client -w client.pcap], [tcpdump0.pid])
--    NETNS_DAEMONIZE([server], [tcpdump -U -i server -w server.pcap], [tcpdump1.pid])
-+    NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -w client.pcap 2>client_err], [tcpdump0.pid])
-+    NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -w server.pcap 2>server_err], [tcpdump1.pid])
- 
-     # Setup a dummy UDP listeners so we don't get "port unreachable".
-     NETNS_DAEMONIZE([client], [nc -l -u 1], [nc0.pid])
-     NETNS_DAEMONIZE([server], [nc -l -u 2], [nc1.pid])
--    sleep 1
-+
-+    OVS_WAIT_UNTIL([grep "listening" client_err])
-+    OVS_WAIT_UNTIL([grep "listening" server_err])
- 
-     # Send UDP client -> server
-     check ovs-ofctl packet-out br-int "in_port=ovs-client,packet=$client_udp,actions=resubmit(,0)"
-@@ -9479,7 +9491,8 @@ name: 'vport' value: '666'
- # Start IPv4 TCP server on vm1.
- NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
- 
--# Make sure connecting to the VIP works.
-+# 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])
- NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
- NS_CHECK_EXEC([vm3], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
- 
-@@ -9572,9 +9585,263 @@ name: 'vport' value: '666'
- # Start IPv6 TCP server on vm1.
- NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
- 
--# Make sure connecting to the VIP works.
-+# 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])
- NS_CHECK_EXEC([vm2], [nc 6666::1 666 -z], [0], [ignore], [ignore])
- NS_CHECK_EXEC([vm3], [nc 6666::1 666 -z], [0], [ignore], [ignore])
- 
- AT_CLEANUP
- ])
-+
-+###########################################################
-+## ls1 -- cluster-router -- join - gr1 -- public1 -- ln1 ##
-+###########################################################
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([Gateway router with dynamic_neigh_routers])
-+
-+CHECK_CONNTRACK()
-+CHECK_CONNTRACK_NAT()
-+ovn_start
-+OVS_TRAFFIC_VSWITCHD_START()
-+ADD_BR([br-int])
-+ADD_BR([br-ex], [set Bridge br-ex fail-mode=standalone])
-+
-+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=provider:br-ex
-+
-+# 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
-+
-+# Add routers
-+check ovn-nbctl lr-add gr1
-+check ovn-nbctl lr-add cluster-router
-+
-+# Add switches
-+check ovn-nbctl ls-add join
-+check ovn-nbctl ls-add public1
-+check ovn-nbctl ls-add ls1
-+
-+# Add ls1 ports
-+check ovn-nbctl lsp-add ls1 ls1p1 \
-+    -- lsp-set-addresses ls1p1 "00:00:00:00:01:11 10.244.2.11"
-+
-+check ovn-nbctl lsp-add ls1 ls1-to-cluster-router \
-+    -- lsp-set-type ls1-to-cluster-router router \
-+    -- lsp-set-options ls1-to-cluster-router router-port=cluster-router-to-ls1 \
-+    -- lsp-set-addresses ls1-to-cluster-router router
-+
-+# Add cluster-router ports
-+check ovn-nbctl lrp-add cluster-router cluster-router-to-ls1 "00:00:00:0f:01:01" 10.244.2.1/24 \
-+    -- lrp-add cluster-router cluster-router-to-join "00:00:00:0f:02:01" 100.64.0.1/16 \
-+    -- lrp-set-gateway-chassis cluster-router-to-ls1 hv1 10 \
-+    -- --policy=src-ip lr-route-add cluster-router 10.244.2.0/24 100.64.0.3
-+
-+# Add join ports
-+check ovn-nbctl lsp-add join join-to-cluster-router \
-+    -- lsp-set-type join-to-cluster-router router \
-+    -- lsp-set-options join-to-cluster-router router-port=cluster-router-to-join \
-+    -- lsp-set-addresses join-to-cluster-router router \
-+    -- lsp-add join join-to-gr1 \
-+    -- lsp-set-type join-to-gr1 router \
-+    -- lsp-set-options join-to-gr1 router-port=gr1-to-join \
-+    -- lsp-set-addresses join-to-gr1 router
-+
-+check ovn-nbctl set logical_router gr1 options:lb_force_snat_ip=router_ip \
-+    -- set logical_router gr1 options:snat-ct-zone=0 \
-+    -- set logical_router gr1 options:dynamic_neigh_routers=true
-+
-+# Add gr1 ports and set natting
-+check ovn-nbctl lrp-add gr1 gr1-to-join "00:00:00:0f:02:03" 100.64.0.3/16 \
-+    -- lr-route-add gr1 10.244.0.0/16 100.64.0.1 \
-+    -- lr-nat-add gr1 snat 10.89.189.12 10.244.0.0/16 \
-+    -- lrp-add gr1 gr1-to-public1 "0a:0a:b6:fc:03:12" 10.89.189.12/24 \
-+    -- set logical_router gr1 options:chassis=hv1
-+
-+# Add public1 ports
-+check ovn-nbctl lsp-add public1 public1-to-gr1 \
-+    -- lsp-set-type public1-to-gr1 router \
-+    -- lsp-set-options public1-to-gr1 router-port=gr1-to-public1 \
-+    -- lsp-set-addresses public1-to-gr1 router \
-+    -- lsp-add public1 ln1 \
-+    -- lsp-set-type ln1 localnet \
-+    -- lsp-set-options ln1 network_name=provider \
-+    -- lsp-set-addresses ln1 unknown
-+
-+check ovn-nbctl --wait=hv sync
-+
-+ADD_NAMESPACES(ns_ls1p1)
-+ADD_VETH(ls1p1, ns_ls1p1, br-int, "10.244.2.11/24", "00:00:00:00:01:11", "10.244.2.1")
-+
-+ADD_NAMESPACES(ns_ext1)
-+ADD_VETH(ln1, ns_ext1, br-ex, "10.89.189.1/24", "0a:0a:b6:fc:03:01")
-+
-+NS_CHECK_EXEC([ns_ls1p1], [ping -q -c 3 -i 0.3 -w 2 10.89.189.1 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+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
-+])
-+
-+OVN_FOR_EACH_NORTHD([
-+AT_SETUP([ACL default_acl_drop])
-+AT_KEYWORDS([acl default_acl_drop])
-+
-+CHECK_CONNTRACK()
-+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
-+
-+ovn-nbctl ls-add sw
-+
-+# Logical port 'vm1' in switch 'sw'.
-+ADD_NAMESPACES(vm1)
-+ADD_VETH(vm1, vm1, br-int, "10.0.0.1/24", "f0:00:00:01:02:03", \
-+         "10.0.0.254")
-+check ovn-nbctl lsp-add sw vm1 \
-+-- lsp-set-addresses vm1 "f0:00:00:01:02:03 10.0.0.1"
-+
-+# Logical port 'vm2' in switch 'sw'.
-+ADD_NAMESPACES(vm2)
-+ADD_VETH(vm2, vm2, br-int, "10.0.0.2/24", "f0:00:00:01:02:05", \
-+"10.0.0.254")
-+check ovn-nbctl lsp-add sw vm2 \
-+-- lsp-set-addresses vm2 "f0:00:00:01:02:05 10.0.0.2"
-+
-+# Wait for ovn-controller to catch up.
-+wait_for_ports_up
-+check ovn-nbctl --wait=hv sync
-+
-+AS_BOX([from-lport acl, default_acl_drop false])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=false \
-+    -- acl-add sw from-lport 20 "ip4 && icmp" allow-related \
-+    -- acl-add sw from-lport 10 "ip4" drop
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+AS_BOX([from-lport acl, default_acl_drop true])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=true \
-+    -- acl-add sw from-lport 20 "ip4 && icmp" allow-related \
-+    -- acl-add sw from-lport 10 "arp" allow \
-+    -- --apply-after-lb acl-add sw from-lport 1 1 allow \
-+    -- acl-add sw to-lport 1 1 allow
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+AS_BOX([from-lport acl, after LB, default_acl_drop false])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=false \
-+    -- --apply-after-lb acl-add sw from-lport 20 "ip4 && icmp" allow-related \
-+    -- --apply-after-lb acl-add sw from-lport 10 "ip4" drop
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+AS_BOX([from-lport acl, after LB, default_acl_drop true])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=true \
-+    -- acl-add sw from-lport 1 1 allow \
-+    -- --apply-after-lb acl-add sw from-lport 20 "ip4 && icmp" allow-related \
-+    -- --apply-after-lb acl-add sw from-lport 20 "arp" allow-related \
-+    -- acl-add sw to-lport 1 1 allow
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+AS_BOX([to-lport acl, default_acl_drop false])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=false \
-+    -- acl-add sw to-lport 20 "ip4 && icmp" allow-related \
-+    -- acl-add sw to-lport 10 "ip4" drop
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+AS_BOX([to-lport acl, default_acl_drop true])
-+check ovn-nbctl acl-del sw
-+check ovn-nbctl set NB_Global . options:default_acl_drop=true \
-+    -- acl-add sw from-lport 1 1 allow \
-+    -- --apply-after-lb acl-add sw from-lport 1 1 allow \
-+    -- acl-add sw to-lport 20 "ip4 && icmp" allow-related \
-+    -- acl-add sw to-lport 20 "arp" allow
-+check ovn-nbctl --wait=hv sync
-+
-+# 'vm1' should be able to ping 'vm2' directly.
-+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.2 | FORMAT_PING], \
-+[0], [dnl
-+3 packets transmitted, 3 received, 0% packet loss, time 0ms
-+])
-+
-+OVS_APP_EXIT_AND_WAIT([ovn-controller])
-+
-+as ovn-sb
-+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-+
-+as ovn-nb
-+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-+
-+as northd
-+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
-+
-+as
-+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
-+/connection dropped.*/d"])
-+AT_CLEANUP
-+])
-diff --git a/utilities/ovn-dbctl.c b/utilities/ovn-dbctl.c
-index a850c2f31..5edb82e7f 100644
---- a/utilities/ovn-dbctl.c
-+++ b/utilities/ovn-dbctl.c
-@@ -109,6 +109,15 @@ static void server_loop(const struct ovn_dbctl_options *dbctl_options,
-                         struct ovsdb_idl *idl, int argc, char *argv[]);
- static void ovn_dbctl_exit(int status);
- 
-+static void
-+destroy_argv(int argc, char **argv)
-+{
-+    for (int i = 0; i < argc; i++) {
-+        free(argv[i]);
-+    }
-+    free(argv);
-+}
-+
- int
- ovn_dbctl_main(int argc, char *argv[],
-                const struct ovn_dbctl_options *dbctl_options)
-@@ -151,6 +160,7 @@ ovn_dbctl_main(int argc, char *argv[],
-     char *error_s = ovs_cmdl_parse_all(argc, argv_, get_all_options(),
-                                        &parsed_options, &n_parsed_options);
-     if (error_s) {
-+        destroy_argv(argc, argv_);
-         ctl_fatal("%s", error_s);
-     }
- 
-@@ -179,6 +189,7 @@ ovn_dbctl_main(int argc, char *argv[],
-     bool daemon_mode = false;
-     if (get_detach()) {
-         if (argc != optind) {
-+            destroy_argv(argc, argv_);
-             ctl_fatal("non-option arguments not supported with --detach "
-                       "(use --help for help)");
-         }
-@@ -204,11 +215,8 @@ ovn_dbctl_main(int argc, char *argv[],
-         if (error) {
-             ovsdb_idl_destroy(idl);
-             idl = the_idl = NULL;
-+            destroy_argv(argc, argv_);
- 
--            for (int i = 0; i < argc; i++) {
--                free(argv_[i]);
--            }
--            free(argv_);
-             ctl_fatal("%s", error);
-         }
- 
-@@ -237,21 +245,15 @@ cleanup:
-         }
-         free(commands);
-         if (error) {
--            for (int i = 0; i < argc; i++) {
--                free(argv_[i]);
--            }
--            free(argv_);
-+            destroy_argv(argc, argv_);
-             ctl_fatal("%s", error);
-         }
-     }
- 
-     ovsdb_idl_destroy(idl);
-     idl = the_idl = NULL;
-+    destroy_argv(argc, argv_);
- 
--    for (int i = 0; i < argc; i++) {
--        free(argv_[i]);
--    }
--    free(argv_);
-     exit(EXIT_SUCCESS);
- }
- 
-@@ -1238,40 +1240,53 @@ dbctl_client(const struct ovn_dbctl_options *dbctl_options,
- 
-     ctl_timeout_setup(timeout);
- 
-+    char *cmd_result = NULL;
-+    char *cmd_error = NULL;
-     struct jsonrpc *client;
-+    int exit_status;
-+    char *error_str;
-+
-     int error = unixctl_client_create(socket_name, &client);
-     if (error) {
--        ctl_fatal("%s: could not connect to %s daemon (%s); "
--                  "unset %s to avoid using daemon",
--                  socket_name, program_name, ovs_strerror(error),
--                  dbctl_options->daemon_env_var_name);
-+        error_str = xasprintf("%s: could not connect to %s daemon (%s); "
-+                              "unset %s to avoid using daemon",
-+                              socket_name, program_name, ovs_strerror(error),
-+                              dbctl_options->daemon_env_var_name);
-+        goto log_error;
-     }
- 
--    char *cmd_result;
--    char *cmd_error;
-     error = unixctl_client_transact(client, "run",
-                                     args.n, args.names,
-                                     &cmd_result, &cmd_error);
-     if (error) {
--        ctl_fatal("%s: transaction error (%s)",
--                  socket_name, ovs_strerror(error));
-+        error_str = xasprintf("%s: transaction error (%s)",
-+                              socket_name, ovs_strerror(error));
-+        goto log_error;
-     }
--    svec_destroy(&args);
- 
--    int exit_status;
-     if (cmd_error) {
--        exit_status = EXIT_FAILURE;
-         fprintf(stderr, "%s: %s", program_name, cmd_error);
--    } else {
--        exit_status = EXIT_SUCCESS;
--        fputs(cmd_result, stdout);
-+        goto error;
-     }
-+
-+    exit_status = EXIT_SUCCESS;
-+    fputs(cmd_result, stdout);
-+    goto cleanup;
-+
-+log_error:
-+    VLOG_ERR("%s", error_str);
-+    ovs_error(0, "%s", error_str);
-+    free(error_str);
-+
-+error:
-+    exit_status = EXIT_FAILURE;
-+
-+cleanup:
-     free(cmd_result);
-     free(cmd_error);
-     jsonrpc_close(client);
--    for (int i = 0; i < argc; i++) {
--        free(argv[i]);
--    }
--    free(argv);
-+    svec_destroy(&args);
-+    destroy_argv(argc, argv);
-+
-     exit(exit_status);
- }
-diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
-index 92e10c012..72d4088f0 100644
---- a/utilities/ovn-nbctl.8.xml
-+++ b/utilities/ovn-nbctl.8.xml
-@@ -814,7 +814,7 @@
-         Attaches the mirror <var>m</var> to the logical port <var>port</var>.
-       </dd>
- 
--      <dt><code>lsp-dettach-mirror</code> <var>port</var> <var>m</var></dt>
-+      <dt><code>lsp-detach-mirror</code> <var>port</var> <var>m</var></dt>
-       <dd>
-         Detaches the mirror <var>m</var> from the logical port <var>port</var>.
-       </dd>
-diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
-index 07ebac5e5..e5766ed67 100644
---- a/utilities/ovn-trace.c
-+++ b/utilities/ovn-trace.c
-@@ -1486,9 +1486,8 @@ ovntrace_node_prune_hard(struct ovs_list *nodes)
- }
- 
- static void
--execute_load(const struct ovnact_load *load,
--             const struct ovntrace_datapath *dp, struct flow *uflow,
--             struct ovs_list *super OVS_UNUSED)
-+execute_load(const struct ovnact *ovnact, const struct ovntrace_datapath *dp,
-+             struct flow *uflow, struct ovs_list *super OVS_UNUSED)
- {
-     const struct ovnact_encode_params ep = {
-         .lookup_port = ovntrace_lookup_port,
-@@ -1498,7 +1497,7 @@ execute_load(const struct ovnact_load *load,
-     uint64_t stub[512 / 8];
-     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
- 
--    ovnacts_encode(&load->ovnact, sizeof *load, &ep, &ofpacts);
-+    ovnacts_encode(ovnact, OVNACT_ALIGN(ovnact->len), &ep, &ofpacts);
- 
-     struct ofpact *a;
-     OFPACT_FOR_EACH (a, ofpacts.data, ofpacts.size) {
-@@ -1506,12 +1505,11 @@ execute_load(const struct ovnact_load *load,
- 
-         if (!mf_is_register(sf->field->id)) {
-             struct ds s = DS_EMPTY_INITIALIZER;
--            ovnacts_format(&load->ovnact, OVNACT_LOAD_SIZE, &s);
--            ds_chomp(&s, ';');
- 
--            char *friendly = ovntrace_make_names_friendly(ds_cstr(&s));
--            ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s", friendly);
--            free(friendly);
-+            ovnacts_format(ovnact, OVNACT_ALIGN(ovnact->len), &s);
-+            ds_chomp(&s, ';');
-+            ovntrace_node_append(super, OVNTRACE_NODE_MODIFY, "%s",
-+                                 ds_cstr(&s));
- 
-             ds_destroy(&s);
-         }
-@@ -3057,7 +3055,7 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
-     const struct ovnact *a;
-     OVNACT_FOR_EACH (a, ovnacts, ovnacts_len) {
-         ds_clear(&s);
--        ovnacts_format(a, sizeof *a * (ovnact_next(a) - a), &s);
-+        ovnacts_format(a, OVNACT_ALIGN(a->len), &s);
-         char *friendly = ovntrace_make_names_friendly(ds_cstr(&s));
-         ovntrace_node_append(super, OVNTRACE_NODE_ACTION, "%s", friendly);
-         free(friendly);
-@@ -3072,7 +3070,7 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
-             break;
- 
-         case OVNACT_LOAD:
--            execute_load(ovnact_get_LOAD(a), dp, uflow, super);
-+            execute_load(a, dp, uflow, super);
-             break;
- 
-         case OVNACT_MOVE:
diff --git a/SOURCES/ovn23.03.patch b/SOURCES/ovn23.03.patch
new file mode 100644
index 0000000..c66abc6
--- /dev/null
+++ b/SOURCES/ovn23.03.patch
@@ -0,0 +1,7003 @@
+diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
+index 0f8d9d193..edf4fb2fd 100644
+--- a/.github/workflows/test.yml
++++ b/.github/workflows/test.yml
+@@ -17,7 +17,8 @@ jobs:
+       dependencies: |
+         automake libtool gcc bc libjemalloc2 libjemalloc-dev    \
+         libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev  \
+-        selinux-policy-dev ncat python3-scapy isc-dhcp-server
++        selinux-policy-dev ncat python3-scapy isc-dhcp-server \
++        iputils-arping
+       m32_dependecies: gcc-multilib
+       ARCH:        ${{ matrix.cfg.arch }}
+       CC:          ${{ matrix.cfg.compiler }}
+diff --git a/NEWS b/NEWS
+index 5e8aed06d..60c460a05 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,3 +1,11 @@
++OVN v23.03.1 - xx xxx xxxx
++--------------------------
++  - CT entries are not flushed by default anymore whenever a load balancer
++    backend is removed.  A new, per-LB, option 'ct_flush' can be used to
++    restore the previous behavior.  Disabled by default.
++  - Always allow IPv6 Router Discovery, Neighbor Discovery, and Multicast
++    Listener Discovery protocols, regardless of ACLs defined.
++
+ OVN v23.03.0 - 03 Mar 2023
+ --------------------------
+   - ovn-controller: Experimental support for co-hosting multiple controller
+diff --git a/configure.ac b/configure.ac
+index b51d0f01e..0ba9e8d7e 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -13,7 +13,7 @@
+ # limitations under the License.
+ 
+ AC_PREREQ(2.63)
+-AC_INIT(ovn, 23.03.0, bugs@openvswitch.org)
++AC_INIT(ovn, 23.03.1, bugs@openvswitch.org)
+ AC_CONFIG_MACRO_DIR([m4])
+ AC_CONFIG_AUX_DIR([build-aux])
+ AC_CONFIG_HEADERS([config.h])
+diff --git a/controller/binding.c b/controller/binding.c
+index 5df62baef..bd810f669 100644
+--- a/controller/binding.c
++++ b/controller/binding.c
+@@ -746,6 +746,19 @@ local_binding_get_lport_ofport(const struct shash *local_bindings,
+             u16_to_ofp(lbinding->iface->ofport[0]) : 0;
+ }
+ 
++bool
++local_binding_is_ovn_installed(struct shash *local_bindings,
++                               const char *pb_name)
++{
++    struct local_binding *lbinding =
++        local_binding_find(local_bindings, pb_name);
++    if (lbinding && lbinding->iface) {
++        return smap_get_bool(&lbinding->iface->external_ids,
++                             OVN_INSTALLED_EXT_ID, false);
++    }
++    return false;
++}
++
+ bool
+ local_binding_is_up(struct shash *local_bindings, const char *pb_name,
+                     const struct sbrec_chassis *chassis_rec)
+@@ -783,6 +796,7 @@ local_binding_is_down(struct shash *local_bindings, const char *pb_name,
+         } else if (b_lport->pb->chassis) {
+             VLOG_DBG("lport %s already claimed by other chassis",
+                      b_lport->pb->logical_port);
++            return true;
+         }
+     }
+ 
+@@ -834,6 +848,38 @@ local_binding_set_up(struct shash *local_bindings, const char *pb_name,
+     }
+ }
+ 
++void
++local_binding_remove_ovn_installed(
++        struct shash *local_bindings,
++        const struct ovsrec_interface_table *iface_table,
++        const char *pb_name, bool ovs_readonly)
++{
++    if (ovs_readonly) {
++        return;
++    }
++    struct local_binding *lbinding =
++        local_binding_find(local_bindings, pb_name);
++    if (lbinding && lbinding->iface) {
++        const struct uuid *iface_uuid = &lbinding->iface->header_.uuid;
++        remove_ovn_installed_for_uuid(iface_table, iface_uuid);
++    }
++}
++
++void
++remove_ovn_installed_for_uuid(const struct ovsrec_interface_table *iface_table,
++                              const struct uuid *iface_uuid)
++{
++    const struct ovsrec_interface *iface_rec =
++        ovsrec_interface_table_get_for_uuid(iface_table, iface_uuid);
++    if (iface_rec && smap_get_bool(&iface_rec->external_ids,
++                                   OVN_INSTALLED_EXT_ID, false)) {
++        VLOG_INFO("Removing iface %s ovn-installed in OVS",
++                  iface_rec->name);
++        ovsrec_interface_update_external_ids_delkey(iface_rec,
++                                                    OVN_INSTALLED_EXT_ID);
++    }
++}
++
+ void
+ local_binding_set_down(struct shash *local_bindings, const char *pb_name,
+                        const struct sbrec_chassis *chassis_rec,
+@@ -853,7 +899,6 @@ local_binding_set_down(struct shash *local_bindings, const char *pb_name,
+ 
+     if (!sb_readonly && b_lport && b_lport->pb->n_up && b_lport->pb->up[0] &&
+             (!b_lport->pb->chassis || b_lport->pb->chassis == chassis_rec)) {
+-        VLOG_INFO("Setting lport %s down in Southbound", pb_name);
+         binding_lport_set_down(b_lport, sb_readonly);
+         LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) {
+             binding_lport_set_down(b_lport, sb_readonly);
+@@ -1239,7 +1284,9 @@ claim_lport(const struct sbrec_port_binding *pb,
+                     return false;
+                 }
+             } else {
+-                if (pb->n_up && !pb->up[0]) {
++                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,
+                                               sb_readonly);
+                 }
+@@ -1464,9 +1511,11 @@ consider_vif_lport_(const struct sbrec_port_binding *pb,
+             const char *requested_chassis_option = smap_get(
+                 &pb->options, "requested-chassis");
+             VLOG_INFO_RL(&rl,
+-                "Not claiming lport %s, chassis %s requested-chassis %s",
++                "Not claiming lport %s, chassis %s requested-chassis %s "
++                "pb->chassis %s",
+                 pb->logical_port, b_ctx_in->chassis_rec->name,
+-                requested_chassis_option ? requested_chassis_option : "[]");
++                requested_chassis_option ? requested_chassis_option : "[]",
++                pb->chassis ? pb->chassis->name: "");
+         }
+     }
+ 
+@@ -2288,6 +2337,11 @@ consider_iface_release(const struct ovsrec_interface *iface_rec,
+                 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);
++        }
+ 
+     } else if (lbinding && b_lport && b_lport->type == LP_LOCALPORT) {
+         /* lbinding is associated with a localport.  Remove it from the
+@@ -2558,6 +2612,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
+     if (ld) {
+         remove_pb_from_local_datapath(pb,
+                                       b_ctx_out, ld);
++        if_status_mgr_release_iface(b_ctx_out->if_mgr, pb->logical_port);
+         return;
+     }
+ 
+@@ -2581,6 +2636,7 @@ handle_deleted_lport(const struct sbrec_port_binding *pb,
+             remove_pb_from_local_datapath(pb, b_ctx_out,
+                                           ld);
+         }
++        if_status_mgr_release_iface(b_ctx_out->if_mgr, pb->logical_port);
+     }
+ }
+ 
+@@ -2627,6 +2683,11 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb,
+     }
+ 
+     handle_deleted_lport(pb, b_ctx_in, b_ctx_out);
++    if (lbinding && lbinding->iface && lbinding->iface->name) {
++        if_status_mgr_remove_ovn_installed(b_ctx_out->if_mgr,
++                                           lbinding->iface->name,
++                                           &lbinding->iface->header_.uuid);
++    }
+     return true;
+ }
+ 
+@@ -3314,6 +3375,24 @@ binding_lport_delete(struct shash *binding_lports,
+     binding_lport_destroy(b_lport);
+ }
+ 
++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)
++{
++        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);
++        } else if (pb->n_up && pb->up[0]) {
++            bool up = false;
++            sbrec_port_binding_set_up(pb, &up, 1);
++            VLOG_INFO("Setting lport %s down in Southbound", pb->logical_port);
++            set_pb_chassis_in_sbrec(pb, chassis_rec, false);
++        }
++}
++
+ static void
+ binding_lport_set_up(struct binding_lport *b_lport, bool sb_readonly)
+ {
+@@ -3331,6 +3410,7 @@ binding_lport_set_down(struct binding_lport *b_lport, bool sb_readonly)
+     if (sb_readonly || !b_lport || !b_lport->pb->n_up || !b_lport->pb->up[0]) {
+         return;
+     }
++    VLOG_INFO("Setting lport %s down in Southbound", b_lport->name);
+ 
+     bool up = false;
+     sbrec_port_binding_set_up(b_lport->pb, &up, 1);
+diff --git a/controller/binding.h b/controller/binding.h
+index 6c3a98b02..5b73c6a4b 100644
+--- a/controller/binding.h
++++ b/controller/binding.h
+@@ -159,6 +159,14 @@ bool local_binding_is_up(struct shash *local_bindings, const char *pb_name,
+ bool local_binding_is_down(struct shash *local_bindings, const char *pb_name,
+                            const struct sbrec_chassis *);
+ 
++bool local_binding_is_ovn_installed(struct shash *local_bindings,
++                                    const char *pb_name);
++void local_binding_remove_ovn_installed(
++        struct shash *local_bindings,
++        const struct ovsrec_interface_table *iface_table,
++        const char *pb_name,
++        bool ovs_readonly);
++
+ void local_binding_set_up(struct shash *local_bindings, const char *pb_name,
+                           const struct sbrec_chassis *chassis_rec,
+                           const char *ts_now_str, bool sb_readonly,
+@@ -195,6 +203,14 @@ void set_pb_chassis_in_sbrec(const struct sbrec_port_binding *pb,
+                              const struct sbrec_chassis *chassis_rec,
+                              bool is_set);
+ 
++void remove_ovn_installed_for_uuid(const struct ovsrec_interface_table *,
++                                   const struct uuid *);
++
++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);
++
+ /* Corresponds to each Port_Binding.type. */
+ enum en_lport_type {
+     LP_UNKNOWN,
+diff --git a/controller/encaps.c b/controller/encaps.c
+index 2662eaf98..b69d72584 100644
+--- a/controller/encaps.c
++++ b/controller/encaps.c
+@@ -36,6 +36,8 @@ VLOG_DEFINE_THIS_MODULE(encaps);
+  */
+ #define	OVN_MVTEP_CHASSISID_DELIM '@'
+ 
++static char *current_br_int_name = NULL;
++
+ void
+ encaps_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+ {
+@@ -386,6 +388,21 @@ chassis_tzones_overlap(const struct sset *transport_zones,
+     return false;
+ }
+ 
++static void
++clear_old_tunnels(const struct ovsrec_bridge *old_br_int, const char *prefix,
++                  size_t prefix_len)
++{
++    for (size_t i = 0; i < old_br_int->n_ports; i++) {
++        const struct ovsrec_port *port = old_br_int->ports[i];
++        const char *id = smap_get(&port->external_ids, "ovn-chassis-id");
++        if (id && !strncmp(port->name, prefix, prefix_len)) {
++            VLOG_DBG("Clearing old tunnel port \"%s\" (%s) from bridge "
++                     "\"%s\".", port->name, id, old_br_int->name);
++            ovsrec_bridge_update_ports_delvalue(old_br_int, port);
++        }
++    }
++}
++
+ void
+ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+            const struct ovsrec_bridge *br_int,
+@@ -393,12 +410,42 @@ encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+            const struct sbrec_chassis *this_chassis,
+            const struct sbrec_sb_global *sbg,
+            const struct ovsrec_open_vswitch_table *ovs_table,
+-           const struct sset *transport_zones)
++           const struct sset *transport_zones,
++           const struct ovsrec_bridge_table *bridge_table)
+ {
+     if (!ovs_idl_txn || !br_int) {
+         return;
+     }
+ 
++    if (!current_br_int_name) {
++        /* The controller has just started, we need to look through all
++         * bridges for old tunnel ports. */
++        char *tunnel_prefix = xasprintf("ovn%s-", get_chassis_idx(ovs_table));
++        size_t prefix_len = strlen(tunnel_prefix);
++
++        const struct ovsrec_bridge *br;
++        OVSREC_BRIDGE_TABLE_FOR_EACH (br, bridge_table) {
++            if (!strcmp(br->name, br_int->name)) {
++                continue;
++            }
++            clear_old_tunnels(br, tunnel_prefix, prefix_len);
++        }
++
++        free(tunnel_prefix);
++        current_br_int_name = xstrdup(br_int->name);
++    } else if (strcmp(current_br_int_name, br_int->name)) {
++        /* The integration bridge was changed, clear tunnel ports from
++         * the old one. */
++        const struct ovsrec_bridge *old_br_int =
++            get_bridge(bridge_table, current_br_int_name);
++        if (old_br_int) {
++            clear_old_tunnels(old_br_int, "", 0);
++        }
++
++        free(current_br_int_name);
++        current_br_int_name = xstrdup(br_int->name);
++    }
++
+     const struct sbrec_chassis *chassis_rec;
+ 
+     struct tunnel_ctx tc = {
+@@ -511,3 +558,9 @@ encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
+ 
+     return !any_changes;
+ }
++
++void
++encaps_destroy(void)
++{
++    free(current_br_int_name);
++}
+diff --git a/controller/encaps.h b/controller/encaps.h
+index 867c6f28c..3e58b3c82 100644
+--- a/controller/encaps.h
++++ b/controller/encaps.h
+@@ -35,7 +35,8 @@ void encaps_run(struct ovsdb_idl_txn *ovs_idl_txn,
+                 const struct sbrec_chassis *,
+                 const struct sbrec_sb_global *,
+                 const struct ovsrec_open_vswitch_table *,
+-                const struct sset *transport_zones);
++                const struct sset *transport_zones,
++                const struct ovsrec_bridge_table *bridge_table);
+ 
+ bool encaps_cleanup(struct ovsdb_idl_txn *ovs_idl_txn,
+                     const struct ovsrec_bridge *br_int);
+@@ -46,4 +47,6 @@ bool  encaps_tunnel_id_parse(const char *tunnel_id, char **chassis_id,
+ bool  encaps_tunnel_id_match(const char *tunnel_id, const char *chassis_id,
+                              const char *encap_ip);
+ 
++void encaps_destroy(void);
++
+ #endif /* controller/encaps.h */
+diff --git a/controller/if-status.c b/controller/if-status.c
+index d1c14ac30..8503e5daa 100644
+--- a/controller/if-status.c
++++ b/controller/if-status.c
+@@ -54,44 +54,54 @@ VLOG_DEFINE_THIS_MODULE(if_status);
+  */
+ 
+ enum if_state {
+-    OIF_CLAIMED,       /* Newly claimed interface. pb->chassis update not yet
+-                          initiated. */
+-    OIF_INSTALL_FLOWS, /* Claimed interface with pb->chassis update sent to
+-                        * SB (but update notification not confirmed, so the
+-                        * update may be resent in any of the following states)
+-                        * and for which flows are still being installed.
+-                        */
+-    OIF_MARK_UP,       /* Interface with flows successfully installed in OVS
+-                        * but not yet marked "up" in the binding module (in
+-                        * SB and OVS databases).
+-                        */
+-    OIF_MARK_DOWN,     /* Released interface but not yet marked "down" in the
+-                        * binding module (in SB and/or OVS databases).
+-                        */
+-    OIF_INSTALLED,     /* Interface flows programmed in OVS and binding marked
+-                        * "up" in the binding module.
+-                        */
++    OIF_CLAIMED,          /* Newly claimed interface. pb->chassis update not
++                             yet initiated. */
++    OIF_INSTALL_FLOWS,    /* Claimed interface with pb->chassis update sent to
++                           * SB (but update notification not confirmed, so the
++                           * update may be resent in any of the following
++                           * states and for which flows are still being
++                           * installed.
++                           */
++    OIF_REM_OLD_OVN_INST, /* Interface with flows successfully installed in OVS
++                           * but with ovn-installed still in OVSDB.
++                           */
++    OIF_MARK_UP,          /* Interface with flows successfully installed in OVS
++                           * but not yet marked "up" in the binding module (in
++                           * SB and OVS databases).
++                           */
++    OIF_MARK_DOWN,        /* Released interface but not yet marked "down" in
++                           * the binding module (in SB and/or OVS databases).
++                           */
++    OIF_INSTALLED,        /* Interface flows programmed in OVS and binding
++                           * marked "up" in the binding module.
++                           */
++    OIF_UPDATE_PORT,      /* Logical ports need to be set down, and pb->chassis
++                           * removed.
++                           */
+     OIF_MAX,
+ };
+ 
+ static const char *if_state_names[] = {
+-    [OIF_CLAIMED]       = "CLAIMED",
+-    [OIF_INSTALL_FLOWS] = "INSTALL_FLOWS",
+-    [OIF_MARK_UP]       = "MARK_UP",
+-    [OIF_MARK_DOWN]     = "MARK_DOWN",
+-    [OIF_INSTALLED]     = "INSTALLED",
++    [OIF_CLAIMED]          = "CLAIMED",
++    [OIF_INSTALL_FLOWS]    = "INSTALL_FLOWS",
++    [OIF_REM_OLD_OVN_INST] = "REM_OLD_OVN_INST",
++    [OIF_MARK_UP]          = "MARK_UP",
++    [OIF_MARK_DOWN]        = "MARK_DOWN",
++    [OIF_INSTALLED]        = "INSTALLED",
++    [OIF_UPDATE_PORT]      = "UPDATE_PORT",
+ };
+ 
+ /*
+  *       +----------------------+
+  * +---> |                      |
+- * | +-> |         NULL         | <--------------------------------------+++-+
+- * | |   +----------------------+                                            |
+- * | |     ^ release_iface   | claim_iface()                                 |
+- * | |     |                 V - sbrec_update_chassis(if sb is rw)           |
+- * | |   +----------------------+                                            |
+- * | |   |                      | <----------------------------------------+ |
+- * | |   |       CLAIMED        | <--------------------------------------+ | |
++ * | +-> |         NULL         |
++ * | |   +----------------------+
++ * | |     ^ release_iface   | claim_iface()
++ * | |     |                 V - sbrec_update_chassis(if sb is rw)
++ * | |   +----------------------+
++ * | |   |                      | <------------------------------------------+
++ * | |   |       CLAIMED        | <----------------------------------------+ |
++ * | |   |                      | <--------------------------------------+ | |
+  * | |   +----------------------+                                        | | |
+  * | |                 |  V  ^                                           | | |
+  * | |                 |  |  | handle_claims()                           | | |
+@@ -109,38 +119,63 @@ static const char *if_state_names[] = {
+  * |     |                      |   - remove ovn-installed from ovsdb    | | |
+  * |     |                      |  mgr_update()                          | | |
+  * |     +----------------------+   - sbrec_update_chassis if needed     | | |
+- * |                    |                                                | | |
+- * |                    |  mgr_run(seqno rcvd)                           | | |
+- * |                    |  - set port up in sb                           | | |
+- * | release_iface      |  - set ovn-installed in ovs                    | | |
+- * |                    V                                                | | |
++ * |        |            |                                               | | |
++ * |        |            +----------------------------------------+      | | |
++ * |        |                                                     |      | | |
++ * |        | mgr_run(seqno rcvd, ovn-installed present)          |      | | |
++ * |        V                                                     |      | | |
++ * |    +--------------------+                                    |      | | |
++ * |    |                    |  mgr_run()                         |      | | |
++ * +--- | REM_OLD_OVN_INST   |  - remove ovn-installed in ovs     |      | | |
++ * |    +--------------------+                                    |      | | |
++ * |               |                                              |      | | |
++ * |               |                                              |      | | |
++ * |               | mgr_update( ovn_installed not present)       |      | | |
++ * |               |                                              |      | | |
++ * |               |  +-------------------------------------------+      | | |
++ * |               |  |                                                  | | |
++ * |               |  |  mgr_run(seqno rcvd, ovn-installed not present)  | | |
++ * |               |  |  - set port up in sb                             | | |
++ * |               |  |  - set ovn-installed in ovs                      | | |
++ * |release_iface  |  |                                                  | | |
++ * |               V  V                                                  | | |
+  * |   +----------------------+                                          | | |
+  * |   |                      |  mgr_run()                               | | |
+- * +-- |       MARK_UP        |  - set port up in sb                     | | |
+- *     |                      |  - set ovn-installed in ovs              | | |
+- *     |                      |  mgr_update()                            | | |
+- *     +----------------------+  - sbrec_update_chassis if needed        | | |
+- *              |                                                        | | |
+- *              | mgr_update(rcvd port up / ovn_installed & chassis set) | | |
+- *              V                                                        | | |
+- *     +----------------------+                                          | | |
+- *     |      INSTALLED       | ------------> claim_iface ---------------+ | |
+- *     +----------------------+                                            | |
+- *              |                                                          | |
+- *              | release_iface                                            | |
+- *              V                                                          | |
+- *     +----------------------+                                            | |
+- *     |                      | ------------> claim_iface -----------------+ |
+- *     |      MARK_DOWN       | ------> mgr_update(rcvd port down) ----------+
+- *     |                      | mgr_run()
+- *     |                      | - set port down in sb
+- *     |                      | mgr_update()
++ * +---|       MARK_UP        |  - set port up in sb                     | | |
++ * |   |                      |  - set ovn-installed in ovs              | | |
++ * |   |                      |  mgr_update()                            | | |
++ * |   +----------------------+  - sbrec_update_chassis if needed        | | |
++ * |            |                                                        | | |
++ * |            | mgr_update(rcvd port up / ovn_installed & chassis set) | | |
++ * |            V                                                        | | |
++ * |   +----------------------+                                          | | |
++ * |   |      INSTALLED       | ------------> claim_iface ---------------+ | |
++ * |   +----------------------+                                            | |
++ * |                  |                                                    | |
++ * |                  | release_iface                                      | |
++ * |mgr_update(       |                                                    | |
++ * |  rcvd port down) |                                                    | |
++ * |                  V                                                    | |
++ * |   +----------------------+                                            | |
++ * |   |                      | ------------> claim_iface -----------------+ |
++ * +---+      MARK_DOWN       | mgr_run()                                    |
++ * |   |                      | - set port down in sb                        |
++ * |   |                      | mgr_update(sb is rw)                         |
++ * |   +----------------------+ - sbrec_update_chassis(NULL)                 |
++ * |                  |                                                      |
++ * |                  | mgr_update(local binding not found)                  |
++ * |                  |                                                      |
++ * |                  V                                                      |
++ * |   +----------------------+                                              |
++ * |   |                      | ------------> claim_iface -------------------+
++ * +---+      UPDATE_PORT     | mgr_run()
+  *     +----------------------+ - sbrec_update_chassis(NULL)
+  */
+ 
+ 
+ struct ovs_iface {
+     char *id;               /* Extracted from OVS external_ids.iface_id. */
++    struct uuid pb_uuid;    /* 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
+@@ -155,6 +190,9 @@ struct if_status_mgr {
+     /* All local interfaces, mapping from 'iface-id' to 'struct ovs_iface'. */
+     struct shash ifaces;
+ 
++    /* local interfaces which need ovn-install removal */
++    struct shash ovn_uninstall_hash;
++
+     /* All local interfaces, stored per state. */
+     struct hmapx ifaces_per_state[OIF_MAX];
+ 
+@@ -170,15 +208,20 @@ struct if_status_mgr {
+ static struct ovs_iface *ovs_iface_create(struct if_status_mgr *,
+                                           const char *iface_id,
+                                           enum if_state );
++static void add_to_ovn_uninstall_hash(struct if_status_mgr *, const char *,
++                                      const struct uuid *);
+ static void ovs_iface_destroy(struct if_status_mgr *, struct ovs_iface *);
++static void ovn_uninstall_hash_destroy(struct if_status_mgr *mgr, char *name);
+ static void ovs_iface_set_state(struct if_status_mgr *, struct ovs_iface *,
+                                 enum if_state);
+ 
+ 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,
+     bool sb_readonly, bool ovs_readonly);
+ 
++static void ovn_uninstall_hash_account_mem(const char *name, bool erase);
+ struct if_status_mgr *
+ if_status_mgr_create(void)
+ {
+@@ -189,6 +232,7 @@ if_status_mgr_create(void)
+         hmapx_init(&mgr->ifaces_per_state[i]);
+     }
+     shash_init(&mgr->ifaces);
++    shash_init(&mgr->ovn_uninstall_hash);
+     return mgr;
+ }
+ 
+@@ -202,6 +246,11 @@ if_status_mgr_clear(struct if_status_mgr *mgr)
+     }
+     ovs_assert(shash_is_empty(&mgr->ifaces));
+ 
++    SHASH_FOR_EACH_SAFE (node, &mgr->ovn_uninstall_hash) {
++        ovn_uninstall_hash_destroy(mgr, node->data);
++    }
++    ovs_assert(shash_is_empty(&mgr->ovn_uninstall_hash));
++
+     for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
+         ovs_assert(hmapx_is_empty(&mgr->ifaces_per_state[i]));
+     }
+@@ -212,6 +261,7 @@ if_status_mgr_destroy(struct if_status_mgr *mgr)
+ {
+     if_status_mgr_clear(mgr);
+     shash_destroy(&mgr->ifaces);
++    shash_destroy(&mgr->ovn_uninstall_hash);
+     for (size_t i = 0; i < ARRAY_SIZE(mgr->ifaces_per_state); i++) {
+         hmapx_destroy(&mgr->ifaces_per_state[i]);
+     }
+@@ -231,6 +281,7 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+         iface = ovs_iface_create(mgr, iface_id, OIF_CLAIMED);
+     }
+ 
++    memcpy(&iface->pb_uuid, &pb->header_.uuid, sizeof(iface->pb_uuid));
+     if (!sb_readonly) {
+         set_pb_chassis_in_sbrec(pb, chassis_rec, true);
+     }
+@@ -238,11 +289,13 @@ if_status_mgr_claim_iface(struct if_status_mgr *mgr,
+     switch (iface->state) {
+     case OIF_CLAIMED:
+     case OIF_INSTALL_FLOWS:
++    case OIF_REM_OLD_OVN_INST:
+     case OIF_MARK_UP:
+         /* Nothing to do here. */
+         break;
+     case OIF_INSTALLED:
+     case OIF_MARK_DOWN:
++    case OIF_UPDATE_PORT:
+         ovs_iface_set_state(mgr, iface, OIF_CLAIMED);
+         break;
+     case OIF_MAX:
+@@ -271,9 +324,10 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
+     switch (iface->state) {
+     case OIF_CLAIMED:
+     case OIF_INSTALL_FLOWS:
+-        /* Not yet fully installed interfaces can be safely deleted. */
+-        ovs_iface_destroy(mgr, iface);
+-        break;
++        /* Not yet fully installed interfaces:
++         * pb->chassis still need to be deleted.
++         */
++    case OIF_REM_OLD_OVN_INST:
+     case OIF_MARK_UP:
+     case OIF_INSTALLED:
+         /* Properly mark interfaces "down" if their flows were already
+@@ -282,6 +336,7 @@ if_status_mgr_release_iface(struct if_status_mgr *mgr, const char *iface_id)
+         ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
+         break;
+     case OIF_MARK_DOWN:
++    case OIF_UPDATE_PORT:
+         /* Nothing to do here. */
+         break;
+     case OIF_MAX:
+@@ -302,9 +357,10 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+     switch (iface->state) {
+     case OIF_CLAIMED:
+     case OIF_INSTALL_FLOWS:
+-        /* Not yet fully installed interfaces can be safely deleted. */
+-        ovs_iface_destroy(mgr, iface);
+-        break;
++        /* Not yet fully installed interfaces:
++         * pb->chassis still need to be deleted.
++         */
++    case OIF_REM_OLD_OVN_INST:
+     case OIF_MARK_UP:
+     case OIF_INSTALLED:
+         /* Properly mark interfaces "down" if their flows were already
+@@ -313,6 +369,7 @@ if_status_mgr_delete_iface(struct if_status_mgr *mgr, const char *iface_id)
+         ovs_iface_set_state(mgr, iface, OIF_MARK_DOWN);
+         break;
+     case OIF_MARK_DOWN:
++    case OIF_UPDATE_PORT:
+         /* Nothing to do here. */
+         break;
+     case OIF_MAX:
+@@ -346,12 +403,34 @@ if_status_handle_claims(struct if_status_mgr *mgr,
+     return rc;
+ }
+ 
++static void
++clean_ovn_installed(struct if_status_mgr *mgr,
++                    const struct ovsrec_interface_table *iface_table)
++{
++    struct shash_node *node;
++
++    SHASH_FOR_EACH_SAFE (node, &mgr->ovn_uninstall_hash) {
++        const struct uuid *iface_uuid = node->data;
++        remove_ovn_installed_for_uuid(iface_table, iface_uuid);
++        free(node->data);
++        char *node_name = shash_steal(&mgr->ovn_uninstall_hash, node);
++        ovn_uninstall_hash_account_mem(node_name, true);
++        free(node_name);
++    }
++}
++
+ void
+ if_status_mgr_update(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 ovs_readonly,
+                      bool sb_readonly)
+ {
++    if (!ovs_readonly) {
++        clean_ovn_installed(mgr, iface_table);
++    }
+     if (!binding_data) {
+         return;
+     }
+@@ -359,6 +438,17 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+     struct shash *bindings = &binding_data->bindings;
+     struct hmapx_node *node;
+ 
++    /* Move all interfaces that have been confirmed without ovn-installed,
++     * from OIF_REM_OLD_OVN_INST to OIF_MARK_UP.
++     */
++    HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_REM_OLD_OVN_INST]) {
++        struct ovs_iface *iface = node->data;
++
++        if (!local_binding_is_ovn_installed(bindings, iface->id)) {
++            ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
++        }
++    }
++
+     /* Interfaces in OIF_MARK_UP/INSTALL_FLOWS state have already set their
+      * pb->chassis. However, the update might still be in fly (confirmation
+      * not received yet) or pb->chassis was overwitten by another chassis.
+@@ -390,6 +480,10 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+     HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_MARK_DOWN]) {
+         struct ovs_iface *iface = node->data;
+ 
++        if (!local_binding_find(bindings, iface->id)) {
++            ovs_iface_set_state(mgr, iface, OIF_UPDATE_PORT);
++            continue;
++        }
+         if (!sb_readonly) {
+             local_binding_set_pb(bindings, iface->id, chassis_rec,
+                                  NULL, false);
+@@ -437,6 +531,21 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+         }
+     }
+ 
++    if (!sb_readonly) {
++        HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_UPDATE_PORT]) {
++            struct ovs_iface *iface = node->data;
++            port_binding_set_down(chassis_rec, pb_table, iface->id,
++                                  &iface->pb_uuid);
++            ovs_iface_destroy(mgr, node->data);
++        }
++    } else {
++        HMAPX_FOR_EACH_SAFE (node, &mgr->ifaces_per_state[OIF_UPDATE_PORT]) {
++            struct ovs_iface *iface = node->data;
++            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
++            VLOG_INFO_RL(&rl, "Not setting lport %s down as sb is readonly",
++                         iface->id);
++        }
++    }
+     /* Register for a notification about flows being installed in OVS for all
+      * newly claimed interfaces for which pb->chassis has been updated.
+      * Request a seqno update when the flows for new interfaces have been
+@@ -450,10 +559,23 @@ if_status_mgr_update(struct if_status_mgr *mgr,
+     }
+ }
+ 
++void
++if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr,
++                                   const char *name,
++                                   const struct uuid *uuid)
++{
++    VLOG_DBG("Adding %s to list of interfaces for which to remove "
++              "ovn-installed", name);
++    if (!shash_find_data(&mgr->ovn_uninstall_hash, name)) {
++        add_to_ovn_uninstall_hash(mgr, name, uuid);
++    }
++}
++
+ void
+ 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,
+                   bool sb_readonly, bool ovs_readonly)
+ {
+     struct ofctrl_acked_seqnos *acked_seqnos =
+@@ -471,12 +593,25 @@ if_status_mgr_run(struct if_status_mgr *mgr,
+                                           iface->install_seqno)) {
+             continue;
+         }
+-        ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
++        /* Wait for ovn-installed to be absent before moving to MARK_UP state.
++         * Most of the times ovn-installed is already absent and hence we will
++         * not have to wait.
++         * If there is no binding_data, we can't determine if ovn-installed is
++         * present or not; hence also go to the OIF_REM_OLD_OVN_INST state.
++         */
++        if (!binding_data ||
++            local_binding_is_ovn_installed(&binding_data->bindings,
++                                           iface->id)) {
++            ovs_iface_set_state(mgr, iface, OIF_REM_OLD_OVN_INST);
++        } else {
++            ovs_iface_set_state(mgr, iface, OIF_MARK_UP);
++        }
+     }
+     ofctrl_acked_seqnos_destroy(acked_seqnos);
+ 
+     /* Update binding states. */
+     if_status_mgr_update_bindings(mgr, binding_data, chassis_rec,
++                                  iface_table,
+                                   sb_readonly, ovs_readonly);
+ }
+ 
+@@ -492,6 +627,18 @@ ovs_iface_account_mem(const char *iface_id, bool erase)
+     }
+ }
+ 
++static void
++ovn_uninstall_hash_account_mem(const char *name, bool erase)
++{
++    uint32_t size = (strlen(name) + sizeof(struct uuid) +
++                     sizeof(struct shash_node));
++    if (erase) {
++        ifaces_usage -= size;
++    } else {
++        ifaces_usage += size;
++    }
++}
++
+ static struct ovs_iface *
+ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
+                  enum if_state state)
+@@ -506,6 +653,16 @@ ovs_iface_create(struct if_status_mgr *mgr, const char *iface_id,
+     return iface;
+ }
+ 
++static void
++add_to_ovn_uninstall_hash(struct if_status_mgr *mgr, const char *name,
++                          const struct uuid *uuid)
++{
++    struct uuid *new_uuid = xzalloc(sizeof *new_uuid);
++    memcpy(new_uuid, uuid, sizeof(*new_uuid));
++    shash_add(&mgr->ovn_uninstall_hash, name, new_uuid);
++    ovn_uninstall_hash_account_mem(name, false);
++}
++
+ static void
+ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
+ {
+@@ -521,6 +678,23 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
+     free(iface);
+ }
+ 
++static void
++ovn_uninstall_hash_destroy(struct if_status_mgr *mgr, char *name)
++{
++    struct shash_node *node = shash_find(&mgr->ovn_uninstall_hash, name);
++    char *node_name = NULL;
++    if (node) {
++        free(node->data);
++        VLOG_DBG("Interface name %s destroy", name);
++        node_name = shash_steal(&mgr->ovn_uninstall_hash, node);
++        ovn_uninstall_hash_account_mem(name, true);
++        free(node_name);
++    } else {
++        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
++        VLOG_WARN_RL(&rl, "Interface name %s not found", name);
++    }
++}
++
+ static void
+ ovs_iface_set_state(struct if_status_mgr *mgr, struct ovs_iface *iface,
+                     enum if_state state)
+@@ -539,6 +713,7 @@ static void
+ 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,
+                               bool sb_readonly, bool ovs_readonly)
+ {
+     if (!binding_data) {
+@@ -558,7 +733,17 @@ if_status_mgr_update_bindings(struct if_status_mgr *mgr,
+                                sb_readonly, ovs_readonly);
+     }
+ 
+-    /* Notifiy the binding module to set "up" all bindings that have had
++    /* Notify the binding module to remove "ovn-installed" for all bindings
++     * in the OIF_REM_OLD_OVN_INST state.
++     */
++    HMAPX_FOR_EACH (node, &mgr->ifaces_per_state[OIF_REM_OLD_OVN_INST]) {
++        struct ovs_iface *iface = node->data;
++
++        local_binding_remove_ovn_installed(bindings, iface_table, iface->id,
++                                           ovs_readonly);
++    }
++
++    /* Notify the binding module to set "up" all bindings that have had
+      * their flows installed but are not yet marked "up" in the binding
+      * module.
+      */
+diff --git a/controller/if-status.h b/controller/if-status.h
+index 5bd187a25..8ba80acd9 100644
+--- a/controller/if-status.h
++++ b/controller/if-status.h
+@@ -17,6 +17,7 @@
+ #define IF_STATUS_H 1
+ 
+ #include "openvswitch/shash.h"
++#include "lib/vswitch-idl.h"
+ 
+ #include "binding.h"
+ 
+@@ -35,9 +36,13 @@ void if_status_mgr_delete_iface(struct if_status_mgr *, const char *iface_id);
+ 
+ void if_status_mgr_update(struct if_status_mgr *, struct local_binding_data *,
+                           const struct sbrec_chassis *chassis,
++                          const struct ovsrec_interface_table *iface_table,
++                          const struct sbrec_port_binding_table *pb_table,
++                          bool ovs_readonly,
+                           bool sb_readonly);
+ 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,
+                        bool sb_readonly, bool ovs_readonly);
+ void if_status_mgr_get_memory_usage(struct if_status_mgr *mgr,
+                                     struct simap *usage);
+@@ -48,5 +53,8 @@ bool if_status_handle_claims(struct if_status_mgr *mgr,
+                              const struct sbrec_chassis *chassis_rec,
+                              struct hmap *tracked_datapath,
+                              bool sb_readonly);
++void if_status_mgr_remove_ovn_installed(struct if_status_mgr *mgr,
++                                        const char *name,
++                                        const struct uuid *uuid);
+ 
+ # endif /* controller/if-status.h */
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 6a98b19e1..0b071138d 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -1729,6 +1729,7 @@ add_lb_vip_hairpin_flows(const struct ovn_controller_lb *lb,
+ 
+ static void
+ add_lb_ct_snat_hairpin_for_dp(const struct ovn_controller_lb *lb,
++                              bool has_vip_port,
+                               const struct sbrec_datapath_binding *datapath,
+                               const struct hmap *local_datapaths,
+                               struct match *dp_match,
+@@ -1742,15 +1743,21 @@ add_lb_ct_snat_hairpin_for_dp(const struct ovn_controller_lb *lb,
+         match_set_metadata(dp_match, htonll(datapath->tunnel_key));
+     }
+ 
++    uint16_t priority = datapath ? 200 : 100;
++    if (!has_vip_port) {
++        /* If L4 ports are not specified for the current LB, we will decrease
++         * the flow priority in order to not collide with other LBs with more
++         * fine-grained configuration.
++         */
++        priority -= 10;
++    }
+     /* A flow added for the "hairpin_snat_ip" case will have an extra
+      * datapath match, but it will also match on the less restrictive
+      * general case.  Therefore, we set the priority in the
+      * "hairpin_snat_ip" case to be higher than the general case. */
+-    ofctrl_add_or_append_flow(flow_table, OFTABLE_CT_SNAT_HAIRPIN,
+-                              datapath ? 200 : 100,
+-                              lb->slb->header_.uuid.parts[0],
+-                              dp_match, dp_acts, &lb->slb->header_.uuid,
+-                              NX_CTLR_NO_METER, NULL);
++    ofctrl_add_flow(flow_table, OFTABLE_CT_SNAT_HAIRPIN,
++                    priority, lb->slb->header_.uuid.parts[0],
++                    dp_match, dp_acts, &lb->slb->header_.uuid);
+ }
+ 
+ /* Add a ct_snat flow for each VIP of the LB.  If this LB does not use
+@@ -1836,8 +1843,8 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
+         }
+     }
+ 
+-    match_set_nw_proto(&match, lb->proto);
+     if (lb_vip->vip_port) {
++        match_set_nw_proto(&match, lb->proto);
+         if (!lb->hairpin_orig_tuple) {
+             match_set_ct_nw_proto(&match, lb->proto);
+             match_set_ct_tp_dst(&match, htons(lb_vip->vip_port));
+@@ -1854,18 +1861,20 @@ add_lb_ct_snat_hairpin_vip_flow(const struct ovn_controller_lb *lb,
+     }
+ 
+     if (!use_hairpin_snat_ip) {
+-        add_lb_ct_snat_hairpin_for_dp(lb, NULL, NULL,
++        add_lb_ct_snat_hairpin_for_dp(lb, !!lb_vip->vip_port, NULL, NULL,
+                                       &match, &ofpacts, flow_table);
+     } else {
+         for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
+-            add_lb_ct_snat_hairpin_for_dp(lb, lb->slb->datapaths[i],
+-                                          local_datapaths,
+-                                          &match, &ofpacts, flow_table);
++            add_lb_ct_snat_hairpin_for_dp(lb, !!lb_vip->vip_port,
++                                          lb->slb->datapaths[i],
++                                          local_datapaths, &match,
++                                          &ofpacts, flow_table);
+         }
+         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(
+-                    lb, lb->slb->datapath_group->datapaths[i],
++                    lb, !!lb_vip->vip_port,
++                    lb->slb->datapath_group->datapaths[i],
+                     local_datapaths, &match, &ofpacts, flow_table);
+             }
+         }
+diff --git a/controller/mirror.c b/controller/mirror.c
+index 665736966..0e5885e9b 100644
+--- a/controller/mirror.c
++++ b/controller/mirror.c
+@@ -22,6 +22,7 @@
+ 
+ /* OVS includes. */
+ #include "lib/vswitch-idl.h"
++#include "lib/socket-util.h"
+ #include "include/openvswitch/shash.h"
+ #include "openvswitch/vlog.h"
+ 
+@@ -69,6 +70,7 @@ static void set_mirror_iface_options(struct ovsrec_interface *,
+ static const struct ovsrec_port *get_iface_port(
+     const struct ovsrec_interface *, const struct ovsrec_bridge *);
+ 
++char *get_mirror_tunnel_type(const struct sbrec_mirror *);
+ 
+ void
+ mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+@@ -244,24 +246,26 @@ set_mirror_iface_options(struct ovsrec_interface *iface,
+     smap_destroy(&options);
+ }
+ 
++char *
++get_mirror_tunnel_type(const struct sbrec_mirror *sb_mirror)
++{
++    bool is_ipv6 = addr_is_ipv6(sb_mirror->sink);
++
++    return xasprintf(is_ipv6 ? "ip6%s" : "%s", sb_mirror->type);
++}
++
+ static void
+ check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+                                  const struct ovsrec_mirror *ovs_mirror)
+ {
+-    char *type;
+-    struct ovsrec_interface *iface =
+-                          ovs_mirror->output_port->interfaces[0];
+-    struct smap *opts = &iface->options;
+-    const char *erspan_ver = smap_get(opts, "erspan_ver");
+-    if (erspan_ver) {
+-        type = "erspan";
+-    } else {
+-        type = "gre";
+-    }
+-    if (strcmp(type, sb_mirror->type)) {
+-        ovsrec_interface_set_type(iface, sb_mirror->type);
++    struct ovsrec_interface *iface = ovs_mirror->output_port->interfaces[0];
++    char *type = get_mirror_tunnel_type(sb_mirror);
++
++    if (strcmp(type, iface->type)) {
++        ovsrec_interface_set_type(iface, type);
+     }
+     set_mirror_iface_options(iface, sb_mirror);
++    free(type);
+ }
+ 
+ static void
+@@ -327,8 +331,11 @@ create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+     char *port_name = xasprintf("ovn-%s", m->name);
+ 
+     ovsrec_interface_set_name(iface, port_name);
+-    ovsrec_interface_set_type(iface, m->sb_mirror->type);
++
++    char *type = get_mirror_tunnel_type(m->sb_mirror);
++    ovsrec_interface_set_type(iface, type);
+     set_mirror_iface_options(iface, m->sb_mirror);
++    free(type);
+ 
+     struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
+     ovsrec_port_set_name(port, port_name);
+diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml
+index ab52e2d34..f61f43008 100644
+--- a/controller/ovn-controller.8.xml
++++ b/controller/ovn-controller.8.xml
+@@ -121,11 +121,11 @@
+           that is needed in the current chassis.
+         </p>
+         <p>
+-          It is more optimal to set it to <code>true</code> in use cases when
+-          the chassis would anyway need to monitor most of the records in
+-          <var>ovs-database</var>, which would save the overhead of conditions
+-          processing, especially for server side.  Typically, set it to
+-          <code>true</code> for environments that all workloads need to be
++          It is more efficient to set it to <code>true</code> in use cases
++          where the chassis would anyway need to monitor most of the records in
++          <var>OVN Southbound</var> database, which would save the overhead of
++          conditions processing, especially for server side.  Typically, set it
++          to <code>true</code> for environments that all workloads need to be
+           reachable from each other.
+         </p>
+         <p>
+@@ -171,16 +171,14 @@
+         </p>
+ 
+         <p>
+-          Supported tunnel types for connecting hypervisors
+-          are <code>geneve</code> and <code>stt</code>.  Gateways may
+-          use <code>geneve</code>, <code>vxlan</code>, or
+-          <code>stt</code>.
++          Supported tunnel types for connecting hypervisors and gateways
++          are <code>geneve</code>, <code>vxlan</code>, and <code>stt</code>.
+         </p>
+ 
+         <p>
+           Due to the limited amount of metadata in <code>vxlan</code>,
+-          the capabilities and performance of connected gateways will be
+-          reduced versus other tunnel formats.
++          the capabilities and performance of connected gateways and
++          hypervisors will be reduced versus other tunnel formats.
+         </p>
+       </dd>
+ 
+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
+index 2d18bbfca..44a4518b9 100644
+--- a/controller/ovn-controller.c
++++ b/controller/ovn-controller.c
+@@ -712,7 +712,7 @@ get_snat_ct_zone(const struct sbrec_datapath_binding *dp)
+ }
+ 
+ static void
+-update_ct_zones(const struct shash *binding_lports,
++update_ct_zones(const struct sset *local_lports,
+                 const struct hmap *local_datapaths,
+                 struct simap *ct_zones, unsigned long *ct_zone_bitmap,
+                 struct shash *pending_ct_zones)
+@@ -725,9 +725,9 @@ update_ct_zones(const struct shash *binding_lports,
+     unsigned long unreq_snat_zones_map[BITMAP_N_LONGS(MAX_CT_ZONES)];
+     struct simap unreq_snat_zones = SIMAP_INITIALIZER(&unreq_snat_zones);
+ 
+-    struct shash_node *shash_node;
+-    SHASH_FOR_EACH (shash_node, binding_lports) {
+-        sset_add(&all_users, shash_node->name);
++    const char *local_lport;
++    SSET_FOR_EACH (local_lport, local_lports) {
++        sset_add(&all_users, local_lport);
+     }
+ 
+     /* Local patched datapath (gateway routers) need zones assigned. */
+@@ -2010,7 +2010,11 @@ addr_sets_update(const struct sbrec_address_set_table *address_set_table,
+         if (sbrec_address_set_is_deleted(as)) {
+             expr_const_sets_remove(addr_sets, as->name);
+             sset_add(deleted, as->name);
+-        } else {
++        }
++    }
++
++    SBREC_ADDRESS_SET_TABLE_FOR_EACH_TRACKED (as, address_set_table) {
++        if (!sbrec_address_set_is_deleted(as)) {
+             struct expr_constant_set *cs_old = shash_find_data(addr_sets,
+                                                                as->name);
+             if (!cs_old) {
+@@ -2381,7 +2385,7 @@ en_ct_zones_run(struct engine_node *node, void *data)
+         EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
+ 
+     restore_ct_zones(bridge_table, ovs_table, ct_zones_data);
+-    update_ct_zones(&rt_data->lbinding_data.lports, &rt_data->local_datapaths,
++    update_ct_zones(&rt_data->local_lports, &rt_data->local_datapaths,
+                     &ct_zones_data->current, ct_zones_data->bitmap,
+                     &ct_zones_data->pending);
+ 
+@@ -2471,8 +2475,10 @@ ct_zones_runtime_data_handler(struct engine_node *node, void *data)
+         SHASH_FOR_EACH (shash_node, &tdp->lports) {
+             struct tracked_lport *t_lport = shash_node->data;
+             if (strcmp(t_lport->pb->type, "")
+-                && strcmp(t_lport->pb->type, "localport")) {
+-                /* We allocate zone-id's only to VIF and localport lports. */
++                && strcmp(t_lport->pb->type, "localport")
++                && strcmp(t_lport->pb->type, "localnet")) {
++                /* We allocate zone-id's only to VIF, localport, and localnet
++                 * lports. */
+                 continue;
+             }
+ 
+@@ -2697,7 +2703,8 @@ static void
+ lb_data_removed_five_tuples_add(struct ed_type_lb_data *lb_data,
+                                 const struct ovn_controller_lb *lb)
+ {
+-    if (!ovs_feature_is_supported(OVS_CT_TUPLE_FLUSH_SUPPORT)) {
++    if (!ovs_feature_is_supported(OVS_CT_TUPLE_FLUSH_SUPPORT) ||
++        !lb->ct_flush) {
+         return;
+     }
+ 
+@@ -2716,7 +2723,8 @@ static void
+ lb_data_removed_five_tuples_remove(struct ed_type_lb_data *lb_data,
+                                    const struct ovn_controller_lb *lb)
+ {
+-    if (!ovs_feature_is_supported(OVS_CT_TUPLE_FLUSH_SUPPORT)) {
++    if (!ovs_feature_is_supported(OVS_CT_TUPLE_FLUSH_SUPPORT) ||
++        !lb->ct_flush) {
+         return;
+     }
+ 
+@@ -5071,7 +5079,8 @@ main(int argc, char *argv[])
+                                chassis,
+                                sbrec_sb_global_first(ovnsb_idl_loop.idl),
+                                ovs_table,
+-                               &transport_zones);
++                               &transport_zones,
++                               bridge_table);
+ 
+                     stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
+                                     time_msec());
+@@ -5225,6 +5234,11 @@ main(int argc, char *argv[])
+                     stopwatch_start(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME,
+                                     time_msec());
+                     if_status_mgr_update(if_mgr, binding_data, chassis,
++                                         ovsrec_interface_table_get(
++                                                    ovs_idl_loop.idl),
++                                         sbrec_port_binding_table_get(
++                                                    ovnsb_idl_loop.idl),
++                                         !ovs_idl_txn,
+                                          !ovnsb_idl_txn);
+                     stopwatch_stop(IF_STATUS_MGR_UPDATE_STOPWATCH_NAME,
+                                    time_msec());
+@@ -5254,11 +5268,12 @@ main(int argc, char *argv[])
+                     stopwatch_start(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
+                                     time_msec());
+                     if_status_mgr_run(if_mgr, binding_data, chassis,
++                                      ovsrec_interface_table_get(
++                                                  ovs_idl_loop.idl),
+                                       !ovnsb_idl_txn, !ovs_idl_txn);
+                     stopwatch_stop(IF_STATUS_MGR_RUN_STOPWATCH_NAME,
+                                    time_msec());
+                 }
+-
+             }
+ 
+             if (!engine_has_run()) {
+@@ -5449,6 +5464,7 @@ loop_done:
+     binding_destroy();
+     patch_destroy();
+     mirror_destroy();
++    encaps_destroy();
+     if_status_mgr_destroy(if_mgr);
+     shash_destroy(&vif_plug_deleted_iface_ids);
+     shash_destroy(&vif_plug_changed_iface_ids);
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index 795847729..761783562 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -2444,19 +2444,19 @@ compose_out_dhcpv6_opts(struct ofpbuf *userdata,
+                         struct ofpbuf *out_dhcpv6_opts, ovs_be32 iaid)
+ {
+     while (userdata->size) {
+-        struct dhcp_opt6_header *userdata_opt = ofpbuf_try_pull(
++        struct dhcpv6_opt_header *userdata_opt = ofpbuf_try_pull(
+             userdata, sizeof *userdata_opt);
+         if (!userdata_opt) {
+             return false;
+         }
+ 
+-        size_t size = ntohs(userdata_opt->size);
++        size_t size = ntohs(userdata_opt->len);
+         uint8_t *userdata_opt_data = ofpbuf_try_pull(userdata, size);
+         if (!userdata_opt_data) {
+             return false;
+         }
+ 
+-        switch (ntohs(userdata_opt->opt_code)) {
++        switch (ntohs(userdata_opt->code)) {
+         case DHCPV6_OPT_SERVER_ID_CODE:
+         {
+             /* The Server Identifier option carries a DUID
+@@ -7190,7 +7190,9 @@ bfd_monitor_send_msg(struct rconn *swconn, long long int *bfd_time)
+         pinctrl_send_bfd_tx_msg(swconn, entry, false);
+ 
+         tx_timeout = MAX(entry->local_min_tx, entry->remote_min_rx);
+-        tx_timeout -= random_range((tx_timeout * 25) / 100);
++        if (tx_timeout >= 4) {
++            tx_timeout -= random_range(tx_timeout / 4);
++        }
+         entry->next_tx = cur_time + tx_timeout;
+ next:
+         if (*bfd_time > entry->next_tx) {
+diff --git a/debian/changelog b/debian/changelog
+index 11a07dd38..02a9953ba 100644
+--- a/debian/changelog
++++ b/debian/changelog
+@@ -1,3 +1,9 @@
++OVN (23.03.1-1) unstable; urgency=low
++   [ OVN team ]
++   * New upstream version
++
++ -- OVN team <dev@openvswitch.org>  Fri, 03 Mar 2023 10:40:37 -0500
++
+ ovn (23.03.0-1) unstable; urgency=low
+ 
+    * New upstream version
+diff --git a/lib/actions.c b/lib/actions.c
+index 781549d75..2b566c85e 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -2882,26 +2882,26 @@ static void
+ encode_put_dhcpv6_option(const struct ovnact_gen_option *o,
+                          struct ofpbuf *ofpacts)
+ {
+-    struct dhcp_opt6_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt);
++    struct dhcpv6_opt_header *opt = ofpbuf_put_uninit(ofpacts, sizeof *opt);
+     const union expr_constant *c = o->value.values;
+     size_t n_values = o->value.n_values;
+     size_t size;
+ 
+-    opt->opt_code = htons(o->option->code);
++    opt->code = htons(o->option->code);
+ 
+     if (!strcmp(o->option->type, "ipv6")) {
+         size = n_values * sizeof(struct in6_addr);
+-        opt->size = htons(size);
++        opt->len = htons(size);
+         for (size_t i = 0; i < n_values; i++) {
+             ofpbuf_put(ofpacts, &c[i].value.ipv6, sizeof(struct in6_addr));
+         }
+     } else if (!strcmp(o->option->type, "mac")) {
+         size = sizeof(struct eth_addr);
+-        opt->size = htons(size);
++        opt->len = htons(size);
+         ofpbuf_put(ofpacts, &c->value.mac, size);
+     } else if (!strcmp(o->option->type, "str")) {
+         size = strlen(c->string);
+-        opt->size = htons(size);
++        opt->len = htons(size);
+         ofpbuf_put(ofpacts, c->string, size);
+     }
+ }
+diff --git a/lib/lb.c b/lib/lb.c
+index e941434c4..f88c1855b 100644
+--- a/lib/lb.c
++++ b/lib/lb.c
+@@ -38,6 +38,7 @@ static const char *lb_neighbor_responder_mode_names[] = {
+ static struct nbrec_load_balancer_health_check *
+ ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
+                         const char *vip_port_str, bool template);
++static void ovn_lb_backends_clear(struct ovn_lb_vip *vip);
+ 
+ struct ovn_lb_ip_set *
+ ovn_lb_ip_set_create(void)
+@@ -238,6 +239,8 @@ ovn_lb_backends_init_template(struct ovn_lb_vip *lb_vip, const char *value_)
+             ds_put_format(&errors, "%s: should be a template of the form: "
+                           "'^backendip_variable1[:^port_variable1|:port]', ",
+                           atom);
++            free(backend_port);
++            free(backend_ip);
+         }
+         free(atom);
+     }
+@@ -285,8 +288,27 @@ ovn_lb_vip_init_template(struct ovn_lb_vip *lb_vip, const char *lb_key_,
+                          lb_key_);
+     }
+ 
++    /* Backends can either be templates or explicit IPs and ports. */
+     lb_vip->address_family = address_family;
+-    return ovn_lb_backends_init_template(lb_vip, lb_value);
++    lb_vip->template_backends = true;
++    char *template_error = ovn_lb_backends_init_template(lb_vip, lb_value);
++
++    if (template_error) {
++        lb_vip->template_backends = false;
++        ovn_lb_backends_clear(lb_vip);
++
++        char *explicit_error = ovn_lb_backends_init_explicit(lb_vip, lb_value);
++        if (explicit_error) {
++            char *error =
++                xasprintf("invalid backend: template (%s) OR explicit (%s)",
++                          template_error, explicit_error);
++            free(explicit_error);
++            free(template_error);
++            return error;
++        }
++        free(template_error);
++    }
++    return NULL;
+ }
+ 
+ /* Returns NULL on success, an error string on failure.  The caller is
+@@ -304,15 +326,29 @@ ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key,
+                                        address_family);
+ }
+ 
+-void
+-ovn_lb_vip_destroy(struct ovn_lb_vip *vip)
++static void
++ovn_lb_backends_destroy(struct ovn_lb_vip *vip)
+ {
+-    free(vip->vip_str);
+-    free(vip->port_str);
+     for (size_t i = 0; i < vip->n_backends; i++) {
+         free(vip->backends[i].ip_str);
+         free(vip->backends[i].port_str);
+     }
++}
++
++static void
++ovn_lb_backends_clear(struct ovn_lb_vip *vip)
++{
++    ovn_lb_backends_destroy(vip);
++    vip->backends = NULL;
++    vip->n_backends = 0;
++}
++
++void
++ovn_lb_vip_destroy(struct ovn_lb_vip *vip)
++{
++    free(vip->vip_str);
++    free(vip->port_str);
++    ovn_lb_backends_destroy(vip);
+     free(vip->backends);
+ }
+ 
+@@ -357,11 +393,10 @@ ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
+ }
+ 
+ void
+-ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s,
+-                           bool template)
++ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s)
+ {
+     bool needs_brackets = vip->address_family == AF_INET6 && vip->port_str
+-                          && !template;
++                          && !vip->template_backends;
+     for (size_t i = 0; i < vip->n_backends; i++) {
+         struct ovn_lb_backend *backend = &vip->backends[i];
+ 
+@@ -798,6 +833,7 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
+     lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
+                                            "hairpin_orig_tuple",
+                                            false);
++    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
+     ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
+                                &lb->hairpin_snat_ips);
+     return lb;
+diff --git a/lib/lb.h b/lib/lb.h
+index 7a67b7426..e24f519db 100644
+--- a/lib/lb.h
++++ b/lib/lb.h
+@@ -96,6 +96,9 @@ struct ovn_lb_vip {
+                           */
+     struct ovn_lb_backend *backends;
+     size_t n_backends;
++    bool template_backends; /* True if the backends are templates. False if
++                             * they're explicitly specified.
++                             */
+     bool empty_backend_rej;
+     int address_family;
+ };
+@@ -188,6 +191,7 @@ struct ovn_controller_lb {
+     bool hairpin_orig_tuple; /* True if ovn-northd stores the original
+                               * destination tuple in registers.
+                               */
++    bool ct_flush; /* True if we should flush CT after backend removal. */
+ 
+     struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
+                                               * as source for hairpinned
+@@ -210,8 +214,7 @@ char *ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key,
+ void ovn_lb_vip_destroy(struct ovn_lb_vip *vip);
+ void ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s,
+                        bool template);
+-void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s,
+-                                bool template);
++void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s);
+ 
+ struct ovn_lb_5tuple {
+     struct hmap_node hmap_node;
+diff --git a/lib/ovn-l7.h b/lib/ovn-l7.h
+index 2b20bc380..d718ed39a 100644
+--- a/lib/ovn-l7.h
++++ b/lib/ovn-l7.h
+@@ -240,12 +240,6 @@ struct dhcp_opt_header {
+ #define DHCP_OPT_PAYLOAD(hdr) \
+     (void *)((char *)hdr + sizeof(struct dhcp_opt_header))
+ 
+-/* Used in the OpenFlow PACKET_IN userdata */
+-struct dhcp_opt6_header {
+-    ovs_be16 opt_code;
+-    ovs_be16 size;
+-};
+-
+ /* These are not defined in ovs/lib/dhcp.h, hence defining here. */
+ #define OVN_DHCP_MSG_DECLINE        4
+ #define OVN_DHCP_MSG_RELEASE        7
+diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
+index d23993a55..fd025c92b 100644
+--- a/northd/inc-proc-northd.c
++++ b/northd/inc-proc-northd.c
+@@ -34,10 +34,13 @@
+ #include "en-lflow.h"
+ #include "en-northd-output.h"
+ #include "en-sync-sb.h"
++#include "unixctl.h"
+ #include "util.h"
+ 
+ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
+ 
++static unixctl_cb_func chassis_features_list;
++
+ #define NB_NODES \
+     NB_NODE(nb_global, "nb_global") \
+     NB_NODE(copp, "copp") \
+@@ -306,6 +309,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
+     engine_ovsdb_node_add_index(&en_sb_address_set,
+                                 "sbrec_address_set_by_name",
+                                 sbrec_address_set_by_name);
++
++    struct northd_data *northd_data =
++        engine_get_internal_data(&en_northd);
++    unixctl_command_register("debug/chassis-features-list", "", 0, 0,
++                             chassis_features_list,
++                             &northd_data->features);
+ }
+ 
+ /* Returns true if the incremental processing ended up updating nodes. */
+@@ -356,3 +365,20 @@ void inc_proc_northd_cleanup(void)
+     engine_cleanup();
+     engine_set_context(NULL);
+ }
++
++static void
++chassis_features_list(struct unixctl_conn *conn, int argc OVS_UNUSED,
++                      const char *argv[] OVS_UNUSED, void *features_)
++{
++    struct chassis_features *features = features_;
++    struct ds ds = DS_EMPTY_INITIALIZER;
++
++    ds_put_format(&ds, "ct_no_masked_label:    %s\n",
++                  features->ct_no_masked_label ? "true" : "false");
++    ds_put_format(&ds, "ct_lb_related:         %s\n",
++                  features->ct_lb_related ? "true" : "false");
++    ds_put_format(&ds, "mac_binding_timestamp: %s\n",
++                  features->mac_binding_timestamp ? "true" : "false");
++    unixctl_command_reply(conn, ds_cstr(&ds));
++    ds_destroy(&ds);
++}
+diff --git a/northd/northd.c b/northd/northd.c
+index 7ad4cdfad..045282fac 100644
+--- a/northd/northd.c
++++ b/northd/northd.c
+@@ -432,6 +432,13 @@ build_chassis_features(const struct northd_input *input_data,
+     const struct sbrec_chassis *chassis;
+ 
+     SBREC_CHASSIS_TABLE_FOR_EACH (chassis, input_data->sbrec_chassis) {
++        /* Only consider local AZ chassis.  Remote ones don't install
++         * flows generated by the local northd.
++         */
++        if (smap_get_bool(&chassis->other_config, "is-remote", false)) {
++            continue;
++        }
++
+         bool ct_no_masked_label =
+             smap_get_bool(&chassis->other_config,
+                           OVN_FEATURE_CT_NO_MASKED_LABEL,
+@@ -552,7 +559,7 @@ free_chassis_queueid(struct hmap *set, const struct uuid *uuid,
+ static inline bool
+ port_has_qos_params(const struct smap *opts)
+ {
+-    return (smap_get(opts, "qos_max_rate") ||
++    return (smap_get(opts, "qos_max_rate") || smap_get(opts, "qos_min_rate") ||
+             smap_get(opts, "qos_burst"));
+ }
+ 
+@@ -1641,6 +1648,10 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
+          * use it. */
+         hmap_remove(ports, &port->key_node);
+ 
++        if (port->peer) {
++            port->peer->peer = NULL;
++        }
++
+         for (int i = 0; i < port->n_lsp_addrs; i++) {
+             destroy_lport_addresses(&port->lsp_addrs[i]);
+         }
+@@ -3881,7 +3892,7 @@ build_lb_vip_actions(struct ovn_lb_vip *lb_vip,
+     const char *ct_lb_action =
+         features->ct_no_masked_label ? "ct_lb_mark" : "ct_lb";
+     bool reject = !lb_vip->n_backends && lb_vip->empty_backend_rej;
+-    bool drop = false;
++    bool drop = !lb_vip->n_backends && !lb_vip->empty_backend_rej;
+ 
+     if (lb_vip_nb->lb_health_check) {
+         ds_put_format(action, "%s(backends=", ct_lb_action);
+@@ -5779,20 +5790,24 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
+      * know about the connection, as the icmp request went through the logical
+      * router on hostA, not hostB. This would only work with distributed
+      * conntrack state across all chassis. */
+-    struct ds match_in = DS_EMPTY_INITIALIZER;
+-    struct ds match_out = DS_EMPTY_INITIALIZER;
+ 
+-    ds_put_format(&match_in, "ip && inport == %s", op->json_key);
+-    ds_put_format(&match_out, "ip && outport == %s", op->json_key);
++    const char *ingress_action = "next;";
++    const char *egress_action = od->has_stateful_acl
++                                ? "next;"
++                                : "ct_clear; next;";
++
++    char *ingress_match = xasprintf("ip && inport == %s", op->json_key);
++    char *egress_match = xasprintf("ip && outport == %s", op->json_key);
++
+     ovn_lflow_add_with_lport_and_hint(lflows, od, in_stage, priority,
+-                                      ds_cstr(&match_in), "next;", op->key,
+-                                      &op->nbsp->header_);
++                                      ingress_match, ingress_action,
++                                      op->key, &op->nbsp->header_);
+     ovn_lflow_add_with_lport_and_hint(lflows, od, out_stage, priority,
+-                                      ds_cstr(&match_out), "next;", op->key,
+-                                      &op->nbsp->header_);
++                                      egress_match, egress_action,
++                                      op->key, &op->nbsp->header_);
+ 
+-    ds_destroy(&match_in);
+-    ds_destroy(&match_out);
++    free(ingress_match);
++    free(egress_match);
+ }
+ 
+ static void
+@@ -5867,7 +5882,8 @@ build_pre_acls(struct ovn_datapath *od, const struct hmap *port_groups,
+         }
+         for (size_t i = 0; i < od->n_localnet_ports; i++) {
+             skip_port_from_conntrack(od, od->localnet_ports[i],
+-                                     S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL,
++                                     S_SWITCH_IN_PRE_ACL,
++                                     S_SWITCH_OUT_PRE_ACL,
+                                      110, lflows);
+         }
+ 
+@@ -6036,10 +6052,17 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
+                                  S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
+                                  110, lflows);
+     }
+-    for (size_t i = 0; i < od->n_localnet_ports; i++) {
+-        skip_port_from_conntrack(od, od->localnet_ports[i],
+-                                 S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
+-                                 110, lflows);
++    /* Localnet ports have no need for going through conntrack, unless
++     * the logical switch has a load balancer. Then, conntrack is necessary
++     * so that traffic arriving via the localnet port can be load
++     * balanced.
++     */
++    if (!od->has_lb_vip) {
++        for (size_t i = 0; i < od->n_localnet_ports; i++) {
++            skip_port_from_conntrack(od, od->localnet_ports[i],
++                                     S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
++                                     110, lflows);
++        }
+     }
+ 
+     /* Do not sent statless flows via conntrack */
+@@ -6700,6 +6723,8 @@ build_port_group_lswitches(struct northd_input *input_data,
+     }
+ }
+ 
++#define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
++
+ static void
+ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
+            struct hmap *lflows, const struct hmap *port_groups,
+@@ -6847,20 +6872,26 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
+         ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
+                       ds_cstr(&match), ct_out_acl_action);
+ 
+-        /* Ingress and Egress ACL Table (Priority 65532).
+-         *
+-         * Not to do conntrack on ND packets. */
+-        ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
+-                      "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
+-        ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
+-                      "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;");
+-
+         /* Reply and related traffic matched by an "allow-related" ACL
+          * should be allowed in the ls_in_acl_after_lb stage too. */
+         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_AFTER_LB, UINT16_MAX - 3,
+                       REGBIT_ACL_HINT_ALLOW_REL" == 1", "next;");
+     }
+ 
++    /* Ingress and Egress ACL Table (Priority 65532).
++     *
++     * Always allow service IPv6 protocols regardless of other ACLs defined.
++     *
++     * Also, don't send them to conntrack because session tracking
++     * for these protocols is not working properly:
++     * https://bugzilla.kernel.org/show_bug.cgi?id=11797. */
++    ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3,
++                  IPV6_CT_OMIT_MATCH, "next;");
++    ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3,
++                  IPV6_CT_OMIT_MATCH, "next;");
++    ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_AFTER_LB, UINT16_MAX - 3,
++                  IPV6_CT_OMIT_MATCH, "next;");
++
+     /* Ingress or Egress ACL Table (Various priorities). */
+     for (size_t i = 0; i < od->nbs->n_acls; i++) {
+         struct nbrec_acl *acl = od->nbs->acls[i];
+@@ -7089,7 +7120,9 @@ build_lb_rules_pre_stateful(struct hmap *lflows, struct ovn_northd_lb *lb,
+  * - load balancing affinity check:
+  *   table=lr_in_lb_aff_check, priority=100
+  *      match=(new_lb_match)
+- *      action=(REGBIT_KNOWN_LB_SESSION = chk_lb_aff(); next;)
++ *      action=(REG_NEXT_HOP_IPV4 = ip4.dst;
++ *              REG_ORIG_TP_DPORT_ROUTER = tcp.dst;
++ *              REGBIT_KNOWN_LB_SESSION = chk_lb_aff(); next;)
+  *
+  * - load balancing:
+  *   table=lr_in_dnat, priority=150
+@@ -7130,16 +7163,11 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+         return;
+     }
+ 
+-    static char *aff_check = REGBIT_KNOWN_LB_SESSION" = chk_lb_aff(); next;";
+-
+-    ovn_lflow_add_with_dp_group(
+-        lflows, dp_bitmap, S_ROUTER_IN_LB_AFF_CHECK, 100,
+-        new_lb_match, aff_check, &lb->nlb->header_);
+-
+     struct ds aff_action = DS_EMPTY_INITIALIZER;
+     struct ds aff_action_learn = DS_EMPTY_INITIALIZER;
+     struct ds aff_match = DS_EMPTY_INITIALIZER;
+     struct ds aff_match_learn = DS_EMPTY_INITIALIZER;
++    struct ds aff_check_action = DS_EMPTY_INITIALIZER;
+ 
+     bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&lb_vip->vip);
+     const char *ip_match = ipv6 ? "ip6" : "ip4";
+@@ -7155,6 +7183,20 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+         ct_flag = "; force_snat";
+     }
+ 
++    /* Create affinity check flow. */
++    ds_put_format(&aff_check_action, "%s = %s.dst; ", reg_vip, ip_match);
++
++    if (lb_vip->port_str) {
++        ds_put_format(&aff_check_action, REG_ORIG_TP_DPORT_ROUTER" = %s.dst; ",
++                      lb->proto);
++    }
++    ds_put_cstr(&aff_check_action, REGBIT_KNOWN_LB_SESSION
++                " = chk_lb_aff(); next;");
++
++    ovn_lflow_add_with_dp_group(
++        lflows, dp_bitmap, S_ROUTER_IN_LB_AFF_CHECK, 100,
++        new_lb_match, ds_cstr(&aff_check_action), &lb->nlb->header_);
++
+     /* Prepare common part of affinity LB and affinity learn action. */
+     ds_put_format(&aff_action, "%s = %s; ", reg_vip, lb_vip->vip_str);
+     ds_put_cstr(&aff_action_learn, "commit_lb_aff(vip = \"");
+@@ -7252,6 +7294,7 @@ build_lb_affinity_lr_flows(struct hmap *lflows, struct ovn_northd_lb *lb,
+     ds_destroy(&aff_action_learn);
+     ds_destroy(&aff_match);
+     ds_destroy(&aff_match_learn);
++    ds_destroy(&aff_check_action);
+ }
+ 
+ /* Builds the logical switch flows related to load balancer affinity.
+@@ -10450,10 +10493,8 @@ enum lrouter_nat_lb_flow_type {
+ 
+ struct lrouter_nat_lb_flows_ctx {
+     const char *new_action[LROUTER_NAT_LB_FLOW_MAX];
+-    const char *est_action[LROUTER_NAT_LB_FLOW_MAX];
+ 
+     struct ds *new_match;
+-    struct ds *est_match;
+     struct ds *undnat_match;
+ 
+     struct ovn_lb_vip *lb_vip;
+@@ -10471,10 +10512,22 @@ build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
+                                      enum lrouter_nat_lb_flow_type type,
+                                      struct ovn_datapath *od)
+ {
+-    char *gw_action = od->is_gw_router ? "ct_dnat;" : "ct_dnat_in_czone;";
++    const char *undnat_action;
++
++    switch (type) {
++    case LROUTER_NAT_LB_FLOW_FORCE_SNAT:
++        undnat_action = "flags.force_snat_for_lb = 1; next;";
++        break;
++    case LROUTER_NAT_LB_FLOW_SKIP_SNAT:
++        undnat_action = "flags.skip_snat_for_lb = 1; next;";
++        break;
++    case LROUTER_NAT_LB_FLOW_NORMAL:
++    case LROUTER_NAT_LB_FLOW_MAX:
++        undnat_action = od->is_gw_router ? "ct_dnat;" : "ct_dnat_in_czone;";
++        break;
++    }
+     /* Store the match lengths, so we can reuse the ds buffer. */
+     size_t new_match_len = ctx->new_match->length;
+-    size_t est_match_len = ctx->est_match->length;
+     size_t undnat_match_len = ctx->undnat_match->length;
+ 
+ 
+@@ -10487,33 +10540,24 @@ build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
+     if (ctx->lb_vip->n_backends || !ctx->lb_vip->empty_backend_rej) {
+         ds_put_format(ctx->new_match, " && is_chassis_resident(%s)",
+                       od->l3dgw_ports[0]->cr_port->json_key);
+-        ds_put_format(ctx->est_match, " && is_chassis_resident(%s)",
+-                      od->l3dgw_ports[0]->cr_port->json_key);
+     }
+ 
+     ovn_lflow_add_with_hint__(ctx->lflows, od, S_ROUTER_IN_DNAT, ctx->prio,
+                               ds_cstr(ctx->new_match), ctx->new_action[type],
+                               NULL, meter, &ctx->lb->nlb->header_);
+-    ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_DNAT, ctx->prio,
+-                            ds_cstr(ctx->est_match), ctx->est_action[type],
+-                            &ctx->lb->nlb->header_);
+ 
+     ds_truncate(ctx->new_match, new_match_len);
+-    ds_truncate(ctx->est_match, est_match_len);
+ 
+     if (!ctx->lb_vip->n_backends) {
+         return;
+     }
+ 
+-    const char *action = (type == LROUTER_NAT_LB_FLOW_NORMAL)
+-                         ? gw_action : ctx->est_action[type];
+-
+     ds_put_format(ctx->undnat_match,
+                   ") && outport == %s && is_chassis_resident(%s)",
+                   od->l3dgw_ports[0]->json_key,
+                   od->l3dgw_ports[0]->cr_port->json_key);
+     ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_UNDNAT, 120,
+-                            ds_cstr(ctx->undnat_match), action,
++                            ds_cstr(ctx->undnat_match), undnat_action,
+                             &ctx->lb->nlb->header_);
+     ds_truncate(ctx->undnat_match, undnat_match_len);
+ }
+@@ -10556,11 +10600,6 @@ build_gw_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
+             ctx->new_action[type], &ctx->lb->nlb->header_);
+     }
+     bitmap_free(dp_non_meter);
+-
+-    ovn_lflow_add_with_dp_group(
+-        ctx->lflows, dp_bitmap, S_ROUTER_IN_DNAT, ctx->prio,
+-        ds_cstr(ctx->est_match), ctx->est_action[type],
+-        &ctx->lb->nlb->header_);
+ }
+ 
+ static void
+@@ -10572,19 +10611,13 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
+                                const struct shash *meter_groups,
+                                const struct chassis_features *features)
+ {
+-    const char *ct_natted = features->ct_no_masked_label
+-                            ? "ct_mark.natted"
+-                            : "ct_label.natted";
+-
+     bool ipv4 = lb_vip->address_family == AF_INET;
+     const char *ip_match = ipv4 ? "ip4" : "ip6";
+-    const char *ip_reg = ipv4 ? REG_NEXT_HOP_IPV4 : REG_NEXT_HOP_IPV6;
+ 
+     int prio = 110;
+ 
+     struct ds skip_snat_act = DS_EMPTY_INITIALIZER;
+     struct ds force_snat_act = DS_EMPTY_INITIALIZER;
+-    struct ds est_match = DS_EMPTY_INITIALIZER;
+     struct ds undnat_match = DS_EMPTY_INITIALIZER;
+     struct ds unsnat_match = DS_EMPTY_INITIALIZER;
+ 
+@@ -10601,19 +10634,14 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
+      * of "ct_lb_mark($targets);". The other flow is for ct.est with
+      * an action of "next;".
+      */
+-    ds_put_format(match, "ct.new && !ct.rel && %s && %s == %s",
+-                  ip_match, ip_reg, lb_vip->vip_str);
++    ds_put_format(match, "ct.new && !ct.rel && %s && %s.dst == %s",
++                  ip_match, ip_match, lb_vip->vip_str);
+     if (lb_vip->port_str) {
+         prio = 120;
+-        ds_put_format(match, " && %s && "REG_ORIG_TP_DPORT_ROUTER" == %s",
+-                      lb->proto, lb_vip->port_str);
++        ds_put_format(match, " && %s && %s.dst == %s",
++                      lb->proto, lb->proto, lb_vip->port_str);
+     }
+ 
+-    ds_put_cstr(&est_match, "ct.est");
+-    /* Clone the match after initial "ct.new" (6 bytes). */
+-    ds_put_cstr(&est_match, ds_cstr(match) + 6);
+-    ds_put_format(&est_match, " && %s == 1", ct_natted);
+-
+     /* Add logical flows to UNDNAT the load balanced reverse traffic in
+      * the router egress pipleine stage - S_ROUTER_OUT_UNDNAT if the logical
+      * router has a gateway router port associated.
+@@ -10650,20 +10678,12 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
+         .lflows = lflows,
+         .meter_groups = meter_groups,
+         .new_match = match,
+-        .est_match = &est_match,
+         .undnat_match = &undnat_match
+     };
+ 
+     ctx.new_action[LROUTER_NAT_LB_FLOW_NORMAL] = ds_cstr(action);
+-    ctx.est_action[LROUTER_NAT_LB_FLOW_NORMAL] = "next;";
+-
+     ctx.new_action[LROUTER_NAT_LB_FLOW_SKIP_SNAT] = ds_cstr(&skip_snat_act);
+-    ctx.est_action[LROUTER_NAT_LB_FLOW_SKIP_SNAT] =
+-                                        "flags.skip_snat_for_lb = 1; next;";
+-
+     ctx.new_action[LROUTER_NAT_LB_FLOW_FORCE_SNAT] = ds_cstr(&force_snat_act);
+-    ctx.est_action[LROUTER_NAT_LB_FLOW_FORCE_SNAT] =
+-                                        "flags.force_snat_for_lb = 1; next;";
+ 
+     enum {
+         LROUTER_NAT_LB_AFF            = LROUTER_NAT_LB_FLOW_MAX,
+@@ -10746,7 +10766,6 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
+ 
+     ds_destroy(&unsnat_match);
+     ds_destroy(&undnat_match);
+-    ds_destroy(&est_match);
+     ds_destroy(&skip_snat_act);
+     ds_destroy(&force_snat_act);
+ 
+@@ -10820,39 +10839,19 @@ build_lrouter_defrag_flows_for_lb(struct ovn_northd_lb *lb,
+         return;
+     }
+ 
+-    struct ds defrag_actions = DS_EMPTY_INITIALIZER;
+     for (size_t i = 0; i < lb->n_vips; i++) {
+         struct ovn_lb_vip *lb_vip = &lb->vips[i];
++        bool ipv6 = lb_vip->address_family == AF_INET6;
+         int prio = 100;
+ 
+-        ds_clear(&defrag_actions);
+         ds_clear(match);
+-
+-        if (lb_vip->address_family == AF_INET) {
+-            ds_put_format(match, "ip && ip4.dst == %s", lb_vip->vip_str);
+-            ds_put_format(&defrag_actions, REG_NEXT_HOP_IPV4" = %s; ",
+-                          lb_vip->vip_str);
+-        } else {
+-            ds_put_format(match, "ip && ip6.dst == %s", lb_vip->vip_str);
+-            ds_put_format(&defrag_actions, REG_NEXT_HOP_IPV6" = %s; ",
+-                          lb_vip->vip_str);
+-        }
+-
+-        if (lb_vip->port_str) {
+-            ds_put_format(match, " && %s", lb->proto);
+-            prio = 110;
+-
+-            ds_put_format(&defrag_actions, REG_ORIG_TP_DPORT_ROUTER
+-                          " = %s.dst; ", lb->proto);
+-        }
+-
+-        ds_put_format(&defrag_actions, "ct_dnat;");
++        ds_put_format(match, "ip && ip%c.dst == %s", ipv6 ? '6' : '4',
++                      lb_vip->vip_str);
+ 
+         ovn_lflow_add_with_dp_group(
+             lflows, lb->nb_lr_map, S_ROUTER_IN_DEFRAG, prio,
+-            ds_cstr(match), ds_cstr(&defrag_actions), &lb->nlb->header_);
++            ds_cstr(match), "ct_dnat;", &lb->nlb->header_);
+     }
+-    ds_destroy(&defrag_actions);
+ }
+ 
+ static void
+@@ -10991,15 +10990,10 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode)
+ }
+ 
+ static inline bool
+-lrouter_nat_is_stateless(const struct nbrec_nat *nat)
++lrouter_dnat_and_snat_is_stateless(const struct nbrec_nat *nat)
+ {
+-    const char *stateless = smap_get(&nat->options, "stateless");
+-
+-    if (stateless && !strcmp(stateless, "true")) {
+-        return true;
+-    }
+-
+-    return false;
++    return smap_get_bool(&nat->options, "stateless", false) &&
++           !strcmp(nat->type, "dnat_and_snat");
+ }
+ 
+ /* Handles the match criteria and actions in logical flow
+@@ -12814,8 +12808,7 @@ build_gateway_redirect_flows_for_lrouter(
+         for (int j = 0; j < od->n_nat_entries; j++) {
+             const struct ovn_nat *nat = &od->nat_entries[j];
+ 
+-            if (!lrouter_nat_is_stateless(nat->nb) ||
+-                strcmp(nat->nb->type, "dnat_and_snat") ||
++            if (!lrouter_dnat_and_snat_is_stateless(nat->nb) ||
+                 (!nat->nb->allowed_ext_ips && !nat->nb->exempted_ext_ips)) {
+                 continue;
+             }
+@@ -13038,9 +13031,27 @@ build_misc_local_traffic_drop_flows_for_lrouter(
+         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
+                       "eth.bcast", debug_drop_action());
+ 
++        /* Avoid ICMP time exceeded for multicast, silent drop instead.
++         * See RFC1812 section 5.3.1:
++         *  If the TTL is reduced to zero (or less), the packet MUST be
++         *  discarded, and if the destination is NOT A MULTICAST address the
++         *  router MUST send an ICMP Time Exceeded message ...
++         *
++         * The reason behind is that TTL has special meanings for multicast.
++         * For example, TTL = 1 means restricted to the same subnet, not
++         * forwarded by the router. So it is very common to see multicast
++         * packets with ttl = 1, and generating ICMP for such packets is
++         * harmful from both slowpath performance and functionality point of
++         * view.
++         *
++         * (priority-31 flows will send ICMP time exceeded) */
++        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 32,
++                      "ip.ttl == {0, 1} && !ip.later_frag && "
++                      "(ip4.mcast || ip6.mcast)", debug_drop_action());
++
+         /* TTL discard */
+         ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
+-                      "ip4 && ip.ttl == {0, 1}", debug_drop_action());
++                      "ip.ttl == {0, 1}", debug_drop_action());
+ 
+         /* Pass other traffic not already handled to the next table for
+          * routing. */
+@@ -13224,7 +13235,7 @@ build_ipv6_input_flows_for_lrouter_port(
+                           "outport = %s; flags.loopback = 1; output; };",
+                           ds_cstr(&ip_ds), op->json_key);
+             ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+-                    100, ds_cstr(match), ds_cstr(actions), NULL,
++                    31, ds_cstr(match), ds_cstr(actions), NULL,
+                     copp_meter_get(COPP_ICMP6_ERR, op->od->nbr->copp,
+                                    meter_groups),
+                     &op->nbrp->header_);
+@@ -13352,7 +13363,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
+                           "outport = %s; flags.loopback = 1; output; };",
+                           ds_cstr(&ip_ds), op->json_key);
+             ovn_lflow_add_with_hint__(lflows, op->od, S_ROUTER_IN_IP_INPUT,
+-                    100, ds_cstr(match), ds_cstr(actions), NULL,
++                    31, ds_cstr(match), ds_cstr(actions), NULL,
+                     copp_meter_get(COPP_ICMP4_ERR, op->od->nbr->copp,
+                                    meter_groups),
+                     &op->nbrp->header_);
+@@ -13597,13 +13608,13 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+         return;
+     }
+ 
+-    bool stateless = lrouter_nat_is_stateless(nat);
++    bool stateless = lrouter_dnat_and_snat_is_stateless(nat);
+     if (od->is_gw_router) {
+         ds_clear(match);
+         ds_clear(actions);
+         ds_put_format(match, "ip && ip%s.dst == %s",
+                       is_v6 ? "6" : "4", nat->external_ip);
+-        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++        if (stateless) {
+             ds_put_format(actions, "next;");
+         } else {
+             ds_put_cstr(actions, "ct_snat;");
+@@ -13628,7 +13639,7 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+                           l3dgw_port->cr_port->json_key);
+         }
+ 
+-        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++        if (stateless) {
+             ds_put_format(actions, "next;");
+         } else {
+             ds_put_cstr(actions, "ct_snat_in_czone;");
+@@ -13670,7 +13681,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+     * IP address that needs to be DNATted from a external IP address
+     * to a logical IP address. */
+     if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) {
+-        bool stateless = lrouter_nat_is_stateless(nat);
++        bool stateless = lrouter_dnat_and_snat_is_stateless(nat);
+ 
+         if (od->is_gw_router) {
+             /* Packet when it goes from the initiator to destination.
+@@ -13692,7 +13703,7 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+                 ds_put_format(actions, "flags.force_snat_for_dnat = 1; ");
+             }
+ 
+-            if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++            if (stateless) {
+                 ds_put_format(actions, "flags.loopback = 1; "
+                               "ip%s.dst=%s; next;",
+                               is_v6 ? "6" : "4", nat->logical_ip);
+@@ -13782,8 +13793,7 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+                       ETH_ADDR_ARGS(mac));
+     }
+ 
+-    if (!strcmp(nat->type, "dnat_and_snat") &&
+-        lrouter_nat_is_stateless(nat)) {
++    if (lrouter_dnat_and_snat_is_stateless(nat)) {
+         ds_put_format(actions, "next;");
+     } else {
+         ds_put_format(actions,
+@@ -13839,7 +13849,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+         return;
+     }
+ 
+-    bool stateless = lrouter_nat_is_stateless(nat);
++    bool stateless = lrouter_dnat_and_snat_is_stateless(nat);
+     if (od->is_gw_router) {
+         ds_clear(match);
+         ds_put_format(match, "ip && ip%s.src == %s",
+@@ -13905,7 +13915,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+                           ETH_ADDR_ARGS(mac));
+         }
+ 
+-        if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
++        if (stateless) {
+             ds_put_format(actions, "ip%s.src=%s; next;",
+                           is_v6 ? "6" : "4", nat->external_ip);
+         } else {
+@@ -14217,10 +14227,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+     ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
+     ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
+ 
+-    /* Ingress DNAT and DEFRAG Table (Priority 50/70).
+-     *
+-     * The defrag stage needs to have flows for ICMP in order to get
+-     * the correct ct_state that can be used by DNAT stage.
++    const char *ct_flag_reg = features->ct_no_masked_label
++                              ? "ct_mark"
++                              : "ct_label";
++    /* Ingress DNAT (Priority 50/70).
+      *
+      * Allow traffic that is related to an existing conntrack entry.
+      * At the same time apply NAT for this traffic.
+@@ -14231,16 +14241,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+      * that's generated from a non-listening UDP port.  */
+     if (od->has_lb_vip && features->ct_lb_related) {
+         ds_clear(match);
+-        const char *ct_flag_reg = features->ct_no_masked_label
+-                                  ? "ct_mark"
+-                                  : "ct_label";
+ 
+         ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
+         size_t match_len = match->length;
+ 
+-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 50, "icmp || icmp6",
+-                      "ct_dnat;");
+-
+         ds_put_format(match, " && %s.skip_snat == 1", ct_flag_reg);
+         ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 70, ds_cstr(match),
+                       "flags.skip_snat_for_lb = 1; ct_commit_nat;");
+@@ -14251,10 +14255,34 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+                       "flags.force_snat_for_lb = 1; ct_commit_nat;");
+ 
+         ds_truncate(match, match_len);
+-        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50,
+-                      "ct.rel && !ct.est && !ct.new", "ct_commit_nat;");
++        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, ds_cstr(match),
++                      "ct_commit_nat;");
++    }
+ 
++    /* Ingress DNAT (Priority 50/70).
++     *
++     * Pass the traffic that is already established to the next table with
++     * proper flags set.
++     */
++    if (od->has_lb_vip) {
+         ds_clear(match);
++
++        ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
++                      ct_flag_reg);
++        size_t match_len = match->length;
++
++        ds_put_format(match, " && %s.skip_snat == 1", ct_flag_reg);
++        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 70, ds_cstr(match),
++                      "flags.skip_snat_for_lb = 1; next;");
++
++        ds_truncate(match, match_len);
++        ds_put_format(match, " && %s.force_snat == 1", ct_flag_reg);
++        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 70, ds_cstr(match),
++                      "flags.force_snat_for_lb = 1; next;");
++
++        ds_truncate(match, match_len);
++        ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, ds_cstr(match),
++                      "next;");
+     }
+ 
+     /* If the router has load balancer or DNAT rules, re-circulate every packet
+@@ -14267,6 +14295,9 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
+      * flag set. Some NICs are unable to offload these flows.
+      */
+     if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
++        /* Do not send ND or ICMP packets to connection tracking. */
++        ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
++                      "nd || nd_rs || nd_ra", "next;");
+         ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 50,
+                       "ip", "flags.loopback = 1; ct_dnat;");
+         ovn_lflow_add(lflows, od, S_ROUTER_OUT_POST_UNDNAT, 50,
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index 2eab2c4ae..e16d7d080 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -748,6 +748,12 @@
+       drop behavior.
+     </p>
+ 
++    <p>
++      A priority-65532 flow is added to allow IPv6 Neighbor solicitation,
++      Neighbor discover, Router solicitation, Router advertisement and MLD
++      packets regardless of other ACLs defined.
++    </p>
++
+     <p>
+       If the logical datapath has a stateful ACL or a load balancer with VIP
+       configured, the following flows will also be added:
+@@ -824,12 +830,6 @@
+         in the request direction are skipped here to let a newly created
+         ACL re-allow this connection.
+       </li>
+-
+-      <li>
+-        A priority-65532 flow that allows IPv6 Neighbor solicitation,
+-        Neighbor discover, Router solicitation, Router advertisement and MLD
+-        packets.
+-      </li>
+     </ul>
+ 
+     <p>
+@@ -2056,6 +2056,16 @@ output;
+       db="OVN_Northbound"/> table.
+     </p>
+ 
++    <p>
++      This table also has a priority-110 flow with the match
++      <code>outport == <var>I</var></code> for all logical switch
++      datapaths to move traffic to the next table, and, if there are no
++      stateful_acl, clear the ct_state. Where <var>I</var>
++      is the peer of a logical router port. This flow is added to
++      skip the connection tracking of packets which will be entering
++      logical router datapath from logical switch datapath for routing.
++    </p>
++
+     <h3>Egress Table 2: Pre-stateful</h3>
+ 
+     <p>
+@@ -2098,6 +2108,12 @@ output;
+       <code>to-lport</code> ACLs.
+     </p>
+ 
++    <p>
++      Similar to ingress table, a priority-65532 flow is added to allow IPv6
++      Neighbor solicitation, Neighbor discover, Router solicitation, Router
++      advertisement and MLD packets regardless of other ACLs defined.
++    </p>
++
+     <p>
+       In addition, the following flows are added.
+     </p>
+@@ -3066,10 +3082,18 @@ nd.tll = <var>external_mac</var>;
+         broadcast address.  By definition this traffic should not be forwarded.
+       </li>
+ 
++      <li>
++        Avoid ICMP time exceeded for multicast.  A priority-32 flow with match
++        <code>ip.ttl == {0, 1} &amp;&amp; !ip.later_frag &amp;&amp;
++        (ip4.mcast || ip6.mcast)</code> and actions <code>drop;</code> drops
++        multicast packets whose TTL has expired without sending ICMP time
++        exceeded.
++      </li>
++
+       <li>
+         <p>
+           ICMP time exceeded.  For each router port <var>P</var>, whose IP
+-          address is <var>A</var>, a priority-100 flow with match <code>inport
++          address is <var>A</var>, a priority-31 flow with match <code>inport
+           == <var>P</var> &amp;&amp; ip.ttl == {0, 1} &amp;&amp;
+           !ip.later_frag</code> matches packets whose TTL has expired, with the
+           following actions to send an ICMP time exceeded reply for IPv4 and
+@@ -3282,35 +3306,16 @@ icmp6 {
+     </p>
+ 
+     <p>
+-      If load balancing rules with only virtual IP addresses are configured in
++      For all load balancing rules that are configured in
+       <code>OVN_Northbound</code> database for a Gateway router,
+       a priority-100 flow is added for each configured virtual IP address
+       <var>VIP</var>. For IPv4 <var>VIPs</var> the flow matches
+       <code>ip &amp;&amp; ip4.dst == <var>VIP</var></code>.  For IPv6
+       <var>VIPs</var>, the flow matches <code>ip &amp;&amp; ip6.dst ==
+-      <var>VIP</var></code>. The flow applies the action <code>reg0 =
+-      <var>VIP</var>; ct_dnat;</code>  (or <code>xxreg0</code> for IPv6) to
+-      send IP packets to the connection tracker for packet de-fragmentation and
+-      to dnat the destination IP for the committed connection before sending it
+-      to the next table.
+-    </p>
+-
+-    <p>
+-      If load balancing rules with virtual IP addresses and ports are
+-      configured in <code>OVN_Northbound</code> database for a Gateway router,
+-      a priority-110 flow is added for each configured virtual IP address
+-      <var>VIP</var>, protocol <var>PROTO</var> and port <var>PORT</var>.
+-      For IPv4 <var>VIPs</var> the flow matches
+-      <code>ip &amp;&amp; ip4.dst == <var>VIP</var> &amp;&amp;
+-      <var>PROTO</var> &amp;&amp; <var>PROTO</var>.dst ==
+-      <var>PORT</var></code>. For IPv6 <var>VIPs</var>, the flow matches
+-      <code>ip &amp;&amp; ip6.dst == <var>VIP</var> &amp;&amp;
+-      <var>PROTO</var> &amp;&amp; <var>PROTO</var>.dst ==
+-      <var>PORT</var></code>. The flow applies the action <code>reg0 =
+-      <var>VIP</var>; reg9[16..31] = <var>PROTO</var>.dst; ct_dnat;</code>
+-      (or <code>xxreg0</code> for IPv6) to send IP packets to the connection
+-      tracker for packet de-fragmentation and to dnat the destination IP for
+-      the committed connection before sending it to the next table.
++      <var>VIP</var></code>. The flow applies the action <code> ct_dnat;</code>
++      to send IP packets to the connection tracker for packet de-fragmentation
++      and to dnat the destination IP for the committed connection before
++      sending it to the next table.
+     </p>
+ 
+     <p>
+@@ -3349,10 +3354,11 @@ icmp6 {
+         column, that includes a L4 port <var>PORT</var> of protocol
+         <var>P</var> and IPv4 or IPv6 address <var>VIP</var>, a priority-100
+         flow that matches on <code>ct.new &amp;&amp; ip &amp;&amp;
+-        reg0 == <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp; reg9[16..31]
++        ip.dst == <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp; P.dst
+         == </code> <code><var>PORT</var></code> (<code>xxreg0 == <var>VIP
+-        </var></code> in the IPv6 case) with an action of <code>reg9[6] =
+-        chk_lb_aff(); next;</code>
++        </var></code> in the IPv6 case) with an action of <code>reg0 = ip.dst;
++        reg9[16..31] = P.dst; reg9[6] = chk_lb_aff(); next;</code>
++        (<code>xxreg0 == <var>ip6.dst</var> </code> in the IPv6 case)
+       </li>
+ 
+       <li>
+@@ -3385,9 +3391,8 @@ icmp6 {
+         column, that includes a L4 port <var>PORT</var> of protocol
+         <var>P</var> and IPv4 or IPv6 address <var>VIP</var>, a priority-150
+         flow that matches on <code>reg9[6] == 1 &amp;&amp; ct.new &amp;&amp;
+-        ip &amp;&amp; reg0 == <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp;
+-        reg9[16..31] == </code> <code><var>PORT</var></code> (<code>xxreg0
+-        == <var>VIP</var></code> in the IPv6 case) with an action of
++        ip &amp;&amp; ip.dst == <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp;
++        P.dst == </code> <code><var>PORT</var></code> with an action of
+         <code>ct_lb_mark(<var>args</var>) </code>, where <var>args</var>
+         contains comma separated IP addresses (and optional port numbers)
+         to load balance to.  The address family of the IP addresses of
+@@ -3410,56 +3415,25 @@ icmp6 {
+           Router with gateway port in <code>OVN_Northbound</code> database that
+           includes a L4 port <var>PORT</var> of protocol <var>P</var> and IPv4
+           or IPv6 address <var>VIP</var>, a priority-120 flow that matches on
+-          <code>ct.new &amp;&amp; !ct.rel &amp;&amp; ip &amp;&amp; reg0 ==
+-          <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp; reg9[16..31] ==
+-          </code> <code><var>PORT</var></code> (<code>xxreg0 == <var>VIP</var>
+-          </code> in the IPv6 case) with an action of
++          <code>ct.new &amp;&amp; !ct.rel &amp;&amp; ip &amp;&amp; ip.dst ==
++          <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp; P.dst ==
++          </code> <code><var>PORT</var></code> with an action of
+           <code>ct_lb_mark(<var>args</var>)</code>, where <var>args</var> contains
+           comma separated IPv4 or IPv6 addresses (and optional port numbers) to
+           load balance to.  If the router is configured to force SNAT any
+           load-balanced packets, the above action will be replaced by
+-          <code>flags.force_snat_for_lb = 1; ct_lb_mark(<var>args</var>);</code>.
++          <code>flags.force_snat_for_lb = 1; ct_lb_mark(<var>args</var>;
++          force_snat);</code>.
+           If the load balancing rule is configured with <code>skip_snat</code>
+           set to true, the above action will be replaced by
+-          <code>flags.skip_snat_for_lb = 1; ct_lb_mark(<var>args</var>);</code>.
++          <code>flags.skip_snat_for_lb = 1; ct_lb_mark(<var>args</var>;
++          skip_snat);</code>.
+           If health check is enabled, then
+           <var>args</var> will only contain those endpoints whose service
+           monitor status entry in <code>OVN_Southbound</code> db is
+           either <code>online</code> or empty.
+         </p>
+ 
+-        <p>
+-          The previous table <code>lr_in_defrag</code> sets the register
+-          <code>reg0</code> (or <code>xxreg0</code> for IPv6) and does
+-          <code>ct_dnat</code>.  Hence for established traffic, this
+-          table just advances the packet to the next stage.
+-        </p>
+-      </li>
+-
+-      <li>
+-        <p>
+-          For all the configured load balancing rules for a router in
+-          <code>OVN_Northbound</code> database that includes a L4 port
+-          <var>PORT</var> of protocol <var>P</var> and IPv4 or IPv6 address
+-          <var>VIP</var>, a priority-120 flow that matches on
+-          <code>ct.est &amp;&amp; !ct.rel &amp;&amp; ip4 &amp;&amp; reg0 ==
+-          <var>VIP</var> &amp;&amp; <var>P</var> &amp;&amp; reg9[16..31] ==
+-          </code> <code><var>PORT</var></code> (<code>ip6</code> and
+-          <code>xxreg0 == <var>VIP</var></code> in the IPv6 case) with an
+-          action of <code>next;</code>. If the router is configured to force
+-          SNAT any load-balanced packets, the above action will be replaced by
+-          <code>flags.force_snat_for_lb = 1; next;</code>. If the load
+-          balancing rule is configured with <code>skip_snat</code> set to true,
+-          the above action will be replaced by
+-          <code>flags.skip_snat_for_lb = 1; next;</code>.
+-        </p>
+-
+-        <p>
+-          The previous table <code>lr_in_defrag</code> sets the register
+-          <code>reg0</code> (or <code>xxreg0</code> for IPv6) and does
+-          <code>ct_dnat</code>.  Hence for established traffic, this
+-          table just advances the packet to the next stage.
+-        </p>
+       </li>
+ 
+       <li>
+@@ -3467,42 +3441,17 @@ icmp6 {
+           For all the configured load balancing rules for a router in
+           <code>OVN_Northbound</code> database that includes just an IP address
+           <var>VIP</var> to match on, a priority-110 flow that matches on
+-          <code>ct.new &amp;&amp; !ct.rel &amp;&amp; ip4 &amp;&amp; reg0 ==
+-          <var>VIP</var></code> (<code>ip6</code> and <code>xxreg0 ==
+-          <var>VIP</var></code> in the IPv6 case) with an action of
++          <code>ct.new &amp;&amp; !ct.rel &amp;&amp; ip4 &amp;&amp; ip.dst ==
++          <var>VIP</var></code> with an action of
+           <code>ct_lb_mark(<var>args</var>)</code>, where <var>args</var> contains
+           comma separated IPv4 or IPv6 addresses.  If the router is configured
+           to force SNAT any load-balanced packets, the above action will be
+           replaced by <code>flags.force_snat_for_lb = 1;
+-          ct_lb_mark(<var>args</var>);</code>.
+-          If the load balancing rule is configured with <code>skip_snat</code>
+-          set to true, the above action will be replaced by
+-          <code>flags.skip_snat_for_lb = 1; ct_lb_mark(<var>args</var>);</code>.
+-        </p>
+-
+-        <p>
+-          The previous table <code>lr_in_defrag</code> sets the register
+-          <code>reg0</code> (or <code>xxreg0</code> for IPv6) and does
+-          <code>ct_dnat</code>.  Hence for established traffic, this
+-          table just advances the packet to the next stage.
+-        </p>
+-      </li>
+-
+-
+-      <li>
+-        <p>
+-          For all the configured load balancing rules for a router in
+-          <code>OVN_Northbound</code> database that includes just an IP address
+-          <var>VIP</var> to match on, a priority-110 flow that matches on
+-          <code>ct.est &amp;&amp; !ct.rel &amp;&amp; ip4 &amp;&amp; reg0 ==
+-          <var>VIP</var></code> (or <code>ip6</code> and
+-          <code>xxreg0 == <var>VIP</var></code>) with an action of
+-          <code>next;</code>. If the router is configured to force SNAT any
+-          load-balanced packets, the above action will be replaced by
+-          <code>flags.force_snat_for_lb = 1; next;</code>.
++          ct_lb_mark(<var>args</var>; force_snat);</code>.
+           If the load balancing rule is configured with <code>skip_snat</code>
+           set to true, the above action will be replaced by
+-          <code>flags.skip_snat_for_lb = 1; next;</code>.
++          <code>flags.skip_snat_for_lb = 1; ct_lb_mark(<var>args</var>;
++          skip_snat);</code>.
+         </p>
+ 
+         <p>
+@@ -3529,7 +3478,20 @@ icmp6 {
+             with an action of <code>ct_commit_nat;</code>, if the router
+             has load balancer assigned to it. Along with two priority 70 flows
+             that match <code>skip_snat</code> and <code>force_snat</code>
+-            flags.
++            flags, setting the <code>flags.force_snat_for_lb = 1</code> or
++            <code>flags.skip_snat_for_lb = 1</code> accordingly.
++        </p>
++      </li>
++      <li>
++        <p>
++          For the established traffic, a priority 50 flow that matches
++          <code>ct.est &amp;&amp; !ct.rel &amp;&amp; !ct.new &amp;&amp;
++          ct_mark.natted</code> with an action of <code>next;</code>,
++          if the router has load balancer assigned to it. Along with two
++          priority 70 flows that match <code>skip_snat</code> and
++          <code>force_snat</code> flags, setting the
++          <code>flags.force_snat_for_lb = 1</code> or
++          <code>flags.skip_snat_for_lb = 1</code> accordingly.
+         </p>
+       </li>
+     </ul>
+@@ -4721,6 +4683,11 @@ nd_ns {
+     <h3>Egress Table 1: UNDNAT on Gateway Routers</h3>
+ 
+     <ul>
++      <li>
++        For IPv6 Neighbor Discovery or Router Solicitation/Advertisement
++        traffic, a priority-100 flow with action <code>next;</code>.
++      </li>
++
+       <li>
+         For all IP packets, a priority-50 flow with an action
+         <code>flags.loopback = 1; ct_dnat;</code>.
+@@ -4998,7 +4965,19 @@ nd_ns {
+       </li>
+     </ul>
+ 
+-    <h3>Egress Table 4: Egress Loopback</h3>
++    <h3>Egress Table 4: Post SNAT</h3>
++
++    <p>
++      Packets reaching this table are processed according to the flows below:
++      <ul>
++        <li>
++          A priority-0 logical flow that matches all packets not already
++          handled (match <code>1</code>) and action <code>next;</code>.
++        </li>
++      </ul>
++    </p>
++
++    <h3>Egress Table 5: Egress Loopback</h3>
+ 
+     <p>
+       For distributed logical routers where one of the logical router
+@@ -5070,7 +5049,7 @@ clone {
+       </li>
+     </ul>
+ 
+-    <h3>Egress Table 5: Delivery</h3>
++    <h3>Egress Table 6: Delivery</h3>
+ 
+     <p>
+       Packets that reach this table are ready for delivery.  It contains:
+diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
+index cb1064f71..86c6258e0 100644
+--- a/ovn-architecture.7.xml
++++ b/ovn-architecture.7.xml
+@@ -1318,7 +1318,7 @@
+         output port is known. These pieces of information are obtained
+         from the tunnel encapsulation metadata (see <code>Tunnel
+         Encapsulations</code> for encoding details). Then the actions resubmit
+-        to table 33 to enter the logical egress pipeline.
++        to table 38 to enter the logical egress pipeline.
+       </p>
+     </li>
+ 
+diff --git a/ovn-nb.xml b/ovn-nb.xml
+index 8d56d0c6e..35acda107 100644
+--- a/ovn-nb.xml
++++ b/ovn-nb.xml
+@@ -2036,6 +2036,14 @@ or
+         the affinity timeslot. Max supported affinity_timeout is 65535
+         seconds.
+       </column>
++
++      <column name="options" key="ct_flush" type='{"type": "boolean"}'>
++        The value indicates whether ovn-controller should flush CT entries
++        that are related to this LB. The flush happens if the LB is removed,
++        any of the backends is updated/removed or the LB is not considered
++        local anymore by the ovn-controller. This option is set to
++        <code>false</code> by default.
++      </column>
+     </group>
+   </table>
+ 
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index a77f8f4ef..8ca206109 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -472,9 +472,8 @@
+ 
+     <column name="type">
+       The encapsulation to use to transmit packets to this chassis.
+-      Hypervisors must use either <code>geneve</code> or
+-      <code>stt</code>.  Gateways may use <code>vxlan</code>,
+-      <code>geneve</code>, or <code>stt</code>.
++      Hypervisors and gateways must use one of: <code>geneve</code>,
++      <code>vxlan</code>, or <code>stt</code>.
+     </column>
+ 
+     <column name="options">
+diff --git a/rhel/usr_lib_systemd_system_ovn-db@.service b/rhel/usr_lib_systemd_system_ovn-db@.service
+index 98556a673..c835e4967 100644
+--- a/rhel/usr_lib_systemd_system_ovn-db@.service
++++ b/rhel/usr_lib_systemd_system_ovn-db@.service
+@@ -33,7 +33,7 @@ EnvironmentFile=-/etc/sysconfig/ovn-%i
+ ExecStartPre=-/usr/bin/chown -R ${OVN_USER_ID} ${OVN_DBDIR}
+ ExecStart=/usr/share/ovn/scripts/ovn-ctl \
+           --ovn-user=${OVN_USER_ID} start_%i_ovsdb $OPTIONS $ovn_%i_opts
+-ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_%i_ovsdb
++ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_%i_ovsdb $OPTIONS $ovn_%i_opts
+ 
+ [Install]
+ WantedBy=multi-user.target
+diff --git a/rhel/usr_lib_systemd_system_ovn-northd.service b/rhel/usr_lib_systemd_system_ovn-northd.service
+index d281f861c..6c4c6621c 100644
+--- a/rhel/usr_lib_systemd_system_ovn-northd.service
++++ b/rhel/usr_lib_systemd_system_ovn-northd.service
+@@ -26,7 +26,7 @@ EnvironmentFile=-/etc/sysconfig/ovn-northd
+ ExecStartPre=-/usr/bin/chown -R ${OVN_USER_ID} ${OVN_DBDIR}
+ ExecStart=/usr/share/ovn/scripts/ovn-ctl \
+           --ovn-user=${OVN_USER_ID} start_northd $OVN_NORTHD_OPTS
+-ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_northd
++ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_northd $OVN_NORTHD_OPTS
+ 
+ [Install]
+ WantedBy=multi-user.target
+diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
+index bbe142ae3..27fc44232 100644
+--- a/tests/ovn-controller.at
++++ b/tests/ovn-controller.at
+@@ -493,7 +493,8 @@ check ovn-nbctl --wait=hv sync
+ 
+ # And check that it gets propagated to br-int external_ids.
+ as hv1
+-OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1])
++OVS_WAIT_FOR_OUTPUT([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], ["1"
++])
+ 
+ nb_cfg_ts=$(fetch_column Chassis_Private nb_cfg_timestamp name=hv1)
+ as hv1
+@@ -672,22 +673,26 @@ check ovs-vsctl del-ssl
+ start_daemon ovn-controller -p $key -c $cert -C $cacert
+ 
+ # SSL should not connect because of key and cert mismatch
+-OVS_WAIT_UNTIL([ovn-appctl -t ovn-controller connection-status], [0], [not connected])
++OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status], [0], [not connected
++])
+ 
+ # Modify the files with the correct key and cert, and reconnect should succeed
+ cp $PKIDIR/$key $key
+ cp $PKIDIR/$cert $cert
+ 
+-OVS_WAIT_UNTIL([ovn-appctl -t ovn-controller connection-status], [0], [connected])
++OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status], [0], [connected
++])
+ 
+ # Remove the files and expect the connection to drop
+ rm $key $cert
+-OVS_WAIT_UNTIL([ovn-appctl -t ovn-controller connection-status], [0], [not connected])
++OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status], [0], [not connected
++])
+ 
+ # Restore the files again and expect the connection to recover
+ cp $PKIDIR/$key $key
+ cp $PKIDIR/$cert $cert
+-OVS_WAIT_UNTIL([ovn-appctl -t ovn-controller connection-status], [0], [connected])
++OVS_WAIT_FOR_OUTPUT([ovn-appctl -t ovn-controller connection-status], [0], [connected
++])
+ 
+ cat hv1/ovn-controller.log
+ 
+@@ -2060,6 +2065,57 @@ AT_CHECK([echo $(($reprocess_count_new - $reprocess_count_old))], [0], [2
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ 
++AT_SETUP([ovn-controller - address set del-and-add])
++
++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
++check ovs-vsctl -- add-port br-int hv1-vif1 -- \
++    set interface hv1-vif1 external-ids:iface-id=ls1-lp1
++
++check ovn-nbctl ls-add ls1
++
++check ovn-nbctl lsp-add ls1 ls1-lp1 \
++-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
++
++wait_for_ports_up
++ovn-appctl -t ovn-controller vlog/set file:dbg
++
++ovn-nbctl create address_set name=as1 addresses=8.8.8.8
++check ovn-nbctl acl-add ls1 to-lport 100 'outport == "ls1-lp1" && ip4.src == $as1' drop
++check ovn-nbctl --wait=hv sync
++AT_CHECK([ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100"], [0], [1
++])
++
++# 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
++# newly added address set. In reality it can happen when CMS deletes an
++# address-set and immediately creates a new address-set with the same name
++# (with same or different content). The notification of the changes can come to
++# ovn-controller in one shot and the order of the "del" and "add" in the IDL is
++# undefined. This test runs the scenario ten times to make sure different
++# orders are covered and handled properly.
++
++flow_count=$(ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100")
++for i in $(seq 10); do
++    # Delete and recreate the SB address set with same name and an extra IP.
++    addrs_=$(fetch_column address_set addresses name=as1)
++    addrs=${addrs_// /,}
++    AT_CHECK([ovn-sbctl destroy address_set as1 -- create address_set name=as1 addresses=$addrs,1.1.1.$i], [0], [ignore])
++    OVS_WAIT_UNTIL([test $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep -c "priority=1100") = "$(($i + 1))"])
++done
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++
+ AT_SETUP([ovn-controller - I-P handle lb_hairpin_use_ct_mark change])
+ 
+ ovn_start --backup-northd=none
+@@ -2161,7 +2217,7 @@ AT_CHECK([ovs-ofctl dump-flows br-int | grep 10.1.2.3], [0], [ignore])
+ sleep 5
+ 
+ # Check after the wait
+-OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4], [0], [ignore])
++OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int | grep 10.1.2.4])
+ 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
+@@ -2172,7 +2228,7 @@ AT_CHECK_UNQUOTED([echo $lflow_run_1], [0], [$lflow_run_2
+ # Restart OVS this time, and wait until flows are reinstalled
+ 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], [0], [ignore])
++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 \
+ -- ls-lb-add ls1 lb3
+diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
+index ee942e8a6..6f2d085ae 100644
+--- a/tests/ovn-macros.at
++++ b/tests/ovn-macros.at
+@@ -817,6 +817,29 @@ ovn_trace_client() {
+     ovs-appctl -t $target trace "$@" | tee trace | sed '/^# /d'
+ }
+ 
++# Receives a string with scapy python code that represents a packet.
++# Returns a hex-string that contains bytes that reflect the packet symbolic
++# description.
++#
++# Scapy docs: https://scapy.readthedocs.io/en/latest/usage.html
++#
++# Example of usage:
++#
++# packet=$(fmt_pkt "
++#     Ether(dst='ff:ff:ff:ff:ff:ff', src='50:64:00:00:00:01') /
++#     IPv6(src='abed::1', dst='ff02::1:ff00:2') /
++#     ICMPv6ND_NS(tgt='abed::2')
++# ")
++#
++# 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
++}
++
+ 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 2fffe1850..478a32f5a 100644
+--- a/tests/ovn-nbctl.at
++++ b/tests/ovn-nbctl.at
+@@ -1482,6 +1482,32 @@ UUID                                    LB                  PROTO      VIP
+ 
+ dnl ---------------------------------------------------------------------
+ 
++OVN_NBCTL_TEST([ovn_nbctl_template_lbs], [Template LBs], [
++check ovn-nbctl --template lb-add lb0 ^vip ^backend
++check ovn-nbctl --template lb-add lb1 ^vip:^vport ^backend udp
++check ovn-nbctl --template lb-add lb2 ^vip:^vport ^backend udp ipv4
++check ovn-nbctl --template lb-add lb3 ^vip:^vport ^backend udp ipv6
++check ovn-nbctl --template lb-add lb4 ^vip:^vport ^backend:^bport udp ipv4
++check ovn-nbctl --template lb-add lb5 ^vip:^vport ^backend:^bport udp ipv6
++check ovn-nbctl --template lb-add lb6 ^vip:^vport 1.1.1.1:111 udp ipv4
++check ovn-nbctl --template lb-add lb7 ^vip:^vport [[1::1]]:111 udp ipv6
++
++AT_CHECK([ovn-nbctl lb-list | uuidfilt], [0], [dnl
++UUID                                    LB                  PROTO      VIP            IPs
++<0>    lb0                 tcp        ^vip           ^backend
++<1>    lb1                 udp        ^vip:^vport    ^backend
++<2>    lb2                 udp        ^vip:^vport    ^backend
++<3>    lb3                 udp        ^vip:^vport    ^backend
++<4>    lb4                 udp        ^vip:^vport    ^backend:^bport
++<5>    lb5                 udp        ^vip:^vport    ^backend:^bport
++<6>    lb6                 udp        ^vip:^vport    1.1.1.1:111
++<7>    lb7                 udp        ^vip:^vport    [[1::1]]:111
++])
++
++])
++
++dnl ---------------------------------------------------------------------
++
+ OVN_NBCTL_TEST([ovn_nbctl_basic_lr], [basic logical router commands], [
+ AT_CHECK([ovn-nbctl lr-add lr0])
+ AT_CHECK([ovn-nbctl lr-list | uuidfilt], [0], [dnl
+@@ -2599,6 +2625,7 @@ OVN_NBCTL_TEST_STOP "/terminating with signal 15/d"
+ AT_CLEANUP
+ 
+ AT_SETUP([ovn-nbctl - daemon ssl files change])
++AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+ dnl Create ovn-nb database.
+ AT_CHECK([ovsdb-tool create ovn-nb.db $abs_top_srcdir/ovn-nb.ovsschema])
+ 
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index 3fa02d2b3..846f10e88 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -2486,6 +2486,7 @@ check ovn-nbctl --wait=sb \
+ 
+ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
+   table=17(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=17(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=17(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=3 (ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=3 (ls_out_acl_hint    ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -2530,9 +2531,12 @@ ovn-nbctl --wait=sb clear logical_switch ls load_balancer
+ 
+ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl
+   table=17(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=17(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=3 (ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
++  table=4 (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=4 (ls_out_acl         ), priority=65535, match=(1), action=(next;)
+   table=7 (ls_in_acl_hint     ), priority=65535, match=(1), action=(next;)
++  table=8 (ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=8 (ls_in_acl          ), priority=65535, match=(1), action=(next;)
+ ])
+ 
+@@ -3757,18 +3761,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.100 && tcp), action=(reg0 = 10.0.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.100), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(ct_lb_mark(backends=10.0.0.4:8080);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(ct_lb_mark(backends=10.0.0.40:8080);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.4:8080);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.100 && tcp && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.40:8080);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -3788,18 +3792,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.100 && tcp), action=(reg0 = 10.0.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.100), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.100 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -3813,6 +3817,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -3838,18 +3843,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.100 && tcp), action=(reg0 = 10.0.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.100), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.100 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -3864,6 +3869,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -3902,18 +3908,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.100 && tcp), action=(reg0 = 10.0.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.100), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.100 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.100 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -3929,6 +3935,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | sed 's/table=./table=?/' | sort], [0], [
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -3953,14 +3960,13 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.100 && tcp), action=(reg0 = 10.0.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.20 && tcp), action=(reg0 = 10.0.0.20; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.20), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.20 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.skip_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.20 && tcp && reg9[[16..31]] == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; skip_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.40:8080; skip_snat);)
++  table=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+ 
+@@ -3970,6 +3976,7 @@ AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sed 's/table=./t
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -4111,6 +4118,7 @@ check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
+ check ovn-nbctl --wait=sb sync
+ 
+ check_stateful_flows() {
++    action=$1
+     ovn-sbctl dump-flows sw0 > sw0flows
+     AT_CAPTURE_FILE([sw0flows])
+ 
+@@ -4144,12 +4152,12 @@ check_stateful_flows() {
+   table=??(ls_in_stateful     ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_mark.blocked = 0; ct_label.label = reg3; }; next;)
+ ])
+ 
+-    AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
++    AT_CHECK_UNQUOTED([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl
+   table=1 (ls_out_pre_lb      ), priority=0    , match=(1), action=(next;)
+   table=1 (ls_out_pre_lb      ), priority=100  , match=(ip), action=(reg0[[2]] = 1; next;)
+   table=1 (ls_out_pre_lb      ), priority=110  , match=(eth.mcast), action=(next;)
+-  table=1 (ls_out_pre_lb      ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+-  table=1 (ls_out_pre_lb      ), priority=110  , match=(ip && outport == "sw0-lr0"), action=(next;)
++  table=1 (ls_out_pre_lb      ), priority=110  , match=(eth.src == \$svc_monitor_mac), action=(next;)
++  table=1 (ls_out_pre_lb      ), priority=110  , match=(ip && outport == "sw0-lr0"), action=($action)
+   table=1 (ls_out_pre_lb      ), priority=110  , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;)
+   table=1 (ls_out_pre_lb      ), priority=110  , match=(reg0[[16]] == 1), action=(next;)
+ ])
+@@ -4169,13 +4177,13 @@ check_stateful_flows() {
+ ])
+ }
+ 
+-check_stateful_flows
++check_stateful_flows "ct_clear; next;"
+ 
+ # Add few ACLs
+ check ovn-nbctl --wait=sb acl-add sw0 from-lport 1002 "ip4 && tcp && tcp.dst == 80" allow-related
+ check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 "ip4 && tcp && tcp.src == 80" drop
+ 
+-check_stateful_flows
++check_stateful_flows "next;"
+ 
+ # Remove load balancers from sw0
+ check ovn-nbctl ls-lb-del sw0 lb0
+@@ -4231,6 +4239,15 @@ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl
+   table=7 (ls_out_stateful    ), priority=100  , match=(reg0[[1]] == 1 && reg0[[13]] == 1), action=(ct_commit { ct_mark.blocked = 0; ct_label.label = reg3; }; next;)
+ ])
+ 
++# LB with event=false and reject=false
++AT_CHECK([ovn-nbctl create load_balancer name=lb1 options:reject=false options:event=false vips:\"10.0.0.20\"=\"\" protocol=tcp], [0], [ignore])
++check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
++
++AT_CHECK([ovn-sbctl dump-flows sw0 | grep "ls_in_lb " | sort ], [0], [dnl
++  table=12(ls_in_lb           ), priority=0    , match=(1), action=(next;)
++  table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 10.0.0.20), action=(drop;)
++])
++
+ AT_CLEANUP
+ ])
+ 
+@@ -5211,25 +5228,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(reg0 = 172.168.0.200; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.100 && tcp), action=(reg0 = 172.168.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+   table=7 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat_in_czone(10.0.0.3);)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.200 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.200 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.80,10.0.0.81);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.4:8080);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.200 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.80,10.0.0.81);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.4:8080);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5284,25 +5299,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(reg0 = 172.168.0.200; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.100 && tcp), action=(reg0 = 172.168.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+   table=7 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20), action=(flags.loopback = 1; ct_dnat(10.0.0.3);)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.200 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.200), action=(ct_lb_mark(backends=10.0.0.80,10.0.0.81);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(ct_lb_mark(backends=10.0.0.4:8080);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082), action=(ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.200), action=(ct_lb_mark(backends=10.0.0.80,10.0.0.81);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.4:8080);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082), action=(ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60), action=(ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5314,6 +5327,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -5349,25 +5363,23 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(reg0 = 172.168.0.200; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.100 && tcp), action=(reg0 = 172.168.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+   table=7 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20), action=(flags.loopback = 1; ct_dnat(10.0.0.3);)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.200 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5379,6 +5391,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -5416,28 +5429,25 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(reg0 = 172.168.0.200; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.10 && tcp), action=(reg0 = 172.168.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.100 && tcp), action=(reg0 = 172.168.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+   table=7 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20), action=(flags.loopback = 1; ct_dnat(10.0.0.3);)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.200 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.10 && tcp && reg9[[16..31]] == 9082 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.10 && tcp && reg9[[16..31]] == 9082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.10 && tcp && tcp.dst == 9082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5449,6 +5459,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -5496,31 +5507,27 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(reg0 = 172.168.0.200; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 10.0.0.10 && tcp), action=(reg0 = 10.0.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.10 && tcp), action=(reg0 = 172.168.0.10; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.100 && tcp), action=(reg0 = 172.168.0.100; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip6.dst == def0::2 && tcp), action=(xxreg0 = def0::2; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 10.0.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.10), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.100), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.200), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip6.dst == def0::2), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+   table=7 (lr_in_dnat         ), priority=100  , match=(ip && ip4.dst == 172.168.0.20), action=(flags.loopback = 1; ct_dnat(10.0.0.3);)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.200 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.10 && tcp && reg9[[16..31]] == 9082 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip6 && xxreg0 == def0::2 && tcp && reg9[[16..31]] == 8000 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.10 && tcp && reg9[[16..31]] == 9082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.100 && tcp && reg9[[16..31]] == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && xxreg0 == def0::2 && tcp && reg9[[16..31]] == 8000), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=[[aef0::2]]:80,[[aef0::3]]:80; force_snat);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.200), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.80,10.0.0.81; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.4:8080; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.10 && tcp && tcp.dst == 9082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.100 && tcp && tcp.dst == 8082), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:82,10.0.0.60:82; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && ip6.dst == def0::2 && tcp && tcp.dst == 8000), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=[[aef0::2]]:80,[[aef0::3]]:80; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5532,6 +5539,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -5572,18 +5580,17 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl
+ 
+ AT_CHECK([grep "lr_in_defrag" lr0flows | sort], [0], [dnl
+   table=5 (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && tcp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = tcp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=110  , match=(ip && ip4.dst == 172.168.0.210 && udp), action=(reg0 = 172.168.0.210; reg9[[16..31]] = udp.dst; ct_dnat;)
+-  table=5 (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=5 (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 172.168.0.210), action=(ct_dnat;)
+ ])
+ 
+ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && tcp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && tcp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.0.210 && udp && reg9[[16..31]] == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && tcp && tcp.dst == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.0.210 && udp && udp.dst == 60), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.50:6062,10.0.0.60:6062; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5594,6 +5601,7 @@ AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sor
+ 
+ AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
+   table=? (lr_out_undnat      ), priority=0    , match=(1), action=(next;)
++  table=? (lr_out_undnat      ), priority=100  , match=(nd || nd_rs || nd_ra), action=(next;)
+   table=? (lr_out_undnat      ), priority=50   , match=(ip), action=(flags.loopback = 1; ct_dnat;)
+ ])
+ 
+@@ -5634,9 +5642,11 @@ ovn-sbctl set service_monitor $sm_vip2 status=offline
+ 
+ AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.10.10 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.10.10), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.10), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5646,9 +5656,11 @@ check ovn-nbctl --wait=sb set load_balancer lb5 options:skip_snat=true
+ 
+ AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.10.10 && ct_mark.natted == 1), action=(flags.skip_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.10.10), action=(flags.skip_snat_for_lb = 1; reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.10), action=(flags.skip_snat_for_lb = 1; reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -5660,9 +5672,58 @@ check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="route
+ 
+ AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.168.10.10 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.168.10.10), action=(flags.force_snat_for_lb = 1; reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.10), action=(flags.force_snat_for_lb = 1; reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=3);};)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
++  table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
++])
++
++# LB with event=false and reject=false
++check ovn-nbctl lr-lb-del lr0
++check ovn-nbctl remove logical_router lr0 options lb_force_snat_ip
++AT_CHECK([ovn-nbctl create load_balancer name=lb6 options:reject=false options:event=false vips:\"172.168.10.30\"=\"\" protocol=tcp], [0], [ignore])
++check ovn-nbctl --wait=sb lr-lb-add lr0 lb6
++
++AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
++  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.30), action=(drop;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
++  table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
++])
++
++# LB with event=false, reject=false and skip_snat
++check ovn-nbctl --wait=sb set load_balancer lb6 options:skip_snat=true
++
++AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
++  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.30), action=(flags.skip_snat_for_lb = 1; drop;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
++  table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
++])
++
++check ovn-nbctl remove load_balancer lb6 options skip_snat
++
++# LB with event=false, reject=false and force_snat
++check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip"
++
++AT_CHECK([ovn-sbctl dump-flows lr0 | grep "lr_in_dnat" | sort], [0], [dnl
++  table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.168.10.30), action=(flags.force_snat_for_lb = 1; drop;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -6692,11 +6753,12 @@ dnl Flows to skip TTL == {0, 1} check for IGMP and MLD packets.
+ AT_CHECK([grep -e 'lr_in_ip_input    ' lrflows | grep -e 'igmp' -e 'mld' -e 'ip.ttl == {0, 1}' | sed 's/table=../table=??/'], [0], [dnl
+   table=??(lr_in_ip_input     ), priority=120  , match=((mldv1 || mldv2) && ip.ttl == 1), action=(next;)
+   table=??(lr_in_ip_input     ), priority=120  , match=(igmp && ip.ttl == 1), action=(next;)
+-  table=??(lr_in_ip_input     ), priority=100  , match=(inport == "lrp1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1 ; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
+-  table=??(lr_in_ip_input     ), priority=100  , match=(inport == "lrp1" && ip6 && ip6.src == 1010::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 1010::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
+-  table=??(lr_in_ip_input     ), priority=100  , match=(inport == "lrp2" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1 ; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
+-  table=??(lr_in_ip_input     ), priority=100  , match=(inport == "lrp2" && ip6 && ip6.src == 2020::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 2020::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
+-  table=??(lr_in_ip_input     ), priority=30   , match=(ip4 && ip.ttl == {0, 1}), action=(drop;)
++  table=??(lr_in_ip_input     ), priority=32   , match=(ip.ttl == {0, 1} && !ip.later_frag && (ip4.mcast || ip6.mcast)), action=(drop;)
++  table=??(lr_in_ip_input     ), priority=31   , match=(inport == "lrp1" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 10.10.10.1 ; ip.ttl = 254; outport = "lrp1"; flags.loopback = 1; output; };)
++  table=??(lr_in_ip_input     ), priority=31   , match=(inport == "lrp1" && ip6 && ip6.src == 1010::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 1010::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp1"; flags.loopback = 1; output; };)
++  table=??(lr_in_ip_input     ), priority=31   , match=(inport == "lrp2" && ip4 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp4 {eth.dst <-> eth.src; icmp4.type = 11; /* Time exceeded */ icmp4.code = 0; /* TTL exceeded in transit */ ip4.dst = ip4.src; ip4.src = 20.20.20.1 ; ip.ttl = 254; outport = "lrp2"; flags.loopback = 1; output; };)
++  table=??(lr_in_ip_input     ), priority=31   , match=(inport == "lrp2" && ip6 && ip6.src == 2020::/64 && ip.ttl == {0, 1} && !ip.later_frag), action=(icmp6 {eth.dst <-> eth.src; ip6.dst = ip6.src; ip6.src = 2020::1 ; ip.ttl = 254; icmp6.type = 3; /* Time exceeded */ icmp6.code = 0; /* TTL exceeded in transit */ outport = "lrp2"; flags.loopback = 1; output; };)
++  table=??(lr_in_ip_input     ), priority=30   , match=(ip.ttl == {0, 1}), action=(drop;)
+ ])
+ 
+ dnl Flows to "route" (statically forward) without decrementing TTL for
+@@ -6755,6 +6817,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
+   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -6809,6 +6872,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+   table=??(ls_in_acl_after_lb ), priority=2003 , match=(reg0[[8]] == 1 && (ip4 && icmp)), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
+   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -6863,6 +6927,7 @@ AT_CHECK([grep -e "ls_in_acl" lsflows | sed 's/table=../table=??/' | sort], [0],
+   table=??(ls_in_acl_after_lb ), priority=2001 , match=(reg0[[9]] == 1 && (ip4)), action=(/* drop */)
+   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[10]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(ct_commit { ct_mark.blocked = 1; }; /* drop */)
+   table=??(ls_in_acl_after_lb ), priority=2004 , match=(reg0[[9]] == 1 && (ip4 && ip4.dst == 10.0.0.2)), action=(/* drop */)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -7154,11 +7219,14 @@ flow="inport == \"lsp1\" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:
+ AS_BOX([No ACL, default_acl_drop not set])
+ check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl          ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+@@ -7173,11 +7241,14 @@ output("lsp2");
+ AS_BOX([No ACL, default_acl_drop false])
+ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl          ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+@@ -7192,11 +7263,14 @@ output("lsp2");
+ AS_BOX([No ACL, default_acl_drop true])
+ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl          ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=65535, match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl         ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=65535, match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+@@ -7218,12 +7292,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7240,12 +7317,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7262,12 +7342,15 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_in_acl          ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7292,6 +7375,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
+   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -7343,13 +7427,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=1001 , match=(ip4 && tcp), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7365,13 +7452,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=1001 , match=(ip4 && tcp), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7387,13 +7477,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_in_acl_after_lb ), priority=1001 , match=(ip4 && tcp), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7418,6 +7511,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[7]] == 1 && (ip4 && tcp)), action=(reg0[[1]] = 1; next;)
+   table=??(ls_in_acl_after_lb ), priority=1001 , match=(reg0[[8]] == 1 && (ip4 && tcp)), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -7469,13 +7563,16 @@ check ovn-nbctl --wait=sb remove NB_Global . options default_acl_drop
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7491,13 +7588,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=false
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(next;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_acl         ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7513,13 +7613,16 @@ check ovn-nbctl --wait=sb set NB_Global . options:default_acl_drop=true
+ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/' | sort], [0], [dnl
+   table=??(ls_in_acl          ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_in_acl          ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;)
++  table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_pre_acl      ), priority=110  , match=(eth.dst == $svc_monitor_mac), action=(next;)
+   table=??(ls_out_acl         ), priority=0    , match=(1), action=(drop;)
+   table=??(ls_out_acl         ), priority=1001 , match=(ip4 && tcp), action=(next;)
+   table=??(ls_out_acl         ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;)
++  table=??(ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_out_acl_hint    ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == $svc_monitor_mac), action=(next;)
+@@ -7542,6 +7645,7 @@ AT_CHECK([ovn-sbctl dump-flows | grep -E "ls_.*_acl" | sed 's/table=../table=??/
+   table=??(ls_in_acl          ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
+   table=??(ls_in_acl          ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=0    , match=(1), action=(drop;)
++  table=??(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=??(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=0    , match=(1), action=(next;)
+   table=??(ls_in_acl_hint     ), priority=1    , match=(ct.est && ct_mark.blocked == 0), action=(reg0[[10]] = 1; next;)
+@@ -7886,8 +7990,10 @@ check ovn-nbctl                                               \
+ AS_BOX([No chassis registered - use ct_lb_mark and ct_mark.natted])
+ check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 66.66.66.66 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 66.66.66.66), action=(ct_lb_mark(backends=42.42.42.2);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 66.66.66.66), action=(ct_lb_mark(backends=42.42.42.2);)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb_mark;)
+   table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb_mark;)
+   table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb_mark(backends=42.42.42.2);)
+@@ -7898,8 +8004,10 @@ AS_BOX([Chassis registered that doesn't support ct_lb_mark - use ct_lb and ct_la
+ check ovn-sbctl chassis-add hv geneve 127.0.0.1
+ check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 66.66.66.66 && ct_label.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 66.66.66.66), action=(ct_lb(backends=42.42.42.2);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 66.66.66.66), action=(ct_lb(backends=42.42.42.2);)
++  table=7 (lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_label.natted && ct_label.force_snat == 1), action=(flags.force_snat_for_lb = 1; next;)
++  table=7 (lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_label.natted && ct_label.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_label.natted), action=(next;)
+   table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb;)
+   table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb;)
+   table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb(backends=42.42.42.2);)
+@@ -7910,8 +8018,10 @@ AS_BOX([Chassis upgrades and supports ct_lb_mark - use ct_lb_mark and ct_mark.na
+ check ovn-sbctl set chassis hv other_config:ct-no-masked-label=true
+ check ovn-nbctl --wait=sb sync
+ AT_CHECK([ovn-sbctl lflow-list | grep -e natted -e ct_lb], [0], [dnl
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 66.66.66.66 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 66.66.66.66), action=(ct_lb_mark(backends=42.42.42.2);)
++  table=7 (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 66.66.66.66), action=(ct_lb_mark(backends=42.42.42.2);)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=6 (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst == 66.66.66.66), action=(reg1 = 66.66.66.66; ct_lb_mark;)
+   table=6 (ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), action=(ct_lb_mark;)
+   table=12(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst == 66.66.66.66), action=(reg0[[1]] = 0; ct_lb_mark(backends=42.42.42.2);)
+@@ -8244,15 +8354,17 @@ AT_CAPTURE_FILE([R1flows])
+ 
+ AT_CHECK([grep "lr_in_lb_aff_check" R1flows | sort], [0], [dnl
+   table=6 (lr_in_lb_aff_check ), priority=0    , match=(1), action=(next;)
+-  table=6 (lr_in_lb_aff_check ), priority=100  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80), action=(reg9[[6]] = chk_lb_aff(); next;)
++  table=6 (lr_in_lb_aff_check ), priority=100  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(reg0 = ip4.dst; reg9[[16..31]] = tcp.dst; reg9[[6]] = chk_lb_aff(); next;)
+ ])
+ AT_CHECK([grep "lr_in_dnat " R1flows | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80), action=(ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
+   table=7 (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; ct_lb_mark(backends=10.0.0.2:80);)
+   table=7 (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; ct_lb_mark(backends=20.0.0.2:80);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -8270,11 +8382,13 @@ AT_CAPTURE_FILE([R1flows_skip_snat])
+ 
+ AT_CHECK([grep "lr_in_dnat " R1flows_skip_snat | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.skip_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 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=7 (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=7 (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=7 (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=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -8289,11 +8403,13 @@ AT_CAPTURE_FILE([R1flows_force_snat])
+ 
+ AT_CHECK([grep "lr_in_dnat " R1flows_force_snat | sort], [0], [dnl
+   table=7 (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1), action=(flags.force_snat_for_lb = 1; next;)
+-  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 172.16.0.10 && tcp && reg9[[16..31]] == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; force_snat);)
++  table=7 (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 172.16.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80; force_snat);)
+   table=7 (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.force_snat_for_lb = 1; ct_lb_mark(backends=10.0.0.2:80; force_snat);)
+   table=7 (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.force_snat_for_lb = 1; ct_lb_mark(backends=20.0.0.2:80; force_snat);)
++  table=7 (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_mark.natted), action=(next;)
+   table=7 (lr_in_dnat         ), priority=50   , match=(ct.rel && !ct.est && !ct.new), action=(ct_commit_nat;)
++  table=7 (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=7 (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=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.force_snat == 1), action=(flags.force_snat_for_lb = 1; ct_commit_nat;)
+   table=7 (lr_in_dnat         ), priority=70   , match=(ct.rel && !ct.est && !ct.new && ct_mark.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; ct_commit_nat;)
+ ])
+@@ -8569,12 +8685,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows0
+ 
+ AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows0], [0], [dnl
+   table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
+-  table=? (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(ct_dnat;)
+   table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_mark.natted == 1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
++  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
++  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;)
+ ])
+@@ -8588,6 +8705,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows0 | grep "priority=65532"],
+   table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
+   table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
+   table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
++  table=?(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+ ])
+ 
+@@ -8599,10 +8717,12 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows1
+ 
+ AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows1], [0], [dnl
+   table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
++  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(ct_dnat;)
+   table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_label.natted == 1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb(backends=192.168.1.10);)
++  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 192.168.0.1), action=(ct_lb(backends=192.168.1.10);)
++  table=? (lr_in_dnat         ), priority=50   , match=(ct.est && !ct.rel && !ct.new && ct_label.natted), action=(next;)
++  table=? (lr_in_dnat         ), priority=70   , match=(ct.est && !ct.rel && !ct.new && ct_label.natted && ct_label.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_label.natted && ct_label.skip_snat == 1), action=(flags.skip_snat_for_lb = 1; next;)
+ ])
+ 
+ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"], [0], [dnl
+@@ -8614,6 +8734,7 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows1 | grep "priority=65532"],
+   table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;)
+   table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;)
+   table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
++  table=?(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+ ])
+ 
+@@ -8625,12 +8746,13 @@ ovn-sbctl dump-flows | DUMP_FLOWS_SORTED > lflows2
+ 
+ AT_CHECK([grep -e "lr_in_defrag" -e "lr_in_dnat" lflows2], [0], [dnl
+   table=? (lr_in_defrag       ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(reg0 = 192.168.0.1; ct_dnat;)
+-  table=? (lr_in_defrag       ), priority=50   , match=(icmp || icmp6), action=(ct_dnat;)
++  table=? (lr_in_defrag       ), priority=100  , match=(ip && ip4.dst == 192.168.0.1), action=(ct_dnat;)
+   table=? (lr_in_dnat         ), priority=0    , match=(1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.est && !ct.rel && ip4 && reg0 == 192.168.0.1 && ct_mark.natted == 1), action=(next;)
+-  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && reg0 == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
++  table=? (lr_in_dnat         ), priority=110  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 192.168.0.1), action=(ct_lb_mark(backends=192.168.1.10);)
++  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;)
+ ])
+@@ -8644,8 +8766,104 @@ AT_CHECK([grep -e "ls_in_acl" -e "ls_out_acl" lflows2 | grep "priority=65532"],
+   table=? (ls_out_acl         ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), action=(next;)
+   table=? (ls_out_acl         ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_mark.blocked == 1)), action=(drop;)
+   table=? (ls_out_acl         ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
++  table=?(ls_in_acl_after_lb ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;)
+   table=?(ls_in_acl_after_lb ), priority=65532, match=(reg0[[17]] == 1), action=(next;)
+ ])
+ 
+ AT_CLEANUP
+ ])
++
++OVN_FOR_EACH_NORTHD_NO_HV([
++AT_SETUP([Chassis-feature compatibitility - remote chassis])
++ovn_start
++
++AS_BOX([Local chassis])
++check ovn-sbctl chassis-add hv1 geneve 127.0.0.1 \
++  -- set chassis hv1 other_config:ct-no-masked-label=true \
++  -- set chassis hv1 other_config:ovn-ct-lb-related=true \
++  -- set chassis hv1 other_config:mac-binding-timestamp=true
++
++check ovn-nbctl --wait=sb sync
++
++AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE debug/chassis-features-list], [0], [dnl
++ct_no_masked_label:    true
++ct_lb_related:         true
++mac_binding_timestamp: true
++])
++
++AS_BOX([Remote chassis])
++check ovn-sbctl chassis-add hv2 geneve 127.0.0.2 \
++  -- set chassis hv2 other_config:is-remote=true \
++  -- set chassis hv2 other_config:ct-no-masked-label=false \
++  -- set chassis hv2 other_config:ovn-ct-lb-related=false \
++  -- set chassis hv2 other_config:mac-binding-timestamp=false
++
++check ovn-nbctl --wait=sb sync
++
++AT_CHECK([as northd ovn-appctl -t NORTHD_TYPE debug/chassis-features-list], [0], [dnl
++ct_no_masked_label:    true
++ct_lb_related:         true
++mac_binding_timestamp: true
++])
++
++AT_CLEANUP
++])
++
++AT_SETUP([Localnet ports on LS with LB])
++ovn_start
++# In the past, traffic arriving on localnet ports has skipped conntrack.
++# This test ensures that we still skip conntrack for localnet ports,
++# *except* for the case where the logical switch has a load balancer
++# configured. In this case, the localnet port will not skip conntrack,
++# allowing for traffic to be load balanced on the localnet port.
++
++check ovn-nbctl ls-add sw
++check ovn-nbctl lsp-add sw sw-ln
++check ovn-nbctl lsp-set-type sw-ln localnet
++check ovn-nbctl lsp-set-addresses sw-ln unknown
++check ovn-nbctl --wait=sb sync
++
++# Since this test is only concerned with logical flows, we don't need to
++# configure anything else that we normally would with regards to localnet
++# ports
++
++
++# First, ensure that conntrack is skipped for the localnet port since there
++# isn't a load balancer configured.
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++  table=??(ls_in_pre_lb       ), priority=110  , match=(ip && inport == "sw-ln"), action=(next;)
++])
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++  table=??(ls_out_pre_lb      ), priority=110  , match=(ip && outport == "sw-ln"), action=(ct_clear; next;)
++])
++
++# Now add a load balancer and ensure that we no longer are skipping conntrack
++# for the localnet port
++
++check ovn-nbctl lb-add lb 10.0.0.1:80 10.0.0.100:8080 tcp
++check ovn-nbctl ls-lb-add sw lb
++check ovn-nbctl --wait=sb sync
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++])
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++])
++
++# And ensure that removing the load balancer from the switch results in skipping
++# conntrack again
++check ovn-nbctl ls-lb-del sw lb
++check ovn-nbctl --wait=sb sync
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++  table=??(ls_in_pre_lb       ), priority=110  , match=(ip && inport == "sw-ln"), action=(next;)
++])
++
++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_pre_lb | grep priority=110 | grep sw-ln | sed 's/table=../table=??/'], [0], [dnl
++  table=??(ls_out_pre_lb      ), priority=110  , match=(ip && outport == "sw-ln"), action=(ct_clear; next;)
++])
++
++AT_CLEANUP
++])
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 55de7c85b..3515a1e3c 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -5753,7 +5753,7 @@ check ovn-nbctl --wait=hv sync
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
+         udp && udp.src==53 && udp.dst==4369"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ 
+ echo "---------NB dump-----"
+@@ -5803,7 +5803,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip &&
+         udp && udp.src==53 && udp.dst==4369"
+ 
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ # The 2nd packet sent shound not be received.
+ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+ 
+@@ -7741,7 +7741,6 @@ ls3_p1_mac=00:00:00:01:02:05
+ check ovn-nbctl --wait=hv lr-policy-add R1 10 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" drop
+ 
+ # Check logical flow
+-ovn-sbctl dump-flows > sbflows
+ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl
+ 1
+ ])
+@@ -7751,15 +7750,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the drop policy
+-AT_CHECK([ovs-ofctl dump-flows br-int | \
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
+     grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24 actions=drop" | \
+-    grep "priority=10" | \
+-    grep "n_packets=1" | wc -l], [0], [dnl
+-1
+-])
++    grep "priority=10" | grep "n_packets=1" -c)"])
+ 
+ # Expected to drop the packet.
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
+@@ -7770,7 +7766,7 @@ AT_FAIL_IF([test "$rcvd_packet" != ""])
+ check ovn-nbctl --wait=hv lr-policy-add R1 20 "ip4.src==192.168.1.0/24 && ip4.dst==172.16.1.0/24" allow
+ 
+ # Check logical flow
+-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l], [0], [dnl
++AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" -c], [0], [dnl
+ 2
+ ])
+ 
+@@ -7778,15 +7774,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "192.168.1.0" | wc -l]
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the allow policy
+-sleep 1
+-AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
+-    grep "192.168.1.0" | \
+-    grep "priority=20" | wc -l], [0], [dnl
+-1
+-])
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
++    grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" | \
++    grep "priority=20" | grep "n_packets=1" -c)"])
+ 
+ # Expected packet has TTL decreased by 1
+ expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
+@@ -7802,7 +7795,7 @@ check ovn-nbctl --wait=hv lr-policy-add R1 30 "ip4.src==192.168.1.0/24 && ip4.ds
+ # Check logical flow
+ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
+     grep "192.168.1.0" | \
+-    grep "priority=30" | wc -l], [0], [dnl
++    grep "priority=30" -c], [0], [dnl
+ 1
+ ])
+ 
+@@ -7810,21 +7803,12 @@ AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip4 && ip.ttl==64 && ip4.src==$ls1_p1_ip && ip4.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+-sleep 1
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+-echo "southbound flows"
+-ovn-sbctl --ovs dump-flows > sbflows
+-AT_CAPTURE_FILE([sbflows])
+-echo "ovs flows"
+-ovs-ofctl dump-flows br-int > brflows
+-AT_CAPTURE_FILE([brflows])
+ # Check if packet hit the allow policy
+-AT_CHECK([grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" brflows | \
+-    grep "priority=30" | \
+-    grep "n_packets=1" | wc -l], [0], [dnl
+-1
+-])
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
++    grep "nw_src=192.168.1.0/24,nw_dst=172.16.1.0/24" | \
++    grep "priority=30" | grep "n_packets=1" -c)"])
+ echo "packet hit reroute policy"
+ 
+ # Expected packet has TTL decreased by 1
+@@ -7927,9 +7911,7 @@ ls3_p1_mac=00:00:00:01:02:05
+ check ovn-nbctl --wait=sb lr-policy-add R1 10 "ip6.src==2001::/64 && ip6.dst==2002::/64" drop
+ 
+ # Check logical flow
+-ovn-sbctl dump-flows > sbflows
+-AT_CAPTURE_FILE([sbflows])
+-AT_CHECK([grep lr_in_policy sbflows | grep "2001" | wc -l], [0], [dnl
++AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" -c], [0], [dnl
+ 1
+ ])
+ 
+@@ -7938,15 +7920,12 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the drop policy
+-AT_CHECK([ovs-ofctl dump-flows br-int | \
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
+     grep "ipv6_src=2001::/64,ipv6_dst=2002::/64 actions=drop" | \
+-    grep "priority=10" | \
+-    grep "n_packets=1" | wc -l], [0], [dnl
+-1
+-])
++    grep "priority=10" | grep "n_packets=1" -c)"])
+ 
+ # Expected to drop the packet.
+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" pbr-hv/vif2-tx.pcap > vif2.packets
+@@ -7956,9 +7935,7 @@ AT_FAIL_IF([test -s vif2.packets])
+ check ovn-nbctl --wait=sb lr-policy-add R1 20 "ip6.src==2001::/64 && ip6.dst==2002::/64" allow
+ 
+ # Check logical flow
+-ovn-sbctl dump-flows > sbflows2
+-AT_CAPTURE_FILE([sbflows2])
+-AT_CHECK([grep lr_in_policy sbflows2 | grep "2001" | wc -l], [0], [dnl
++AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | grep "2001" -c], [0], [dnl
+ 2
+ ])
+ 
+@@ -7966,16 +7943,12 @@ AT_CHECK([grep lr_in_policy sbflows2 | grep "2001" | wc -l], [0], [dnl
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the allow policy
+-ovn-sbctl dump-flows > sbflows3
+-AT_CAPTURE_FILE([sbflows3])
+-AT_CHECK([grep lr_in_policy sbflows3 | \
+-    grep "2001" | \
+-    grep "priority=20" | wc -l], [0], [dnl
+-1
+-])
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
++    grep "ipv6_src=2001::/64,ipv6_dst=2002::/64"  | \
++    grep "priority=20" | grep "n_packets=1" -c)"])
+ 
+ # Expected packet has TTL decreased by 1
+ expected="eth.src==$ls2_ro_mac && eth.dst==$ls2_p1_mac &&
+@@ -7989,11 +7962,9 @@ OVN_CHECK_PACKETS([pbr-hv/vif2-tx.pcap], [expected])
+ check ovn-nbctl --wait=sb lr-policy-add R1 30 "ip6.src==2001::/64 && ip6.dst==2002::/64" reroute 2003::2
+ 
+ # Check logical flow
+-ovn-sbctl dump-flows > sbflows4
+-AT_CAPTURE_FILE([sbflows4])
+-AT_CHECK([grep lr_in_policy sbflows4 | \
++AT_CHECK([ovn-sbctl dump-flows | grep lr_in_policy | \
+     grep "2001" | \
+-    grep "priority=30" | wc -l], [0], [dnl
++    grep "priority=30" -c], [0], [dnl
+ 1
+ ])
+ 
+@@ -8001,19 +7972,12 @@ AT_CHECK([grep lr_in_policy sbflows4 | \
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$ls1_ro_mac &&
+        ip6 && ip.ttl==64 && ip6.src==$ls1_p1_ip && ip6.dst==$ls2_p1_ip &&
+        udp && udp.src==53 && udp.dst==4369"
+-as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"
+-sleep 1
++OVS_WAIT_UNTIL([as pbr-hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+-ovn-sbctl dump-flows > sbflows5
+-ovs-ofctl dump-flows br-int > offlows5
+-AT_CAPTURE_FILE([sbflows5])
+-AT_CAPTURE_FILE([offlows5])
+ # Check if packet hit the allow policy
+-AT_CHECK([grep "ipv6_src=2001::/64,ipv6_dst=2002::/64" offlows5 | \
+-    grep "priority=30" | \
+-    grep "n_packets=1" | wc -l], [0], [dnl
+-1
+-])
++OVS_WAIT_UNTIL([test "1" = "$(ovs-ofctl dump-flows br-int | \
++    grep "ipv6_src=2001::/64,ipv6_dst=2002::/64"  | \
++    grep "priority=30" | grep "n_packets=1" -c)"])
+ 
+ # Expected packet has TTL decreased by 1
+ expected="eth.src==$ls3_ro_mac && eth.dst==$ls3_p1_mac &&
+@@ -9531,73 +9495,73 @@ AT_CAPTURE_FILE([sbflows])
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==80"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be dropped with logging in the ingress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4361 && tcp.dst==81"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be dropped without logging in the eggress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4360 && tcp.dst==180"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be dropped with logging in the egress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4361 && tcp.dst==181"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be allowed without logging.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4362 && tcp.dst==82"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be allowed with logging.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4363 && tcp.dst==83"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should allow related flows without logging.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4364 && tcp.dst==84"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should allow related flows with logging.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4365 && tcp.dst==85"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be rejected without logging in the ingress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4366 && tcp.dst==86"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be rejected with logging in the ingress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==87"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be rejected without logging in the egress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4366 && tcp.dst==186"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Send packet that should be rejected with logging in the egress pipeline.
+ packet="inport==\"lp1\" && eth.src==$lp1_mac && eth.dst==$lp2_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$lp1_ip && ip4.dst==$lp2_ip &&
+         tcp && tcp.flags==2 && tcp.src==4367 && tcp.dst==187"
+-as hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ OVS_WAIT_UNTIL([ test 8 = $(grep -c 'acl_log' hv/ovn-controller.log) ])
+ 
+@@ -12254,7 +12218,7 @@ nexthop_mac="f00000010204"
+ AS_BOX([Send ip packet from foo1 to 8.8.8.8])
+ src_mac="f00000010203"
+ dst_mac="000001010203"
+-packet=${foo_mac}${foo1_mac}08004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000
++packet=${foo_mac}${foo1_mac}080045000028000000004006a916${foo1_ip}${dst_ip}0035111112345678000000005002faf069450000
+ 
+ AS_BOX([Wait for GARPs announcing gw IP to arrive])
+ OVS_WAIT_UNTIL([
+@@ -12265,15 +12229,12 @@ grep actions=mod_dl_dst:f0:00:00:01:02:04 | wc -l` -eq 1
+ AS_BOX([Verify VLAN tagged packet on bridge connecting hv1 and hv2])
+ # VLAN tagged packet with router port(192.168.1.1) MAC as destination MAC
+ # is expected on bridge connecting hv1 and hv2
+-expected=${foo_mac}${foo1_mac}8100000208004500001c0000000040110000${foo1_ip}${dst_ip}0035111100080000
++expected=${foo_mac}${foo1_mac}81000002080045000028000000004006a916${foo1_ip}${dst_ip}0035111112345678000000005002faf069450000
+ echo $expected > hv1-br-ex_n2.expected
+ 
+ AS_BOX([Verify packet at outside1 i.e nexthop(172.16.1.1) port])
+ # Packet to Expect at outside1 i.e nexthop(172.16.1.1) port.
+-# As connection tracking not enabled for this test, snat can't be done on the packet.
+-# We still see foo1 as the source ip address. But source mac(gateway MAC) and
+-# dest mac(nexthop mac) are properly configured.
+-expected=${nexthop_mac}${gw_mac}08004500001c000000003f110100${foo1_ip}${dst_ip}0035111100080000
++expected=${nexthop_mac}${gw_mac}080045000028000000003f06beaa${gw_ip}${dst_ip}0035111112345678000000005002faf07dd90000
+ echo $expected > hv3-vif1.expected
+ 
+ check as hv1 ovs-appctl dpctl/del-flows
+@@ -12304,7 +12265,7 @@ cat hv1-br-ex_n2.expected > expout
+ AT_CHECK([sort hv1-br-ex_n2], [0], [expout])
+ 
+ AS_BOX([Check expected packet on nexthop interface])
+-$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/vif1-tx.pcap | grep ${foo1_ip}${dst_ip} | uniq > hv3-vif1
++$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv3/vif1-tx.pcap | grep ${gw_ip}${dst_ip} | uniq > hv3-vif1
+ cat hv3-vif1.expected > expout
+ AT_CHECK([sort hv3-vif1], [0], [expout])
+ 
+@@ -13268,30 +13229,27 @@ as hv2 ovs-ofctl dump-flows br-int table=37
+ gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
+ gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
+ 
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+-# make sure that flows for handling the outside router port reside on gw1
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
++# make sure that flows for handling the outside router port reside on gw1 through ls_in_l2_lkup table
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
+ ]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ ]])
+ 
+-# make sure ARP responder flows for outside router port reside on gw1 too
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \
+-grep arp_tpa=192.168.0.101 | wc -l], [0], [[1
+-]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0
+-]])
++# make sure ARP responder flows for outside router port reside on gw1 too through ls_in_arp_rsp table
++OVS_WAIT_UNTIL([test `as gw1 ovs-ofctl dump-flows br-int table=27 | \
++grep arp_tpa=192.168.0.101 | wc -l` -ge 1])
+ 
+ # check that the chassis redirect port has been claimed by the gw1 chassis
+ wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
+@@ -13314,13 +13272,13 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ 
+ # we make sure that the hypervisors noticed, and inverted the slave ports
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv1_gw2_ofport,$hv1_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv2_gw2_ofport,$hv2_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+@@ -13372,11 +13330,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+ ]])
+ 
+ # make sure that flows for handling the outside router port reside on gw2 now
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
+ ]])
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ ]])
+ 
+ # disconnect GW2 from the network, GW1 should take over
+@@ -13386,12 +13344,12 @@ as main ovs-vsctl del-port n1 $port
+ 
+ bfd_dump
+ 
+-# make sure that flows for handling the outside router port reside on gw2 now
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
++# make sure that flows for handling the outside router port reside on gw1 now
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
+ ]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=25 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ ]])
+ 
+ # check that the chassis redirect port has been reclaimed by the gw1 chassis
+@@ -13470,45 +13428,16 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid
+ wait_row_count HA_Chassis_Group 1
+ wait_row_count HA_Chassis 2
+ 
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
+-| wc -l], [0], [1
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
++| wc -l], [0], [0
+ ])
+ 
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
+-| wc -l], [0], [1
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
++| wc -l], [0], [0
+ ])
+ 
+-# make sure that flows for handling the outside router port reside on gw1
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
+-]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
+-]])
+-
+-# make sure ARP responder flows for outside router port reside on gw1 too
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=9 | \
+-grep arp_tpa=192.168.0.101 | wc -l], [0], [[1
+-]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=9 | grep arp_tpa=192.168.0.101 | wc -l], [0], [[0
+-]])
+-
+-# check that the chassis redirect port has been claimed by the gw1 chassis
+-#
+-# XXX actually it doesn't happen, the test has always been wrong here
+-# because the following just checks that "wc -l" succeeds (and it always
+-# does):
+-#
+-#   OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
+-#   logical_port=cr-outside | grep $gw1_chassis | wc -l], [0],[[1
+-#   ]])
+-#
+-# If it were correct, then the following would be a good substitute:
+-#
+-#   wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
+-
+ # Re add the ovs ports.
+ for i in 1 2; do
+     as hv$i
+@@ -13519,6 +13448,34 @@ for i in 1 2; do
+         ofport-request=1
+ done
+ 
++# Re-add gw2
++as gw2 ovn_attach n1 br-phys 192.168.0.1
++
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv1_gw1_ofport,$hv1_gw2_ofport \
++| wc -l], [0], [1
++])
++
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv2_gw1_ofport,$hv2_gw2_ofport \
++| wc -l], [0], [1
++])
++
++# make sure that flows for handling the outside router port reside on gw1
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
++]])
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst:00:00:02:01:02:04" | wc -l], [0], [[0
++]])
++
++# make sure ARP responder flows for outside router port reside on gw1 too
++OVS_WAIT_UNTIL([test `as gw1 ovs-ofctl dump-flows br-int table=27 | \
++grep arp_tpa=192.168.0.101 | wc -l` -ge 1 ])
++
++# check that the chassis redirect port has been claimed by the gw1 chassis
++wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw1_chassis
++
+ hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
+ hv2_ch_uuid=$(fetch_column Chassis _uuid name=hv2)
+ exp_ref_ch_list="$hv1_ch_uuid $hv2_ch_uuid"
+@@ -13527,29 +13484,18 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis
+ # Increase the priority of gw2
+ ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
+ 
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv1_gw2_ofport,$hv1_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+-grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=37 | \
++grep active_backup | grep members:$hv2_gw2_ofport,$hv2_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+ 
+ # check that the chassis redirect port has been reclaimed by the gw2 chassis
+-#
+-# XXX actually it doesn't happen, the test has always been wrong here
+-# because the following just checks that "wc -l" succeeds (and it always
+-# does):
+-#
+-#   OVS_WAIT_UNTIL([ovn-sbctl --columns chassis --bare find Port_Binding \
+-#   logical_port=cr-outside | grep $gw2_chassis | wc -l], [0],[[1
+-#   ]])
+-#
+-# If it were correct, then the following would be a good substitute:
+-#
+-#   wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis
++wait_row_count Port_Binding 1 logical_port=cr-outside chassis=$gw2_chassis
+ 
+ # check BFD enablement on tunnel ports from gw1 #########
+ as gw1
+@@ -13588,11 +13534,11 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find Interface name=ovn-hv1-0],[0],
+ ]])
+ 
+ # make sure that flows for handling the outside router port reside on gw2 now
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
+ ]])
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ ]])
+ 
+ # disconnect GW2 from the network, GW1 should take over
+@@ -13603,11 +13549,11 @@ as main ovs-vsctl del-port n1 $port
+ bfd_dump
+ 
+ # make sure that flows for handling the outside router port reside on gw2 now
+-OVS_WAIT_UNTIL([as gw1 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[1
++OVS_WAIT_FOR_OUTPUT([as gw1 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[1
+ ]])
+-OVS_WAIT_UNTIL([as gw2 ovs-ofctl dump-flows br-int table=24 | \
+-grep 00:00:02:01:02:04 | wc -l], [0], [[0
++OVS_WAIT_FOR_OUTPUT([as gw2 ovs-ofctl dump-flows br-int table=33 | \
++grep "dl_dst=00:00:02:01:02:04" | wc -l], [0], [[0
+ ]])
+ 
+ # check that the chassis redirect port has been reclaimed by the gw1 chassis
+@@ -13889,6 +13835,133 @@ OVN_CLEANUP([gw1],[gw2],[hv1])
+ AT_CLEANUP
+ ])
+ 
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([allow IPv6 RA / NA / MLD by default])
++AT_SKIP_IF([test $HAVE_SCAPY = no])
++ovn_start
++net_add n1
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++lsp_mac_prefix=50:64:00:00:00:0
++lsp_ip_prefix=10.0.0.
++lsp_ip6_prefix=aef0::5264:00ff:fe00:000
++
++check ovn-nbctl ls-add ls0
++for i in 1 2; do
++  check ovn-nbctl lsp-add ls0 lsp$i
++  check ovn-nbctl lsp-set-addresses lsp$i \
++    "${lsp_mac_prefix}$i ${lsp_ip_prefix}$i ${lsp_ip6_prefix}$i"
++
++  # forbid all traffic for the ports
++  check ovn-nbctl acl-add ls0 \
++      from-lport 1000 "inport == \"lsp$i\"" drop
++  check ovn-nbctl --apply-after-lb acl-add ls0\
++      from-lport 1000 "inport == \"lsp$i\"" drop
++  check ovn-nbctl acl-add ls0 \
++      to-lport 1000 "outport == \"lsp$i\"" drop
++
++  check ovs-vsctl -- add-port br-int vif$i -- \
++      set interface vif$i external-ids:iface-id=lsp$i \
++      options:tx_pcap=hv1/vif$i-tx.pcap \
++      options:rxq_pcap=hv1/vif$i-rx.pcap
++  : > $i.expected
++done
++
++router_mac=fa:16:3e:00:00:01
++router_prefix=fdad:1234:5678::
++router_ip=${router_prefix}1
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lrp0 ${router_mac} ${router_ip}/64
++check ovn-nbctl set Logical_Router_Port lrp0 ipv6_ra_configs:address_mode="slaac"
++check ovn-nbctl \
++    -- lsp-add ls0 rp0 \
++    -- set Logical_Switch_Port rp0 type=router \
++                     options:router-port=lrp0 \
++                     addresses='"${router_mac} ${router_ip}"'
++
++wait_for_ports_up
++
++test_ns_na() {
++    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
++
++    packet=$(fmt_pkt "
++        Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}') /
++        IPv6(src='${src_ip}', dst='ff02::1:ff00:2') /
++        ICMPv6ND_NS(tgt='${dst_ip}')
++    ")
++    as hv1 ovs-appctl netdev-dummy/receive vif${inport} $packet
++
++    expected_packet=$(fmt_pkt "
++        Ether(dst='${src_mac}', src='${dst_mac}') /
++        IPv6(src='${dst_ip}', dst='${src_ip}') /
++        ICMPv6ND_NA(tgt='${dst_ip}', R=0, S=1) /
++        ICMPv6NDOptDstLLAddr(lladdr='${dst_mac}')
++    ")
++    echo $expected_packet >> $inport.expected
++}
++
++test_rs_ra() {
++    local inport=$1 src_mac=$2 src_ip=$3
++    local router_mac=$4 router_prefix=$5 router_ip=$6
++
++    packet=$(fmt_pkt "
++        Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}') /
++        IPv6(src='${src_ip}', dst='ff02::2') /
++        ICMPv6ND_RS()
++    ")
++    as hv1 ovs-appctl netdev-dummy/receive vif${inport} $packet
++
++    expected_packet=$(fmt_pkt "
++        Ether(dst='${src_mac}', src='${router_mac}') /
++        IPv6(src='${router_ip}', dst='${src_ip}') /
++        ICMPv6ND_RA(chlim=255, prf=0, routerlifetime=65535) /
++        ICMPv6NDOptSrcLLAddr(lladdr='${router_mac}') /
++        ICMPv6NDOptPrefixInfo(prefix='${router_prefix}')
++    ")
++    echo $expected_packet >> $inport.expected
++}
++
++test_mldv2() {
++    local inport=$1 outport=$2 src_mac=$3 src_ip=$4
++
++    packet=$(fmt_pkt "
++        Ether(dst='ff:ff:ff:ff:ff:ff', src='${src_mac}') /
++        IPv6(src='${src_ip}', dst='ff02::2') /
++        ICMPv6MLQuery2()
++    ")
++    as hv1 ovs-appctl netdev-dummy/receive vif${inport} $packet
++
++    expected_packet=$packet
++    echo $expected_packet >> $outport.expected
++}
++
++src_mac=${lsp_mac_prefix}1
++dst_mac=${lsp_mac_prefix}2
++src_ip=${lsp_ip6_prefix}1
++dst_ip=${lsp_ip6_prefix}2
++
++as hv1
++test_ns_na 1 $src_mac $dst_mac $src_ip $dst_ip
++
++as hv1
++router_local_ip=fe80::f816:3eff:fe00:1
++test_rs_ra 1 $src_mac $src_ip $router_mac $router_prefix $router_local_ip
++
++as hv1
++src_ip=fe80::1
++test_mldv2 1 2 $src_mac $src_ip
++
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
++OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [2.expected])
++
++OVN_CLEANUP([hv1])
++
++AT_CLEANUP
++])
++
+ OVN_FOR_EACH_NORTHD([
+ AT_SETUP([IPv6 Neighbor Solicitation for unknown MAC])
+ AT_KEYWORDS([ovn-nd_ns for unknown mac])
+@@ -17210,7 +17283,7 @@ test_icmp() {
+                   icmp4.code==0"
+     shift; shift; shift; shift; shift; shift
+     hv=hv`vif_to_hv $inport`
+-    as $hv ovs-appctl -t ovn-controller inject-pkt "$packet"
++    OVS_WAIT_UNTIL([as $hv ovs-appctl -t ovn-controller inject-pkt "$packet"])
+     in_ls=`vif_to_ls $inport`
+     in_lrp=`vif_to_lrp $inport`
+     for outport; do
+@@ -18276,7 +18349,7 @@ AT_SETUP([TTL exceeded])
+ AT_KEYWORDS([ttl-exceeded])
+ ovn_start
+ 
+-# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IPV4_ROUTER IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM
++# test_ip_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IPV4_ROUTER IP_CHKSUM EXP_IP_CHKSUM EXP_ICMP_CHKSUM SHOULD_REPLY
+ #
+ # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv4 packet with
+ # ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM as specified and TTL set to 1.
+@@ -18292,6 +18365,7 @@ test_ip_packet() {
+     local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_router=$7 ip_chksum=$8
+     local exp_ip_chksum=$9 exp_icmp_chksum=${10}
+     shift 10
++    local should_reply=$1
+ 
+     local ip_ttl=01
+     local packet=${eth_dst}${eth_src}08004500001400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}
+@@ -18300,27 +18374,31 @@ test_ip_packet() {
+     local icmp_type_code_response=0b00
+     local icmp_data=00000000
+     local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_data}
+-    local reply=${eth_src}${eth_dst}08004500003000004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload}
+-    echo $reply$orig_pkt_in_reply >> vif$inport.expected
++    if test $should_reply == yes; then
++        local reply=${eth_src}${eth_dst}08004500003000004000${reply_icmp_ttl}01${exp_ip_chksum}${ip_router}${ipv4_src}${reply_icmp_payload}
++        echo $reply$orig_pkt_in_reply >> vif$inport.expected
++    fi
+ 
+     as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ }
+ 
+-# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_ROUTER EXP_ICMP_CHKSUM
++# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_ROUTER EXP_ICMP_CHKSUM SHOULD_REPLY
+ #
+ # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6
+ # packet with ETH_SRC, ETH_DST, IPV6_SRC and IPV6_DST as specified.
+ # IPV6_ROUTER and EXP_ICMP_CHKSUM are the source IP and checksum of the icmpv6 ttl exceeded
+ # packet sent by OVN logical router
+ test_ip6_packet() {
+-    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8
++    local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 ipv6_router=$7 exp_icmp_chksum=$8 should_reply=$9
+     shift 8
+ 
+     local ip6_hdr=6000000000151101${ipv6_src}${ipv6_dst}
+     local packet=${eth_dst}${eth_src}86dd${ip6_hdr}dbb8303900155bac6b646f65206676676e6d66720a
+ 
+-    local reply=${eth_src}${eth_dst}86dd6000000000453afe${ipv6_router}${ipv6_src}0300${exp_icmp_chksum}00000000${ip6_hdr}dbb8303900155bac6b646f65206676676e6d66720a
+-    echo $reply >> vif$inport.expected
++    if test $should_reply == yes; then
++        local reply=${eth_src}${eth_dst}86dd6000000000453afe${ipv6_router}${ipv6_src}0300${exp_icmp_chksum}00000000${ip6_hdr}dbb8303900155bac6b646f65206676676e6d66720a
++        echo $reply >> vif$inport.expected
++    fi
+ 
+     as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ }
+@@ -18343,6 +18421,8 @@ for i in 1 2; do
+             options:tx_pcap=hv$i/vif$i-tx.pcap \
+             options:rxq_pcap=hv$i/vif$i-rx.pcap \
+             ofport-request=$i
++
++    ovs-appctl -t ovn-controller vlog/set file:dbg:pinctrl
+ done
+ 
+ ovn-nbctl lr-add lr0
+@@ -18358,10 +18438,22 @@ OVN_POPULATE_ARP
+ wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+ 
+-test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96
+-test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000200000000000000000011 20010db8000100000000000000000001 1c22
++test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96 yes
++test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011 20010db8000200000000000000000011 20010db8000100000000000000000001 1c22 yes
++
++# Should not send ICMP for multicast
++test_ip_packet 1 1 000000000001 01005e7f0001 $(ip_to_hex 192 168 1 1) $(ip_to_hex 239 255 0 1) $(ip_to_hex 192 168 1 254) 0000 000000000 no
++test_ip6_packet 1 1 000000000001 333300000001 20010db8000100000000000000000011 ff020000000000000000000000000001 20010db8000100000000000000000001 0000 no
++
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+ 
++# Confirm from debug log that we only see 2 packet-ins (no packet-ins for
++# multicasts). This is necessary because not seeing ICMP messages doesn't
++# necessarily mean the packet-in didn't happen. It is possible that packet-in
++# is processed but the ICMP message got dropped.
++AT_CHECK([grep -c packet-in hv1/ovn-controller.log], [0], [2
++])
++
+ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+ ])
+@@ -18656,7 +18748,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+ # Start by Sending the packet and make sure it makes it there as expected
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Expected packet has TTL decreased by 1
+ expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
+@@ -18670,7 +18762,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+ as hv2 ovs-appctl -t ovn-controller exit
+ 
+ # Now send the packet again. This time, it should not arrive.
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+ 
+@@ -19552,7 +19644,7 @@ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+ # Start by Sending the packet and make sure it makes it there as expected
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Expected packet has TTL decreased by 1
+ expected="eth.src==$sw2_ro_mac && eth.dst==$sw2_p1_mac &&
+@@ -19566,7 +19658,7 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+ as hv2 ovs-appctl -t ovn-controller exit --restart
+ 
+ # Now send the packet again. This time, it should still arrive
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ cat expected expected > expected2
+ 
+@@ -19705,7 +19797,7 @@ test_ip_packet_larger() {
+     # Set the packet length to 114.
+     pkt_len=0072
+     packet=${dst_mac}${src_mac}08004500${pkt_len}000000004001c3dd
+-    orig_packet_l3=${src_ip}${dst_ip}0304fcfb00000000
++    orig_packet_l3=${src_ip}${dst_ip}0800f7ff00000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+@@ -19729,10 +19821,10 @@ test_ip_packet_larger() {
+         # Packet to expect at br-phys.
+         src_mac="000020201213"
+         dst_mac="00000012af11"
+-        src_ip=`ip_to_hex 10 0 0 3`
++        src_ip=`ip_to_hex 172 168 0 100`
+         dst_ip=`ip_to_hex 172 168 0 3`
+-        expected=${dst_mac}${src_mac}08004500${pkt_len}000000003f01c4dd
+-        expected=${expected}${src_ip}${dst_ip}0304fcfb00000000
++        expected=${dst_mac}${src_mac}08004500${pkt_len}000000003f0121d4
++        expected=${expected}${src_ip}${dst_ip}0800f7ff00000000
+         expected=${expected}000000000000000000000000000000000000
+         expected=${expected}000000000000000000000000000000000000
+         expected=${expected}000000000000000000000000000000000000
+@@ -19793,7 +19885,7 @@ test_ip_packet_larger_ext() {
+     # Set the packet length to 114.
+     pkt_len=0072
+     packet=${dst_mac}${src_mac}08004500${pkt_len}000000004001${checksum}
+-    orig_packet_l3=${src_ip}${dst_ip}0900000000000000
++    orig_packet_l3=${src_ip}${dst_ip}0800f7ff00000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+     orig_packet_l3=${orig_packet_l3}000000000000000000000000000000000000
+@@ -19810,7 +19902,7 @@ test_ip_packet_larger_ext() {
+     dst_ip=`ip_to_hex 172 168 0 4`
+     # pkt len should be 146 (28 (icmp packet) + 118 (orig ip + payload))
+     reply_pkt_len=008e
+-    ip_csum=f39b
++    ip_csum=$7
+     icmp_reply=${src_mac}${dst_mac}08004500${reply_pkt_len}00004000fe01${reply_checksum}
+     icmp_reply=${icmp_reply}${src_ip}${dst_ip}0304${ip_csum}0000$(printf "%04x" $mtu)
+     icmp_reply=${icmp_reply}4500${pkt_len}000000004001${checksum}
+@@ -19985,10 +20077,10 @@ OVS_WAIT_FOR_OUTPUT([
+ ])
+ 
+ AS_BOX([testing ingress traffic mtu 100 - IPv4])
+-test_ip_packet_larger_ext 1 000020201213 $(ip_to_hex 172 168 0 100) 20cf 100 22b6
++test_ip_packet_larger_ext 1 000020201213 $(ip_to_hex 172 168 0 100) 20d3 100 22b6 fc97
+ 
+ AS_BOX([testing ingress traffic mtu 100 - IPv4 FIP])
+-test_ip_packet_larger_ext 2 f00000010204 $(ip_to_hex 172 168 0 110) 20c5 100 22ac
++test_ip_packet_larger_ext 2 f00000010204 $(ip_to_hex 172 168 0 110) 20c5 100 22ac fc9b
+ 
+ AS_BOX([testing ingress traffic mtu 100 - IPv6])
+ test_ip6_packet_larger_ext 1 000020201213 20000000000000000000000000000001 100 cc7a
+@@ -20055,10 +20147,10 @@ OVS_WAIT_FOR_OUTPUT([
+ ])
+ 
+ AS_BOX([testing ingress traffic mtu 100 for gw router - IPv4])
+-test_ip_packet_larger_ext 1 000020201213 $(ip_to_hex 172 168 0 100) 20cf 100 22b6
++test_ip_packet_larger_ext 1 000020201213 $(ip_to_hex 172 168 0 100) 20d3 100 22b6 fc97
+ 
+ AS_BOX([testing ingress traffic mtu 100 for gw router - IPv6])
+-test_ip6_packet_larger_ext 1 000020201213 20000000000000000000000000000001 100 cc7a
++test_ip6_packet_larger_ext 1 000020201213 20000000000000000000000000000001 100 cc7a fc9b
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -21116,7 +21208,7 @@ check_virtual_offlows_not_present hv2
+ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
++logical_port=sw0-vir) = x$hv1_ch_uuid])
+ 
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+ logical_port=sw0-vir) = xsw0-p1])
+@@ -21184,7 +21276,7 @@ tpa=$(ip_to_hex 10 0 0 10)
+ send_garp 1 2 $eth_src $eth_dst $spa $tpa
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
++logical_port=sw0-vir) = x$hv1_ch_uuid])
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+ logical_port=sw0-vir) = xsw0-p3])
+@@ -21217,7 +21309,7 @@ tpa=$(ip_to_hex 10 0 0 10)
+ send_garp 2 1 $eth_src $eth_dst $spa $tpa
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x$hv2_ch_uuid], [0], [])
++logical_port=sw0-vir) = x$hv2_ch_uuid])
+ 
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+ logical_port=sw0-vir) = xsw0-p2])
+@@ -21249,7 +21341,7 @@ tpa=$(ip_to_hex 10 0 0 4)
+ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x$hv1_ch_uuid], [0], [])
++logical_port=sw0-vir) = x$hv1_ch_uuid])
+ sleep 1
+ 
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+@@ -21275,7 +21367,7 @@ check_virtual_offlows_not_present hv2
+ as hv1 ovs-vsctl del-port hv1-vif1
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x], [0], [])
++logical_port=sw0-vir) = x])
+ sleep 1
+ 
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+@@ -21310,7 +21402,7 @@ send_arp_reply 2 1 $eth_src $eth_dst $spa $tpa
+ sleep 1
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x$hv2_ch_uuid], [0], [])
++logical_port=sw0-vir) = x$hv2_ch_uuid])
+ sleep 1
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+ logical_port=sw0-vir) = xsw0-p2])
+@@ -21335,7 +21427,7 @@ check_virtual_offlows_not_present hv1
+ ovn-nbctl lsp-del sw0-p2
+ 
+ OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \
+-logical_port=sw0-vir) = x], [0], [])
++logical_port=sw0-vir) = x])
+ AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \
+ logical_port=sw0-vir) = x])
+ 
+@@ -21516,7 +21608,7 @@ AT_CAPTURE_FILE([offlows])
+ packet0="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:00:00:21 &&
+          ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.1.100 &&
+          tcp && tcp.src==10000 && tcp.dst==80"
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet0"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet0"])
+ ovn-nbctl --wait=hv
+ 
+ ovn-sbctl list controller_event > events
+@@ -21545,7 +21637,7 @@ packet1="inport==\"sw1-p0\" && eth.src==00:00:00:00:00:33 && eth.dst==00:00:00:0
+          ip4 && ip.ttl==64 && ip4.src==192.168.2.11 && ip4.dst==192.168.2.100 &&
+          tcp && tcp.src==10000 && tcp.dst==80"
+ 
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet1"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet1"])
+ ovn-nbctl --wait=hv
+ ovn-sbctl list controller_event
+ uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}')
+@@ -21561,7 +21653,7 @@ packet2="inport==\"sw0-p11\" && eth.src==00:00:00:00:00:11 && eth.dst==00:00:00:
+          ip6 && ip.ttl==64 && ip6.src==2001::11 && ip6.dst==2001::10 &&
+          tcp && tcp.src==10000 && tcp.dst==50051"
+ 
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet2"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet2"])
+ ovn-nbctl --wait=hv
+ ovn-sbctl list controller_event
+ uuid=$(ovn-sbctl list controller_event | awk '/_uuid/{print $3}')
+@@ -23744,7 +23836,7 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+ 
+ wait_row_count MAC_Binding 1
+ 
+-OVS_WAIT_UNTIL([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
++OVS_WAIT_FOR_OUTPUT([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
+ list mac_binding], [0], [lr0-sw0
+ 10.0.0.30
+ 50:54:00:00:00:03
+@@ -23791,7 +23883,7 @@ grep table_id=10 | wc -l`])
+ 
+ check_row_count MAC_Binding 1
+ 
+-OVS_WAIT_UNTIL([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
++OVS_WAIT_FOR_OUTPUT([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
+ list mac_binding], [0], [lr0-sw0
+ 10.0.0.30
+ 50:54:00:00:00:13
+@@ -23820,7 +23912,7 @@ OVS_WAIT_UNTIL(
+ | wc -l`]
+ )
+ 
+-OVS_WAIT_UNTIL([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
++OVS_WAIT_FOR_OUTPUT([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
+ find mac_binding ip=10.0.0.50], [0], [lr0-sw0
+ 10.0.0.50
+ 50:54:00:00:00:33
+@@ -24377,7 +24469,7 @@ AT_CAPTURE_FILE([sbflows2])
+ OVS_WAIT_FOR_OUTPUT(
+   [ovn-sbctl dump-flows > sbflows2
+    ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
+-  [  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
++  [  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+ ])
+ 
+ # get the svc monitor mac.
+@@ -24419,8 +24511,7 @@ AT_CHECK(
+ AT_CAPTURE_FILE([sbflows4])
+ ovn-sbctl dump-flows lr0 > sbflows4
+ AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
+-  (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && reg0 == 10.0.0.10 && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
++  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip4 && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
+ ])
+ 
+ # Delete sw0-p1
+@@ -24576,7 +24667,7 @@ AT_CAPTURE_FILE([sbflows2])
+ OVS_WAIT_FOR_OUTPUT(
+   [ovn-sbctl dump-flows > sbflows2
+    ovn-sbctl dump-flows lr0 | grep ct_lb_mark | grep priority=120 | sed 's/table=..//'], 0,
+-  [  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
++  [  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && ip6.dst == 2001::a && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+ ])
+ 
+ # get the svc monitor mac.
+@@ -24618,8 +24709,7 @@ AT_CHECK(
+ AT_CAPTURE_FILE([sbflows4])
+ ovn-sbctl dump-flows lr0 > sbflows4
+ AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
+-  (lr_in_dnat         ), priority=120  , match=(ct.est && !ct.rel && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && ct_mark.natted == 1 && is_chassis_resident("cr-lr0-public")), action=(next;)
+-  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && xxreg0 == 2001::a && tcp && reg9[[16..31]] == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
++  (lr_in_dnat         ), priority=120  , match=(ct.new && !ct.rel && ip6 && ip6.dst == 2001::a && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
+ ])
+ 
+ # Delete sw0-p1
+@@ -25447,7 +25537,7 @@ for s_az in $(seq 1 $n_az); do
+                     udp && udp.src==53 && udp.dst==4369"
+             echo "sending: $packet"
+             AT_CHECK([ovn_trace --ovs "$packet" > ${s_az}-${d_az}-$i.ovn-trace])
+-            AT_CHECK([ovs-appctl -t ovn-controller inject-pkt "$packet"])
++            OVS_WAIT_UNTIL([ovs-appctl -t ovn-controller inject-pkt "$packet"])
+             ovs_inport=$(ovs-vsctl --bare --columns=ofport find Interface external-ids:iface-id="$ovn_inport")
+ 
+             ovs_packet=$(echo $packet | ovstest test-ovn expr-to-packets)
+@@ -26002,7 +26092,7 @@ for i in $(seq 5001 5010); do
+     packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+             ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==10.0.0.123 &&
+             tcp && tcp.src==$i && tcp.dst==80"
+-    AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++    OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+     for j in 1 2; do
+         # Assume all packets go to lsp2${j}.
+@@ -26121,7 +26211,7 @@ wait_for_ports_up
+ # Test 1
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+         ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==2.2.2.2 && icmp"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Assume no packets go neither to lsp21 nor to lsp22.
+ > expected_lsp21
+@@ -26151,7 +26241,7 @@ done
+ # Test 2
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+         ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==1.1.1.1 && icmp"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Assume all packets go to lsp22.
+ exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:22 &&
+@@ -26181,7 +26271,7 @@ done
+ # Test 3
+ packet="inport==\"lsp21\" && eth.src==f0:00:00:00:02:21 && eth.dst==00:00:00:01:02:01 &&
+         ip4 && ip.ttl==64 && ip4.src==192.168.2.21 && ip4.dst==2.2.2.2 && icmp"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Assume all packets go to lsp21.
+ exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 &&
+@@ -26278,7 +26368,7 @@ wait_for_ports_up
+ # test 1
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+         ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.21 && icmp"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Assume all packets go to lsp21.
+ exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 && ip4 &&
+@@ -26312,7 +26402,7 @@ ovs-vsctl set interface hv1-vif2 options:tx_pcap=hv1/vif2-tx.pcap
+ # test 2
+ packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 &&
+         ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.200 && icmp"
+-AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Assume all packets go to lsp11.
+ exp_packet="eth.src==00:00:00:01:01:01 && eth.dst==f0:00:00:00:01:11 && ip4 &&
+@@ -26417,7 +26507,7 @@ for i in $(seq 1 2); do
+     packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} &&
+             eth.dst==00:00:00:01:0${i}:01 && ip4 && ip.ttl==64 &&
+             ip4.src==192.168.${i}.${i}1 && ip4.dst==10.0.0.1 && icmp"
+-    AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++    OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+     # Assume all packets go to lsp${di}1.
+     exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} &&
+@@ -26530,7 +26620,7 @@ for i in $(seq 1 2); do
+     packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} &&
+             eth.dst==00:00:00:01:0${i}:01 && ip6 && ip.ttl==64 &&
+             ip6.src==2001:db8:${i}::${i}1 && ip6.dst==2001:db8:2000::1 && icmp6"
+-    AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
++    OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+     # Assume all packets go to lsp${di}1.
+     exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} && ip6 &&
+@@ -26650,7 +26740,7 @@ dst_ip=172.16.1.11
+ packet="inport==\"lsp11\" && eth.src==$src_mac && eth.dst==$dst_mac &&
+         ip4 && ip.ttl==64 && ip4.src==$src_ip && ip4.dst==$dst_ip &&
+         udp && udp.src==53 && udp.dst==4369"
+-check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if the packet hit the forwarding group policy
+ AT_CAPTURE_FILE([offlows2])
+@@ -27173,7 +27263,7 @@ ovn_attach n1 br-phys 192.168.0.1
+ 
+ # Chassis hv1 should add flows for the ls1 datapath in table 8 (ls_in_port_sec_l2).
+ dp_key=$(ovn-sbctl --bare --columns tunnel_key list Datapath_Binding ls1)
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=8.*metadata=0x${dp_key}"], [0], [ignore])
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=8.*metadata=0x${dp_key}"])
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -27199,7 +27289,7 @@ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+ 
+ # Port_Binding should be released.
+-OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl show | grep Port_Binding -c)], [0])
++OVS_WAIT_UNTIL([test 0 = $(ovn-sbctl show | grep Port_Binding -c)])
+ 
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+@@ -27332,22 +27422,24 @@ sleep 5
+ send_ipv4_pkt() {
+     local hv=$1 inport=$2 eth_src=$3 eth_dst=$4
+     local ip_src=$5 ip_dst=$6
+-    packet=${eth_dst}${eth_src}08004500001c0000000040110000${ip_src}${ip_dst}0035111100080000
++    local ip_cksum=$7 tcp_cksum=$8
++    packet=${eth_dst}${eth_src}080045000028000000004006${ip_cksum}${ip_src}${ip_dst}0035111112345678000000005002faf0${tcp_cksum}0000
+     tcpdump_hex $packet
+     as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
+ }
+ 
+ send_icmp6_packet() {
+-    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6
++    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_dst=$6 tcp_cksum=$7
+ 
+-    local ip6_hdr=6000000000083aff${ipv6_src}${ipv6_dst}
+-    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}8000dcb662f00001
++    local ip6_hdr=60000000001406ff${ipv6_src}${ipv6_dst}
++    local packet=${eth_dst}${eth_src}86dd${ip6_hdr}0035111112345678000000005002faf0${tcp_cksum}0000
+ 
+     as $hv ovs-appctl netdev-dummy/receive ${inport} ${packet}
+ }
+ 
+ send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \
+-    $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120)
++    $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120) \
++    c3ad 83dc
+ 
+ AT_CAPTURE_FILE([offlows2])
+ OVS_WAIT_UNTIL([
+@@ -27364,7 +27456,8 @@ AT_CHECK([
+ 
+ # Send the pkt from sw0-port2. Packet should not be marked.
+ send_ipv4_pkt hv1 hv1-vif2 505400000004 00000000ff01 \
+-    $(ip_to_hex 10 0 0 4) $(ip_to_hex 172 168 0 120)
++    $(ip_to_hex 10 0 0 4) $(ip_to_hex 172 168 0 120) \
++    c3ac 83db
+ 
+ AT_CHECK([
+     test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
+@@ -27398,7 +27491,8 @@ AT_CHECK([
+ 
+ ovn-nbctl set logical_router_policy $pol1 options:pkt_mark=2
+ send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \
+-    $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120)
++    $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120) \
++    c3ad 83dc
+ 
+ OVS_WAIT_UNTIL([
+     test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=23 | \
+@@ -27431,7 +27525,8 @@ AT_CHECK([
+ # Send with src ip 10.0.0.5. The reroute policy should be hit
+ # and the packet should be marked with 5.
+ send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \
+-    $(ip_to_hex 10 0 0 5) $(ip_to_hex 172 168 0 120)
++    $(ip_to_hex 10 0 0 5) $(ip_to_hex 172 168 0 120) \
++    c3ab 83da
+ 
+ OVS_WAIT_UNTIL([
+     test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
+@@ -27443,7 +27538,7 @@ OVS_WAIT_UNTIL([
+ src_ip6=aef00000000000000000000000000004
+ dst_ip6=bef00000000000000000000000000004
+ 
+-send_icmp6_packet hv1 hv1-vif2 505400000004 00000000ff01 ${src_ip6} ${dst_ip6}
++send_icmp6_packet hv1 hv1-vif2 505400000004 00000000ff01 ${src_ip6} ${dst_ip6} cd16
+ 
+ OVS_WAIT_UNTIL([
+     test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
+@@ -27463,7 +27558,7 @@ AT_CHECK([
+ src_ip6=aef00000000000000000000000000004
+ dst_ip6=bef00000000000000000000000000005
+ 
+-send_icmp6_packet hv1 hv1-vif2 505400000004 00000000ff01 ${src_ip6} ${dst_ip6}
++send_icmp6_packet hv1 hv1-vif2 505400000004 00000000ff01 ${src_ip6} ${dst_ip6} cd15
+ 
+ OVS_WAIT_UNTIL([
+     test 1 -eq $(as hv1 ovs-ofctl dump-flows br-phys table=0 | \
+@@ -28737,7 +28832,7 @@ src_mac="f00000000102"
+ dst_mac="000000000101"
+ src_ip=`ip_to_hex 10 0 1 2`
+ dst_ip=`ip_to_hex 10 0 1 1`
+-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
++packet=${dst_mac}${src_mac}08004500001c00000000401164cf${src_ip}${dst_ip}0035111100080000
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+ 
+ # Even after configuring a router owned IP for SNAT, no packet-ins should
+@@ -28763,7 +28858,7 @@ src_mac="f00000000202"
+ dst_mac="000000000201"
+ src_ip=`ip_to_hex 10 0 2 2`
+ dst_ip=`ip_to_hex 10 0 1 1`
+-packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
++packet=${dst_mac}${src_mac}08004500001c00000000401163cf${src_ip}${dst_ip}0035111100080000
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif2 $packet
+ 
+ # Still no packet-ins should reach ovn-controller.
+@@ -29548,7 +29643,9 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw1-p1) = xup])
+ 
+ check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
+ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
++check ovn-nbctl lb-add lb-ipv4 88.88.88.89 42.42.42.2
+ check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
++check ovn-nbctl lb-add lb-ipv6 8800::0089 4200::2
+ check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
+ 
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
+@@ -29839,6 +29936,119 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -
+  table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+ 
++check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4
++OVS_WAIT_UNTIL(
++    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 7]
++)
++
++OVS_WAIT_UNTIL(
++    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 7]
++)
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=68, priority=100,ct_mark=0x2/0x2,ip,reg1=0x58585859,nw_src=42.42.42.2,nw_dst=42.42.42.2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=90,ip,reg1=0x58585859 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.89))
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=68, priority=100,ct_mark=0x2/0x2,ip,reg1=0x58585859,nw_src=42.42.42.2,nw_dst=42.42.42.2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=90,ip,reg1=0x58585859 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.89))
++])
++
++check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6
++OVS_WAIT_UNTIL(
++    [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 8]
++)
++
++OVS_WAIT_UNTIL(
++    [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 8]
++)
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=68, priority=100,ct_mark=0x2/0x2,ip,reg1=0x58585859,nw_src=42.42.42.2,nw_dst=42.42.42.2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,ipv6,reg4=0x88000000,reg5=0,reg6=0,reg7=0x89,ipv6_src=4200::2,ipv6_dst=4200::2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=90,ip,reg1=0x58585859 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.89))
++ table=70, priority=90,ipv6,reg4=0x88000000,reg5=0,reg6=0,reg7=0x89 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::89))
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=68, priority=100,ct_mark=0x2/0x2,ip,reg1=0x58585859,nw_src=42.42.42.2,nw_dst=42.42.42.2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,ipv6,reg4=0x88000000,reg5=0,reg6=0,reg7=0x89,ipv6_src=4200::2,ipv6_dst=4200::2 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::89,NXM_OF_IP_PROTO[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68, priority=100,ct_mark=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021 actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST], [0], [dnl
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=90,ip,reg1=0x58585859 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.89))
++ table=70, priority=90,ipv6,reg4=0x88000000,reg5=0,reg6=0,reg7=0x89 actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::89))
++])
++
++check ovn-nbctl --wait=hv ls-lb-del sw0 lb-ipv4
++check ovn-nbctl --wait=hv ls-lb-del sw0 lb-ipv6
++
+ # Check backwards compatibility with ovn-northd versions that don't store the
+ # original destination tuple.
+ #
+@@ -31743,7 +31953,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
+        ip4.src==10.0.0.100 && ip4.dst==20.0.0.200 &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the drop rule
+ AT_CHECK([ovs-ofctl dump-flows br-int | grep "nw_dst=20.0.0.0/24" | \
+@@ -31770,7 +31980,7 @@ packet="inport==\"sw1-lp1\" && eth.src==00:00:04:01:02:03 &&
+        ip4.src==10.0.0.100 && ip4.dst==20.0.0.200 &&
+        udp && udp.src==53 && udp.dst==4369"
+ 
+-as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ # Check if packet hit the drop rule
+ AT_CHECK([ovs-ofctl dump-flows br-int "nw_src=10.0.0.0/24" | \
+@@ -31857,7 +32067,7 @@ packet="inport==\"ls1-lp1\" && eth.src==$ls1_p1_mac && eth.dst==$bcast_mac &&
+        arp.op==1 && arp.sha==$ls1_p1_mac && arp.spa==$ls1_p1_ip &&
+        arp.tha==$bcast_mac && arp.tpa==$proxy_ip1"
+ 
+-as hv1 ovn-appctl -t ovn-controller inject-pkt "$packet"
++OVS_WAIT_UNTIL([as hv1 ovn-appctl -t ovn-controller inject-pkt "$packet"])
+ 
+ as hv1 ovs-ofctl dump-flows br-int| grep 169.254.239.254 | grep priority=50 > debug1
+ AT_CAPTURE_FILE([debug1])
+@@ -32108,7 +32318,6 @@ ovn-nbctl lrp-set-gateway-chassis DR-S3 hv4
+ 
+ ovn-nbctl --wait=sb sync
+ OVN_POPULATE_ARP
+-
+ vif_to_ls () {
+     case ${1} in dnl (
+         vif?[[11]]) echo ls ;; dnl (
+@@ -32222,6 +32431,9 @@ echo "Send Dummy ARP"
+ sip=`ip_to_hex 172 16 1 10`
+ tip=`ip_to_hex 172 16 1 50`
+ test_arp vif-north1 f0f000000011 $sip $tip
++OVS_WAIT_UNTIL(
++    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=f0:f0:00:00:00:11 | wc -l`]
++)
+ 
+ echo "Send traffic North to South"
+ sip=`ip_to_hex 172 16 1 10`
+@@ -32242,6 +32454,9 @@ echo "Send Dummy ARP"
+ sip=`ip_to_hex 10 0 0 10`
+ tip=`ip_to_hex 10 0 0 50`
+ test_arp vif-north2 f0f000000022 $sip $tip
++OVS_WAIT_UNTIL(
++    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=f0:f0:00:00:00:22 | wc -l`]
++)
+ 
+ echo "Send traffic South to North2"
+ sip=`ip_to_hex 20 0 0 10`
+@@ -32255,6 +32470,9 @@ echo "Send Dummy ARP"
+ sip=`ip_to_hex 192 168 0 10`
+ tip=`ip_to_hex 192 168 0 50`
+ test_arp vif-north3 f0f000000033 $sip $tip
++OVS_WAIT_UNTIL(
++    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=f0:f0:00:00:00:33 | wc -l`]
++)
+ 
+ echo "Send traffic South to North3"
+ sip=`ip_to_hex 20 0 0 10`
+@@ -34926,7 +35144,8 @@ check ovs-vsctl add-port br-int p1 -- set interface p1 external_ids:iface-id=lsp
+ wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+ 
+-check ovn-nbctl lb-add lb1 "192.168.0.10" "192.168.10.10,192.168.10.20"
++check ovn-nbctl lb-add lb1 "192.168.0.10" "192.168.10.10,192.168.10.20" \
++    -- set load_balancer lb1 options:ct_flush="true"
+ check ovn-nbctl ls-lb-add sw lb1
+ 
+ # Remove a single backend
+@@ -34949,7 +35168,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.0.10:0, backend=192.168.
+ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.0.10:0, backend=192.168.10.30:0, protocol=0" hv1/ovn-controller.log], [0])
+ 
+ # Check flush for LB with port and protocol
+-check ovn-nbctl lb-add lb1 "192.168.30.10:80" "192.168.40.10:8080,192.168.40.20:8090" udp
++check ovn-nbctl lb-add lb1 "192.168.30.10:80" "192.168.40.10:8080,192.168.40.20:8090" udp \
++    -- set load_balancer lb1 options:ct_flush="true"
+ check ovn-nbctl ls-lb-add sw lb1
+ check ovn-nbctl lb-del lb1
+ check ovn-nbctl --wait=hv sync
+@@ -34958,7 +35178,8 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.30.10:80, backend=192.16
+ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.30.10:80, backend=192.168.40.20:8090, protocol=17" hv1/ovn-controller.log], [0])
+ 
+ # Check recompute when LB is no longer local
+-check ovn-nbctl lb-add lb1 "192.168.50.10:80" "192.168.60.10:8080"
++check ovn-nbctl lb-add lb1 "192.168.50.10:80" "192.168.60.10:8080" \
++    -- set load_balancer lb1 options:ct_flush="true"
+ check ovn-nbctl ls-lb-add sw lb1
+ check ovs-vsctl remove interface p1 external_ids iface-id
+ check ovn-appctl inc-engine/recompute
+@@ -34968,6 +35189,193 @@ AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.50.10:80, backend=192.16
+ 
+ AT_CHECK([test "$(grep -c "Flushing CT for 5-tuple" hv1/ovn-controller.log)" = "6"], [0])
+ 
++# Check if CT flush is disabled by default
++check ovn-nbctl lb-del lb1
++check ovn-nbctl lb-add lb1 "192.168.70.10:80" "192.168.80.10:8080,192.168.90.10:8080"
++check ovn-nbctl ls-lb-add sw lb1
++check ovs-vsctl set interface p1 external_ids:iface-id=lsp1
++check ovn-nbctl --wait=hv sync
++
++AT_CHECK([test "$(grep -c "Flushing CT for 5-tuple" hv1/ovn-controller.log)" = "6"], [0])
++
++# Remove one backend
++check ovn-nbctl --wait=hv set load_balancer lb1 vips='"192.168.70.10:80"="192.168.80.10:8080"'
++
++AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.70.10:80, backend=192.168.90.10:8080, protocol=6" hv1/ovn-controller.log], [1])
++AT_CHECK([test "$(grep -c "Flushing CT for 5-tuple" hv1/ovn-controller.log)" = "6"], [0])
++
++check ovn-nbctl --wait=hv lb-del lb1
++AT_CHECK([grep -q "Flushing CT for 5-tuple: vip=192.168.70.10:80, backend=192.168.80.10:8080, protocol=6" hv1/ovn-controller.log], [1])
++AT_CHECK([test "$(grep -c "Flushing CT for 5-tuple" hv1/ovn-controller.log)" = "6"], [0])
++
++OVN_CLEANUP([hv1])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Re-create encap tunnels during integration bridge migration])
++ovn_start
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++
++sim_add hv2
++as hv2
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.2
++
++check ovn-nbctl --wait=hv sync
++
++check_tunnel_port() {
++    local hv=$1
++    local br=$2
++    local id=$3
++
++    as $hv
++    OVS_WAIT_UNTIL([
++        test "$(ovs-vsctl --format=table --no-headings find port external_ids:ovn-chassis-id="$id" | wc -l)" = "1"
++    ])
++    local tunnel_id=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="$id")
++    AT_CHECK([ovs-vsctl --bare --columns ports find bridge name="$br" | grep -q "$tunnel_id"])
++}
++
++# Check that both chassis have tunnel
++check_tunnel_port hv1 br-int hv2@192.168.0.2
++check_tunnel_port hv2 br-int hv1@192.168.0.1
++
++# Stop ovn-controller on hv1
++check as hv1 ovn-appctl -t ovn-controller exit --restart
++
++# The tunnel should remain intact
++check_tunnel_port hv1 br-int hv2@192.168.0.2
++
++# Change the bridge to br-int1 on hv1
++as hv1
++check ovs-vsctl add-br br-int1
++check ovs-vsctl set open . external_ids:ovn-bridge="br-int1"
++start_daemon ovn-controller --verbose="encaps:dbg"
++check ovn-nbctl --wait=hv sync
++
++# Check that the tunnel was created on br-int1 instead
++check_tunnel_port hv1 br-int1 hv2@192.168.0.2
++check grep -q "Clearing old tunnel port \"ovn-hv2-0\" (hv2@192.168.0.2) from bridge \"br-int\"" hv1/ovn-controller.log
++
++# Change the bridge to br-int1 on hv2
++as hv2
++check ovn-appctl vlog/set encaps:dbg
++check ovs-vsctl add-br br-int1
++check ovs-vsctl set open . external_ids:ovn-bridge="br-int1"
++check ovn-nbctl --wait=hv sync
++
++
++# Check that the tunnel was created on br-int1 instead
++check_tunnel_port hv2 br-int1 hv1@192.168.0.1
++check grep -q "Clearing old tunnel port \"ovn-hv1-0\" (hv1@192.168.0.1) from bridge \"br-int\"" hv2/ovn-controller.log
++
++# Stop ovn-controller on hv1
++check as hv1 ovn-appctl -t ovn-controller exit --restart
++
++# The tunnel should remain intact
++check_tunnel_port hv1 br-int1 hv2@192.168.0.2
++prev_id=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv2@192.168.0.2")
++
++# Start the controller again
++start_daemon ovn-controller --verbose="encaps:dbg"
++check ovn-nbctl --wait=hv sync
++check_tunnel_port hv1 br-int1 hv2@192.168.0.2
++current_id=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv2@192.168.0.2")
++
++# The tunnel should be the same after restart
++check test "$current_id" = "$prev_id"
++
++OVN_CLEANUP([hv1],[hv2])
++AT_CLEANUP
++])
++
++# NOTE: This test case runs two ovn-controllers inside the same sandbox (hv1).
++# Each controller uses a unique chassis name - hv1 and hv2 - and manage
++# different bridges with different ports.
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([Encaps tunnel cleanup does not interfere with multiple controller on the same host])
++ovn_start
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys-1
++ovn_attach n1 br-phys-1 192.168.0.1 24
++
++
++# now start the second virtual controller
++ovs-vsctl add-br br-phys-2
++
++
++# the file is read once at startup so it's safe to write it
++# here after the first ovn-controller has started
++echo hv2 > ${OVN_SYSCONFDIR}/system-id-override
++
++# for some reason SSL ovsdb configuration overrides CLI, so
++# delete ssl config from ovsdb to give CLI arguments priority
++ovs-vsctl del-ssl
++
++start_virtual_controller n1 br-phys-2 br-int-2 192.168.0.2 24 geneve,vxlan hv2 \
++    --pidfile=${OVS_RUNDIR}/ovn-controller-2.pid \
++    --log-file=${OVS_RUNDIR}/ovn-controller-2.log \
++    -p $PKIDIR/testpki-hv2-privkey.pem \
++    -c $PKIDIR/testpki-hv2-cert.pem \
++    -C $PKIDIR/testpki-cacert.pem
++pidfile="$OVS_RUNDIR"/ovn-controller-2.pid
++on_exit "test -e \"$pidfile\" && kill \`cat \"$pidfile\"\`"
++
++ovn-nbctl --wait=hv sync
++
++check_tunnel_port() {
++    local hv=$1
++    local br=$2
++    local id=$3
++
++    as $hv
++    OVS_WAIT_UNTIL([
++        test "$(ovs-vsctl --format=table --no-headings find port external_ids:ovn-chassis-id="$id" | wc -l)" = "1"
++    ])
++    local tunnel_id=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="$id")
++    AT_CHECK([ovs-vsctl --bare --columns ports find bridge name="$br" | grep -q "$tunnel_id"])
++}
++
++check_tunnel_port hv1 br-int hv2@192.168.0.2
++check_tunnel_port hv1 br-int-2 hv1@192.168.0.1
++prev_id1=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv1@192.168.0.1")
++prev_id2=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv2@192.168.0.2")
++
++# The hv2 is running we can remove the override file
++rm -f ${OVN_SYSCONFDIR}/system-id-override
++
++check ovn-appctl -t ovn-controller exit --restart
++
++# for some reason SSL ovsdb configuration overrides CLI, so
++# delete ssl config from ovsdb to give CLI arguments priority
++ovs-vsctl del-ssl
++
++start_daemon ovn-controller --verbose="encaps:dbg" \
++    -p $PKIDIR/testpki-hv1-privkey.pem \
++    -c $PKIDIR/testpki-hv1-cert.pem \
++    -C $PKIDIR/testpki-cacert.pem
++
++check ovn-nbctl --wait=hv sync
++
++check_tunnel_port hv1 br-int hv2@192.168.0.2
++check_tunnel_port hv1 br-int-2 hv1@192.168.0.1
++current_id1=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv1@192.168.0.1")
++current_id2=$(ovs-vsctl --bare --columns _uuid find port external_ids:ovn-chassis-id="hv2@192.168.0.2")
++
++# Check that restart of hv1 ovn-controller did not interfere with hv2
++AT_CHECK([grep -q "Clearing old tunnel port \"ovn0-hv1-0\" (hv1@192.168.0.1) from bridge \"br-int-2\"" hv1/ovn-controller.log], [1])
++check test "$current_id1" = "$prev_id1"
++check test "$current_id2" = "$prev_id2"
++
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
+ ])
+diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
+index 36b58b5ae..cc5f6e3b1 100644
+--- a/tests/ovs-macros.at
++++ b/tests/ovs-macros.at
+@@ -256,6 +256,13 @@ ovs_wait () {
+     ovs_wait_failed
+     AT_FAIL_IF([:])
+ }
++
++check_ovs_wait_until_args() {
++   AT_FAIL_IF([test $1 -ge 3])
++   dnl The second argument should not be a number (confused with AT_CHECK ?).
++   AT_FAIL_IF([test $1 -eq 2 && test "$2" -eq "$2" 2>/dev/null])
++}
++
+ OVS_END_SHELL_HELPERS
+ m4_define([OVS_WAIT], [dnl
+ ovs_wait_cond () {
+@@ -276,7 +283,8 @@ dnl zero code within reasonable time limit, then
+ dnl the test fails.  In that case, runs IF-FAILED
+ dnl before aborting.
+ m4_define([OVS_WAIT_UNTIL],
+-  [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
++  [check_ovs_wait_until_args "$#" "$2"
++   OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
+ 
+ dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR)
+ dnl OVS_WAIT_FOR_OUTPUT_UNQUOTED(COMMAND, EXIT-STATUS, STDOUT, STDERR)
+diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
+index d65f359a6..b8c5ae9ad 100644
+--- a/tests/system-common-macros.at
++++ b/tests/system-common-macros.at
+@@ -44,43 +44,8 @@ m4_define([NS_CHECK_EXEC],
+ # appropriate type, and allows additional arguments to be passed.
+ m4_define([ADD_BR], [ovs-vsctl _ADD_BR([$1]) -- $2])
+ 
+-# ADD_INT([port], [namespace], [ovs-br], [ip_addr] [ip6_addr])
+-#
+-# Add an internal port to 'ovs-br', then shift it into 'namespace' and
+-# configure it with 'ip_addr' (specified in CIDR notation).
+-# Optionally add an ipv6 address
+-m4_define([ADD_INT],
+-    [ AT_CHECK([ovs-vsctl add-port $3 $1 -- set int $1 type=internal])
+-      AT_CHECK([ip link set $1 netns $2])
+-      NS_CHECK_EXEC([$2], [ip addr add $4 dev $1])
+-      NS_CHECK_EXEC([$2], [ip link set dev $1 up])
+-      if test -n "$5"; then
+-        NS_CHECK_EXEC([$2], [ip -6 addr add $5 dev $1])
+-      fi
+-    ]
+-)
+-
+-# NS_ADD_INT([port], [namespace], [ovs-br], [ip_addr] [mac_addr] [ip6_addr] [default_gw] [default_ipv6_gw])
+-# Create a namespace
+-# Add an internal port to 'ovs-br', then shift it into 'namespace'.
+-# Configure it with 'ip_addr' (specified in CIDR notation) and ip6_addr.
+-# Set mac_addr
+-# Add default gw for ipv4 and ipv6
+-m4_define([NS_ADD_INT],
+-    [ AT_CHECK([ovs-vsctl add-port $3 $1 -- set int $1 type=internal  external_ids:iface-id=$1])
+-      ADD_NAMESPACES($2)
+-      AT_CHECK([ip link set $1 netns $2])
+-      NS_CHECK_EXEC([$2], [ip link set $1 address $5])
+-      NS_CHECK_EXEC([$2], [ip link set dev $1 up])
+-      NS_CHECK_EXEC([$2], [ip addr add $4 dev $1])
+-      NS_CHECK_EXEC([$2], [ip addr add $6 dev $1])
+-      NS_CHECK_EXEC([$2], [ip route add default via $7 dev $1])
+-      NS_CHECK_EXEC([$2], [ip -6 route add default via $8 dev $1])
+-    ]
+-)
+-
+ # ADD_VETH([port], [namespace], [ovs-br], [ip_addr] [mac_addr], [gateway],
+-#          [ip_addr_flags])
++#          [ip_addr_flags] [ip6_addr] [gateway6])
+ #
+ # Add a pair of veth ports. 'port' will be added to name space 'namespace',
+ # and "ovs-'port'" will be added to ovs bridge 'ovs-br'.
+@@ -108,6 +73,12 @@ m4_define([ADD_VETH],
+       if test -n "$6"; then
+         NS_CHECK_EXEC([$2], [ip route add default via $6])
+       fi
++      if test -n "$8"; then
++        NS_CHECK_EXEC([$2], [ip addr add $8 dev $1])
++      fi
++      if test -n "$9"; then
++        NS_CHECK_EXEC([$2], [ip route add default via $9])
++      fi
+       on_exit "ip link del ovs-$1"
+     ]
+ )
+@@ -263,7 +234,7 @@ m4_define([STRIP_MONITOR_CSUM], [grep "csum:" | sed 's/csum:.*/csum: <skip>/'])
+ # and limit the output to the rows containing 'ip-addr'.
+ #
+ m4_define([FORMAT_CT],
+-    [[grep -F "dst=$1" | sed -e 's/port=[0-9]*/port=<cleared>/g' -e 's/id=[0-9]*/id=<cleared>/g' -e 's/state=[0-9_A-Z]*/state=<cleared>/g' | sort | uniq]])
++    [[grep -F "dst=$1," | sed -e 's/port=[0-9]*/port=<cleared>/g' -e 's/id=[0-9]*/id=<cleared>/g' -e 's/state=[0-9_A-Z]*/state=<cleared>/g' | sort | uniq]])
+ 
+ # NETNS_DAEMONIZE([namespace], [command], [pidfile])
+ #
+diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
+index dd4996041..3c3e5bc61 100644
+--- a/tests/system-ovn-kmod.at
++++ b/tests/system-ovn-kmod.at
+@@ -215,3 +215,139 @@ as
+ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ /connection dropped.*/d"])
+ AT_CLEANUP
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([LB correctly de-fragments traffic])
++AT_KEYWORDS([ovnlb])
++
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++AT_SKIP_IF([test $HAVE_SCAPY = no])
++
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-ext])
++
++# Logical network:
++# 2 logical switches "public" (192.168.1.0/24) and "internal" (172.16.1.0/24)
++# connected to a router lr.
++# internal has a server.
++# client is connected through localnet.
++#
++# Load balancer for udp 192.168.1.20:4242 172.16.1.2 4242.
++
++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
++
++
++# 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(client)
++ADD_VETH(client, client, br-ext, "192.168.1.2/24", "f0:00:00:01:02:03", \
++         "192.168.1.1")
++
++ADD_NAMESPACES(server)
++ADD_VETH(server, server, br-int, "172.16.1.2/24", "f0:00:0f:01:02:03", \
++         "172.16.1.1")
++check ovn-nbctl lsp-add internal server \
++-- lsp-set-addresses server "f0:00:0f:01:02:03 172.16.1.2"
++
++# Config OVN load-balancer with a VIP.
++check ovn-nbctl lb-add lb1 192.168.1.20:4242 172.16.1.2:4242 udp
++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
++
++ovn-nbctl --wait=hv sync
++
++NETNS_DAEMONIZE([server], [nc -l -u 172.16.1.2 4242 > /dev/null], [server.pid])
++
++# Collect ICMP packets on client side
++NETNS_DAEMONIZE([client], [tcpdump -l -U -i client -vnne \
++icmp > client.pcap 2>client_err], [tcpdump0.pid])
++OVS_WAIT_UNTIL([grep "listening" client_err])
++
++# Collect UDP packets on server side
++NETNS_DAEMONIZE([server], [tcpdump -l -U -i server -vnne \
++'udp and ip[[6:2]] > 0 and not ip[[6]] = 64' > server.pcap 2>server_err], [tcpdump1.pid])
++OVS_WAIT_UNTIL([grep "listening" server_err])
++
++check ip netns exec client python3 << EOF
++import os
++import socket
++import sys
++import time
++
++FILE="client.pcap"
++
++
++def contains_string(file, str):
++    file = open(file, "r")
++    for line in file.readlines():
++        if str in line:
++            return True
++    return False
++
++
++def need_frag_received():
++    for _ in range(20):
++        if os.path.getsize(FILE) and contains_string(FILE, "need to frag"):
++            return True
++        time.sleep(0.5)
++    return False
++
++
++sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
++sock.sendto(b"x" * 1000, ("192.168.1.20", 4242))
++if need_frag_received():
++    sock.sendto(b"x" * 1000, ("192.168.1.20", 4242))
++else:
++    print("Missing need frag")
++    sys.exit(1)
++EOF
++
++OVS_WAIT_UNTIL([test "$(cat server.pcap | wc -l)" = "4"])
++
++
++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 84a459d6a..40f808515 100644
+--- a/tests/system-ovn.at
++++ b/tests/system-ovn.at
+@@ -1569,7 +1569,6 @@ bar3_ct=$(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1.4 -c)
+ AT_CHECK([test $(ovs-appctl dpctl/dump-conntrack | grep 30.0.0.2 | grep 172.16.1 -c) -ne 0])
+ 
+ if [[ "$bar1_ct" == "20" ]]; then
+-    AT_CHECK([test $bar1_ct -eq 20])
+     AT_CHECK([test $bar2_ct -eq 0])
+     AT_CHECK([test $bar3_ct -eq 0])
+ else
+@@ -1577,17 +1576,15 @@ else
+ fi
+ 
+ if [[ "$bar2_ct" == "20" ]]; then
+-    AT_CHECK([test $bar1_ct -eq 20])
+-    AT_CHECK([test $bar2_ct -eq 0])
++    AT_CHECK([test $bar1_ct -eq 0])
+     AT_CHECK([test $bar3_ct -eq 0])
+ else
+     AT_CHECK([test $bar2_ct -eq 0])
+ fi
+ 
+ if [[ "$bar3_ct" == "20" ]]; then
+-    AT_CHECK([test $bar1_ct -eq 20])
++    AT_CHECK([test $bar1_ct -eq 0])
+     AT_CHECK([test $bar2_ct -eq 0])
+-    AT_CHECK([test $bar3_ct -eq 0])
+ else
+     AT_CHECK([test $bar3_ct -eq 0])
+ fi
+@@ -4850,9 +4847,9 @@ NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp ${filter} > lsp.pcap 2>tcpdump_
+ OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+ 
+ # Generate IPv4 UDP hairpin traffic.
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.88 4040 &], [0])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.89 4040 &], [0])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.90 2021 &], [0])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.88 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.89 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 88.88.88.90 2021], [ignore], [ignore], [ignore])
+ 
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+@@ -4949,9 +4946,9 @@ NS_CHECK_EXEC([lsp], [tcpdump -l -nn -c 3 -i lsp $filter > lsp.pcap 2>tcpdump_er
+ OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+ 
+ # Generate IPv6 UDP hairpin traffic.
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0088 4040 &], [0])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0089 4040 &], [0])
+-NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0090 2021 &], [0])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0088 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0089 4040], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([lsp], [echo a | nc -u 8800::0090 2021], [ignore], [ignore], [ignore])
+ 
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+@@ -7190,7 +7187,7 @@ NS_EXEC([sw01], [tcpdump -l -n -i sw01 icmp -Q in > reject.pcap &])
+ check ovn-nbctl --may-exist meter-add acl-meter drop 10 pktps 0
+ ip netns exec sw01 scapy -H <<-EOF
+ 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 = 100)
++send (p, iface='sw01', loop = 0, verbose = 0, count = 40)
+ EOF
+ 
+ # 10pps
+@@ -8482,11 +8479,18 @@ check ovn-nbctl lsp-set-addresses ln unknown
+ 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
+ 
+-NS_ADD_INT(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01", "2001::1/64", "192.168.1.254", "2001::a" )
+-NS_ADD_INT(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02", "2001::2/64", "192.168.1.254", "2001::a" )
++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_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_NAMESPACES(ext1)
+-ADD_INT(ext1, ext1, br0, 172.16.1.1/24, 1711::1/64)
++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")
++
+ 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)" = ""])
+@@ -8548,25 +8552,17 @@ wait_igmp_flows_installed()
+ }
+ 
+ ADD_NAMESPACES(vm1)
+-ADD_INT([vm1], [vm1], [br-int], [42.42.42.1/24])
+-NS_CHECK_EXEC([vm1], [ip link set vm1 address 00:00:00:00:00:01], [0])
+-NS_CHECK_EXEC([vm1], [ip route add default via 42.42.42.5], [0])
+-check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
++ADD_VETH(vm1, vm1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
++         "42.42.42.5")
+ 
+ ADD_NAMESPACES(vm2)
+-ADD_INT([vm2], [vm2], [br-int], [42.42.42.2/24])
+-NS_CHECK_EXEC([vm2], [ip link set vm2 address 00:00:00:00:00:02], [0])
+-NS_CHECK_EXEC([vm2], [ip link set lo up], [0])
+-check ovs-vsctl set Interface vm2 external_ids:iface-id=vm2
++ADD_VETH(vm2, vm2, br-int, "42.42.42.2/24", "00:00:00:00:00:02")
+ 
+ ADD_NAMESPACES(vm3)
+ NETNS_DAEMONIZE([vm3], [tcpdump -n -i any -nnleX > vm3.pcap 2>/dev/null], [tcpdump3.pid])
+ 
+-ADD_INT([vm3], [vm3], [br-int], [42.42.42.3/24])
+-NS_CHECK_EXEC([vm3], [ip link set vm3 address 00:00:00:00:00:03], [0])
+-NS_CHECK_EXEC([vm3], [ip link set lo up], [0])
+-NS_CHECK_EXEC([vm3], [ip route add default via 42.42.42.5], [0])
+-check ovs-vsctl set Interface vm3 external_ids:iface-id=vm3
++ADD_VETH(vm3, vm3, br-int, "42.42.42.3/24", "00:00:00:00:00:03", \
++         "42.42.42.5")
+ 
+ NS_CHECK_EXEC([vm2], [sysctl -w net.ipv4.igmp_max_memberships=100], [ignore], [ignore])
+ NS_CHECK_EXEC([vm3], [sysctl -w net.ipv4.igmp_max_memberships=100], [ignore], [ignore])
+@@ -9639,7 +9635,7 @@ start_daemon ovn-controller
+ #         |
+ # VM2 ----+
+ #
+-# Two templated load balancer applied on LS1 and GW-Router with
++# Four templated load balancer applied on LS1 and GW-Router with
+ # VM1 as backend.  The VIPs should be accessible from both VM2 and VM3.
+ 
+ check ovn-nbctl                                                   \
+@@ -9667,7 +9663,7 @@ check ovn-nbctl                                                   \
+ # VIP=66.66.66.66:777 backends=42.42.42.2:4343 proto=udp
+ 
+ AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" \
+-    variables="{vip=66.66.66.66,vport1=666,backends1=\"42.42.42.2:4242\",vport2=777,backends2=\"42.42.42.2:4343\"}"],
++    variables="{vip=66.66.66.66,vport1=666,backends1=\"42.42.42.2:4242\",vport2=777,backends2=\"42.42.42.2:4343\",vport3=888,vport4=999}"],
+          [0], [ignore])
+ 
+ check ovn-nbctl --template lb-add lb-test-tcp "^vip:^vport1" "^backends1" tcp \
+@@ -9678,6 +9674,18 @@ check ovn-nbctl --template lb-add lb-test-udp "^vip:^vport2" "^backends2" udp \
+     -- ls-lb-add ls1 lb-test-udp                                              \
+     -- lr-lb-add rtr lb-test-udp
+ 
++# Add a TCP template LB with explicit backends that eventually expands to:
++# VIP=66.66.66.66:888 backends=42.42.42.2:4242 proto=tcp
++# And a UDP template LB that eventually expands to:
++# VIP=66.66.66.66:999 backends=42.42.42.2:4343 proto=udp
++check ovn-nbctl --template lb-add lb-test-tcp2 "^vip:^vport3" "42.42.42.2:4242" tcp ipv4 \
++    -- ls-lb-add ls1 lb-test-tcp2                                                        \
++    -- lr-lb-add rtr lb-test-tcp2
++
++check ovn-nbctl --template lb-add lb-test-udp2 "^vip:^vport4" "42.42.42.2:4343" udp ipv4 \
++    -- ls-lb-add ls1 lb-test-udp2                                                        \
++    -- lr-lb-add rtr lb-test-udp2
++
+ ADD_NAMESPACES(vm1)
+ ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1")
+ 
+@@ -9698,13 +9706,15 @@ name: 'backends2' value: '42.42.42.2:4343'
+ name: 'vip' value: '66.66.66.66'
+ name: 'vport1' value: '666'
+ name: 'vport2' value: '777'
++name: 'vport3' value: '888'
++name: 'vport4' value: '999'
+ ])
+ 
+ # Start IPv4 TCP server on vm1.
+ NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid])
+ 
+ NETNS_DAEMONIZE([vm1],
+-    [tcpdump -n -i vm1 -nnleX -c3 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>/dev/null],
+     [tcpdump1.pid])
+ 
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+@@ -9712,13 +9722,21 @@ NS_CHECK_EXEC([vm1], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+ NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+ NS_CHECK_EXEC([vm3], [nc 66.66.66.66 666 -z], [0], [ignore], [ignore])
+ 
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 66.66.66.66 777 &], [0])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 777 &], [0])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 777 &], [0])
++NS_CHECK_EXEC([vm1], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 777], [ignore], [ignore], [ignore])
++
++NS_CHECK_EXEC([vm1], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [nc 66.66.66.66 888 -z], [0], [ignore], [ignore])
++
++NS_CHECK_EXEC([vm1], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | nc -u 66.66.66.66 999], [ignore], [ignore], [ignore])
+ 
+ OVS_WAIT_UNTIL([
+     requests=`grep "UDP" -c vm1.pcap`
+-    test "${requests}" -ge "3"
++    test "${requests}" -ge "6"
+ ])
+ 
+ AT_CLEANUP
+@@ -9753,7 +9771,7 @@ start_daemon ovn-controller
+ #         |
+ # VM2 ----+
+ #
+-# Two templated load balancer applied on LS1 and GW-Router with
++# Four templated load balancer applied on LS1 and GW-Router with
+ # VM1 as backend.  The VIPs should be accessible from both VM2 and VM3.
+ 
+ check ovn-nbctl                                                   \
+@@ -9781,7 +9799,7 @@ check ovn-nbctl                                                   \
+ # VIP=[6666::1]:777 backends=[4242::2]:4343 proto=udp
+ 
+ AT_CHECK([ovn-nbctl -- create chassis_template_var chassis="hv1" \
+-    variables="{vip=\"6666::1\",vport1=666,backends1=\"[[4242::2]]:4242\",vport2=777,backends2=\"[[4242::2]]:4343\"}"],
++    variables="{vip=\"6666::1\",vport1=666,backends1=\"[[4242::2]]:4242\",vport2=777,backends2=\"[[4242::2]]:4343\",vport3=888,vport4=999}"],
+          [0], [ignore])
+ 
+ check ovn-nbctl --template lb-add lb-test-tcp "^vip:^vport1" "^backends1" tcp ipv6 \
+@@ -9792,6 +9810,18 @@ check ovn-nbctl --template lb-add lb-test-udp "^vip:^vport2" "^backends2" udp ip
+     -- ls-lb-add ls1 lb-test-udp                                                   \
+     -- lr-lb-add rtr lb-test-udp
+ 
++# Add a TCP template LB with explicit backends that eventually expands to:
++# VIP=[6666::1]:888 backends=[4242::2]:4242 proto=tcp
++# And a UDP template LB that eventually expands to:
++# VIP=[6666::1]:999 backends=[4242::2]:4343 proto=udp
++check ovn-nbctl --template lb-add lb-test-tcp2 "^vip:^vport3" "[[4242::2]]:4242" tcp ipv6 \
++    -- ls-lb-add ls1 lb-test-tcp2                                                         \
++    -- lr-lb-add rtr lb-test-tcp2
++
++check ovn-nbctl --template lb-add lb-test-udp2 "^vip:^vport4" "[[4242::2]]:4343" udp ipv6 \
++    -- ls-lb-add ls1 lb-test-udp2                                                         \
++    -- 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)" = ""])
+@@ -9815,13 +9845,15 @@ name: 'backends2' value: '[[4242::2]]:4343'
+ name: 'vip' value: '6666::1'
+ name: 'vport1' value: '666'
+ name: 'vport2' value: '777'
++name: 'vport3' value: '888'
++name: 'vport4' value: '999'
+ ])
+ 
+ # Start IPv6 TCP server on vm1.
+ NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid])
+ 
+ NETNS_DAEMONIZE([vm1],
+-    [tcpdump -n -i vm1 -nnleX -c3 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>/dev/null],
+     [tcpdump1.pid])
+ 
+ # Make sure connecting to the VIP works (hairpin, via ls and via lr).
+@@ -9829,13 +9861,21 @@ NS_CHECK_EXEC([vm1], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+ NS_CHECK_EXEC([vm2], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+ NS_CHECK_EXEC([vm3], [nc 6666::1 666 -z], [0], [ignore], [ignore])
+ 
+-NS_CHECK_EXEC([vm1], [echo a | nc -u 6666::1 777 &], [0])
+-NS_CHECK_EXEC([vm2], [echo a | nc -u 6666::1 777 &], [0])
+-NS_CHECK_EXEC([vm3], [echo a | nc -u 6666::1 777 &], [0])
++NS_CHECK_EXEC([vm1], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | nc -u 6666::1 777], [ignore], [ignore], [ignore])
++
++NS_CHECK_EXEC([vm1], [nc 6666::1 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [nc 6666::1 888 -z], [0], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [nc 6666::1 888 -z], [0], [ignore], [ignore])
++
++NS_CHECK_EXEC([vm1], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm2], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
++NS_CHECK_EXEC([vm3], [echo a | nc -u 6666::1 999], [ignore], [ignore], [ignore])
+ 
+ OVS_WAIT_UNTIL([
+     requests=`grep "UDP" -c vm1.pcap`
+-    test "${requests}" -ge "3"
++    test "${requests}" -ge "6"
+ ])
+ 
+ AT_CLEANUP
+@@ -10587,11 +10627,13 @@ check ovn-nbctl lsp-add bar bar3 \
+ -- lsp-set-addresses bar3 "f0:00:0f:01:02:05 172.16.1.4"
+ 
+ # Config OVN load-balancer with a VIP.
+-check ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
++check ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4" \
++    -- set load_balancer lb1 options:ct_flush="true"
+ check ovn-nbctl ls-lb-add foo lb1
+ 
+ # Create another load-balancer with another VIP.
+ lb2_uuid=`ovn-nbctl create load_balancer name=lb2 vips:30.0.0.3="172.16.1.2,172.16.1.3,172.16.1.4"`
++check ovn-nbctl set load_balancer lb2 options:ct_flush="true"
+ check ovn-nbctl ls-lb-add foo lb2
+ 
+ # Config OVN load-balancer with another VIP (this time with ports).
+@@ -10607,16 +10649,18 @@ OVS_START_L7([bar1], [http])
+ OVS_START_L7([bar2], [http])
+ OVS_START_L7([bar3], [http])
+ 
+-OVS_WAIT_FOR_OUTPUT([
+-    for i in `seq 1 20`; do
+-        ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+-    done
+-    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
++m4_define([LB1_CT_ENTRIES], [dnl
+ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.3,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ tcp,orig=(src=192.168.1.2,dst=30.0.0.1,sport=<cleared>,dport=<cleared>),reply=(src=172.16.1.4,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
+ ])
+ 
++OVS_WAIT_FOR_OUTPUT([
++    for i in `seq 1 20`; do
++        ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++    done
++    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
++
+ OVS_WAIT_FOR_OUTPUT([
+     for i in `seq 1 20`; do
+         ip netns exec foo1 wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
+@@ -10690,6 +10734,191 @@ check ovn-nbctl lb-del lb2
+ 
+ OVS_WAIT_UNTIL([test "$(ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.3) | wc -l)" = "0"])
+ 
++# Check that LB has CT flush disabled by default
++check ovn-nbctl lb-add lb1 30.0.0.1 "172.16.1.2,172.16.1.3,172.16.1.4"
++check ovn-nbctl ls-lb-add foo lb1
++
++OVS_WAIT_FOR_OUTPUT([
++    for i in `seq 1 20`; do
++        ip netns exec foo1 wget 30.0.0.1 -t 5 -T 1 --retry-connrefused -v -o wget$i.log;
++    done
++    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
++
++# Remove one backend
++check ovn-nbctl --wait=hv set load_balancer lb1 vips='"30.0.0.1"="172.16.1.2,172.16.1.3"'
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
++
++# Remove whole LB
++check ovn-nbctl --wait=hv lb-del lb1
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [LB1_CT_ENTRIES])
++
++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
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ACL and committing to conntrack])
++AT_KEYWORDS([acl])
++
++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 lrp-add r1 r1_s1 00:de:ad:fe:00:01 173.0.1.1/24
++check ovn-nbctl lrp-add r1 r1_s2 00:de:ad:fe:00:02 173.0.2.1/24
++
++check ovn-nbctl ls-add s1
++check ovn-nbctl lsp-add s1 s1_r1
++check ovn-nbctl lsp-set-type s1_r1 router
++check ovn-nbctl lsp-set-addresses s1_r1 router
++check ovn-nbctl lsp-set-options s1_r1 router-port=r1_s1
++
++check ovn-nbctl ls-add s2
++check ovn-nbctl lsp-add s2 s2_r1
++check ovn-nbctl lsp-set-type s2_r1 router
++check ovn-nbctl lsp-set-addresses s2_r1 router
++check ovn-nbctl lsp-set-options s2_r1 router-port=r1_s2
++
++check ovn-nbctl lsp-add s1 vm1
++check ovn-nbctl lsp-set-addresses vm1 "00:de:ad:01:00:01 173.0.1.2"
++
++check ovn-nbctl lsp-add s2 vm2
++check ovn-nbctl lsp-set-addresses vm2 "00:de:ad:01:00:02 173.0.2.2"
++
++check ovn-nbctl lsp-add s2 vm3
++check ovn-nbctl lsp-set-addresses vm3 "00:de:ad:01:00:03 173.0.2.3"
++
++check ovn-nbctl lb-add lb1 30.0.0.1:80 173.0.2.2:80 udp
++check ovn-nbctl lb-add lb2 20.0.0.1:80 173.0.1.2:80 udp
++check ovn-nbctl lb-add lb1 30.0.0.1 173.0.2.2
++check ovn-nbctl lb-add lb2 173.0.2.250 173.0.1.3
++check ovn-nbctl ls-lb-add s1 lb1
++check ovn-nbctl ls-lb-add s2 lb2
++
++ADD_NAMESPACES(vm1)
++ADD_VETH(vm1, vm1, br-int, "173.0.1.2/24", "00:de:ad:01:00:01", \
++         "173.0.1.1")
++ADD_NAMESPACES(vm2)
++ADD_VETH(vm2, vm2, br-int, "173.0.2.2/24", "00:de:ad:01:00:02", \
++         "173.0.2.1")
++ADD_NAMESPACES(vm3)
++ADD_VETH(vm3, vm3, br-int, "173.0.2.250/24", "00:de:ad:01:00:03", \
++         "173.0.2.1")
++
++check ovn-nbctl acl-add s1 from-lport 1001 "ip" allow
++check ovn-nbctl acl-add s1 to-lport 1002 "ip" allow
++check ovn-nbctl acl-add s2 from-lport 1003 "ip" allow
++check ovn-nbctl acl-add s2 to-lport 1004 "ip" allow
++check ovn-nbctl --wait=hv sync
++AS_BOX([initial ping])
++# Send ping in background. Same ping, same flow throughout the test
++on_exit 'kill $(pidof ping)'
++NS_EXEC([vm1], [ping -c 10000 -i 0.1 30.0.0.1 > icmp.txt &])
++
++# Check for conntrack entries
++OVS_WAIT_FOR_OUTPUT([
++    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(173.0.1.2) | \
++      sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
++icmp,orig=(src=173.0.1.2,dst=173.0.2.2,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>
++icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=2
++])
++
++# Now check for multiple ct_commits
++ovs-appctl dpctl/dump-flows > dp_flows
++zone_id=$(ovn-appctl -t ovn-controller ct-zone-list | grep vm1 | cut -d ' ' -f2)
++AT_CHECK([test 1 = `cat dp_flows | grep "commit,zone=$zone_id" | wc -l`])
++
++check ovn-nbctl acl-del s1 from-lport 1001 "ip"
++check ovn-nbctl acl-del s1 to-lport 1002 "ip"
++check ovn-nbctl acl-del s2 from-lport 1003 "ip"
++check ovn-nbctl acl-del s2 to-lport 1004 "ip"
++
++AS_BOX([acl drop echo request])
++check ovn-nbctl --log --severity=alert --name=drop-flow-s1 acl-add s1 to-lport 2001 icmp4 drop
++# acl-drop to-lport s1 apply to traffic from s1 to vm1 and s1 to r1.
++check ovn-nbctl --wait=hv sync
++
++# Check that traffic is blocked
++# Wait for some packets to hit the rule to avoid potential race conditions. Then count packets.
++OVS_WAIT_UNTIL([test `cat ovn-controller.log | grep acl_log | grep -c drop-flow-s1` -gt "0"])
++total_icmp_pkts=$(cat icmp.txt | grep ttl | wc -l)
++
++# Wait some time and check whether packets went through. In the worse race condition, the sleep is too short
++# and this test will still succeed.
++sleep 1
++OVS_WAIT_UNTIL([
++        total_icmp1_pkts=$(cat icmp.txt | grep ttl | wc -l)
++        test "${total_icmp1_pkts}" -eq "${total_icmp_pkts}"
++])
++
++AS_BOX([acl allow-related echo request])
++check ovn-nbctl acl-add s1 to-lport 2002 "icmp4 && ip4.src == 173.0.1.2" allow-related
++# This rule has higher priority than to-lport 2001 icmp4 drop.
++# So traffic from s1 (w/ src=173.0.1.2) to r1 should be accepted
++# (return) traffic from s1 to vm1 should be accepted as return traffic
++check ovn-nbctl --wait=hv sync
++OVS_WAIT_UNTIL([
++        total_icmp1_pkts=$(cat icmp.txt | grep ttl | wc -l)
++        test "${total_icmp1_pkts}" -gt "${total_icmp_pkts}"
++])
++
++# Check we did not break handling acl-drop for existing flows
++AS_BOX([acl drop echo request in s2])
++check ovn-nbctl acl-del s1 to-lport 2001 icmp4
++check ovn-nbctl --log --severity=alert --name=drop-flow-s2 acl-add s2 to-lport 2001 icmp4 drop
++check ovn-nbctl --wait=hv sync
++
++OVS_WAIT_UNTIL([test `cat ovn-controller.log | grep acl_log | grep -c drop-flow-s2` -gt "0"])
++
++OVS_WAIT_FOR_OUTPUT([
++    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
++      sed -e 's/zone=[[0-9]]*/zone=<cleared>/' | \
++      sed -e 's/mark=[[0-9]]*/mark=<cleared>/'], [0], [dnl
++icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=<cleared>
++])
++total_icmp_pkts=$(cat icmp.txt | grep ttl | wc -l)
++
++# Allow ping again
++AS_BOX([acl allow echo request in s2])
++check ovn-nbctl acl-add s2 to-lport 2005 icmp4 allow
++check ovn-nbctl --wait=hv sync
++OVS_WAIT_FOR_OUTPUT([
++    ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
++      sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
++icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=2
++])
++OVS_WAIT_UNTIL([
++        total_icmp1_pkts=$(cat icmp.txt | grep ttl | wc -l)
++        test "${total_icmp1_pkts}" -gt "${total_icmp_pkts}"
++])
++
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+ 
+ as ovn-sb
+@@ -10706,3 +10935,605 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+ /connection dropped.*/d"])
+ AT_CLEANUP
+ ])
++
++# This tests port->up/down and ovn-installed after adding and removing Ports and Interfaces.
++# 3 Conditions x 3 tests:
++# - 3 Conditions:
++#   - In normal conditions
++#   - Remove interface while starting and stopping SB and Controller
++#   - Remove and add back interface while starting and stopping SB and Controller
++# - 3 tests:
++#   - Add/Remove Logical Port
++#   - Add/Remove iface-id
++#   - Add/Remove Interface
++# Each tests/conditions checks for
++# - Port_binding->chassis
++# - Port up or down
++# - ovn-installed
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ovn-install on slow ovsdb])
++AT_KEYWORDS([ovn-install])
++
++OVS_TRAFFIC_VSWITCHD_START()
++# Restart ovsdb-server, this time with tcp
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++start_daemon ovsdb-server --remote=punix:"$OVS_RUNDIR"/db.sock --remote=ptcp:0:127.0.0.1
++
++ovn_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
++PARSE_LISTENING_PORT([$ovs_base/ovsdb-server.log], [TCP_PORT])
++start_daemon ovn-controller tcp:127.0.0.1:$TCP_PORT
++
++check ovn-nbctl ls-add ls1
++check ovn-nbctl set Logical_Switch ls1 other_config:subnet=10.1.0.0/16
++
++check ovn-nbctl --wait=hv sync
++
++add_logical_ports() {
++  echo Adding logical ports
++  check ovn-nbctl lsp-add ls1 lsp1
++  check ovn-nbctl lsp-add ls1 lsp2
++}
++
++remove_logical_ports() {
++  echo Removing logical ports
++  check ovn-nbctl lsp-del lsp1
++  check ovn-nbctl lsp-del lsp2
++}
++
++add_ovs_interface() {
++  echo Adding interface $1 $2
++  ovs-vsctl --no-wait -- add-port br-int $1 \
++                      -- set Interface $1 external_ids:iface-id=$2 \
++                      -- set Interface $1 type=internal
++}
++add_ovs_interfaces() {
++  add_ovs_interface vif1 lsp1
++  add_ovs_interface vif2 lsp2
++}
++remove_ovs_interface() {
++  echo Removing interface $1
++  check ovs-vsctl --no-wait -- del-port $1
++}
++remove_ovs_interfaces() {
++  remove_ovs_interface vif1
++  remove_ovs_interface vif2
++}
++add_iface_ids() {
++  echo Adding iface-id vif1 lsp1
++  ovs-vsctl --no-wait -- set Interface vif1 external_ids:iface-id=lsp1
++  echo Adding iface-id vif2 lsp2
++  ovs-vsctl --no-wait -- set Interface vif2 external_ids:iface-id=lsp2
++}
++remove_iface_id() {
++  echo Removing iface-id $1
++  check ovs-vsctl remove Interface $1 external_ids iface-id
++}
++remove_iface_ids() {
++  remove_iface_id vif1
++  remove_iface_id vif2
++}
++wait_for_local_bindings() {
++  OVS_WAIT_UNTIL(
++      [test `ovs-appctl -t ovn-controller debug/dump-local-bindings | grep interface | wc -l` -eq 2],
++      [kill -CONT $(cat ovn-sb/ovsdb-server.pid)]
++  )
++}
++sleep_sb() {
++  echo SB going to sleep
++  AT_CHECK([kill -STOP $(cat ovn-sb/ovsdb-server.pid)])
++}
++wake_up_sb() {
++  echo SB waking up
++  AT_CHECK([kill -CONT $(cat ovn-sb/ovsdb-server.pid)])
++}
++sleep_controller() {
++  echo Controller going to sleep
++  ovn-appctl debug/pause
++  OVS_WAIT_UNTIL([test x$(ovn-appctl -t ovn-controller debug/status) = "xpaused"])
++}
++
++stop_ovsdb_controller_updates() {
++  TCP_PORT=$1
++  echo Stopping updates from ovn-controller to ovsdb using port $TCP_PORT
++  on_exit 'iptables -C INPUT -p tcp --destination-port $TCP_PORT -j DROP 2>/dev/null && iptables -D INPUT -p tcp --destination-port $TCP_PORT -j DROP'
++  iptables -A INPUT -p tcp --destination-port $TCP_PORT -j DROP
++}
++restart_ovsdb_controller_updates() {
++  TCP_PORT=$1
++  echo Restarting updates from ovn-controller to ovsdb
++  iptables -D INPUT -p tcp --destination-port $TCP_PORT  -j DROP
++}
++wake_up_controller() {
++  echo Controller waking up
++  ovn-appctl debug/resume
++}
++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
++  OVS_WAIT_UNTIL([test x$(ovn-appctl -t ovn-controller debug/status) = "xrunning"])
++  OVS_WAIT_UNTIL([test x$(ovn-appctl -t ovn-controller debug/status) = "xrunning"])
++}
++sleep_ovsdb() {
++  echo OVSDB going to sleep
++  AT_CHECK([kill -STOP $(cat ovsdb-server.pid)])
++}
++wake_up_ovsdb() {
++  echo OVSDB waking up
++  AT_CHECK([kill -CONT $(cat ovsdb-server.pid)])
++}
++check_ovn_installed() {
++  OVS_WAIT_UNTIL([test `ovs-vsctl get Interface vif1 external_ids:ovn-installed` = '"true"'])
++  OVS_WAIT_UNTIL([test `ovs-vsctl get Interface vif2 external_ids:ovn-installed` = '"true"'])
++}
++check_ovn_uninstalled() {
++  OVS_WAIT_UNTIL([test x`ovs-vsctl get Interface vif2 external_ids:ovn-installed` = x])
++  OVS_WAIT_UNTIL([test x`ovs-vsctl get Interface vif1 external_ids:ovn-installed` = x])
++}
++check_ports_up() {
++  OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding lsp1 up` = 'true'])
++  OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding lsp2 up` = 'true'])
++}
++check_ports_down() {
++  OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding lsp1 up` = 'false'])
++  OVS_WAIT_UNTIL([test `ovn-sbctl get Port_Binding lsp2 up` = 'false'])
++}
++
++check_ports_bound() {
++  ch=$(fetch_column Chassis _uuid name=hv1)
++  wait_row_count Port_Binding 1 logical_port=lsp1 chassis=$ch
++  wait_row_count Port_Binding 1 logical_port=lsp2 chassis=$ch
++}
++check_ports_unbound() {
++  wait_column "" Port_Binding chassis logical_port=lsp1
++  wait_column "" Port_Binding chassis logical_port=lsp2
++}
++add_logical_ports
++add_ovs_interfaces
++wait_for_local_bindings
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++############################################################
++########## Remove interface while removing iface-id ########
++############################################################
++AS_BOX(["Remove interface while removing iface-id"])
++stop_ovsdb_controller_updates $TCP_PORT
++remove_iface_id vif1
++ensure_controller_run
++# OVSDB should be seen as ro now
++remove_iface_id vif2
++ensure_controller_run
++# Controller delaying ovn-install removal for vif2 as ovsdb ro
++sleep_controller
++restart_ovsdb_controller_updates $TCP_PORT
++remove_ovs_interface vif2
++# vif2, for which we want to remove ovn-install, is deleted
++wake_up_controller
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++add_ovs_interface vif2 lsp2
++add_iface_ids
++check_ovn_installed
++check_ports_up
++check_ports_bound
++############################################################
++################### Add/Remove iface-id ####################
++############################################################
++AS_BOX(["iface-id removal and added back (no sleeping sb or controller)"])
++remove_iface_ids
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++add_iface_ids
++check_ovn_installed
++check_ports_up
++check_ports_bound
++
++AS_BOX(["iface-id removal"])
++sleep_sb
++remove_iface_ids
++ensure_controller_run
++sleep_controller
++wake_up_sb
++wake_up_controller
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++add_iface_ids
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["iface-id removal 2"])
++# Block IDL from ovn-controller to OVSDB
++stop_ovsdb_controller_updates $TCP_PORT
++remove_iface_id vif2
++ensure_controller_run
++
++# OVSDB should now be seen as read-only by ovn-controller
++remove_iface_id vif1
++ensure_controller_run
++
++# Restart connection from ovn-controller to OVSDB
++restart_ovsdb_controller_updates $TCP_PORT
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++
++add_iface_ids
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["iface-id removal and added back"])
++sleep_sb
++remove_iface_ids
++ensure_controller_run
++sleep_controller
++add_iface_ids
++wake_up_sb
++wake_up_controller
++check_ovn_installed
++check_ports_up
++check_ports_bound
++############################################################
++###################### Add/Remove Interface ################
++############################################################
++AS_BOX(["Interface removal and added back (no sleeping sb or controller)"])
++remove_ovs_interfaces
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++add_ovs_interfaces
++check_ovn_installed
++check_ports_up
++check_ports_bound
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["Interface removal"])
++sleep_sb
++remove_ovs_interfaces
++ensure_controller_run
++sleep_controller
++wake_up_sb
++wake_up_controller
++check_ovn_uninstalled
++check_ports_down
++check_ports_unbound
++add_ovs_interfaces
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["Interface removal and added back"])
++sleep_sb
++remove_ovs_interfaces
++ensure_controller_run
++sleep_controller
++add_ovs_interfaces
++wake_up_sb
++wake_up_controller
++check_ovn_installed
++check_ports_up
++check_ports_bound
++check ovn-nbctl --wait=hv sync
++############################################################
++###################### Add/Remove Logical Port #############
++############################################################
++AS_BOX(["Logical port removal and added back (no sleeping sb or controller)"])
++remove_logical_ports
++check_ovn_uninstalled
++check_ports_unbound
++sleep_ovsdb
++add_logical_ports
++ensure_controller_run
++wake_up_ovsdb
++check_ovn_installed
++check_ports_up
++check_ports_bound
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["Logical port removal"])
++sleep_sb
++remove_logical_ports
++ensure_controller_run
++sleep_controller
++wake_up_sb
++wake_up_controller
++check_ovn_uninstalled
++check_ports_unbound
++add_logical_ports
++check ovn-nbctl --wait=hv sync
++
++AS_BOX(["Logical port removal and added back"])
++sleep_sb
++remove_logical_ports
++ensure_controller_run
++sleep_controller
++add_logical_ports
++wake_up_sb
++wake_up_controller
++check_ovn_installed
++check_ports_up
++check_ports_bound
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++AT_CLEANUP
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([ovn mirroring])
++AT_KEYWORDS([mirror])
++AT_SKIP_IF([test $HAVE_TCPDUMP = no])
++
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-mirror])
++
++# 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
++
++ovs-ofctl add-flow br-mirror action=normal
++
++ovn-nbctl create Logical_Router name=R1 options:chassis=hv1
++
++ovn-nbctl ls-add foo
++ovn-nbctl ls-add bar
++
++# Connect foo to R1
++ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24 2001::1/64
++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 2002::1/64
++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\"
++
++# Logical port 'foo1' in switch 'foo'.
++ADD_NAMESPACES(foo1)
++ADD_VETH(foo1, foo1, br-int, "2001::2/64", "f0:00:00:01:02:03", \
++         "2001::1", "nodad", "192.168.1.2/24", "192.168.1.1")
++ovn-nbctl lsp-add foo foo1 \
++-- lsp-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2 2001::2"
++
++# Logical port 'bar1' in switch 'bar'.
++ADD_NAMESPACES(bar1)
++ADD_VETH(bar1, bar1, br-int, "2002::2/64", "f0:00:00:01:02:05", \
++         "2002::1", "nodad", "192.168.2.2/24", "192.168.2.1")
++ovn-nbctl lsp-add bar bar1 \
++-- lsp-set-addresses bar1 "f0:00:00:01:02:05 192.168.2.2 2002::2"
++
++ovn-nbctl mirror-add mirror0 gre 1 to-lport 172.16.0.100
++ovn-nbctl lsp-attach-mirror bar1 mirror0
++
++ADD_NAMESPACES(mirror)
++ADD_VETH(mirror, mirror, br-mirror, "2003::b/64", "f0:00:00:01:07:06", \
++         "2003::1", "nodad", "172.16.0.100/24", "172.16.0.1")
++AT_CHECK([ip addr add 172.16.0.101/24 dev br-mirror])
++AT_CHECK([ip addr add 2003::a/64 dev br-mirror nodad])
++AT_CHECK([ip link set dev br-mirror up])
++
++NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror proto GRE > gre_mirror4.pcap 2>gre_mirror4_error &])
++OVS_WAIT_UNTIL([grep "listening" gre_mirror4_error])
++
++NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++OVS_WAIT_UNTIL([
++    n_packets=$(grep "GRE" -c gre_mirror4.pcap)
++    test "${n_packets}" = "3"
++])
++
++killall tcpdump
++
++ovn-nbctl mirror-del mirror0
++ovn-nbctl mirror-add mirror1 gre 2 to-lport 2003::b
++ovn-nbctl lsp-attach-mirror bar1 mirror1
++
++NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
++OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
++
++NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++OVS_WAIT_UNTIL([
++    n_packets=$(grep "GRE" -c gre_mirror6.pcap)
++    test "${n_packets}" = "3"
++])
++
++killall tcpdump
++
++ovn-nbctl mirror-del mirror1
++ovn-nbctl mirror-add mirror2 erspan 3 to-lport 172.16.0.100
++ovn-nbctl lsp-attach-mirror bar1 mirror2
++
++NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip[[22:2]]=0x88be > erspan_mirror4.pcap 2>erspan_mirror4_error &])
++OVS_WAIT_UNTIL([grep "listening" erspan_mirror4_error])
++
++NS_CHECK_EXEC([foo1], [ping -q -c 3 -i 0.3 -w 2 192.168.2.2 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++OVS_WAIT_UNTIL([
++    n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror4.pcap)
++    test "${n_packets}" = "3"
++])
++
++killall tcpdump
++
++ovn-nbctl mirror-del mirror2
++ovn-nbctl mirror-add mirror3 erspan 4 to-lport 2003::b
++ovn-nbctl lsp-attach-mirror bar1 mirror3
++
++NS_CHECK_EXEC([mirror], [tcpdump -l -c 3 -neei mirror ip6[[42:2]]=0x88be > erspan_mirror6.pcap 2>erspan_mirror6_error &])
++OVS_WAIT_UNTIL([grep "listening" erspan_mirror6_error])
++
++NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++OVS_WAIT_UNTIL([
++    n_packets=$(grep "gre-proto-0x88be" -c erspan_mirror6.pcap)
++    test "${n_packets}" = "3"
++])
++
++killall tcpdump
++
++uuid=$(fetch_column nb:mirror _uuid name="mirror3")
++ovn-nbctl set mirror $uuid type=gre
++
++NS_CHECK_EXEC([mirror], [tcpdump -c 3 -l -neei mirror proto GRE > gre_mirror6.pcap 2>gre_mirror6_error &])
++OVS_WAIT_UNTIL([grep "listening" gre_mirror6_error])
++
++NS_CHECK_EXEC([foo1], [ping6 -q -c 3 -i 0.3 -w 2 2002::2 | FORMAT_PING], \
++[0], [dnl
++3 packets transmitted, 3 received, 0% packet loss, time 0ms
++])
++
++OVS_WAIT_UNTIL([
++    n_packets=$(grep "GRE" -c gre_mirror6.pcap)
++    test "${n_packets}" = "3"
++])
++
++killall tcpdump
++
++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
++])
++
++OVN_FOR_EACH_NORTHD([
++AT_SETUP([load balancer with localnet port])
++CHECK_CONNTRACK()
++CHECK_CONNTRACK_NAT()
++ovn_start
++OVS_TRAFFIC_VSWITCHD_START()
++ADD_BR([br-int])
++ADD_BR([br-phys], [set Bridge br-phys fail-mode=standalone])
++
++# Set external-ids in br-int needed for ovn-controller
++ovs-vsctl \
++        -- set Open_vSwitch . external-ids:system-id=hv1 \
++        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
++        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
++        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
++        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
++
++start_daemon ovn-controller
++
++check ovn-nbctl lr-add ro
++check ovn-nbctl lrp-add ro ro-sw 00:00:00:00:00:01 192.168.0.1/24
++check ovn-nbctl lrp-add ro ro-pub 00:00:00:00:01:01 10.0.0.1/24
++
++check ovn-nbctl ls-add sw
++check ovn-nbctl lsp-add sw sw-vm1 \
++    -- lsp-set-addresses sw-vm1 "00:00:00:00:00:02 192.168.0.2"
++check ovn-nbctl lsp-add sw sw-ro \
++    -- lsp-set-type sw-ro router \
++    -- lsp-set-addresses sw-ro router \
++    -- lsp-set-options sw-ro router-port=ro-sw
++
++check ovn-nbctl ls-add pub
++check ovn-nbctl lsp-add pub sw-ln \
++    -- lsp-set-type sw-ln localnet \
++    -- lsp-set-addresses sw-ln unknown \
++    -- lsp-set-options sw-ln network_name=phys
++check ovn-nbctl lsp-add pub pub-ro \
++    -- lsp-set-type pub-ro router \
++    -- lsp-set-addresses pub-ro router \
++    -- lsp-set-options pub-ro router-port=ro-pub
++
++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
++
++ADD_NAMESPACES(sw-vm1)
++ADD_VETH(sw-vm1, sw-vm1, br-int, "192.168.0.2/24", "00:00:00:00:00:02", \
++         "192.168.0.1")
++
++ADD_NAMESPACES(ln)
++ADD_VETH(ln, ln, br-phys, "10.0.0.2/24", "00:00:00:00:01:02", \
++         "10.0.0.1")
++
++# We have the basic network set up. Now let's add a load balancer
++# on the "pub" logical switch.
++
++check ovn-nbctl lb-add ln-lb 172.16.0.1:80 192.168.0.2:80 tcp
++check ovn-nbctl ls-lb-add pub ln-lb
++check ovn-nbctl --wait=hv sync
++
++# Add a route so that the localnet port can reach the load balancer
++# VIP.
++NS_CHECK_EXEC([ln], [ip route add 172.16.0.1 via 10.0.0.1])
++NS_CHECK_EXEC([ln], [ip route add 192.168.0.0/24 via 10.0.0.1])
++
++OVS_START_L7([sw-vm1], [http])
++
++NS_CHECK_EXEC([ln], [wget 172.16.0.1 -t 5 -T 1 --retry-connrefused -v -o wget.log])
++
++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.1) | \
++sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
++tcp,orig=(src=10.0.0.2,dst=172.16.0.1,sport=<cleared>,dport=<cleared>),reply=(src=192.168.0.2,dst=10.0.0.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
++])
++
++OVS_APP_EXIT_AND_WAIT([ovn-controller])
++
++as ovn-sb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as ovn-nb
++OVS_APP_EXIT_AND_WAIT([ovsdb-server])
++
++as northd
++OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
++
++as
++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
++/connection dropped.*/d"])
++
++AT_CLEANUP
++])
+diff --git a/utilities/containers/py-requirements.txt b/utilities/containers/py-requirements.txt
+index d7bd21e0d..0d90765c9 100644
+--- a/utilities/containers/py-requirements.txt
++++ b/utilities/containers/py-requirements.txt
+@@ -1,5 +1,6 @@
+ flake8
+ hacking>=3.0
++scapy
+ sphinx
+ setuptools
+ pyelftools
+diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
+index 45572fd30..9399f9462 100644
+--- a/utilities/ovn-nbctl.c
++++ b/utilities/ovn-nbctl.c
+@@ -3033,7 +3033,7 @@ nbctl_lb_add(struct ctl_context *ctx)
+     }
+ 
+     ovn_lb_vip_format(&lb_vip_parsed, &lb_vip_normalized, template);
+-    ovn_lb_vip_backends_format(&lb_vip_parsed, &lb_ips_new, template);
++    ovn_lb_vip_backends_format(&lb_vip_parsed, &lb_ips_new);
+     ovn_lb_vip_destroy(&lb_vip_parsed);
+ 
+     const struct nbrec_load_balancer *lb = NULL;
+@@ -4204,8 +4204,7 @@ print_routing_policy(const struct nbrec_logical_router_policy *policy,
+                       policy->match, policy->action);
+         for (int i = 0; i < policy->n_nexthops; i++) {
+             char *next_hop = normalize_prefix_str(policy->nexthops[i]);
+-            char *fmt = i ? ", %s" : " %25s";
+-            ds_put_format(s, fmt, next_hop);
++            ds_put_format(s, i ? ", %s" : " %25s", next_hop ? next_hop : "");
+             free(next_hop);
+         }
+     } else {
+@@ -6586,18 +6585,17 @@ print_route(const struct nbrec_logical_router_static_route *route,
+ {
+ 
+     char *prefix = normalize_prefix_str(route->ip_prefix);
+-    char *next_hop = "";
++    char *next_hop = NULL;
+ 
+     if (!strcmp(route->nexthop, "discard")) {
+         next_hop = xasprintf("discard");
+     } else if (route->nexthop[0]) {
+         next_hop = normalize_prefix_str(route->nexthop);
+     }
+-    ds_put_format(s, "%25s %25s", prefix, next_hop);
++    ds_put_format(s, "%25s %25s", prefix ? prefix : "",
++                  next_hop ? next_hop : "");
+     free(prefix);
+-    if (next_hop[0]) {
+-        free(next_hop);
+-    }
++    free(next_hop);
+ 
+     if (route->policy) {
+         ds_put_format(s, " %s", route->policy);
diff --git a/SPECS/ovn22.12.spec b/SPECS/ovn22.12.spec
deleted file mode 100644
index 3536329..0000000
--- a/SPECS/ovn22.12.spec
+++ /dev/null
@@ -1,650 +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 ovn22.12
-
-# 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: 22.12.0
-Release: 34%{?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
-
-# Always pull an upstream release, since this is what we rebase to.
-Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz
-
-%define ovscommit a787fbbf9dd6a108a53053afb45fb59a0b58b514
-%define ovsshortcommit a787fbb
-
-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-%{version} -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 Mar 16 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-34
-- northd: Ignore remote chassis when computing the supported feature set.
-[Upstream: 31ffefe9f6cc65192a8158adc41ad7adb02f634b]
-
-* Thu Mar 09 2023 Ales Musil <amusil@redhat.com> - 22.12.0-33
-- northd: Fix missig "); " from LB flows
-[Upstream: cf205ca0e52c425a14f49145fa74b3fe293b547e]
-
-* Thu Mar 02 2023 Ilya Maximets <i.maximets@ovn.org> - 22.12.0-32
-- ovn-util: Remove unused ovn_parse_internal_version_minor.
-[Upstream: 5b9e7a70386ee9863469fbf2ce4b9a4d922716d8]
-
-* Thu Mar 02 2023 Felix Hüttner <felix.huettner@mail.schwarz> - 22.12.0-31
-- northd: fix comments on functions
-[Upstream: 79dca2c4d0f3550757b6e3bb0813703348dab541]
-
-* Thu Mar 02 2023 Ales Musil <amusil@redhat.com> - 22.12.0-30
-- system-tests: Reduce flakiness of ACL reject tests
-[Upstream: b3196d1b140c98b409086ca72880a76da96c6c62]
-
-* Thu Mar 02 2023 Ales Musil <amusil@redhat.com> - 22.12.0-29
-- dbctl: Fix a couple of memory leaks
-[Upstream: 2733558a2e76ad6db3fc639f1bd8235d6382248b]
-
-* Thu Mar 02 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-28
-- ci: ovn-kubernetes: Bump libovsdb to a6a173993830.
-[Upstream: 81ae7831f8d64a114a1be265782b6aa9ad0c52db]
-
-* Wed Mar 01 2023 Xavier Simonart <xsimonar@redhat.com> - 22.12.0-27
-- tests: Fixed some tests failing on (very) slow systems
-[Upstream: 2bd8697b25ac4342af33985899248248557c5481]
-
-* Mon Feb 27 2023 Ales Musil <amusil@redhat.com> - 22.12.0-26
-- tests: Decrease the number of zones and switches for interconnection
-[Upstream: 2a24ebc3064959c57ded1060a6a31be5397382b3]
-
-* Thu Feb 16 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-25
-- lb: northd: Properly format IPv6 SB load balancer VIPs.
-[Upstream: 7053ae61267ebcb282d5ef18b5bd8f2f6c6c37e0]
-
-* Wed Feb 15 2023 Ales Musil <amusil@redhat.com> - 22.12.0-24
-- system-test: Use OVS_WAIT_UNTIL for tcpdump start instead fo sleep
-[Upstream: d5273f929513458a569cdfb297bffd9922d44c01]
-
-* Wed Feb 15 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 22.12.0-23
-- docs: fix the max number of ports per network for vxlan
-[Upstream: 4dfa4ba431ab634b6068f27e886a4d403d589c87]
-
-* Wed Feb 15 2023 Ales Musil <amusil@redhat.com> - 22.12.0-22
-- ovn-nbctl: Fix documentation typo (#2168009)
-[Upstream: 0c44d7dbf4a013f08c79d5818e89a8f55ecd09e0]
-
-* Wed Feb 15 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 22.12.0-21
-- northd: do not create flows for reserved multicast IPv6 groups (#2154930)
-[Upstream: 61e030ed59c2d2a1029866dce6769428e0abbc0c]
-
-* Thu Feb 02 2023 Han Zhou <hzhou@ovn.org> - 22.12.0-20
-- northd.c: Validate port type to avoid unexpected behavior.
-[Upstream: b67009fdb6312e95367183c65b439fd3b7a288bf]
-
-* Tue Jan 31 2023 Surya Seetharaman <suryaseetharaman.9@gmail.com> - 22.12.0-19
-- Add the metalLB install flag for CI actions
-[Upstream: 65990b8398e8e7ff29c6d7e9903fd0cf7ef64965]
-
-* Mon Jan 23 2023 Ales Musil <amusil@redhat.com> - 22.12.0-18
-- ovn-trace: Use the original ovnact for execute_load
-[Upstream: 4c78bef966927f4083b601a6a4f5fc76a839fd1a]
-
-* Mon Jan 23 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-17
-- northd: Add logical flows to allow rpl/rel traffic in acl_after_lb stage. (#1947807)
-[Upstream: d6914efd53ac28a6e3da6e65f9e026674f05dc4c]
-
-* Fri Jan 20 2023 Mark Michelson <mmichels@redhat.com> - 22.12.0-16
-- ovn-controller: Fix initial requested SNAT zone assignment. (#2160403)
-[Upstream: 17f1e9e0148e298b6ec525d5d6b149082a864dca]
-
-* Thu Jan 19 2023 Han Zhou <hzhou@ovn.org> - 22.12.0-15
-- northd: Drop packets destined to router owned NAT IP for DGP.
-[Upstream: 481f25b784896eec07fedc77631992a009bcdada]
-
-* Thu Jan 19 2023 Ales Musil <amusil@redhat.com> - 22.12.0-14
-- northd: Add flag for CT related (#2126083)
-[Upstream: 2619f6a27aca2a5925e25297f75e6a925cf1eb6a]
-
-* Wed Jan 18 2023 Xavier Simonart <xsimonar@redhat.com> - 22.12.0-13
-- tests: Fixed load balancing system-tests
-[Upstream: 1791a107debbaa474669a794b4d2a6dff4cb1dcb]
-
-* Wed Jan 18 2023 Xavier Simonart <xsimonar@redhat.com> - 22.12.0-12
-- tests: Fixed flaky ACL fair Meters
-[Upstream: f9fb0bb4de4e7cb0a02fcb0794e226e6af8e8f5c]
-
-* Wed Jan 18 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 22.12.0-11
-- northd: move hairpin stages before acl_after_lb (#2103086)
-[Upstream: 3723a6d6e39dcffc502e094ccc10a8d638fa5efa]
-
-* Tue Jan 17 2023 Xavier Simonart <xsimonar@redhat.com> - 22.12.0-10
-- controller: Fix missing first ping from pod to external (#2129283)
-[Upstream: 7109f02b78f5087b5bae2885f153378e627d90f7]
-
-* Mon Jan 16 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 22.12.0-9
-- controller: use packet proto for hairpin traffic learned action if not specified (#2157846)
-[Upstream: 588291528fc0568e7da402c05b596c6c855d2c5f]
-
-* Fri Jan 13 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-8
-- .ci: ovn-kubernetes: Add a "prepare" stage to allow for custom actions.
-[Upstream: 29fb21e6ec0a1203e3f5b2bfff4c3ccea8df4d37]
-
-* Wed Jan 11 2023 Han Zhou <hzhou@ovn.org> - 22.12.0-7
-- build-aux/sodepends.py: Fix flake8 error.
-[Upstream: 1fd28ef34bef9b19ca350f15bd03e10265a911dc]
-
-* Wed Jan 11 2023 Han Zhou <hzhou@ovn.org> - 22.12.0-6
-- build-aux/sodepends.py: Fix broken build when manpage changes.
-[Upstream: 79edad8a1e547f4120ea3d20f08aafe1e40a6f65]
-
-* Tue Jan 10 2023 Dumitru Ceara <dceara@redhat.com> - 22.12.0-5
-- ovn-ic: Only monitor useful tables and columns.
-[Upstream: fdad33f2348f34b5fb886a5a3143d91f44021811]
-
-* Fri Dec 16 2022 Mark Michelson <mmichels@redhat.com> - 22.12.0-4
-- Prepare for 22.12.1.
-[Upstream: 78af8b76ab30ad3e704211256c313dec67f63cb8]
-
diff --git a/SPECS/ovn23.03.spec b/SPECS/ovn23.03.spec
new file mode 100644
index 0000000..e3ac702
--- /dev/null
+++ b/SPECS/ovn23.03.spec
@@ -0,0 +1,714 @@
+# 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.03
+
+# 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.03.0
+Release: 50%{?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
+
+# Always pull an upstream release, since this is what we rebase to.
+Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz
+
+%define ovscommit 8986d4d5564401eeef3dea828b51fe8bae2cc8aa
+%define ovsshortcommit 8986d4d
+
+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-%{version} -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
+* Fri May 26 2023 Han Zhou <hzhou@ovn.org> - 23.03.0-50
+- ovn-controller.c: Fix assertion failure during address set update.
+[Upstream: 777786f38a61041898891ccbb3f139b0552e5794]
+
+* Fri May 19 2023 Mark Michelson <mmichels@redhat.com> - 23.03.0-49
+- Pass localnet traffic through CT when a LB is configured. (#2164652)
+[Upstream: 2449608303464d62ff5b1a89e20e476248d1e82b]
+
+* Fri May 19 2023 Mark Michelson <mmichels@redhat.com> - 23.03.0-48
+- tests: Use stricter IP match for FORMAT_CT.
+[Upstream: a1d8ebd306021844646629c42bd638203399c568]
+
+* Wed May 17 2023 Ales Musil <amusil@redhat.com> - 23.03.0-47
+- tests: Fix flakiness of policy based routing on slower systems
+[Upstream: bb22fe9c4591dc96d98bf2c2c3f629efb5721757]
+
+* Wed May 17 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-46
+- ovn-controller docs: fix typo in ovn-monitor-all description
+[Upstream: 0ba1609d6426cd779f48cb5a97e63c0a7811dc03]
+
+* Wed May 10 2023 Ales Musil <amusil@redhat.com> - 23.03.0-45
+- system-tests: Replace use of ADD_INT with ADD_VETH
+[Upstream: c2f36b530613728100b1dbdf7f967c4a87053155]
+
+* Wed May 10 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-44
+- controller: fix possible unaligned accesses in DHCPv6 code
+[Upstream: 080cbcd95d3d065524272f5f1d0bed11df35a9d4]
+
+* Tue May 09 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-43
+- mirror: fix ovn mirror support with IPv6 (#2168119)
+[Upstream: 5255303229f4da0c1478656597c774510ceda4e9]
+
+* Mon May 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-42
+- ovn-controller: fixed port not always set down when unbinding interface (#2150905)
+[Upstream: 39930c0254bc8758b5722e0f2c3a7fdc43256888]
+
+* Mon May 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-41
+- ovn-controller: fixed ovn-installed not always properly added or removed.
+[Upstream: 395eac485b871ef75bb0b5f29b09ebd1cb877ca8]
+
+* Mon May 08 2023 Wei Li <liw@yusur.tech> - 23.03.0-40
+- documentation: packets that arrive from other chassis resubmit to table 38
+[Upstream: 4ad402df2870c44234945b462345bfcb0c95b6f6]
+
+* Mon May 08 2023 Tao Liu <taoliu828@163.com> - 23.03.0-39
+- northd: fix use-after-free after lrp destroyed
+[Upstream: fd0111e59c6a78acf70dd47e10e724210c2e94ea]
+
+* Mon May 08 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-38
+- docs: document that vxlan is supported for encap type
+[Upstream: 31bc347255a9206b29dc3f9af14c8af75ea9600e]
+
+* Mon May 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-37
+- tests: Fixed flaky lr multiple gw ports
+[Upstream: 94dea8bb6bba439de673fa4fa72e3fa3f8c44593]
+
+* Mon May 08 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-36
+- Fix test "load-balancing"
+[Upstream: bfdfb9a0565b2f21f59120b360db1aa59b24f96d]
+
+* Fri May 05 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-35
+- tests: Retry inject-pkt in case ovn-controller is still busy.
+[Upstream: d6cad0cc05f752a83f72c82881a76642301125b9]
+
+* Tue May 02 2023 wangchuanlei <wangchuanlei@inspur.com> - 23.03.0-34
+- pinctrl: fix restart of controller when bfd min_tx is too low.
+[Upstream: 77d0ff0dace95ce6c8453c2b95f65d18892133b6]
+
+* Tue May 02 2023 Ales Musil <amusil@redhat.com> - 23.03.0-33
+- ovn-nbctl: Fix unhandled NULL return from normalize_prefix_str
+[Upstream: 86ceec393a9b776990bb24625bf9ca13ce9dfe05]
+
+* Mon May 01 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-32
+- tests: decrease risk of flaky failures of ovn -- CoPP system test
+[Upstream: b81229835436b63a593e0e6a55bb639309101592]
+
+* Mon May 01 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-31
+- tests: check arguments count of OVS_WAIT_UNTIL
+[Upstream: 352c584c403ce914b59e3c3c95028949ec3ede05]
+
+* Mon May 01 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-30
+- tests: Fixed wrong usage of OVS_WAIT_UNTIL
+[Upstream: b88d8bbba3ba59ee0a3c2d3fb62cab748e1b59dc]
+
+* Mon May 01 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-29
+- tests: Fixed "1 LR with HA distributed router gateway port"
+[Upstream: 1758332a51766231e74479302ee8efdfc07bf33c]
+
+* Fri Apr 21 2023 Mark Michelson <mmichels@redhat.com> - 23.03.0-28
+- tests: Skip "daemon ssl files change" when SSL is disabled.
+[Upstream: 3d231640d852bc4c7b49d17de80ba747bebe8817]
+
+* Wed Apr 19 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-27
+- Omit ACLs for nd || nd_ra || nd_rs || mldv1 || mldv2 (#2149731)
+[Upstream: 6e6cc27bdb9735a6461b53771b3969a2ca230cab]
+
+* Wed Apr 19 2023 Ihar Hrachyshka <ihrachys@redhat.com> - 23.03.0-26
+- tests: define fmt_pkt function to construct packets with scapy
+[Upstream: b797e5dbd955ee61d503bb38dade490957bce563]
+
+* Wed Apr 19 2023 Ales Musil <amusil@redhat.com> - 23.03.0-25
+- controller: Prevent race in tunnel cleanup
+[Upstream: 40befbb1d508db48f894cb190637ee33cccf0977]
+
+* Thu Apr 13 2023 Han Zhou <hzhou@ovn.org> - 23.03.0-24
+- northd.c: Avoid sending ICMP time exceeded for multicast packets.
+[Upstream: a38a5df4a49ddf678d6a50de39b613b8f0305e26]
+
+* Thu Apr 13 2023 Han Zhou <hzhou@ovn.org> - 23.03.0-23
+- northd.c: TTL discard flow should support for both ipv4 and ipv6.
+[Upstream: d42d070bd2598b72c13d2a67209d368acc8128b8]
+
+* Tue Apr 11 2023 Ales Musil <amusil@redhat.com> - 23.03.0-22
+- northd: Update the is_stateless helper for router nat
+[Upstream: 99b42566998c9e9b952cdfe3f8435e8bd79eca43]
+
+* Tue Apr 11 2023 Eelco Chaudron <echaudro@redhat.com> - 23.03.0-21
+- ci: Add arping package to run floating IP tests.
+[Upstream: 825164cd4164e9c9bf5c66ebe00e147f1e2ac1fd]
+
+* Tue Apr 11 2023 Ales Musil <amusil@redhat.com> - 23.03.0-20
+- controller: Clear tunnels from old integration bridge (#2173635)
+[Upstream: 8481abcc04fee5e0ecd881e599be178625ad1522]
+
+* Tue Apr 11 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-19
+- northd: revert ct.inv drop flows
+[Upstream: 04d7552479736bd2da84dd5d2be146fa4a51e3e6]
+
+* Thu Apr 06 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-18
+- northd: take into account qos_min_rate in port_has_qos_params
+[Upstream: 0bbcfb52f0248d839e413acaf2b116bb7b1c4db6]
+
+* Thu Mar 30 2023 Ales Musil <amusil@redhat.com> - 23.03.0-17
+- system-tests: Reduce flakiness of netcat UDP clients
+[Upstream: 6c2d80c5bb6a2cd7c353b796a7d46b3962a06c9f]
+
+* Tue Mar 28 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-16
+- Revert "DOWNSTREAM: Forcefully disable backend conntrack flushing."
+[Upstream: 8c98303437610a32faff8c0e7d8eff419b40619e]
+
+* Tue Mar 28 2023 Xavier Simonart <xsimonar@redhat.com> - 23.03.0-15
+- northd: prevents sending packet to conntrack for router ports (#2062431)
+[Upstream: 2bab96e899b5da5ae0c3b24bd04ece93d1339824]
+
+* Tue Mar 28 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-14
+- lb: Allow IPv6 template LBs to use explicit backends.
+[Upstream: d01fdfdb2c97222cf326c8ab5579f670ded6e3cb]
+
+* Tue Mar 28 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-13
+- controller: lflow: do not use tcp as default IP protocol for ct_snat_to_vip action (#2157846)
+[Upstream: 6a16c741e5a10a817ca8251898f48bf9eeb971f5]
+
+* Tue Mar 28 2023 Ales Musil <amusil@redhat.com> - 23.03.0-12
+- northd: Drop packets for LBs with no backends (#2177173)
+[Upstream: 77384b7fe3f7d3260fd2f94a3bd75b8ca79f56ae]
+
+* Mon Mar 27 2023 Ales Musil <amusil@redhat.com> - 23.03.0-11
+- northd: Use generic ct.est flows for LR LBs (#2172048 2170885)
+[Upstream: 81eaa98bbb608bda320abfa0122ba073de6597d7]
+
+* Thu Mar 23 2023 Lorenzo Bianconi <lorenzo.bianconi@redhat.com> - 23.03.0-10
+- northd: drop ct.inv packets in post snat and lb_aff_learn stages (#2160685)
+[Upstream: 0af110c400cc29bb037172cdfd674794716771df]
+
+* Mon Mar 20 2023 Ales Musil <amusil@redhat.com> - 23.03.0-9
+- controller: Add config option per LB to enable/disable CT flush (#2178962)
+[Upstream: 89fc85fa7f2b00f404ec5aef4ce8f2236474fbab]
+
+* Thu Mar 16 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-8
+- DOWNSTREAM: Forcefully disable backend conntrack flushing. (#2178962)
+[Upstream: b7522be8bf28534cc422234e8d9484b9ef4220d9]
+
+* Thu Mar 16 2023 Dumitru Ceara <dceara@redhat.com> - 23.03.0-7
+- northd: Ignore remote chassis when computing the supported feature set.
+[Upstream: 80b7e48a877abd337eb54b9bb9c7b4280aa9ff74]
+
+* Wed Mar 08 2023 Ilya Maximets <i.maximets@ovn.org> - 23.03.0-6
+- controller: Use ofctrl_add_flow for CT SNAT hairpin flows.
+[Upstream: 888215e2164b476462f12d206a3d734958ef79e2]
+
+* Wed Mar 08 2023 Vladislav Odintsov <odivlad@gmail.com> - 23.03.0-5
+- rhel: pass options to stop daemon command in systemd units
+[Upstream: ed7095613abf3d36cbcf347e1238b84e6843eaf1]
+
+* Fri Mar 03 2023 Mark Michelson <mmichels@redhat.com> - 23.03.0-4
+- Prepare for 23.03.1.
+[Upstream: e98ea52f12de2a7d6a9a7547b6b0a493a78f0fed]
+