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/.github/workflows/test.yml b/.github/workflows/test.yml
index 456ab5c69..22e4d339d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -315,7 +315,7 @@ jobs:
   build-linux-rpm:
     name: linux rpm fedora
     runs-on: ubuntu-22.04
-    container: fedora:latest
+    container: fedora:40
     timeout-minutes: 30
 
     strategy:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index bbc843940..b5eb4dc73 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -451,6 +451,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 b00876737..ac515716d 100644
--- a/Documentation/automake.mk
+++ b/Documentation/automake.mk
@@ -57,6 +57,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 4d16d97ff..0fb8d24cc 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,10 @@
+OVN v24.03.4 - xx xxx xxxx
+--------------------------
+
+OVN v24.03.3 - 16 Aug 2024
+--------------------------
+  - Bug fixes
+
 OVN v24.03.2 - 09 May 2024
 --------------------------
   - Bug fixes
diff --git a/configure.ac b/configure.ac
index 962422bd2..6a05ba90b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(ovn, 24.03.2, bugs@openvswitch.org)
+AC_INIT(ovn, 24.03.4, bugs@openvswitch.org)
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 113d3e05c..4a32406eb 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -731,7 +731,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;
@@ -842,6 +842,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
@@ -1127,6 +1128,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);
@@ -2962,7 +2964,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;
 
@@ -2971,12 +2973,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
@@ -3001,7 +2999,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);
@@ -3047,9 +3045,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;
@@ -3087,7 +3089,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) ||
@@ -4026,6 +4028,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(
@@ -4039,6 +4043,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;
@@ -5735,17 +5750,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/pinctrl.c b/controller/pinctrl.c
index b2a380437..984714b4b 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -660,8 +660,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;
@@ -5811,6 +5809,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);
@@ -6742,6 +6744,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;
 
@@ -6936,6 +6941,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;
@@ -7694,19 +7702,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);
     }
 
@@ -7762,24 +7765,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 3025c9a40..02d897994 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,10 +1,22 @@
-OVN (24.03.2-1) unstable; urgency=low
+ovn (24.03.4-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 16 Aug 2024 13:51:03 -0400
+
+ovn (24.03.3-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 16 Aug 2024 13:51:03 -0400
+
+ovn (24.03.2-1) unstable; urgency=low
    [ OVN team ]
    * New upstream version
 
  -- OVN team <dev@openvswitch.org>  Thu, 09 May 2024 15:10:30 -0400
 
-OVN (24.03.1-1) unstable; urgency=low
+ovn (24.03.1-1) unstable; urgency=low
    [ OVN team ]
    * New upstream version
 
diff --git a/ic/ovn-ic.c b/ic/ovn-ic.c
index e947323bf..be23f199d 100644
--- a/ic/ovn-ic.c
+++ b/ic/ovn-ic.c
@@ -2409,7 +2409,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 8854dae7a..ce79b501c 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -82,7 +82,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 */
@@ -138,8 +137,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 682b0b100..6a61719e1 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/aging.c b/northd/aging.c
index b76963a2d..9685044e7 100644
--- a/northd/aging.c
+++ b/northd/aging.c
@@ -421,7 +421,7 @@ en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED)
         if (!parse_aging_threshold(smap_get(&od->nbr->options,
                                             "mac_binding_age_threshold"),
                                    &threshold_config)) {
-            return;
+            continue;
         }
 
         aging_context_set_threshold(&ctx, &threshold_config);
diff --git a/northd/northd.c b/northd/northd.c
index a4bd3798b..8862c788b 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -118,6 +118,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"
@@ -5369,13 +5370,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;
         }
 
@@ -5751,6 +5750,13 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct lflow_table *lflows,
                     &op->od->localnet_ports[0]->nbsp->header_,
                     op->lflow_ref);
         }
+    } 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_,
+                                          op->lflow_ref);
     }
 }
 
@@ -6083,8 +6089,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,
@@ -6093,8 +6098,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,
@@ -7882,13 +7886,16 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct lflow_table *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);
@@ -7919,9 +7926,9 @@ build_fwd_group_lflows(struct ovn_datapath *od, struct lflow_table *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]);
@@ -8882,16 +8889,21 @@ build_lswitch_arp_nd_responder_known_ips(struct ovn_port *op,
                                                   op->lflow_ref);
             }
 
