Blob Blame History Raw
diff --git a/.ci/ovn-kubernetes/Dockerfile b/.ci/ovn-kubernetes/Dockerfile
index eda8b6d02..554b5551b 100644
--- a/.ci/ovn-kubernetes/Dockerfile
+++ b/.ci/ovn-kubernetes/Dockerfile
@@ -1,4 +1,4 @@
-ARG OVNKUBE_COMMIT=master
+ARG OVNKUBE_COMMIT
 ARG GO_VERSION
 
 FROM fedora:37 AS ovnbuilder
diff --git a/.github/workflows/ovn-kubernetes.yml b/.github/workflows/ovn-kubernetes.yml
index 0f2b30497..f7dfd1c39 100644
--- a/.github/workflows/ovn-kubernetes.yml
+++ b/.github/workflows/ovn-kubernetes.yml
@@ -13,7 +13,7 @@ concurrency:
   cancel-in-progress: true
 
 env:
-  OVNKUBE_COMMIT: "master"
+  OVNKUBE_COMMIT: "release-1.0"
   KIND_CLUSTER_NAME: ovn
   KIND_INSTALL_INGRESS: true
   KIND_ALLOW_SYSTEM_WRITES: true
@@ -41,6 +41,7 @@ jobs:
       with:
           path: src/github.com/ovn-org/ovn-kubernetes
           repository: ovn-org/ovn-kubernetes
+          ref: ${{ env.OVNKUBE_COMMIT }}
 
     - name: Prepare
       run: |
@@ -108,6 +109,7 @@ jobs:
       with:
           path: src/github.com/ovn-org/ovn-kubernetes
           repository: ovn-org/ovn-kubernetes
+          ref: ${{ env.OVNKUBE_COMMIT }}
 
     - name: Prepare
       run: |
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 65c8761f4..351523fd1 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -448,6 +448,7 @@ xushengping                        shengping.xu@huawei.com
 yinpeijun                          yinpeijun@huawei.com
 zangchuanqiang                     zangchuanqiang@huawei.com
 zhang yanxian                      zhangyanxian@pmlabs.com.cn
+zhangqiang45                       zhangqiang45@lenovo.com
 zhaojingjing                       zhao.jingjing1@zte.com.cn
 zhongbaisong                       zhongbaisong@huawei.com
 zhaozhanxu                         zhaozhanxu@163.com
diff --git a/Documentation/automake.mk b/Documentation/automake.mk
index 7fcd186ca..6628bd3e0 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -59,6 +59,7 @@ DOC_SOURCE = \
 	Documentation/internals/security.rst \
 	Documentation/internals/contributing/index.rst \
 	Documentation/internals/contributing/backporting-patches.rst \
+	Documentation/internals/contributing/inclusive-language.rst \
 	Documentation/internals/contributing/coding-style.rst \
 	Documentation/internals/contributing/documentation-style.rst \
 	Documentation/internals/contributing/libopenvswitch-abi.rst \
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 730595c05..6dabd8a28 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -82,6 +82,7 @@ Learn more about the Open Virtual Network (OVN) project and about how you can co
 
 - **Contributing:** :doc:`internals/contributing/submitting-patches` |
   :doc:`internals/contributing/backporting-patches` |
+  :doc:`internals/contributing/inclusive-language` |
   :doc:`internals/contributing/coding-style`
 
 - **Maintaining:** :doc:`internals/maintainers` |
diff --git a/Documentation/internals/contributing/inclusive-language.rst b/Documentation/internals/contributing/inclusive-language.rst
new file mode 100644
index 000000000..65e9c4fbd
--- /dev/null
+++ b/Documentation/internals/contributing/inclusive-language.rst
@@ -0,0 +1,57 @@
+..
+      Licensed under the Apache License, Version 2.0 (the "License"); you may
+      not use this file except in compliance with the License. You may obtain
+      a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+      License for the specific language governing permissions and limitations
+      under the License.
+
+      Convention for heading levels in OVN documentation:
+
+      =======  Heading 0 (reserved for the title in a document)
+      -------  Heading 1
+      ~~~~~~~  Heading 2
+      +++++++  Heading 3
+      '''''''  Heading 4
+
+      Avoid deeper levels because they do not render well.
+
+==================
+Inclusive Language
+==================
+
+In order to help facilitate an inclusive environment in the OVN
+community we recognise the role of language in framing our
+communication with each other. It is important that terms that
+may exclude people through racial, cultural or other bias, are avoided
+as they may make people feel excluded.
+
+We recognise that this is subjective, and to some extent is a journey.
+But we also recognise that we cannot begin that journey without taking
+positive action. To this end OVN is adopting the practice of an
+inclusive word list, which helps to guide the use of language within
+the project.
+
+.. _word list:
+
+Word List
+---------
+
+The intent of this document is to formally document the acceptance of a
+inclusive word list by OVN.  Accordingly, this document specifies
+use of the use the `Inclusive Naming Word List
+<https://inclusivenaming.org/word-lists/>`__ v1.0 (the word list) for
+OVN.
+
+The adoption of the word list intended that this act as a guide for
+developers creating patches to the OVN repository, including both
+source code and documentation. And to aid maintainers in their role of
+shepherding changes into the repository.
+
+Further steps to align usage of language in OVN, including clarification
+of application of the word list, to new and existing work, may follow.
diff --git a/Documentation/internals/contributing/index.rst b/Documentation/internals/contributing/index.rst
index 77b52964b..4f09ad473 100644
--- a/Documentation/internals/contributing/index.rst
+++ b/Documentation/internals/contributing/index.rst
@@ -31,6 +31,7 @@ The below guides provide information on contributing to OVN itself.
    :maxdepth: 2
 
    submitting-patches
+   inclusive-language
    backporting-patches
    coding-style
    documentation-style
diff --git a/NEWS b/NEWS
index 5c80fa482..9265e0836 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,10 @@
+OVN v23.09.6 - xx xxx xxxx
+--------------------------
+
+OVN v23.09.5 - 16 Aug 2024
+--------------------------
+  - Bug fixes
+
 OVN v23.09.4 - 09 May 2024
 --------------------------
   - Bug fixes
diff --git a/configure.ac b/configure.ac
index c71ade236..055d339e3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(ovn, 23.09.4, bugs@openvswitch.org)
+AC_INIT(ovn, 23.09.6, bugs@openvswitch.org)
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/controller/if-status.c b/controller/if-status.c
index 6e8aa1f7e..2d6d06d9b 100644
--- a/controller/if-status.c
+++ b/controller/if-status.c
@@ -219,7 +219,8 @@ ovs_iface_create(struct if_status_mgr *, const char *iface_id,
 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 ovn_uninstall_hash_destroy(struct if_status_mgr *mgr,
+                                       struct shash_node *node);
 static void ovs_iface_set_state(struct if_status_mgr *, struct ovs_iface *,
                                 enum if_state);
 
@@ -256,7 +257,7 @@ 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);
+        ovn_uninstall_hash_destroy(mgr, node);
     }
     ovs_assert(shash_is_empty(&mgr->ovn_uninstall_hash));
 
@@ -789,20 +790,13 @@ ovs_iface_destroy(struct if_status_mgr *mgr, struct ovs_iface *iface)
 }
 
 static void
-ovn_uninstall_hash_destroy(struct if_status_mgr *mgr, char *name)
+ovn_uninstall_hash_destroy(struct if_status_mgr *mgr, struct shash_node *node)
 {
-    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);
-    }
+    free(node->data);
+    VLOG_DBG("Interface name %s destroy", node->name);
+    char *node_name = shash_steal(&mgr->ovn_uninstall_hash, node);
+    ovn_uninstall_hash_account_mem(node_name, true);
+    free(node_name);
 }
 
 static void
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index e8e7ac6bd..66b656044 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -750,7 +750,7 @@ update_ct_zones(const struct sset *local_lports,
     const char *user;
     struct sset all_users = SSET_INITIALIZER(&all_users);
     struct simap req_snat_zones = SIMAP_INITIALIZER(&req_snat_zones);
-    unsigned long unreq_snat_zones_map[BITMAP_N_LONGS(MAX_CT_ZONES)];
+    unsigned long *unreq_snat_zones_map = bitmap_allocate(MAX_CT_ZONES);
     struct simap unreq_snat_zones = SIMAP_INITIALIZER(&unreq_snat_zones);
 
     const char *local_lport;
@@ -861,6 +861,7 @@ update_ct_zones(const struct sset *local_lports,
     simap_destroy(&req_snat_zones);
     simap_destroy(&unreq_snat_zones);
     sset_destroy(&all_users);
+    bitmap_free(unreq_snat_zones_map);
 }
 
 static void
@@ -1146,6 +1147,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_queue);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_queue_col_other_config);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_queue_col_external_ids);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_interface_col_link_state);
 
     chassis_register_ovs_idl(ovs_idl);
     encaps_register_ovs_idl(ovs_idl);