-            /* 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,
@@ -8949,7 +8961,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,",
@@ -9003,7 +9017,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),
@@ -9279,15 +9294,14 @@ 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),
                           lflow_ref);
 
         /* 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),
                           lflow_ref);
@@ -9355,16 +9369,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 lflow_table *lflows,
@@ -9378,9 +9384,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;
@@ -9426,7 +9429,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 "\"; "
@@ -17144,7 +17147,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,
@@ -17154,6 +17158,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/northd.h b/northd/northd.h
index 5e9fa4745..3f1cd8341 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -362,12 +362,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;
-}
-
 /* Pipeline stages. */
 
 /* The two purposes for which ovn-northd uses OVN logical datapaths. */
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 474f54017..a55733b4b 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1940,13 +1940,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 b1be73cb2..096698fff 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -1107,6 +1107,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 a9789e14e..d8bdf2053 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 10aff7b67..cfdc7b6e2 100644
--- a/tests/ovn-controller.at
+++ b/tests/ovn-controller.at
@@ -2907,3 +2907,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-ic.at b/tests/ovn-ic.at
index ad24011f2..536886fac 100644
--- a/tests/ovn-ic.at
+++ b/tests/ovn-ic.at
@@ -2040,20 +2040,256 @@ 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
 
+# Send an IP multicast packet from LSP2, it should be forwarded
+# to lsp1 and lsp3.
+> expected_az1
+> expected_az2
+send_ip_multicast_pkt hv2-vif1 hv2 \
+    000000000001 01005e000144 \
+    $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 20 7c6b 11 \
+    e518e518000aed350000
+store_ip_multicast_pkt \
+    000000010100 01005e000144 \
+    $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+    e518e518000aed350000 expected_az1
+store_ip_multicast_pkt \
+    000000020200 01005e000144 \
+    $(ip_to_hex 44 44 44 2) $(ip_to_hex 239 0 1 68) 1e 1e 7e6b 11 \
+    e518e518000aed350000 expected_az2
+
+send_ip6_multicast_pkt hv2-vif1 hv2 \
+    000000000001 333300000001 \
+    00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+    000e 40 11 \
+    93407a69000e2b4e61736461640a
+store_ip6_multicast_pkt \
+    000000010100 333300000001 \
+    00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+    000e 3e 11 \
+    93407a69000e2b4e61736461640a \
+    expected_az1
+store_ip6_multicast_pkt \
+    000000020200 333300000001 \
+    00100000000000000000000000000042 ff0adeadbeef00000000000000000001 \
+    000e 3e 11 \
+    93407a69000e2b4e61736461640a \
+    expected_az2
+
+OVS_WAIT_UNTIL(
+  [check_packets 'hv1/vif1-tx.pcap expected_az1' \
+                 'hv2/vif2-tx.pcap expected_az2'],
+  [$at_diff -F'^---' exp rcv])
+
+OVN_CLEANUP_SBOX([hv1], ["/IGMP Querier enabled without a valid IPv4/d
+/IGMP Querier enabled with invalid ETH src/d"])
+
+OVN_CLEANUP_SBOX([hv2], ["/IGMP Querier enabled without a valid IPv4/d
+/IGMP Querier enabled with invalid ETH src/d"])
+
+OVN_CLEANUP_IC([az1],[az2])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([interconnection - IGMP/MLD multicast - TS flood])
+AT_KEYWORDS([IP-multicast])
+
+# Logical network:
+#
+#       AZ1                     |                     AZ2
+#   ---------------------------------------------------------------------
+#                               |
+#                               |     +-- LR2 --- LS2 --- LSP2 (sender)
+#                               |    /
+#     LSP1  --- LS1 --- LR1 --- TS ---
+#   (receiver)                  |    \
+#                               |     +-- LR3 --- LS3 --- LSP3 (receiver)
+#
+# LS1, LS2, LS3, TS configured to snoop IP multicast.
+# LR1, LR2, LR3 configured to relay IP multicast.
+# LR1-TS configured to flood IP multicast traffic unconditionally.
+# LR2-TS configured to flood IP multicast traffic unconditionally.
+# LR3-TS configured to flood IP multicast traffic unconditionally.
+
+AT_CAPTURE_FILE([exp])
+AT_CAPTURE_FILE([rcv])
+check_packets() {
+    > exp
+    > rcv
+    if test "$1" = --uniq; then
+        sort="sort -u"; shift
+    else
+        sort=sort
+    fi
+    for tuple in "$@"; do
+        set $tuple; pcap=$1 type=$2
+        echo "--- $pcap" | tee -a exp >> rcv
+        $sort "$type" >> exp
+        $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv
+        echo | tee -a exp >> rcv
+    done
+
+    $at_diff exp rcv >/dev/null
+}
+
+ovn_init_ic_db
+ovn_start az1
+ovn_start az2
+
+net_add n1
+
+sim_add hv1
+as hv1
+check ovs-vsctl add-br br-phys
+ovn_az_attach az1 n1 br-phys 192.168.1.1 16
+check ovs-vsctl -- add-port br-int hv1-vif1 \
+    -- set interface hv1-vif1 external-ids:iface-id=lsp1 \
+       options:tx_pcap=hv1/vif1-tx.pcap \
+       options:rxq_pcap=hv1/vif1-rx.pcap
+check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+
+sim_add hv2
+as hv2
+check ovs-vsctl add-br br-phys
+ovn_az_attach az2 n1 br-phys 192.168.2.1 16
+check ovs-vsctl -- add-port br-int hv2-vif1 \
+    -- set interface hv2-vif1 external-ids:iface-id=lsp2 \
+       options:tx_pcap=hv2/vif1-tx.pcap \
+       options:rxq_pcap=hv2/vif1-rx.pcap
+check ovs-vsctl -- add-port br-int hv2-vif2 \
+    -- set interface hv2-vif2 external-ids:iface-id=lsp3 \
+       options:tx_pcap=hv2/vif2-tx.pcap \
+       options:rxq_pcap=hv2/vif2-rx.pcap
+check ovs-vsctl set open . external-ids:ovn-is-interconn=true
+
+AT_CHECK([ovn-ic-nbctl --wait=sb create Transit_Switch name=ts], [0], [ignore])
+check ovn_as az1 ovn-nbctl wait-until logical_switch ts
+check ovn_as az2 ovn-nbctl wait-until logical_switch ts
+
+ovn_as az1
+check ovn-nbctl lr-add lr1 \
+    -- lrp-add lr1 lr1-ts 00:00:00:01:00:01 42.42.42.1/24 4242::1/64 \
+    -- lrp-add lr1 lr1-ls1 00:00:00:01:01:00 43.43.43.1/24 4343::1/64\
+    -- lrp-set-gateway-chassis lr1-ts hv1
+check ovn-nbctl ls-add ls1 \
+    -- lsp-add ls1 ls1-lr1 \
+    -- lsp-set-addresses ls1-lr1 router \
+    -- lsp-set-type ls1-lr1 router \
+    -- lsp-set-options ls1-lr1 router-port=lr1-ls1 \
+    -- lsp-add ls1 lsp1
+check ovn-nbctl lsp-add ts ts-lr1 \
+    -- lsp-set-addresses ts-lr1 router \
+    -- lsp-set-type ts-lr1 router \
+    -- lsp-set-options ts-lr1 router-port=lr1-ts
+wait_for_ports_up
+
+ovn_as az2
+check ovn-nbctl lr-add lr2 \
+    -- lrp-add lr2 lr2-ts 00:00:00:02:00:01 42.42.42.2/24 4242::2/64 \
+    -- lrp-add lr2 lr2-ls2 00:00:00:02:01:00 44.44.44.1/24 4444::1/64 \
+    -- lrp-set-gateway-chassis lr2-ts hv2
+check ovn-nbctl ls-add ls2 \
+    -- lsp-add ls2 ls2-lr2 \
+    -- lsp-set-addresses ls2-lr2 router \
+    -- lsp-set-type ls2-lr2 router \
+    -- lsp-set-options ls2-lr2 router-port=lr2-ls2 \
+    -- lsp-add ls2 lsp2
+check ovn-nbctl lsp-add ts ts-lr2 \
+    -- lsp-set-addresses ts-lr2 router \
+    -- lsp-set-type ts-lr2 router \
+    -- lsp-set-options ts-lr2 router-port=lr2-ts
+
+check ovn-nbctl lr-add lr3 \
+    -- lrp-add lr3 lr3-ts 00:00:00:02:00:02 42.42.42.3/24 4242::3/64 \
+    -- lrp-add lr3 lr3-ls3 00:00:00:02:02:00 44.44.45.1/24 4445::1/64 \
+    -- lrp-set-gateway-chassis lr3-ts hv2
+check ovn-nbctl ls-add ls3 \
+    -- lsp-add ls3 ls3-lr3 \
+    -- lsp-set-addresses ls3-lr3 router \
+    -- lsp-set-type ls3-lr3 router \
+    -- lsp-set-options ls3-lr3 router-port=lr3-ls3 \
+    -- lsp-add ls3 lsp3
+check ovn-nbctl lsp-add ts ts-lr3 \
+    -- lsp-set-addresses ts-lr3 router \
+    -- lsp-set-type ts-lr3 router \
+    -- lsp-set-options ts-lr3 router-port=lr3-ts
+
+wait_for_ports_up
+check ovn-ic-nbctl --wait=sb sync
+ovn_as az1
+check ovn-nbctl lsp-set-options ts-lr2 requested-chassis=hv2
+check ovn-nbctl lsp-set-options ts-lr3 requested-chassis=hv2
+
+ovn_as az2
+check ovn-nbctl lsp-set-options ts-lr1 requested-chassis=hv1
+
+dnl Enable IP multicast snooping and IP multicast relay.  Reports are
+dnl forwarded statically.
+ovn_as az1
+check ovn-nbctl set logical_switch ls1 other_config:mcast_snoop="true"
+check ovn-nbctl set logical_router lr1 options:mcast_relay="true"
+check ovn-nbctl set logical_router_port lr1-ts options:mcast_flood="true"
+check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
+check ovn-nbctl set logical_switch ts other_config:mcast_flood_unregistered="true"
+
+ovn_as az2
+check ovn-nbctl set logical_switch ls2 other_config:mcast_snoop="true"
+check ovn-nbctl set logical_router lr2 options:mcast_relay="true"
+check ovn-nbctl set logical_router_port lr2-ts options:mcast_flood="true"
+check ovn-nbctl set logical_switch ls3 other_config:mcast_snoop="true"
+check ovn-nbctl set logical_router lr3 options:mcast_relay="true"
+check ovn-nbctl set logical_router_port lr3-ts options:mcast_flood="true"
+check ovn-nbctl set logical_switch ts other_config:mcast_snoop="true"
+check ovn-nbctl set logical_switch ts other_config:mcast_flood_unregistered="true"
+
+check ovn_as az1 ovn-nbctl --wait=hv sync
+check ovn_as az2 ovn-nbctl --wait=hv sync
+
+# 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).
+OVN_POPULATE_ARP
+
+# Inject IGMP Join for 239.0.1.68 on LSP1.
+send_igmp_v3_report hv1-vif1 hv1 \
+    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+    $(ip_to_hex 239 0 1 68) 04 e9b9 \
+    /dev/null
+
+# Inject MLD Join for ff0a:dead:beef::1 on LSP1.
+send_mld_v2_report hv1-vif1 hv1 \
+    000000000001 10000000000000000000000000000001 \
+    ff0adeadbeef00000000000000000001 04 c0e4 \
+    /dev/null
+
+# Inject IGMP Join for 239.0.1.68 on LSP3.
+send_igmp_v3_report hv2-vif2 hv2 \
+    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+    $(ip_to_hex 239 0 1 68) 04 e9b9 \
+    /dev/null
+
+# Inject MLD Join for ff0a:dead:beef::1 on LSP3.
+send_mld_v2_report hv2-vif2 hv2 \
+    000000000001 10000000000000000000000000000001 \
+    ff0adeadbeef00000000000000000001 04 c0e4 \
+    /dev/null
+
+# Check that the IGMP and MLD groups are learned on both AZs (on the LS
+# and TS).
+ovn_as az1
+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
 
 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.
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 344fdd69c..7fb570ea9 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -1029,16 +1029,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 3576594e3..de56058f6 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -7825,6 +7825,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 c947b2d30..254e693b6 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
 
 # ct_commit_to_zone
 ct_commit_to_zone(dnat);
@@ -5849,13 +5852,24 @@ for i in 1 2 3; do
 
     # Test ICMPv6 MLD reports (v1 and v2) and NS for DAD
     sip=::
-    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 f0:00:00:00:00:21 $sip ff02::16:0 "ICMPv6MLReport()" 21
-    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 f0:00:00:00:00:21 $sip ff02::16:0 "ICMPv6MLReport2()" 21
-    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 f0:00:00:00:00:21 $sip ff02::ea:2aea:fffe:2800 "ICMPv6ND_NS()" 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 f0:00:00:00:0${i}:${i}3 33:33:00:16:00:00 $sip ff02::16:0 "ICMPv6MLReport()" ${out_ports}
+    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 33:33:00:16:00:00 $sip ff02::16:0 "ICMPv6MLReport2()" ${out_ports}
+    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 33:33:ff:fe:28:00 $sip ff02::ea:2aea:fffe:2800 "ICMPv6ND_NS()" ${out_ports}
     # Traffic to non-multicast traffic should be dropped
     test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 f0:00:00:00:00:21 $sip $tip "ICMPv6MLReport()"
     # Traffic of other ICMPv6 types should be dropped
-    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 f0:00:00:00:00:21 $sip ff02::16:0 "ICMPv6EchoRequest()"
+    test_icmpv6 ${i}3 f0:00:00:00:0${i}:${i}3 33:33:00:16:00:00 $sip ff02::16:0 "ICMPv6EchoRequest()"
 
     # should be dropped
     sip=ae80::ea2aeafffe2800aa
@@ -8536,7 +8550,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
@@ -14333,7 +14347,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}')
     ")
@@ -14373,8 +14387,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
@@ -26987,7 +27001,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
@@ -32566,7 +32587,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
@@ -32579,7 +32600,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
@@ -32593,7 +32614,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;)
@@ -32605,7 +32626,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
@@ -34329,10 +34350,15 @@ AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
 AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
 AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
 
-AT_CHECK([ovn-nbctl lsp-add public public-gw])
-AT_CHECK([ovn-nbctl lsp-set-type public-gw router])
-AT_CHECK([ovn-nbctl lsp-set-addresses public-gw 00:00:00:00:10:00 router])
-AT_CHECK([ovn-nbctl lsp-set-options public-gw router-port=gw-public])
+AT_CHECK([ovn-nbctl lsp-add public public-gw-1])
+AT_CHECK([ovn-nbctl lsp-set-type public-gw-1 router])
+AT_CHECK([ovn-nbctl lsp-set-addresses public-gw-1 00:00:00:00:10:00 router])
+AT_CHECK([ovn-nbctl lsp-set-options public-gw-1 router-port=gw-1-public])
+
+AT_CHECK([ovn-nbctl lsp-add public public-gw-2])
+AT_CHECK([ovn-nbctl lsp-set-type public-gw-2 router])
+AT_CHECK([ovn-nbctl lsp-set-addresses public-gw-2 00:00:00:00:30:00 router])
+AT_CHECK([ovn-nbctl lsp-set-options public-gw-2 router-port=gw-2-public])
 
 AT_CHECK([ovn-nbctl lsp-add internal internal-gw])
 AT_CHECK([ovn-nbctl lsp-set-type internal-gw router])
@@ -34345,9 +34371,12 @@ AT_CHECK([ovn-nbctl lsp-set-addresses vif1 "00:00:00:00:20:10 192.168.20.10"])
 AT_CHECK([ovn-nbctl lsp-add internal vif2])
 AT_CHECK([ovn-nbctl lsp-set-addresses vif2 "00:00:00:00:20:20 192.168.20.20"])
 
-AT_CHECK([ovn-nbctl lr-add gw])
-AT_CHECK([ovn-nbctl lrp-add gw gw-public 00:00:00:00:10:00 192.168.10.1/24])
-AT_CHECK([ovn-nbctl lrp-add gw gw-internal 00:00:00:00:20:00 192.168.20.1/24])
+AT_CHECK([ovn-nbctl lr-add gw-1])
+AT_CHECK([ovn-nbctl lrp-add gw-1 gw-1-public 00:00:00:00:10:00 192.168.10.1/24])
+AT_CHECK([ovn-nbctl lrp-add gw-1 gw-internal 00:00:00:00:20:00 192.168.20.1/24])
+
+AT_CHECK([ovn-nbctl lr-add gw-2])
+AT_CHECK([ovn-nbctl lrp-add gw-2 gw-2-public 00:00:00:00:30:00 192.168.10.2/24])
 
 sim_add hv1
 as hv1
@@ -34415,21 +34444,27 @@ send_udp() {
     as $hv ovs-appctl netdev-dummy/receive $dev $packet
 }
 # Check if the option is not present by default
-AT_CHECK([fetch_column nb:logical_router options name="gw" | grep -q mac_binding_age_threshold], [1])
+AT_CHECK([fetch_column nb:logical_router options name="gw-1" | grep -q mac_binding_age_threshold], [1])
+AT_CHECK([fetch_column nb:logical_router options name="gw-2" | grep -q mac_binding_age_threshold], [1])
 
 # Send GARP to populate MAC binding table records
 send_garp hv1 ext1 10
 send_garp hv2 ext2 20
 
-wait_row_count mac_binding 1 ip="192.168.10.10"
-wait_row_count mac_binding 1 ip="192.168.10.20"
+# Two rows present for each IP, one corresponding to each logical_port
+wait_row_count mac_binding 2 ip="192.168.10.10"
+wait_row_count mac_binding 2 ip="192.168.10.20"
 
-dp_key=$(printf "0x%x" $(as hv1 fetch_column datapath tunnel_key external_ids:name=gw))
-port_key=$(printf "0x%x" $(as hv1 fetch_column port_binding tunnel_key logical_port=gw-public))
+dp_key_1=$(printf "0x%x" $(as hv1 fetch_column datapath tunnel_key external_ids:name=gw-1))
+port_key_1=$(printf "0x%x" $(as hv1 fetch_column port_binding tunnel_key logical_port=gw-1-public))
+dp_key_2=$(printf "0x%x" $(as hv1 fetch_column datapath tunnel_key external_ids:name=gw-2))
+port_key_2=$(printf "0x%x" $(as hv1 fetch_column port_binding tunnel_key logical_port=gw-2-public))
 
 AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int table=79 --no-stats | strip_cookie | sort], [0], [dnl
- table=79, priority=100,ip,reg14=${port_key},metadata=${dp_key},dl_src=00:00:00:00:10:10,nw_src=192.168.10.10 actions=drop
- table=79, priority=100,ip,reg14=${port_key},metadata=${dp_key},dl_src=00:00:00:00:10:20,nw_src=192.168.10.20 actions=drop
+ table=79, priority=100,ip,reg14=${port_key_1},metadata=${dp_key_1},dl_src=00:00:00:00:10:10,nw_src=192.168.10.10 actions=drop
+ table=79, priority=100,ip,reg14=${port_key_1},metadata=${dp_key_1},dl_src=00:00:00:00:10:20,nw_src=192.168.10.20 actions=drop
+ table=79, priority=100,ip,reg14=${port_key_2},metadata=${dp_key_2},dl_src=00:00:00:00:10:10,nw_src=192.168.10.10 actions=drop
+ table=79, priority=100,ip,reg14=${port_key_2},metadata=${dp_key_2},dl_src=00:00:00:00:10:20,nw_src=192.168.10.20 actions=drop
 ])
 
 timestamp=$(fetch_column mac_binding timestamp ip="192.168.10.20")
@@ -34440,8 +34475,8 @@ send_udp hv2 ext2 20
 OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=79 | grep "192.168.10.10" | grep -q "n_packets=1"])
 OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=79 | grep "192.168.10.20" | grep -q "n_packets=1"])
 
-# Set the MAC binding aging threshold
-AT_CHECK([ovn-nbctl set logical_router gw options:mac_binding_age_threshold=5])
+# Set the MAC binding aging threshold for gw-1 router. No option for gw-2 router.
+AT_CHECK([ovn-nbctl set logical_router gw-1 options:mac_binding_age_threshold=5])
 AT_CHECK([fetch_column nb:logical_router options | grep -q mac_binding_age_threshold=5])
 AT_CHECK([ovn-nbctl --wait=sb sync])
 
@@ -34450,28 +34485,31 @@ send_udp hv2 ext2 20
 sleep 1
 send_udp hv2 ext2 20
 
-# Set the timeout for OVS_WAIT* functions to 5 seconds
+# Set the timeout for OVS_WAIT* functions to 10 seconds
 OVS_CTL_TIMEOUT=10
 OVS_WAIT_UNTIL([
     test "$timestamp" != "$(fetch_column mac_binding timestamp ip='192.168.10.20')"
 ])
 check $(test "$(fetch_column mac_binding timestamp ip='192.168.10.20')" != "")
 