@@ -2990,7 +2992,7 @@ lb_data_local_lb_add(struct ed_type_lb_data *lb_data,
 
 static void
 lb_data_local_lb_remove(struct ed_type_lb_data *lb_data,
-                        struct ovn_controller_lb *lb, bool tracked)
+                        struct ovn_controller_lb *lb)
 {
     const struct uuid *uuid = &lb->slb->header_.uuid;
 
@@ -2999,12 +3001,8 @@ lb_data_local_lb_remove(struct ed_type_lb_data *lb_data,
 
     lb_data_removed_five_tuples_add(lb_data, lb);
 
-    if (tracked) {
-        hmap_insert(&lb_data->old_lbs, &lb->hmap_node, uuid_hash(uuid));
-        uuidset_insert(&lb_data->deleted, uuid);
-    } else {
-        ovn_controller_lb_destroy(lb);
-    }
+    hmap_insert(&lb_data->old_lbs, &lb->hmap_node, uuid_hash(uuid));
+    uuidset_insert(&lb_data->deleted, uuid);
 }
 
 static bool
@@ -3029,7 +3027,7 @@ lb_data_handle_changed_ref(enum objdep_type type, const char *res_name,
             continue;
         }
 
-        lb_data_local_lb_remove(lb_data, lb, true);
+        lb_data_local_lb_remove(lb_data, lb);
 
         const struct sbrec_load_balancer *sbrec_lb =
             sbrec_load_balancer_table_get_for_uuid(ctx_in->lb_table, uuid);
@@ -3075,9 +3073,13 @@ en_lb_data_run(struct engine_node *node, void *data)
     const struct sbrec_load_balancer_table *lb_table =
         EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
 
+    objdep_mgr_clear(&lb_data->deps_mgr);
+
     struct ovn_controller_lb *lb;
     HMAP_FOR_EACH_SAFE (lb, hmap_node, &lb_data->local_lbs) {
-        lb_data_local_lb_remove(lb_data, lb, false);
+        hmap_remove(&lb_data->local_lbs, &lb->hmap_node);
+        lb_data_removed_five_tuples_add(lb_data, lb);
+        ovn_controller_lb_destroy(lb);
     }
 
     const struct sbrec_load_balancer *sbrec_lb;
@@ -3115,7 +3117,7 @@ lb_data_sb_load_balancer_handler(struct engine_node *node, void *data)
                 continue;
             }
 