-# Check if the records are removed after some inactivity
+# Check if the records are removed after some inactivity for gw-1. Only 1 entry should be present for gw-2.
 OVS_WAIT_UNTIL([
-    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
+    test "1" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
 ])
 # The second one takes longer because it got refreshed
 OVS_WAIT_UNTIL([
-    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.20')"
+    test "1" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.20')"
 ])
 
-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=79 --no-stats | strip_cookie], [0], [])
+AT_CHECK_UNQUOTED([as hv1 ovs-ofctl dump-flows br-int table=79 --no-stats | strip_cookie | sort], [0], [dnl
+ table=79, priority=100,ip,reg14=${port_key_2},metadata=${dp_key_2},dl_src=00:00:00:00:10:10,nw_src=192.168.10.10 actions=drop
+ table=79, priority=100,ip,reg14=${port_key_2},metadata=${dp_key_2},dl_src=00:00:00:00:10:20,nw_src=192.168.10.20 actions=drop
+])
 
 # Test CIDR-based threshold configuration
-check ovn-nbctl set logical_router gw options:mac_binding_age_threshold="192.168.10.0/255.255.255.0:2;192.168.10.64/26:0;192.168.10.20:0"
+check ovn-nbctl set logical_router gw-1 options:mac_binding_age_threshold="192.168.10.0/255.255.255.0:2;192.168.10.64/26:0;192.168.10.20:0"
 check ovn-nbctl --wait=sb sync
-uuid=$(fetch_column datapath _uuid external_ids:name=gw)
+uuid=$(fetch_column datapath _uuid external_ids:name=gw-1)
 AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold], [0], [dnl
 "2"
 ])
@@ -34481,22 +34519,32 @@ send_garp hv1 ext1 10 # belong to 192.168.10.0/24
 send_garp hv2 ext2 20 # belong to 192.168.10.20/32
 send_garp hv2 ext2 65 # belong to 192.168.10.64/26
 
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.10"])
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.20"])
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.65"])
+ip_addresses=("192.168.10.10" "192.168.10.20" "192.168.10.65")
+logical_ports=("gw-1-public" "gw-2-public")
+for ip in "${ip_addresses[@]}"; do
+    for port in "${logical_ports[@]}"; do
+        wait_row_count mac_binding 1 ip=$ip logical_port=$port
+    done
+done
 
 OVS_WAIT_UNTIL([
-    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
+    test "1" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
 ])
+
 # The other two should remain because the corresponding prefixes have threshold 0
-AT_CHECK([ovn-sbctl list mac_binding | grep -q "192.168.10.20"])
-AT_CHECK([ovn-sbctl list mac_binding | grep -q "192.168.10.65"])
+ip_addresses=("192.168.10.20" "192.168.10.65")
+for ip in "${ip_addresses[@]}"; do
+    for port in "${logical_ports[@]}"; do
+        check_row_count mac_binding 1 ip=$ip logical_port=$port
+    done
+done
+
 check ovn-sbctl --all destroy mac_binding
 
 # Set the aging threshold mixed with IPv6 prefixes and default threshold