-            lb_data_local_lb_remove(lb_data, lb, true);
+            lb_data_local_lb_remove(lb_data, lb);
         }
 
         if (sbrec_load_balancer_is_deleted(sbrec_lb) ||
@@ -4054,6 +4056,8 @@ en_lflow_output_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
     const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
     const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    const struct ovsrec_flow_sample_collector_set_table *flow_collector_table =
+        EN_OVSDB_GET(engine_get_input("OVS_flow_sample_collector_set", node));
 
     struct ovsdb_idl_index *sbrec_chassis_by_name =
         engine_ovsdb_node_get_index(
@@ -4067,6 +4071,17 @@ en_lflow_output_run(struct engine_node *node, void *data)
 
     ovs_assert(br_int && chassis);
 
+    const struct ovsrec_flow_sample_collector_set *set;
+    OVSREC_FLOW_SAMPLE_COLLECTOR_SET_TABLE_FOR_EACH (set,
+                                                    flow_collector_table) {
+        if (set->bridge == br_int) {
+            struct ed_type_lflow_output *lfo = data;
+            flow_collector_ids_clear(&lfo->collector_ids);
+            flow_collector_ids_init_from_table(&lfo->collector_ids,
+                                               flow_collector_table);
+        }
+    }
+
     struct ed_type_lflow_output *fo = data;
     struct ovn_desired_flow_table *lflow_table = &fo->flow_table;
     struct ovn_extend_table *group_table = &fo->group_table;
@@ -5719,17 +5734,14 @@ main(int argc, char *argv[])
                                            br_int ? br_int->name : NULL)) {
                 VLOG_INFO("OVS feature set changed, force recompute.");
                 engine_set_force_recompute(true);
-                if (ovs_feature_set_discovered()) {
-                    uint32_t max_groups = ovs_feature_max_select_groups_get();
-                    uint32_t max_meters = ovs_feature_max_meters_get();
-                    struct ed_type_lflow_output *lflow_out_data =
-                        engine_get_internal_data(&en_lflow_output);
-
-                    ovn_extend_table_reinit(&lflow_out_data->group_table,
-                                            max_groups);
-                    ovn_extend_table_reinit(&lflow_out_data->meter_table,
-                                            max_meters);
-                }
+
+                struct ed_type_lflow_output *lflow_out_data =
+                    engine_get_internal_data(&en_lflow_output);
+
+                ovn_extend_table_reinit(&lflow_out_data->group_table,
+                                        ovs_feature_max_select_groups_get());
+                ovn_extend_table_reinit(&lflow_out_data->meter_table,
+                                        ovs_feature_max_meters_get());
             }
 
             if (br_int) {
diff --git a/controller/physical.c b/controller/physical.c
index c93fe9aa6..54bae81d1 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -1212,6 +1212,12 @@ reply_imcp_error_if_pkt_too_big(struct ovn_desired_flow_table *flow_table,
     ofpact_put_set_field(
         &inner_ofpacts, mf_from_id(MFF_LOG_FLAGS), &value, &mask);
 
+    /* inport <-> outport */
+    put_stack(MFF_LOG_INPORT, ofpact_put_STACK_PUSH(&inner_ofpacts));
+    put_stack(MFF_LOG_OUTPORT, ofpact_put_STACK_PUSH(&inner_ofpacts));
+    put_stack(MFF_LOG_INPORT, ofpact_put_STACK_POP(&inner_ofpacts));
+    put_stack(MFF_LOG_OUTPORT, ofpact_put_STACK_POP(&inner_ofpacts));
+
     /* eth.src <-> eth.dst */
     put_stack(MFF_ETH_DST, ofpact_put_STACK_PUSH(&inner_ofpacts));
     put_stack(MFF_ETH_SRC, ofpact_put_STACK_PUSH(&inner_ofpacts));
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 98821d275..9e80556ef 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -655,8 +655,6 @@ pinctrl_forward_pkt(struct rconn *swconn, int64_t dp_key,
     put_load(dp_key, MFF_LOG_DATAPATH, 0, 64, &ofpacts);
     put_load(in_port_key, MFF_LOG_INPORT, 0, 32, &ofpacts);
     put_load(out_port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
-    /* Avoid re-injecting packet already consumed. */
-    put_load(1, MFF_LOG_FLAGS, MLF_IGMP_IGMP_SNOOP_INJECT_BIT, 1, &ofpacts);
 
     struct ofpact_resubmit *resubmit = ofpact_put_RESUBMIT(&ofpacts);
     resubmit->in_port = OFPP_CONTROLLER;
@@ -5756,6 +5754,10 @@ get_localnet_vifs_l3gwports(
             if (!pb || pb->chassis != chassis) {
                 continue;
             }
+            if (!iface_rec->link_state ||
+                    strcmp(iface_rec->link_state, "up")) {
+                continue;
+            }
             struct local_datapath *ld
                 = get_local_datapath(local_datapaths,
                                      pb->datapath->tunnel_key);
@@ -6687,6 +6689,9 @@ struct svc_monitor {
     long long int timestamp;
     bool is_ip6;
 
+    struct eth_addr src_mac;
+    struct in6_addr src_ip;
+
     long long int wait_time;
     long long int next_send_time;
 
@@ -6881,6 +6886,9 @@ sync_svc_monitors(struct ovsdb_idl_txn *ovnsb_idl_txn,
             svc_mon->n_success = 0;
             svc_mon->n_failures = 0;
 
+            eth_addr_from_string(sb_svc_mon->src_mac, &svc_mon->src_mac);
+            ip46_parse(sb_svc_mon->src_ip, &svc_mon->src_ip);
+
             hmap_insert(&svc_monitors_map, &svc_mon->hmap_node, hash);
             ovs_list_push_back(&svc_monitors, &svc_mon->list_node);
             changed = true;
@@ -7639,19 +7647,14 @@ svc_monitor_send_tcp_health_check__(struct rconn *swconn,
     struct dp_packet packet;
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
 
-    struct eth_addr eth_src;
-    eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
     if (svc_mon->is_ip6) {
-        struct in6_addr ip6_src;
-        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
-        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
-                             &ip6_src, &svc_mon->ip, IPPROTO_TCP,
+        pinctrl_compose_ipv6(&packet, svc_mon->src_mac, svc_mon->ea,
+                             &svc_mon->src_ip, &svc_mon->ip, IPPROTO_TCP,
                              63, TCP_HEADER_LEN);
     } else {
-        ovs_be32 ip4_src;
-        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
-        pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
-                             ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+        pinctrl_compose_ipv4(&packet, svc_mon->src_mac, svc_mon->ea,
+                             in6_addr_get_mapped_ipv4(&svc_mon->src_ip),
+                             in6_addr_get_mapped_ipv4(&svc_mon->ip),
                              IPPROTO_TCP, 63, TCP_HEADER_LEN);
     }
 
@@ -7707,24 +7710,18 @@ svc_monitor_send_udp_health_check(struct rconn *swconn,
                                   struct svc_monitor *svc_mon,
                                   ovs_be16 udp_src)
 {
-    struct eth_addr eth_src;
-    eth_addr_from_string(svc_mon->sb_svc_mon->src_mac, &eth_src);
-
     uint64_t packet_stub[128 / 8];
     struct dp_packet packet;
     dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
 
     if (svc_mon->is_ip6) {
-        struct in6_addr ip6_src;
-        ipv6_parse(svc_mon->sb_svc_mon->src_ip, &ip6_src);
-        pinctrl_compose_ipv6(&packet, eth_src, svc_mon->ea,
-                             &ip6_src, &svc_mon->ip, IPPROTO_UDP,
+        pinctrl_compose_ipv6(&packet, svc_mon->src_mac, svc_mon->ea,
+                             &svc_mon->src_ip, &svc_mon->ip, IPPROTO_UDP,
                              63, UDP_HEADER_LEN + 8);
     } else {
-        ovs_be32 ip4_src;
-        ip_parse(svc_mon->sb_svc_mon->src_ip, &ip4_src);
-        pinctrl_compose_ipv4(&packet, eth_src, svc_mon->ea,
-                             ip4_src, in6_addr_get_mapped_ipv4(&svc_mon->ip),
+        pinctrl_compose_ipv4(&packet, svc_mon->src_mac, svc_mon->ea,
+                             in6_addr_get_mapped_ipv4(&svc_mon->src_ip),
+                             in6_addr_get_mapped_ipv4(&svc_mon->ip),
                              IPPROTO_UDP, 63, UDP_HEADER_LEN + 8);
     }
 
diff --git a/debian/changelog b/debian/changelog
index e06f13536..244bb95ce 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+ovn (23.09.6-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 16 Aug 2024 13:51:01 -0400
+
+OVN (23.09.5-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 16 Aug 2024 13:51:01 -0400
+
 OVN (23.09.4-1) unstable; urgency=low
    [ OVN team ]
    * New upstream version
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index 7446accad..cf0558c0e 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -2324,7 +2324,7 @@ ovn_ic_resume(struct unixctl_conn *conn, int argc OVS_UNUSED,
 {
     struct ic_state *state = state_;
     state->paused = false;
-
+    poll_immediate_wake();
     unixctl_command_reply(conn, NULL);
 }
 
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index e3236fa24..f07c4c42e 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -80,7 +80,6 @@ enum mff_log_flags_bits {
     MLF_LOCALNET_BIT = 15,
     MLF_RX_FROM_TUNNEL_BIT = 16,
     MLF_ICMP_SNAT_BIT = 17,
-    MLF_IGMP_IGMP_SNOOP_INJECT_BIT = 18,
 };
 
 /* MFF_LOG_FLAGS_REG flag assignments */
@@ -136,8 +135,6 @@ enum mff_log_flags {
     MLF_RX_FROM_TUNNEL = (1 << MLF_RX_FROM_TUNNEL_BIT),
 
     MLF_ICMP_SNAT = (1 << MLF_ICMP_SNAT_BIT),
-
-    MLF_IGMP_IGMP_SNOOP = (1 << MLF_IGMP_IGMP_SNOOP_INJECT_BIT),
 };
 
 /* OVN logical fields
diff --git a/lib/actions.c b/lib/actions.c
index 2b3fc915c..5c10edf53 100644
--- a/lib/actions.c
+++ b/lib/actions.c
@@ -875,6 +875,9 @@ encode_CT_COMMIT_V2(const struct ovnact_nest *on,
                     const struct ovnact_encode_params *ep OVS_UNUSED,
                     struct ofpbuf *ofpacts)
 {
+    size_t ct_offset = ofpacts->size;
+    ofpbuf_pull(ofpacts, ct_offset);
+
     struct ofpact_conntrack *ct = ofpact_put_CT(ofpacts);
     ct->flags = NX_CT_F_COMMIT;
     ct->recirc_table = NX_CT_RECIRC_NONE;
@@ -906,6 +909,7 @@ encode_CT_COMMIT_V2(const struct ovnact_nest *on,
     ofpacts->header = ofpbuf_push_uninit(ofpacts, set_field_offset);
     ct = ofpacts->header;
     ofpact_finish(ofpacts, &ct->ofpact);
+    ofpbuf_push_uninit(ofpacts, ct_offset);
 }
 
 static void
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index 68892dba5..d84528ef5 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -139,10 +139,6 @@ ovn_init_symtab(struct shash *symtab)
                              flags_str);
     snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_RX_FROM_TUNNEL_BIT);
     expr_symtab_add_subfield(symtab, "flags.tunnel_rx", NULL, flags_str);
-    snprintf(flags_str, sizeof flags_str, "flags[%d]",
-             MLF_IGMP_IGMP_SNOOP_INJECT_BIT);
-    expr_symtab_add_subfield(symtab, "flags.igmp_loopback", NULL,
-                             flags_str);
 
     /* Connection tracking state. */
     expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
@@ -247,7 +243,7 @@ ovn_init_symtab(struct shash *symtab)
     expr_symtab_add_field(symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4",
               false);
 
-    expr_symtab_add_predicate(symtab, "igmp", "ip4 && ip.proto == 2");
+    expr_symtab_add_predicate(symtab, "igmp", "ip4.mcast && ip.proto == 2");
 
     expr_symtab_add_field(symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false);
     expr_symtab_add_field(symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false);
@@ -307,6 +303,9 @@ ovn_init_symtab(struct shash *symtab)
               "icmp6.type == {135, 136} && icmp6.code == 0 && ip.ttl == 255");
     expr_symtab_add_predicate(symtab, "nd_ns",
               "icmp6.type == 135 && icmp6.code == 0 && ip.ttl == 255");
+    expr_symtab_add_predicate(symtab, "nd_ns_mcast",
+              "ip6.mcast && icmp6.type == 135 && icmp6.code == 0 && "
+              "ip.ttl == 255");
     expr_symtab_add_predicate(symtab, "nd_na",
               "icmp6.type == 136 && icmp6.code == 0 && ip.ttl == 255");
     expr_symtab_add_predicate(symtab, "nd_rs",
@@ -321,11 +320,12 @@ ovn_init_symtab(struct shash *symtab)
      * (RFC 2710 and RFC 3810).
      */
     expr_symtab_add_predicate(symtab, "mldv1",
-                              "ip6.src == fe80::/10 && "
+                              "eth.mcastv6 && ip6.src == fe80::/10 && "
                               "icmp6.type == {130, 131, 132}");
     /* MLDv2 packets are sent to ff02::16 (RFC 3810, 5.2.14) */
     expr_symtab_add_predicate(symtab, "mldv2",
-                              "ip6.dst == ff02::16 && icmp6.type == 143");
+                              "eth.mcastv6 && ip6.dst == ff02::16 && "
+                              "icmp6.type == 143");
 
     expr_symtab_add_predicate(symtab, "tcp", "ip.proto == 6");
     expr_symtab_add_field(symtab, "tcp.src", MFF_TCP_SRC, "tcp", false);
diff --git a/northd/northd.c b/northd/northd.c
index 32240b856..6a4f8f222 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -119,6 +119,7 @@ static bool default_acl_drop;
 #define REGBIT_PORT_SEC_DROP      "reg0[15]"
 #define REGBIT_ACL_STATELESS      "reg0[16]"
 #define REGBIT_ACL_HINT_ALLOW_REL "reg0[17]"
+#define REGBIT_FROM_ROUTER_PORT   "reg0[18]"
 
 #define REG_ORIG_DIP_IPV4         "reg1"
 #define REG_ORIG_DIP_IPV6         "xxreg1"
@@ -805,12 +806,6 @@ ovn_datapath_is_stale(const struct ovn_datapath *od)
     return !od->nbr && !od->nbs;
 }
 
-static inline bool
-ovn_datapath_is_transit_switch(const struct ovn_datapath *od)
-{
-    return od->tunnel_key >= OVN_MIN_DP_KEY_GLOBAL;
-}
-
 static struct ovn_datapath *
 ovn_datapath_from_sbrec(const struct hmap *ls_datapaths,
                         const struct hmap *lr_datapaths,
@@ -6126,13 +6121,11 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
             continue;
         }
 
-        /* If this is already a port of a router on which relay is enabled
-         * and it's not a transit switch to router port, skip it for the
-         * group.  Traffic is flooded there anyway.
+        /* If this is already a port of a router on which relay is enabled,
+         * skip it for the group. Traffic is flooded there anyway.
          */
         if (port->peer && port->peer->od &&
-                port->peer->od->mcast_info.rtr.relay &&
-                !ovn_datapath_is_transit_switch(port->od)) {
+                port->peer->od->mcast_info.rtr.relay) {
             continue;
         }
 
@@ -6956,6 +6949,12 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct hmap *lflows,
                     op->od->localnet_ports[0]->key,
                     &op->od->localnet_ports[0]->nbsp->header_);
         }
+    } else if (lsp_is_router(op->nbsp)) {
+        ds_put_format(actions, REGBIT_FROM_ROUTER_PORT" = 1; next;");
+        ovn_lflow_add_with_lport_and_hint(lflows, op->od,
+                                          S_SWITCH_IN_CHECK_PORT_SEC, 70,
+                                          ds_cstr(match), ds_cstr(actions),
+                                          op->key, &op->nbsp->header_);
     }
 }
 
@@ -7252,8 +7251,7 @@ build_interconn_mcast_snoop_flows(struct ovn_datapath *od,
             continue;
         }
         /* Punt IGMP traffic to controller. */
-        char *match = xasprintf("inport == %s && igmp && "
-                                "flags.igmp_loopback == 0", op->json_key);
+        char *match = xasprintf("inport == %s && igmp", op->json_key);
         ovn_lflow_metered(lflows, od, S_SWITCH_OUT_PRE_LB, 120, match,
                           "clone { igmp; }; next;",
                           copp_meter_get(COPP_IGMP, od->nbs->copp,
@@ -7261,8 +7259,7 @@ build_interconn_mcast_snoop_flows(struct ovn_datapath *od,
         free(match);
 
         /* Punt MLD traffic to controller. */
-        match = xasprintf("inport == %s && (mldv1 || mldv2) && "
-                          "flags.igmp_loopback == 0", op->json_key);
+        match = xasprintf("inport == %s && (mldv1 || mldv2)", op->json_key);
         ovn_lflow_metered(lflows, od, S_SWITCH_OUT_PRE_LB, 120, match,
                           "clone { igmp; }; next;",
                           copp_meter_get(COPP_IGMP, od->nbs->copp,
@@ -8933,13 +8930,16 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
     struct ds actions = DS_EMPTY_INITIALIZER;
     struct ds group_ports = DS_EMPTY_INITIALIZER;
 
-    for (int i = 0; i < od->nbs->n_forwarding_groups; ++i) {
+    for (size_t i = 0; i < od->nbs->n_forwarding_groups; ++i) {
         const struct nbrec_forwarding_group *fwd_group = NULL;
         fwd_group = od->nbs->forwarding_groups[i];
         if (!fwd_group->n_child_port) {
             continue;
         }
 
+        ds_clear(&match);
+        ds_clear(&actions);
+
         /* ARP responder for the forwarding group's virtual IP */
         ds_put_format(&match, "arp.tpa == %s && arp.op == 1",
                       fwd_group->vip);
@@ -8970,9 +8970,9 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct hmap *lflows)
             ds_put_cstr(&group_ports, "liveness=\"true\",");
         }
         ds_put_cstr(&group_ports, "childports=");
-        for (i = 0; i < (fwd_group->n_child_port - 1); ++i) {
+        for (size_t j = 0; j < (fwd_group->n_child_port - 1); ++j) {
             ds_put_format(&group_ports, "\"%s\",",
-                         fwd_group->child_port[i]);
+                          fwd_group->child_port[j]);
         }
         ds_put_format(&group_ports, "\"%s\"",
                       fwd_group->child_port[fwd_group->n_child_port - 1]);
@@ -9912,16 +9912,21 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
                                                   &op->nbsp->header_);
             }
 
-            /* For ND solicitations, we need to listen for both the
-             * unicast IPv6 address and its all-nodes multicast address,
-             * but always respond with the unicast IPv6 address. */
+            /* For ND solicitations:
+             *   - Reply only for the all-nodes multicast address(es) of the
+             *     logical port IPv6 address(es).
+             *
+             *   - Do not reply for unicast ND solicitations.  Let the target
+             *     reply to it, so that the sender has the ability to monitor
+             *     the target liveness via the unicast ND solicitations.
+             */
             for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
                 ds_clear(match);
-                ds_put_format(match,
-                        "nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
-                        op->lsp_addrs[i].ipv6_addrs[j].addr_s);
+                ds_put_format(
+                    match,
+                    "nd_ns_mcast && ip6.dst == %s && nd.target == %s",
+                    op->lsp_addrs[i].ipv6_addrs[j].sn_addr_s,
+                    op->lsp_addrs[i].ipv6_addrs[j].addr_s);
 
                 ds_clear(actions);
                 ds_put_format(actions,
@@ -9977,7 +9982,9 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
         if (op->proxy_arp_addrs.n_ipv4_addrs) {
             /* Match rule on all proxy ARP IPs. */
             ds_clear(match);
-            ds_put_cstr(match, "arp.op == 1 && arp.tpa == {");
+            ds_put_cstr(match,
+                        REGBIT_FROM_ROUTER_PORT" == 0 "
+                        "&& arp.op == 1 && arp.tpa == {");
 
             for (i = 0; i < op->proxy_arp_addrs.n_ipv4_addrs; i++) {
                 ds_put_format(match, "%s/%u,",
@@ -10027,7 +10034,8 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
             ds_truncate(&nd_target_match, nd_target_match.length - 2);
             ds_clear(match);
             ds_put_format(match,
-                          "nd_ns "
+                          REGBIT_FROM_ROUTER_PORT" == 0 "
+                          "&& nd_ns "
                           "&& ip6.dst == { %s } "
                           "&& nd.target == { %s }",
                           ds_cstr(&ip6_dst_match),
@@ -10288,14 +10296,13 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
         ds_put_cstr(actions, "igmp;");
         /* Punt IGMP traffic to controller. */
         ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "flags.igmp_loopback == 0 && igmp", ds_cstr(actions),
+                          "igmp", ds_cstr(actions),
                           copp_meter_get(COPP_IGMP, od->nbs->copp,
                                          meter_groups));
 
         /* Punt MLD traffic to controller. */
         ovn_lflow_metered(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "flags.igmp_loopback == 0 && (mldv1 || mldv2)",
-                          ds_cstr(actions),
+                          "mldv1 || mldv2", ds_cstr(actions),
                           copp_meter_get(COPP_IGMP, od->nbs->copp,
                                          meter_groups));
 
@@ -10359,16 +10366,8 @@ build_lswitch_destination_lookup_bmcast(struct ovn_datapath *od,
 }
 
 
-/* Ingress table 27: Add IP multicast flows learnt from IGMP/MLD
- * (priority 90).
- *
- * OR, for transit switches:
- *
- * Add IP multicast flows learnt from IGMP/MLD to forward traffic
- * explicitly to the ports that are part of the IGMP/MLD group,
- * and ignore MROUTER Ports.
- * (priority 90).
- */
+/* 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,
                                 struct hmap *lflows,
@@ -10382,9 +10381,6 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
         ds_clear(match);
         ds_clear(actions);
 
-        bool transit_switch =
-            ovn_datapath_is_transit_switch(igmp_group->datapath);
-
         struct mcast_switch_info *mcast_sw_info =
             &igmp_group->datapath->mcast_info.sw;
         uint64_t table_size = mcast_sw_info->table_size;
@@ -10430,7 +10426,7 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
         }
 
         /* Also flood traffic to all multicast routers with relay enabled. */
-        if (mcast_sw_info->flood_relay && !transit_switch) {
+        if (mcast_sw_info->flood_relay) {
             ds_put_cstr(actions,
                         "clone { "
                             "outport = \""MC_MROUTER_FLOOD "\"; "
@@ -10779,8 +10775,9 @@ build_bfd_table(struct ovsdb_idl_txn *ovnsb_txn,
             }
             build_bfd_update_sb_conf(nb_bt, bfd_e->sb_bt);
             if (op && op->sb && op->sb->chassis &&
-                strcmp(op->sb->chassis->name, sb_bt->chassis_name)) {
-                sbrec_bfd_set_chassis_name(sb_bt, op->sb->chassis->name);
+                strcmp(op->sb->chassis->name, bfd_e->sb_bt->chassis_name)) {
+                sbrec_bfd_set_chassis_name(bfd_e->sb_bt,
+                                           op->sb->chassis->name);
             }
 
             hmap_remove(&sb_only, &bfd_e->hmap_node);
@@ -17618,7 +17615,8 @@ build_static_mac_binding_table(
     struct hmap *lr_ports)
 {
     /* Cleanup SB Static_MAC_Binding entries which do not have corresponding
-     * NB Static_MAC_Binding entries. */
+     * NB Static_MAC_Binding entries, and SB Static_MAC_Binding entries for
+     * which there is not a NB Logical_Router_Port of the same name. */
     const struct nbrec_static_mac_binding *nb_smb;
     const struct sbrec_static_mac_binding *sb_smb;
     SBREC_STATIC_MAC_BINDING_TABLE_FOR_EACH_SAFE (sb_smb,
@@ -17628,6 +17626,12 @@ build_static_mac_binding_table(
                                                sb_smb->ip);
         if (!nb_smb) {
             sbrec_static_mac_binding_delete(sb_smb);
+            continue;
+        }
+
+        struct ovn_port *op = ovn_port_find(lr_ports, nb_smb->logical_port);
+        if (!op || !op->nbrp || !op->od || !op->od->sb) {
+            sbrec_static_mac_binding_delete(sb_smb);
         }
     }
 
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 897e626b4..f36afd999 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1983,13 +1983,13 @@ output;
       </li>
 
       <li>
-        Priority-90 flows for non-transit switches that forward registered
-        IP multicast traffic to their corresponding multicast group, which
-        <code>ovn-northd</code> creates based on learnt
-        <ref table="IGMP_Group" db="OVN_Southbound"/> entries.  The flows
-        also forward packets to the <code>MC_MROUTER_FLOOD</code> multicast
-        group, which <code>ovn-nortdh</code> populates with all the logical
-        ports that are connected to logical routers with
+        Priority-90 flows that forward registered IP multicast traffic to
+        their corresponding multicast group, which <code>ovn-northd</code>
+        creates based on learnt <ref table="IGMP_Group" db="OVN_Southbound"/>
+        entries.  The flows also forward packets to the
+        <code>MC_MROUTER_FLOOD</code> multicast group, which
+        <code>ovn-nortdh</code> populates with all the logical ports that
+        are connected to logical routers with
         <ref column="options" table="Logical_Router"/>:mcast_relay='true'.
       </li>
 
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 00ff6ea4f..22c392b67 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -1098,6 +1098,7 @@ ovn_northd_resume(struct unixctl_conn *conn, int argc OVS_UNUSED,
 {
     struct northd_state *state = state_;
     state->paused = false;
+    poll_immediate_wake();
 
     unixctl_command_reply(conn, NULL);
 }
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 94e722512..01d1a3f4d 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1155,6 +1155,7 @@
       <ul>
         <li><code>eth.bcast</code> expands to <code>eth.dst == ff:ff:ff:ff:ff:ff</code></li>
         <li><code>eth.mcast</code> expands to <code>eth.dst[40]</code></li>
+        <li><code>eth.mcastv6</code> expands to <code>eth.dst[32..47] == 0x3333</code></li>
         <li><code>vlan.present</code> expands to <code>vlan.tci[12]</code></li>
         <li><code>ip4</code> expands to <code>eth.type == 0x800</code></li>
         <li><code>ip4.src_mcast</code> expands to
@@ -1170,8 +1171,10 @@
         <li><code>ip.first_frag</code> expands to <code>ip.is_frag &amp;&amp; !ip.later_frag</code></li>
         <li><code>arp</code> expands to <code>eth.type == 0x806</code></li>
         <li><code>rarp</code> expands to <code>eth.type == 0x8035</code></li>
+        <li><code>ip6.mcast</code> expands to <code>eth.mcastv6 &amp;&amp; ip6.dst[120..127] == 0xff</code></li>
         <li><code>nd</code> expands to <code>icmp6.type == {135, 136} &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
         <li><code>nd_ns</code> expands to <code>icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
+        <li><code>nd_ns_mcast</code> expands to <code>ip6.mcast  &amp;&amp; icmp6.type == 135 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
         <li><code>nd_na</code> expands to <code>icmp6.type == 136 &amp;&amp; icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
         <li><code>nd_rs</code> expands to <code>icmp6.type == 133 &amp;&amp;
         icmp6.code == 0 &amp;&amp; ip.ttl == 255</code></li>
diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at
index 83d13213f..678afe462 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -2772,3 +2772,41 @@ priority=1100,ip,reg15=0x1,metadata=0x1,nw_src=10.0.0.4 actions=load:0x1->OXM_OF
 
 OVN_CLEANUP([hv1])
 AT_CLEANUP
+
+AT_SETUP([ovn-controller - LB remove after disconnect])
+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 vif1 -- \
+    set interface vif1 external-ids:iface-id=lsp
+
+check ovs-vsctl set Open_vSwitch . external-ids:ovn-remote-probe-interval="5000"
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl lsp-add ls lsp \
+-- lsp-set-addresses lsp "f0:00:00:00:00:01 172.16.0.10"
+
+check ovn-nbctl lb-add lb 192.168.100.100 172.16.0.10
+check ovn-nbctl ls-lb-add ls lb
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+sleep_sb
+OVS_WAIT_UNTIL([grep -q 'OVNSB commit failed' hv1/ovn-controller.log])
+
+sleep_controller hv1
+wake_up_sb
+
+ovn-nbctl lb-del lb
+
+wake_up_controller hv1
+check ovn-nbctl --wait=hv sync
+
+OVN_CLEANUP([hv1
+/no response to inactivity probe after .* seconds, disconnecting/d])
+AT_CLEANUP
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index f5de46a0b..fc89b5bab 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -957,16 +957,18 @@ stop_ovsdb_controller_updates() {
   TCP_PORT=$1
   echo Stopping updates from ovn-controller to ovsdb using port $TCP_PORT
   on_exit 'nft list tables | grep ovn-test && nft delete table ip ovn-test'
-  nft add table ip ovn-test
-  nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'
-  nft add rule ip ovn-test INPUT tcp dport $TCP_PORT counter drop
+  # Report the test as skipped if proper nft related packages are not installed.
+  AT_SKIP_IF([! which nft])
+  AT_CHECK([nft add table ip ovn-test])
+  AT_CHECK([nft 'add chain ip ovn-test INPUT { type filter hook input priority 0; policy accept; }'])
+  AT_CHECK([nft add rule ip ovn-test INPUT tcp dport $TCP_PORT counter drop])
 }
 
 restart_ovsdb_controller_updates() {
   TCP_PORT=$1
   echo Restarting updates from ovn-controller to ovsdb
-  nft list ruleset | grep $TCP_PORT
-  nft delete table ip ovn-test
+  AT_CHECK([nft list ruleset | grep $TCP_PORT], [0], [ignore])
+  AT_CHECK([nft delete table ip ovn-test])
 }
 
 trim_zeros() {
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 4bebb2673..22c881d6a 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -7619,6 +7619,19 @@ wait_row_count Static_MAC_Binding 1 logical_port=lr0-p0 ip=192.168.10.100 mac="0
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([LR NB Static_MAC_Binding garbage collection])
+ovn_start
+
+check ovn-nbctl lr-add lr -- lrp-add lr lr-lrp 00:00:00:00:00:01 1.1.1.1/24
+check ovn-nbctl static-mac-binding-add lr-lrp 1.1.1.2 00:00:00:00:00:02
+wait_row_count sb:Static_MAC_Binding 1 logical_port=lr-lrp ip=1.1.1.2 mac="00\:00\:00\:00\:00\:02"
+check ovn-nbctl lr-del lr
+wait_row_count sb:Static_MAC_Binding 0
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([LR neighbor lookup and learning flows])
 ovn_start
diff --git a/tests/ovn.at b/tests/ovn.at
index ffd9f653e..d44a36be9 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1304,6 +1304,9 @@ ct_commit { ct_label=0x181716151413121110090807060504030201; };
     141-bit constant is not compatible with 128-bit field ct_label.
 ct_commit { ip4.dst = 192.168.0.1; };
     Field ip4.dst is not modifiable.
+reg8[17] = 1; ct_commit { ct_mark.blocked = 1; };
+    encodes as set_field:0x2000000000000/0x2000000000000->xreg4,ct(commit,zone=NXM_NX_REG13[0..15],exec(set_field:0x1/0x1->ct_mark))
+    has prereqs ip
 
 # Legact ct_commit_v1 action.
 ct_commit();
@@ -5767,13 +5770,24 @@ for i in 1 2 3; do
 
     # Test ICMPv6 MLD reports (v1 and v2) and NS for DAD
     sip=00000000000000000000000000000000
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 83 21
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 8f 21
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff0200000000000000ea2aeafffe2800 87 21
+    # Multicast traffic is delivered to all ports, except for the source.
+    out_ports=
+    for j in 1 2 3; do
+        for k in 1 2 3; do
+            if test "${j}${k}" -eq "${i}3"; then
+                continue
+            else
+                out_ports="$out_ports ${j}${k}"
+            fi
+        done
+    done
+    test_icmpv6 ${i}3 f00000000${i}${i}3 333300160000 $sip ff020000000000000000000000160000 83 ${out_ports}
+    test_icmpv6 ${i}3 f00000000${i}${i}3 333300160000 $sip ff020000000000000000000000160000 8f ${out_ports}
+    test_icmpv6 ${i}3 f00000000${i}${i}3 3333fffe2800 $sip ff0200000000000000ea2aeafffe2800 87 ${out_ports}
     # Traffic to non-multicast traffic should be dropped
     test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 83
     # Traffic of other ICMPv6 types should be dropped
-    test_icmpv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip ff020000000000000000000000160000 80
+    test_icmpv6 ${i}3 f00000000${i}${i}3 333300160000 $sip ff020000000000000000000000160000 80
 
     # should be dropped
     sip=ae80000000000000ea2aeafffe2800aa
@@ -8428,7 +8442,7 @@ done
 # Complete Neighbor Solicitation packet and Neighbor Advertisement packet
 # vif1 -> NS -> vif2.  vif1 <- NA <- ovn-controller.
 # vif2 will not receive NS packet, since ovn-controller will reply for it.
-ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598fd81ce49a9480000f8163efffea1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598
+ns_packet=3333ffa1f9aefa163e94059886dd6000000000203afffd81ce49a9480000f8163efffe940598ff0200000000000000000001ffa1f9ae8700e01160000000fd81ce49a9480000f8163efffea1f9ae0101fa163e940598
 na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffea1f9aefd81ce49a9480000f8163efffe9405988800e9ed60000000fd81ce49a9480000f8163efffea1f9ae0201fa163ea1f9ae
 
 as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet
@@ -14131,7 +14145,7 @@ 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}') /
+        Ether(dst='33:33:ff:ff:ff:ff', src='${src_mac}') /
         IPv6(src='${src_ip}', dst='ff02::1:ff00:2') /
         ICMPv6ND_NS(tgt='${dst_ip}')
     ")
@@ -14171,8 +14185,8 @@ 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') /
+        Ether(dst='33:33:00:00:00:01', src='${src_mac}') /
+        IPv6(src='${src_ip}', dst='ff02::1') /
         ICMPv6MLQuery2()
     ")
     as hv1 ovs-appctl netdev-dummy/receive vif${inport} $packet
@@ -15451,14 +15465,17 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
    second_mac=00:00:00:00:00:02
    multi1_mac=00:00:00:00:00:f0
    multi2_mac=00:00:00:00:00:f1
+   external_mac=00:00:00:00:ee:ff
    first_ip=10.0.0.1
    second_ip=10.0.0.2
    multi1_ip=10.0.0.10
    multi2_ip=10.0.0.20
+   external_ip=10.0.0.30
    first_ip6=abcd::1
    second_ip6=abcd::2
    multi1_ip6=abcd::f0
    multi2_ip6=abcd::f1
+   external_ip6=abcd::eeff
 
    check ovn-nbctl ls-add ls0
    check ovn-nbctl lsp-add ls0 first
@@ -15577,6 +15594,24 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
 
    reset_env
 
+   AS_BOX([Multi with "unknown" to external doesn't produce wrong FDB])
+   len=3000
+   check ovn-nbctl --wait=hv lsp-set-addresses multi1 unknown
+
+   packet=$(send_ip_packet multi1 1 $multi1_mac $external_mac $multi1_ip $external_ip $(payload $len) 1 ${expected_ip_mtu})
+   echo $packet >> hv1/multi1.expected
+
+   packet=$(send_ip6_packet multi1 1 $multi1_mac $external_mac $multi1_ip6 $external_ip6 $(payload $len) 1 ${expected_ip_mtu})
+   echo $packet >> hv1/multi1.expected
+
+   check_pkts
+   reset_env
+
+   check_row_count fdb 0 mac="$external_mac"
+   ovn-sbctl --all destroy fdb
+
+   check ovn-nbctl --wait=hv lsp-set-addresses multi1 "${multi1_mac} ${multi1_ip} ${multi1_ip6}"
+
    AS_BOX([Packets of proper size are delivered from multichassis to regular ports])
 
    len=1000
@@ -15676,9 +15711,6 @@ m4_define([MULTICHASSIS_PATH_MTU_DISCOVERY_TEST],
    packet=$(send_ip6_packet multi1 1 $multi1_mac $multi2_mac $multi1_ip6 $multi2_ip6 $(payload $len) 1)
    echo $packet >> hv1/multi1.expected
 
-   check_pkts
-   reset_env
-
    AS_BOX([MTU updates are honored in ICMP Path MTU calculation])
 
    set_mtu() {
@@ -26674,20 +26706,10 @@ wait_row_count IGMP_Group 2 address=239.0.1.68
 wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
 check ovn-nbctl --wait=hv sync
 
-#Validate that Multicast Group contains all registered ports for
-# specific igmp group.
-ts_dp=$(fetch_column datapath_binding _uuid external_ids:name=ts)
-ports=$(fetch_column multicast_group ports name="239.0.1.68" datapath=$ts_dp)
-check test X2 = X$(echo $ports | wc -w)
-
-
 ovn_as az2
 wait_row_count IGMP_Group 2 address=239.0.1.68
 wait_row_count IGMP_Group 2 address='"ff0a:dead:beef::1"'
 check ovn-nbctl --wait=hv sync
-ts_dp=$(fetch_column datapath_binding _uuid external_ids:name=ts)
-ports=$(fetch_column multicast_group ports name="239.0.1.68" datapath=$ts_dp)
-check test X2 = X$(echo $ports | wc -w)
 
 # Send an IP multicast packet from LSP2, it should be forwarded
 # to lsp1 and lsp3.
@@ -27428,7 +27450,14 @@ check ovs-vsctl add-port br-int vif3 -- set Interface vif3 external-ids:iface-id
 # Add a forwarding group on ls2 with lsp21 and lsp22 as child ports
 # virtual IP - 172.16.1.11, virtual MAC - 00:11:de:ad:be:ef
 check ovn-nbctl --wait=hv fwd-group-add fwd_grp1 ls2 172.16.1.11 00:11:de:ad:be:ef lsp21 lsp22
+check ovn-nbctl --wait=hv fwd-group-add fwd_grp2 ls2 172.16.2.11 00:22:de:ad:be:ef lsp21 lsp22
 
+# Check logical flow
+AT_CAPTURE_FILE([sbflows])
+ovn-sbctl dump-flows > sbflows
+AT_CHECK([grep ls_in_l2_lkup sbflows | grep fwd_group | wc -l], [0], [2
+])
+check ovn-nbctl --wait=hv fwd-group-del fwd_grp2
 # Check logical flow
 AT_CAPTURE_FILE([sbflows])
 ovn-sbctl dump-flows > sbflows
@@ -32805,7 +32834,7 @@ AT_CHECK([ovn-sbctl dump-flows |
           grep ls_in_arp_rsp |
           grep "${arp_proxy_ls1[[1]]}" |
           sed 's/table=../table=??/'], [0], [dnl
-  table=??(ls_in_arp_rsp      ), priority=30   , match=(arp.op == 1 && dnl
+  table=??(ls_in_arp_rsp      ), priority=30   , match=(reg0[[18]] == 0 && arp.op == 1 && dnl
 arp.tpa == {169.254.238.0/24,169.254.239.2/32}), dnl
 action=(eth.dst = eth.src; eth.src = 00:00:00:01:02:f1; arp.op = 2; dnl
 /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:01:02:f1; dnl
@@ -32818,7 +32847,7 @@ AT_CHECK([ovn-sbctl dump-flows |
           grep "${arp_proxy_ls1[[3]]}" |
           sed 's/table=../table=??/'], [0], [dnl
   table=??(ls_in_arp_rsp      ), priority=30   , dnl
-match=(nd_ns && ip6.dst == { fd7b:6b4d:7b25:d22d::/64, ff02::1:ff00:0/64, dnl
+match=(reg0[[18]] == 0 && nd_ns && ip6.dst == { fd7b:6b4d:7b25:d22d::/64, ff02::1:ff00:0/64, dnl
 fd7b:6b4d:7b25:d22f::1/128, ff02::1:ff00:1/128 } && dnl
 nd.target == { fd7b:6b4d:7b25:d22d::/64, fd7b:6b4d:7b25:d22f::1/128 }), dnl
 action=(nd_na_router { eth.src = 00:00:00:01:02:f1; ip6.src = nd.target; dnl
@@ -32832,7 +32861,7 @@ AT_CHECK([ovn-sbctl dump-flows |
           grep "${arp_proxy_ls2[[2]]}" |
           sed 's/table=../table=??/'], [0], [dnl
   table=??(ls_in_arp_rsp      ), priority=30   , dnl
-match=(arp.op == 1 && arp.tpa == {169.254.236.0/24,169.254.237.2/32}), dnl
+match=(reg0[[18]] == 0 && arp.op == 1 && arp.tpa == {169.254.236.0/24,169.254.237.2/32}), dnl
 action=(eth.dst = eth.src; eth.src = 00:00:00:02:02:f1; arp.op = 2; dnl
 /* ARP reply */ arp.tha = arp.sha; arp.sha = 00:00:00:02:02:f1; dnl
 arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1; output;)
@@ -32844,7 +32873,7 @@ AT_CHECK([ovn-sbctl dump-flows |
           grep "${arp_proxy_ls2[[4]]}" |
           sed 's/table=../table=??/'], [0], [dnl
   table=??(ls_in_arp_rsp      ), priority=30   , dnl
-match=(nd_ns && ip6.dst == { fd7b:6b4d:7b25:d22b::/64, ff02::1:ff00:0/64, dnl
+match=(reg0[[18]] == 0 && nd_ns && ip6.dst == { fd7b:6b4d:7b25:d22b::/64, ff02::1:ff00:0/64, dnl
 fd7b:6b4d:7b25:d22c::1/128, ff02::1:ff00:1/128 } && dnl
 nd.target == { fd7b:6b4d:7b25:d22b::/64, fd7b:6b4d:7b25:d22c::1/128 }), dnl
 action=(nd_na_router { eth.src = 00:00:00:02:02:f1; ip6.src = nd.target; dnl
@@ -35202,7 +35231,10 @@ check_default_flows() {
 
 # Check that every drop flow gets sampled.
 check_sample_drops() {
-
+    hv=hv$1
+    remote_hv=hv$((${1}%2 + 1))
+    race_condition=$2
+    ovs-vsctl destroy Flow_Sample_Collector_Set 123
     check ovn-nbctl -- remove NB_Global . options debug_drop_collector_set \
                     -- remove NB_Global . options debug_drop_domain_id
     check ovn-nbctl --wait=hv sync
@@ -35212,14 +35244,52 @@ check_sample_drops() {
     # Take match part of flows that contain "drop".
     drop_matches="$(grep 'drop' oflows_nosample | grep -oP 'table=\d*, priority=.* ')"
 
+    if [[ x$race_condition = x"true" ]]; then
+        sleep_controller $hv
+        # Get ofport used by the geneve interface
+        OVS_WAIT_UNTIL([
+            ofport=$(as $hv ovs-vsctl --bare --columns ofport find Interface name=ovn-${remote_hv}-0)
+            test 1 -le $ofport
+        ])
+
+        # Add a vif while ovn-controller sleeps, and make it request the ofport used by the geneve interface.
+        # This is used to cause the geneve interface to change ofport.
+        ovs-vsctl -- add-port br-int vif3 -- set interface vif3\
+            options:tx_pcap=${hv}/vif3-tx.pcap \
+            options:rxq_pcap=${hv}/vif3-rx.pcap \
+            ofport-request=$ofport
+        OVS_WAIT_UNTIL([
+            vif_ofport=$(as $hv ovs-vsctl --bare --columns ofport find Interface name=vif3)
+            test 1 -le $vif_ofport
+        ])
+        # For the geneve interface ofport change to happen...
+        ovs-vsctl -- add-port br-int vif4 -- set interface vif4\
+            options:tx_pcap=${hv}/vif4-tx.pcap \
+            options:rxq_pcap=${hv}/vif4-rx.pcap
+        OVS_WAIT_UNTIL([
+            new_ofport=$(as $hv ovs-vsctl --bare --columns ofport find Interface name=ovn-${remote_hv}-0)
+            test $ofport -ne $new_ofport
+        ])
+    fi
+
     ovs-vsctl --id=@br get Bridge br-int --  \
         --id=@i create IPFIX targets=\"192.168.1.1\"  -- \
         create Flow_Sample_Collector_Set bridge=@br id=123 ipfix=@i
 
     check ovn-nbctl -- set NB_Global . options:debug_drop_collector_set="123" \
                     -- set NB_Global . options:debug_drop_domain_id="1"
-    check ovn-nbctl --wait=hv sync
 
+    if [[ x$race_condition = x"true" ]]; then
+        # Wait sb as ovn-controller sleeps.
+        check ovn-nbctl --wait=sb sync
+        # Wake up ovn-controller. It should most of the times receive non-vif ofport change and sb changes at the same time.
+        wake_up_controller $hv
+        # Check twice ovn-controller is running to guarantee if run a full loop.
+        OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+        OVS_WAIT_UNTIL([test x$(as $hv ovn-appctl -t ovn-controller debug/status) = "xrunning"])
+    else
+        check ovn-nbctl --wait=hv sync
+    fi
     ovs-ofctl dump-flows --no-stats br-int > oflows_sample
     AT_CAPTURE_FILE([oflows_sample])
 
@@ -35227,6 +35297,8 @@ check_sample_drops() {
     save_IFS=$IFS
     IFS=$'\n'
     for flow in $drop_matches; do
+        # Restore IFS to avoid "printf %s\n: command not found" errors.
+        IFS=$save_IFS
         AT_CHECK([grep "${flow}actions=.*sample.*" oflows_sample], [0], [ignore], [ignore], [echo "Flow $flow has a drop and did not get sampled"])
     done
     IFS=$save_IFS
@@ -35239,9 +35311,9 @@ check_drops() {
     check_default_flows
 
     as hv1
-    check_sample_drops
+    check_sample_drops 1 $1
     as hv2
-    check_sample_drops
+    check_sample_drops 2 $1
 }
 
 # Logical network:
@@ -35309,7 +35381,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
 wait_for_ports_up
 check ovn-nbctl --wait=hv sync
 
-check_debug
+check_drops
+check_drops true
 
 # Add stateless ACL
 check ovn-nbctl --wait=sb \
@@ -35317,7 +35390,7 @@ check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 'ip4' allow-stateless
 
-check_debug
+check_drops
 
 check ovn-nbctl --wait=sb acl-del ls1
 check ovn-nbctl --wait=sb acl-del ls2
@@ -35328,7 +35401,7 @@ check ovn-nbctl --wait=sb \
 check ovn-nbctl --wait=sb \
                 -- acl-add ls2 from-lport 100 "udp" allow-related
 
-check_debug
+check_drops
 
 check ovn-nbctl --wait=sb acl-del ls1
 check ovn-nbctl --wait=sb acl-del ls2
@@ -37663,9 +37736,9 @@ AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
                     --ca-cert=$PKIDIR/testpki-cacert.pem \
                     --ssl-ciphers='HIGH:!aNULL:!MD5:@SECLEVEL=1' \
                     --ssl-protocols='TLSv1,TLSv1.1,TLSv1.2' \
-                    chassis-add ch vxlan 1.2.4.8], [1], [ignore],
-[ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
-])
+                    chassis-add ch vxlan 1.2.4.8 2>&1 | grep 'transaction error]', [0], [dnl
+ovn-sbctl: transaction error: {"details":"insert operation not allowed when database server is in read only mode","error":"not allowed"}
+], [ignore])
 
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP
@@ -37840,3 +37913,171 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=1
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+dnl This test checks that the megaflows translated by ovs-vswitchd
+dnl don't match on IPv6 source and destination addresses for
+dnl simple switching.
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([IPv6 switching - megaflow check for IPv6 src/dst matches])
+AT_SKIP_IF([test $HAVE_SCAPY = no])
+ovn_start
+
+check ovn-nbctl ls-add sw0
+
+check ovn-nbctl lsp-add sw0 vm0
+check ovn-nbctl lsp-set-addresses vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
+
+check ovn-nbctl lsp-add sw0 vm1
+check ovn-nbctl lsp-set-addresses vm1 "f0:00:0f:01:02:04 10.0.0.4 1000::4"
+
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lr0-sw0 fa:16:3e:00:00:01 10.0.0.1 1000::1/64
+check ovn-nbctl lsp-add sw0 sw0-lr0
+check ovn-nbctl lsp-set-type sw0-lr0 router
+check ovn-nbctl lsp-set-addresses sw0-lr0 router
+check ovn-nbctl --wait=hv lsp-set-options sw0-lr0 router-port=lr0-sw0
+
+net_add n1
+sim_add hv
+as hv
+check ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl add-port br-int vif1 -- \
+    set Interface vif1 external-ids:iface-id=vm0 \
+    options:tx_pcap=hv/vif1-tx.pcap \
+    options:rxq_pcap=hv/vif1-rx.pcap \
+    ofport-request=1
+check ovs-vsctl add-port br-int vif2 -- \
+    set Interface vif2  external-ids:iface-id=vm1 \
+    options:tx_pcap=hv/vif2-tx.pcap \
+    options:rxq_pcap=hv/vif2-rx.pcap \
+    ofport-request=2
+
+check ovn-nbctl --wait=sb sync
+wait_for_ports_up
+
+AS_BOX([No port security, to vm1])
+packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:04', src='f0:00:0f:01:02:03')/ \
+                  IPv6(dst='1000::4', src='1000::3')/ \
+                  UDP(sport=53, dport=4369)")
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif1 $packet
+
+AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
+0
+])
+
+dnl Make sure that the packet was received by vm1.  This ensures that the
+dnl packet was delivered as expected and the megaflow didn't have any matches
+dnl on IPv6 src or dst.
+
+echo $packet >> expected-vif2
+OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])
+
+AS_BOX([No port security, to vm0])
+packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='f0:00:0f:01:02:04')/ \
+                  IPv6(dst='1000::3', src='1000::4')/ \
+                  UDP(sport=53, dport=4369)")
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=2 $packet > vm1_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif2 $packet
+
+AT_CAPTURE_FILE([vm1_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
+0
+])
+
+dnl Make sure that the packet was received by vm0.  This ensures that the
+dnl packet was delivered as expected and the megaflow didn't have any matches
+dnl on IPv6 src or dst.
+echo $packet >> expected-vif1
+OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
+
+AS_BOX([With port security, to vm1])
+dnl Add port security to vm0.  The megaflow should now match on ipv6 src/dst.
+check ovn-nbctl lsp-set-port-security vm0 "f0:00:0f:01:02:03 10.0.0.3 1000::3"
+check ovn-nbctl --wait=hv sync
+
+packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:04', src='f0:00:0f:01:02:03')/ \
+                  IPv6(dst='1000::4', src='1000::3')/ \
+                  UDP(sport=53, dport=4369)")
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif1 $packet
+
+AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [0], [dnl
+1
+])
+
+dnl Make sure that the packet was received by vm1.
+echo $packet >> expected-vif2
+OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])
+
+AS_BOX([Clear port security, to vm1])
+dnl Clear port security.
+check ovn-nbctl lsp-set-port-security vm0 ""
+check ovn-nbctl --wait=hv sync
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif1 $packet
+
+AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [1], [dnl
+0
+])
+
+dnl Make sure that the packet was received by vm1.
+echo $packet >> expected-vif2
+OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])
+
+AS_BOX([With proxy arp/nd, to vm1])
+dnl Configure proxy arp/nd on the router port.  The megaflow should now match
+dnl on ipv6 src/dst.
+check ovn-nbctl --wait=hv lsp-set-options sw0-lr0 router-port=lr0-sw0 arp_proxy="2000::1/64"
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=1 $packet > vm0_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif1 $packet
+
+AT_CAPTURE_FILE([vm0_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [0], [dnl
+1
+])
+
+dnl Make sure that the packet was received by vm1.
+echo $packet >> expected-vif2
+OVN_CHECK_PACKETS([hv/vif2-tx.pcap], [expected-vif2])
+
+AS_BOX([With proxy arp/nd, to vm0])
+packet=$(fmt_pkt "Ether(dst='f0:00:0f:01:02:03', src='f0:00:0f:01:02:04')/ \
+                  IPv6(dst='1000::3', src='1000::4')/ \
+                  UDP(sport=53, dport=4369)")
+
+as hv
+ovs-appctl ofproto/trace br-int in_port=2 $packet > vm1_ip6_ofproto_trace.txt
+ovs-appctl netdev-dummy/receive vif2 $packet
+
+AT_CAPTURE_FILE([vm1_ip6_ofproto_trace.txt])
+
+AT_CHECK([grep Megaflow vm0_ip6_ofproto_trace.txt | grep -e ipv6_src -e ipv6_dst -c], [0], [dnl
+1
+])
+
+dnl Make sure that the packet was received by vm0.
+echo $packet >> expected-vif1
+OVN_CHECK_PACKETS([hv/vif1-tx.pcap], [expected-vif1])
+
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 463075ac0..28474ed66 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -10628,7 +10628,7 @@ check ovn-nbctl ls-add bar
 # Connect foo to R1
 check ovn-nbctl lrp-add R1 foo 00:00:01:01:02:03 192.168.1.1/24
 check ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo \
-    type=router options:arp_proxy="0a:58:a9:fe:01:01 169.254.239.254 169.254.239.2 169.254.238.0/24 192.168.1.100" options:router-port=foo addresses='"router"'
+    type=router options:arp_proxy="0a:58:a9:fe:01:01 169.254.239.254 169.254.239.2 169.254.238.0/24 192.168.1.100 192.168.1.200" options:router-port=foo addresses='"router"'
 
 # Connect bar to R1
 check ovn-nbctl lrp-add R1 bar 00:00:01:01:02:04 192.168.2.1/24
@@ -10657,6 +10657,12 @@ ADD_VETH(foo3, foo3, br-int, "192.168.1.4/24", "f0:00:00:01:02:05", \
 check ovn-nbctl lsp-add foo foo3 \
 -- lsp-set-addresses foo3 "f0:00:00:01:02:05 192.168.1.4"
 
+ADD_NAMESPACES(foo4)
+ADD_VETH(foo4, foo4, br-int, "192.168.1.6/24", "f0:00:00:01:02:11", \
+         "192.168.1.1")
+check ovn-nbctl lsp-add foo foo4 \
+-- lsp-set-addresses foo4 "f0:00:00:01:02:11 192.168.1.6"
+
 # Logical port 'ext1' in switch 'foo'
 ADD_NAMESPACES(ext1)
 ADD_VETH(ext1, ext1, br-ext, "192.168.1.5/24", "f0:00:00:01:02:06", \
@@ -10672,6 +10678,12 @@ ADD_VETH(bar1, bar1, br-int, "192.168.2.2/24", "f0:00:00:01:02:07", \
 check ovn-nbctl lsp-add bar bar1 \
 -- lsp-set-addresses bar1 "f0:00:00:01:02:07 192.168.2.2"
 
+ADD_NAMESPACES(bar2)
+ADD_VETH(bar2, bar2, br-int, "192.168.2.3/24", "f0:00:00:01:02:10", \
+"192.168.2.1")
+check ovn-nbctl lsp-add bar bar2 \
+-- lsp-set-addresses bar2 "f0:00:00:01:10:10 192.168.2.3"
+
 # wait for ovn-controller to catch up.
 check ovn-nbctl --wait=hv sync
 
@@ -10723,6 +10735,14 @@ OVS_WAIT_UNTIL([
     test "${total_pkts}" = "3"
 ])
 
+check ovn-nbctl lr-route-add R1 169.254.240.0/24 192.168.1.200
+NETNS_START_TCPDUMP([foo4], [-nn -c 4 -e -i foo4 arp[[24:4]]=0xc0a801c8], [foo4-arp])
+
+NS_CHECK_EXEC([bar2], [ping -q -c 5 -i 0.3 -w 2 169.254.240.10],[ignore],[ignore])
+OVS_WAIT_UNTIL([
+    total_pkts=$(cat foo4-arp.tcpdump| wc -l)
+    test "${total_pkts}" = "4"
+])
 
 OVS_APP_EXIT_AND_WAIT([ovn-controller])
 
@@ -11183,7 +11203,25 @@ check_ovn_installed
 check_ports_up
 check_ports_bound
 
-OVS_APP_EXIT_AND_WAIT([ovn-controller])
+AS_BOX(["Leave some ovn-installed while closing ovn-controller"])
+# 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
+check ovn-nbctl --wait=hv sync
+
+# Stop ovsdb before ovn-controller to ensure it's not updated
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+# Don't use OVS_APP_EXIT... to use --restart to avoid cleaning up the databases.
+TMPPID=$(cat $OVS_RUNDIR/ovn-controller.pid)
+check ovs-appctl -t ovn-controller exit --restart
+OVS_WAIT_WHILE([kill -0 $TMPPID 2>/dev/null])
 
 as ovn-sb
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
@@ -11194,9 +11232,6 @@ 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/automake.mk b/utilities/automake.mk
index 6a2b96e66..b7d2ebeef 100644
--- a/utilities/automake.mk
+++ b/utilities/automake.mk
@@ -41,6 +41,7 @@ EXTRA_DIST += \
     utilities/checkpatch.py \
     utilities/containers/Makefile \
     utilities/containers/py-requirements.txt \
+    utilities/containers/prepare.sh \
     utilities/containers/fedora/Dockerfile \
     utilities/containers/ubuntu/Dockerfile \
     utilities/docker/Makefile \
diff --git a/utilities/containers/fedora/Dockerfile b/utilities/containers/fedora/Dockerfile
index c11ea37b7..f5852378c 100755
--- a/utilities/containers/fedora/Dockerfile
+++ b/utilities/containers/fedora/Dockerfile
@@ -27,6 +27,7 @@ RUN dnf -y update \
         libcap-ng-devel \
         libtool \
         net-tools \
+        nftables \
         nmap-ncat \
         openssl \
         openssl-devel \
@@ -44,23 +45,12 @@ RUN dnf -y update \
     && \
     dnf clean all
 
-# Compile sparse from source
-WORKDIR /workspace/sparse
-
-RUN git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git \
-    /workspace/sparse \
-    && \
-    make -j4 PREFIX=/usr HAVE_LLVM= HAVE_SQLITE= install
-
 WORKDIR /workspace
 
 COPY $CONTAINERS_PATH/py-requirements.txt /tmp/py-requirements.txt
 
-# Update and install pip dependencies
-RUN python3 -m pip install --upgrade pip \
-    && \
-    python3 -m pip install wheel \
-    && \
-    python3 -m pip install -r /tmp/py-requirements.txt
+COPY $CONTAINERS_PATH/prepare.sh /tmp/prepare.sh
+
+RUN /tmp/prepare.sh
 
 CMD ["/usr/sbin/init"]
diff --git a/utilities/containers/prepare.sh b/utilities/containers/prepare.sh
new file mode 100755
index 000000000..d3e8c4253
--- /dev/null
+++ b/utilities/containers/prepare.sh
@@ -0,0 +1,22 @@
+#!/bin/bash -xe
+
+function compile_sparse()
+{
+    git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git \
+        /workspace/sparse
+
+    pushd sparse
+    make -j4 PREFIX=/usr HAVE_LLVM= HAVE_SQLITE= install
+    popd
+}
+
+function install_python_dep()
+{
+    # The --user should be removed once pip can be upgraded on Ubuntu.
+    python3 -m pip install --user --upgrade pip
+    python3 -m pip install wheel
+    python3 -m pip install -r /tmp/py-requirements.txt
+}
+
+compile_sparse
+install_python_dep
diff --git a/utilities/containers/ubuntu/Dockerfile b/utilities/containers/ubuntu/Dockerfile
index 3c7fe7775..4e9599004 100755
--- a/utilities/containers/ubuntu/Dockerfile
+++ b/utilities/containers/ubuntu/Dockerfile
@@ -32,6 +32,7 @@ RUN apt update -y \
         llvm-dev \
         ncat \
         net-tools \
+        nftables \
         python3-dev \
         python3-pip \
         selinux-policy-dev \
@@ -43,23 +44,12 @@ RUN apt update -y \
     && \
     apt clean
 
-# Compile sparse from source
-WORKDIR /workspace/sparse
-
-RUN git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git \
-    /workspace/sparse \
-    && \
-    make -j4 PREFIX=/usr HAVE_LLVM= HAVE_SQLITE= install
-
 WORKDIR /workspace
 
 COPY $CONTAINERS_PATH/py-requirements.txt /tmp/py-requirements.txt
 
-# Update and install pip dependencies
-RUN python3 -m pip install --upgrade pip \
-    && \
-    python3 -m pip install wheel \
-    && \
-    python3 -m pip install -r /tmp/py-requirements.txt
+COPY $CONTAINERS_PATH/prepare.sh /tmp/prepare.sh
+
+RUN /tmp/prepare.sh
 
 CMD ["/sbin/init"]