-check ovn-nbctl set logical_router gw options:mac_binding_age_threshold="2;192.168.10.64/26:0;ff00:1234::/32:888;ff00::abcd:1"
+check ovn-nbctl set logical_router gw-1 options:mac_binding_age_threshold="2;192.168.10.64/26:0;ff00:1234::/32:888;ff00::abcd:1"
 check ovn-nbctl --wait=sb sync
-uuid=$(fetch_column datapath _uuid external_ids:name=gw)
+uuid=$(fetch_column datapath _uuid external_ids:name=gw-1)
 AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold], [0], [dnl
 "1"
 ])
@@ -34505,28 +34553,69 @@ AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold],
 send_garp hv1 ext1 10 # belong to default
 send_garp hv2 ext2 65 # belong to 192.168.10.64/26
 
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.10"])
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.65"])
+for ip in "${ip_addresses[@]}"; do
+    for port in "${logical_ports[@]}"; do
+        wait_row_count mac_binding 1 ip=$ip logical_port=$port
+    done
+done
 
 OVS_WAIT_UNTIL([
-    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
+    test "1" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
 ])
-AT_CHECK([ovn-sbctl list mac_binding | grep -q "192.168.10.65"])
+for port in "${logical_ports[@]}"; do
+    check_row_count mac_binding 1 ip="192.168.10.65" logical_port=$port
+done
 check ovn-sbctl --all destroy mac_binding
 
 # Set the aging threshold with invalid format
-check ovn-nbctl set logical_router gw options:mac_binding_age_threshold="1;abc/26:0"
+check ovn-nbctl set logical_router gw-1 options:mac_binding_age_threshold="1;abc/26:0"
 check ovn-nbctl --wait=sb sync
-uuid=$(fetch_column datapath _uuid external_ids:name=gw)
+uuid=$(fetch_column datapath _uuid external_ids:name=gw-1)
 AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold], [1], [ignore], [ignore])
 
 # Send GARP to populate MAC binding table records
 send_garp hv1 ext1 10
 
-OVS_WAIT_UNTIL([ovn-sbctl list mac_binding | grep -q "192.168.10.10"])
+wait_row_count mac_binding 1 ip="192.168.10.10" logical_port="gw-1-public"
+wait_row_count mac_binding 1 ip="192.168.10.10" logical_port="gw-2-public"
 # The record is not deleted
 sleep 5
-AT_CHECK([ovn-sbctl list mac_binding | grep -q "192.168.10.10"])
+check_row_count mac_binding 1 ip="192.168.10.10" logical_port=gw-1-public
+check_row_count mac_binding 1 ip="192.168.10.10" logical_port=gw-2-public
+check ovn-sbctl --all destroy mac_binding
+
+# Set the aging threshold on both routers and ensure that they are aged out of both the routers
+AT_CHECK([ovn-nbctl set logical_router gw-1 options:mac_binding_age_threshold=5])
+AT_CHECK([ovn-nbctl set logical_router gw-2 options:mac_binding_age_threshold=5])
+check ovn-nbctl --wait=sb sync
+uuid=$(fetch_column datapath _uuid external_ids:name=gw-1)
+AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold], [0], [dnl
+"5"
+])
+uuid=$(fetch_column datapath _uuid external_ids:name=gw-2)
+AT_CHECK([ovn-sbctl get datapath $uuid external_ids:mac_binding_age_threshold], [0], [dnl
+"5"
+])
+
+# Send GARP to populate MAC binding table records
+send_garp hv1 ext1 10 # belong to 192.168.10.0/24
+send_garp hv2 ext2 20 # belong to 192.168.10.20/32
+
+ip_addresses=("192.168.10.10" "192.168.10.20")
+logical_ports=("gw-1-public" "gw-2-public")
+for ip in "${ip_addresses[@]}"; do
+    for port in "${logical_ports[@]}"; do
+        wait_row_count mac_binding 1 ip=$ip logical_port=$port
+    done
+done
+
+
+OVS_WAIT_UNTIL([
+    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.10')"
+])
+OVS_WAIT_UNTIL([
+    test "0" = "$(ovn-sbctl list mac_binding | grep -c '192.168.10.20')"
+])
 
 OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
@@ -35033,7 +35122,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
@@ -35043,14 +35135,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])
 
@@ -35058,6 +35188,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
@@ -35070,9 +35202,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:
@@ -35140,7 +35272,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 \
@@ -35148,7 +35281,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
@@ -35159,7 +35292,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
@@ -37634,9 +37767,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
@@ -37894,3 +38027,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 b5b68e32e..7d85dc4d4 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -10752,7 +10752,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
@@ -10781,6 +10781,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", \
@@ -10796,6 +10802,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
 
@@ -10847,6 +10859,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])
 
diff --git a/utilities/automake.mk b/utilities/automake.mk
index de4f6efb5..03e9096fa 100644
--- a/utilities/automake.mk
+++ b/utilities/automake.mk
@@ -42,6 +42,7 @@ EXTRA_DIST += \
     utilities/containers/Makefile \
     utilities/containers/openbfdd.patch \
     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 9b8386aae..9d06df99b 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,41 +45,14 @@ 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
-
-# Compile OpenBFDD from source
-WORKDIR /workspace/OpenBFDD
+WORKDIR /workspace
 
 COPY $CONTAINERS_PATH/openbfdd.patch /tmp/openbfdd.patch
 
-RUN git clone https://github.com/dyninc/OpenBFDD.git \
-    /workspace/OpenBFDD \
-    && \
-    git apply /tmp/openbfdd.patch \
-    && \
-    ./autogen.sh \
-    && \
-    ./configure --enable-silent-rules \
-    && \
-    make \
-    && \
-    make 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..b3baa4345
--- /dev/null
+++ b/utilities/containers/prepare.sh
@@ -0,0 +1,37 @@
+#!/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 compile_openbfdd()
+{
+    git clone https://github.com/dyninc/OpenBFDD.git \
+        /workspace/OpenBFDD
+
+    pushd OpenBFDD
+    git apply /tmp/openbfdd.patch
+    ./autogen.sh
+    ./configure --enable-silent-rules
+    make
+    make 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
+compile_openbfdd
+install_python_dep
diff --git a/utilities/containers/ubuntu/Dockerfile b/utilities/containers/ubuntu/Dockerfile
index ac1e6a5bf..61b4bec39 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,41 +44,14 @@ 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
-
-# Compile OpenBFDD from source
-WORKDIR /workspace/OpenBFDD
+WORKDIR /workspace
 
 COPY $CONTAINERS_PATH/openbfdd.patch /tmp/openbfdd.patch
 
-RUN git clone https://github.com/dyninc/OpenBFDD.git \
-    /workspace/OpenBFDD \
-    && \
-    git apply /tmp/openbfdd.patch \
-    && \
-    ./autogen.sh \
-    && \
-    ./configure --enable-silent-rules \
-    && \
-    make \
-    && \
-    make 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"]
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index ea2b201a5..340312b38 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -1095,7 +1095,8 @@
     <h2>Logical Router Policy Commands</h2>
 
     <dl>
-      <dt>[<code>--may-exist</code>]<code>lr-policy-add</code>
+      <dt>[<code>--may-exist</code>] [<code>--bfd</code>]
+          <code>lr-policy-add</code>
           <var>router</var> <var>priority</var> <var>match</var>
           <var>action</var> [<var>nexthop</var>[,<var>nexthop</var>,...]]
           [<var>options key=value]</var>] </dt>