Blob Blame History Raw
diff --git a/NEWS b/NEWS
index ef6a99fed..0392d8d23 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+OVN v22.09.1 - xx xxx xxxx
+--------------------------
+
 OVN v22.09.0 - 16 Sep 2022
 --------------------------
   - ovn-controller: Add configuration knob, through OVS external-id
diff --git a/configure.ac b/configure.ac
index 765aacb17..c79d79ffe 100644
--- a/configure.ac
+++ b/configure.ac
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 AC_PREREQ(2.63)
-AC_INIT(ovn, 22.09.0, bugs@openvswitch.org)
+AC_INIT(ovn, 22.09.1, bugs@openvswitch.org)
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_HEADERS([config.h])
diff --git a/controller/binding.c b/controller/binding.c
index 8f6b4b19d..c3d2b2e42 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -2666,7 +2666,7 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
                 get_local_datapath(b_ctx_out->local_datapaths,
                                    peer->datapath->tunnel_key);
         }
-        if (peer_ld && need_add_patch_peer_to_local(
+        if (peer_ld && need_add_peer_to_local(
                 b_ctx_in->sbrec_port_binding_by_name, peer,
                 b_ctx_in->chassis_rec)) {
             add_local_datapath(
@@ -2681,7 +2681,7 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
         /* Add the peer datapath to the local datapaths if it's
          * not present yet.
          */
-        if (need_add_patch_peer_to_local(
+        if (need_add_peer_to_local(
                 b_ctx_in->sbrec_port_binding_by_name, pb,
                 b_ctx_in->chassis_rec)) {
             add_local_datapath_peer_port(
diff --git a/controller/local_data.c b/controller/local_data.c
index 9eee568d1..035f10fff 100644
--- a/controller/local_data.c
+++ b/controller/local_data.c
@@ -115,14 +115,19 @@ local_datapath_destroy(struct local_datapath *ld)
     free(ld);
 }
 
-/* Checks if pb is a patch port and the peer datapath should be added to local
- * datapaths. */
+/* Checks if pb is running on local gw router or pb is a patch port
+ * and the peer datapath should be added to local datapaths. */
 bool
-need_add_patch_peer_to_local(
+need_add_peer_to_local(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     const struct sbrec_port_binding *pb,
     const struct sbrec_chassis *chassis)
 {
+    /* This port is running on local gw router. */
+    if (!strcmp(pb->type, "l3gateway") && pb->chassis == chassis) {
+        return true;
+    }
+
     /* If it is not a patch port, no peer to add. */
     if (strcmp(pb->type, "patch")) {
         return false;
@@ -571,7 +576,7 @@ add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
                                             peer_name);
 
                 if (peer && peer->datapath) {
-                    if (need_add_patch_peer_to_local(
+                    if (need_add_peer_to_local(
                             sbrec_port_binding_by_name, pb, chassis)) {
                         struct local_datapath *peer_ld =
                             add_local_datapath__(sbrec_datapath_binding_by_key,
diff --git a/controller/local_data.h b/controller/local_data.h
index d898c8aa5..b5429eb58 100644
--- a/controller/local_data.h
+++ b/controller/local_data.h
@@ -66,7 +66,7 @@ struct local_datapath *local_datapath_alloc(
 struct local_datapath *get_local_datapath(const struct hmap *,
                                           uint32_t tunnel_key);
 bool
-need_add_patch_peer_to_local(
+need_add_peer_to_local(
     struct ovsdb_idl_index *sbrec_port_binding_by_name,
     const struct sbrec_port_binding *,
     const struct sbrec_chassis *);
diff --git a/controller/physical.c b/controller/physical.c
index f3c8bddce..705146316 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -803,6 +803,14 @@ put_replace_router_port_mac_flows(struct ovsdb_idl_index
 
         ofpact_put_OUTPUT(ofpacts_p)->port = ofport;
 
+        /* Replace the MAC back and strip vlan. In case of l2 flooding
+         * traffic (ARP/ND) we need to restore previous state so other ports
+         * do not receive the traffic tagged and with wrong MAC. */
+        ofpact_put_SET_ETH_SRC(ofpacts_p)->mac = router_port_mac;
+        if (tag) {
+            ofpact_put_STRIP_VLAN(ofpacts_p);
+        }
+
         ofctrl_add_flow(flow_table, OFTABLE_LOG_TO_PHY, 150,
                         localnet_port->header_.uuid.parts[0],
                         &match, ofpacts_p, &localnet_port->header_.uuid);
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 3f5d0af79..1e4230ed3 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -4378,7 +4378,7 @@ run_buffered_binding(struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
         const struct sbrec_port_binding *pb;
         SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
                                            sbrec_port_binding_by_datapath) {
-            if (strcmp(pb->type, "patch")) {
+            if (strcmp(pb->type, "patch") && strcmp(pb->type, "l3gateway")) {
                 continue;
             }
             struct buffered_packets *cur_qp;
diff --git a/debian/changelog b/debian/changelog
index 267e12baa..5ed83900b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+OVN (22.09.1-1) unstable; urgency=low
+   [ OVN team ]
+   * New upstream version
+
+ -- OVN team <dev@openvswitch.org>  Fri, 16 Sep 2022 13:54:11 -0400
+
 ovn (22.09.0-1) unstable; urgency=low
 
    * New upstream version
diff --git a/northd/northd.c b/northd/northd.c
index 84440a47f..a7b1c6404 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -1040,7 +1040,16 @@ init_mcast_info_for_switch_datapath(struct ovn_datapath *od)
     mcast_sw_info->query_max_response =
         smap_get_ullong(&od->nbs->other_config, "mcast_query_max_response",
                         OVN_MCAST_DEFAULT_QUERY_MAX_RESPONSE_S);
+}
 
+static void
+init_mcast_flow_count(struct ovn_datapath *od)
+{
+    if (od->nbr) {
+        return;
+    }
+
+    struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
     mcast_sw_info->active_v4_flows = ATOMIC_VAR_INIT(0);
     mcast_sw_info->active_v6_flows = ATOMIC_VAR_INIT(0);
 }
@@ -8451,6 +8460,10 @@ build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
             if (atomic_compare_exchange_strong(
                         &mcast_sw_info->active_v4_flows, &table_size,
                         mcast_sw_info->table_size)) {
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+                VLOG_INFO_RL(&rl, "Too many active mcast flows: %"PRIu64,
+                             mcast_sw_info->active_v4_flows);
                 return;
             }
             atomic_add(&mcast_sw_info->active_v4_flows, 1, &dummy);
@@ -13633,7 +13646,8 @@ static void
 build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
                                 const struct hmap *ports, struct ds *match,
                                 struct ds *actions,
-                                const struct shash *meter_groups)
+                                const struct shash *meter_groups,
+                                bool ct_lb_mark)
 {
     if (!od->nbr) {
         return;
@@ -13827,6 +13841,26 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
         }
     }
 
+    if (od->nbr->n_nat) {
+        ds_clear(match);
+        const char *ct_natted = ct_lb_mark ?
+                                "ct_mark.natted" :
+                                "ct_label.natted";
+        ds_put_format(match, "ip && %s == 1", ct_natted);
+        /* This flow is unique since it is in the egress pipeline but checks
+         * the value of ct_label.natted, which would have been set in the
+         * ingress pipeline. If a change is ever introduced that clears or
+         * otherwise invalidates the ct_label between the ingress and egress
+         * pipelines, then an alternative will need to be devised.
+         */
+        ds_clear(actions);
+        ds_put_cstr(actions, REGBIT_DST_NAT_IP_LOCAL" = 1; next;");
+        ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_CHECK_DNAT_LOCAL,
+                                50, ds_cstr(match), ds_cstr(actions),
+                                &od->nbr->header_);
+
+    }
+
     /* Handle force SNAT options set in the gateway router. */
     if (od->is_gw_router) {
         if (dnat_force_snat_ip) {
@@ -13925,7 +13959,8 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
     build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
     build_lrouter_arp_nd_for_datapath(od, lsi->lflows, lsi->meter_groups);
     build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports, &lsi->match,
-                                    &lsi->actions, lsi->meter_groups);
+                                    &lsi->actions, lsi->meter_groups,
+                                    lsi->features->ct_no_masked_label);
 }
 
 /* Helper function to combine all lflow generation which is iterated by port.
@@ -15148,6 +15183,11 @@ build_mcast_groups(struct lflow_input *input_data,
 
     hmap_init(mcast_groups);
     hmap_init(igmp_groups);
+    struct ovn_datapath *od;
+
+    HMAP_FOR_EACH (od, key_node, datapaths) {
+        init_mcast_flow_count(od);
+    }
 
     HMAP_FOR_EACH (op, key_node, ports) {
         if (op->nbrp && lrport_is_enabled(op->nbrp)) {
@@ -15205,8 +15245,7 @@ build_mcast_groups(struct lflow_input *input_data,
         }
 
         /* If the datapath value is stale, purge the group. */
-        struct ovn_datapath *od =
-            ovn_datapath_from_sbrec(datapaths, sb_igmp->datapath);
+        od = ovn_datapath_from_sbrec(datapaths, sb_igmp->datapath);
 
         if (!od || ovn_datapath_is_stale(od)) {
             sbrec_igmp_group_delete(sb_igmp);
@@ -15251,7 +15290,6 @@ build_mcast_groups(struct lflow_input *input_data,
      * IGMP groups are based on the groups learnt by their multicast enabled
      * peers.
      */
-    struct ovn_datapath *od;
     HMAP_FOR_EACH (od, key_node, datapaths) {
 
         if (ovs_list_is_empty(&od->mcast_info.groups)) {
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index dae961c87..177b6341d 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -4392,6 +4392,22 @@ nd_ns {
       </li>
     </ul>
 
+    <p>
+      This table also installs a priority-50 logical flow for each logical
+      router that has NATs configured on it. The flow has match
+      <code>ip &amp;&amp; ct_label.natted == 1</code> and action
+      <code>REGBIT_DST_NAT_IP_LOCAL = 1; next;</code>. This is intended
+      to ensure that traffic that was DNATted locally will use a separate
+      conntrack zone for SNAT if SNAT is required later in the egress
+      pipeline. Note that this flow checks the value of
+      <code>ct_label.natted</code>, which is set in the ingress pipeline.
+      This means that ovn-northd assumes that this value is carried over
+      from the ingress pipeline to the egress pipeline and is not altered
+      or cleared. If conntrack label values are ever changed to be cleared
+      between the ingress and egress pipelines, then the match conditions
+      of this flow will be updated accordingly.
+    </p>
+
     <h3>Egress Table 1: UNDNAT</h3>
 
     <p>
diff --git a/rhel/ovn-fedora.spec.in b/rhel/ovn-fedora.spec.in
index 821eb03cc..57dc977c1 100644
--- a/rhel/ovn-fedora.spec.in
+++ b/rhel/ovn-fedora.spec.in
@@ -65,6 +65,7 @@ BuildRequires: tcpdump
 BuildRequires: unbound unbound-devel
 
 Requires: openssl hostname iproute module-init-tools openvswitch
+Requires: python3-openvswitch
 
 Requires(post): systemd-units
 Requires(preun): systemd-units
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 7c3c84007..15588cc52 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -5019,6 +5019,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.10 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.20 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.30 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
@@ -5093,6 +5094,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.10 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.20 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
   table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ip4.dst == 172.168.0.30 && is_chassis_resident("cr-lr0-public")), action=(reg9[[4]] = 1; next;)
@@ -5161,6 +5163,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
 ])
 
 AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
@@ -5221,6 +5224,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
 ])
 
 AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
@@ -5286,6 +5290,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
 ])
 
 AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
@@ -5364,6 +5369,7 @@ AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl
 
 AT_CHECK([grep "lr_out_chk_dnat_local" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
   table=? (lr_out_chk_dnat_local), priority=0    , match=(1), action=(reg9[[4]] = 0; next;)
+  table=? (lr_out_chk_dnat_local), priority=50   , match=(ip && ct_mark.natted == 1), action=(reg9[[4]] = 1; next;)
 ])
 
 AT_CHECK([grep "lr_out_undnat" lr0flows | sed 's/table=./table=?/' | sort], [0], [dnl
@@ -6129,7 +6135,6 @@ AT_CHECK([grep -e "(lr_in_ip_routing   ).*outport" lr0flows | sed 's/table=../ta
 ])
 
 AT_CLEANUP
-])
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([check exclude-lb-vips-from-garp option])
diff --git a/tests/ovn.at b/tests/ovn.at
index 80e9192ca..dd72996dd 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -32889,3 +32889,32 @@ check ovn-nbctl --wait=hv sync
 OVN_CLEANUP([hv1])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([ovn-controller: batch add port and delete port in same IDL])
+ovn_start
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+check ovs-vsctl add-port br-int p1
+
+check ovs-vsctl set interface p1 external-ids:iface-id=sw0-port1
+check ovn-nbctl --wait=hv sync
+ovn-appctl debug/pause
+OVS_WAIT_UNTIL([test x$(as hv1 ovn-appctl -t ovn-controller debug/status) = "xpaused"])
+
+check ovn-nbctl ls-add sw0 -- lsp-add sw0 sw0-port1
+check ovn-nbctl lsp-del sw0-port1
+check ovn-nbctl --wait=sb sync
+
+ovn-appctl debug/resume
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl ls-del sw0
+check ovn-nbctl --wait=hv sync
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at
index 616a87fcf..8e6cb415c 100644
--- a/tests/system-common-macros.at
+++ b/tests/system-common-macros.at
@@ -44,15 +44,38 @@ m4_define([NS_CHECK_EXEC],
 # appropriate type, and allows additional arguments to be passed.
 m4_define([ADD_BR], [ovs-vsctl _ADD_BR([$1]) -- $2])
 
-# ADD_INT([port], [namespace], [ovs-br], [ip_addr])
+# ADD_INT([port], [namespace], [ovs-br], [ip_addr] [ip6_addr])
 #
 # Add an internal port to 'ovs-br', then shift it into 'namespace' and
 # configure it with 'ip_addr' (specified in CIDR notation).
+# Optionally add an ipv6 address
 m4_define([ADD_INT],
     [ AT_CHECK([ovs-vsctl add-port $3 $1 -- set int $1 type=internal])
       AT_CHECK([ip link set $1 netns $2])
       NS_CHECK_EXEC([$2], [ip addr add $4 dev $1])
       NS_CHECK_EXEC([$2], [ip link set dev $1 up])
+      if test -n "$5"; then
+        NS_CHECK_EXEC([$2], [ip -6 addr add $5 dev $1])
+      fi
+    ]
+)
+
+# NS_ADD_INT([port], [namespace], [ovs-br], [ip_addr] [mac_addr] [ip6_addr] [default_gw] [default_ipv6_gw])
+# Create a namespace
+# Add an internal port to 'ovs-br', then shift it into 'namespace'.
+# Configure it with 'ip_addr' (specified in CIDR notation) and ip6_addr.
+# Set mac_addr
+# Add default gw for ipv4 and ipv6
+m4_define([NS_ADD_INT],
+    [ AT_CHECK([ovs-vsctl add-port $3 $1 -- set int $1 type=internal  external_ids:iface-id=$1])
+      ADD_NAMESPACES($2)
+      AT_CHECK([ip link set $1 netns $2])
+      NS_CHECK_EXEC([$2], [ip link set $1 address $5])
+      NS_CHECK_EXEC([$2], [ip link set dev $1 up])
+      NS_CHECK_EXEC([$2], [ip addr add $4 dev $1])
+      NS_CHECK_EXEC([$2], [ip addr add $6 dev $1])
+      NS_CHECK_EXEC([$2], [ip route add default via $7 dev $1])
+      NS_CHECK_EXEC([$2], [ip -6 route add default via $8 dev $1])
     ]
 )
 
@@ -333,4 +356,166 @@ m4_define([OVS_CHECK_CT_CLEAR],
 
 # OVS_CHECK_CT_ZERO_SNAT()
 m4_define([OVS_CHECK_CT_ZERO_SNAT],
-    [AT_SKIP_IF([! grep -q "Datapath supports ct_zero_snat" ovs-vswitchd.log])]))
+    [AT_SKIP_IF([! grep -q "Datapath supports ct_zero_snat" ovs-vswitchd.log])])
+
+# OVN_TEST_IPV6_PREFIX_DELEGATION()
+m4_define([OVN_TEST_IPV6_PREFIX_DELEGATION],
+[
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+ADD_NAMESPACES(sw01)
+ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
+         "192.168.1.1")
+ADD_NAMESPACES(sw11)
+ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
+         "192.168.2.1")
+ADD_NAMESPACES(server)
+ADD_VETH(s1, server, br-ext, "2001:1db8:3333::2/64", "f0:00:00:01:02:05", \
+         "2001:1db8:3333::1")
+
+if test X"$1" = X"GR"; then
+   ovn-nbctl create Logical_Router name=R1 options:chassis=hv1
+else
+   ovn-nbctl lr-add R1
+fi
+
+ovn-nbctl ls-add sw0
+ovn-nbctl ls-add sw1
+ovn-nbctl ls-add public
+
+ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
+ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24
+
+if test X"$1" != X"GR"; then
+    ovn-nbctl lrp-set-gateway-chassis rp-public hv1
+fi
+
+ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+    type=router options:router-port=rp-sw0 \
+    -- lsp-set-addresses sw0-rp router
+ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
+    type=router options:router-port=rp-sw1 \
+    -- lsp-set-addresses sw1-rp router
+
+ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
+    type=router options:router-port=rp-public \
+    -- lsp-set-addresses public-rp router
+
+ovn-nbctl lsp-add sw0 sw01 \
+    -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
+
+ovn-nbctl lsp-add sw1 sw11 \
+    -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
+
+OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep 2001:1db8:3333::2 | grep tentative)" = ""])
+OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
+ovn-nbctl lsp-add public public1 \
+        -- lsp-set-addresses public1 unknown \
+        -- lsp-set-type public1 localnet \
+        -- lsp-set-options public1 network_name=phynet
+
+ovn-nbctl set logical_router_port rp-public options:prefix_delegation=true
+ovn-nbctl set logical_router_port rp-public options:prefix=true
+ovn-nbctl set logical_router_port rp-sw0 options:prefix=true
+ovn-nbctl set logical_router_port rp-sw1 options:prefix=true
+
+OVN_POPULATE_ARP
+
+ovn-nbctl --wait=hv sync
+
+cat > /etc/dhcp/dhcpd.conf <<EOF
+option dhcp-rebinding-time 15;
+option dhcp-renewal-time 10;
+option dhcp6.unicast 2001:1db8:3333::1;
+subnet6 2001:1db8:3333::/64 {
+    prefix6 2001:1db8:3333:100:: 2001:1db8:3333:111:: /96;
+}
+EOF
+rm -f /var/lib/dhcp/dhcpd6.leases
+touch /var/lib/dhcp/dhcpd6.leases
+chown root:dhcpd /var/lib/dhcp /var/lib/dhcp/dhcpd6.leases
+chmod 775 /var/lib/dhcp
+chmod 664 /var/lib/dhcp/dhcpd6.leases
+
+NS_CHECK_EXEC([server], [tcpdump -nni s1 > pkt.pcap &])
+
+NETNS_DAEMONIZE([server], [dhcpd -6 -f s1 > dhcpd.log 2>&1], [dhcpd.pid])
+ovn-nbctl --wait=hv sync
+
+OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c4-15)" = ""])
+OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c4-15)" = ""])
+OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c4-15)" = ""])
+
+AT_CHECK([ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c3-16], [0], [dnl
+[2001:1db8:3333]
+])
+AT_CHECK([ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16], [0], [dnl
+[2001:1db8:3333]
+])
+AT_CHECK([ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c3-16], [0], [dnl
+[2001:1db8:3333]
+])
+
+prefix=$(ovn-nbctl list logical_router_port rp-public | awk -F/ '/ipv6_prefix/{print substr($ 1,25,9)}' | sed 's/://g')
+ovn-nbctl list logical_router_port rp-public > /tmp/rp-public
+ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
+ovn-nbctl set logical_router_port rp-sw1 options:prefix=false
+# Renew message
+NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix} > renew.pcap &])
+# Reply message with Status OK
+NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x07 and ip6[[81:4]]=0x${prefix} > reply.pcap &])
+
+OVS_WAIT_UNTIL([
+    total_pkts=$(cat renew.pcap | wc -l)
+    test "${total_pkts}" = "1"
+])
+
+OVS_WAIT_UNTIL([
+    total_pkts=$(cat reply.pcap | wc -l)
+    test "${total_pkts}" = "1"
+])
+
+kill $(pidof tcpdump)
+
+ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
+ovn-nbctl clear logical_router_port rp-sw0 ipv6_prefix
+OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16)" = "[2001:1db8:3333]"])
+AT_CHECK([ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16], [0], [dnl
+[]
+])
+
+kill $(pidof ovn-controller)
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/failed to query port patch-.*/d
+/.*terminating with signal 15.*/d"])
+]))
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 8acfb3e39..20c058415 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -5272,158 +5272,22 @@ AT_CLEANUP
 ])
 
 OVN_FOR_EACH_NORTHD([
-AT_SETUP([IPv6 prefix delegation])
+AT_SETUP([IPv6 prefix delegation - distributed router])
 AT_SKIP_IF([test $HAVE_DHCPD = no])
 AT_SKIP_IF([test $HAVE_TCPDUMP = no])
 AT_KEYWORDS([ovn-ipv6-prefix_d])
 
-ovn_start
-OVS_TRAFFIC_VSWITCHD_START()
-
-ADD_BR([br-int])
-ADD_BR([br-ext])
-
-ovs-ofctl add-flow br-ext action=normal
-# Set external-ids in br-int needed for ovn-controller
-ovs-vsctl \
-        -- set Open_vSwitch . external-ids:system-id=hv1 \
-        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
-        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
-        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
-        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
-
-# Start ovn-controller
-start_daemon ovn-controller
-
-ovn-nbctl lr-add R1
-
-ovn-nbctl ls-add sw0
-ovn-nbctl ls-add sw1
-ovn-nbctl ls-add public
-
-ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
-ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
-ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 \
-    -- lrp-set-gateway-chassis rp-public hv1
-
-ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
-    type=router options:router-port=rp-sw0 \
-    -- lsp-set-addresses sw0-rp router
-ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
-    type=router options:router-port=rp-sw1 \
-    -- lsp-set-addresses sw1-rp router
-
-ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \
-    type=router options:router-port=rp-public \
-    -- lsp-set-addresses public-rp router
-
-ADD_NAMESPACES(sw01)
-ADD_VETH(sw01, sw01, br-int, "192.168.1.2/24", "f0:00:00:01:02:03", \
-         "192.168.1.1")
-ovn-nbctl lsp-add sw0 sw01 \
-    -- lsp-set-addresses sw01 "f0:00:00:01:02:03 192.168.1.2"
-
-ADD_NAMESPACES(sw11)
-ADD_VETH(sw11, sw11, br-int, "192.168.2.2/24", "f0:00:00:02:02:03", \
-         "192.168.2.1")
-ovn-nbctl lsp-add sw1 sw11 \
-    -- lsp-set-addresses sw11 "f0:00:00:02:02:03 192.168.2.2"
-
-ADD_NAMESPACES(server)
-ADD_VETH(s1, server, br-ext, "2001:1db8:3333::2/64", "f0:00:00:01:02:05", \
-         "2001:1db8:3333::1")
-
-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep 2001:1db8:3333::2 | grep tentative)" = ""])
-OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""])
-
-AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
-ovn-nbctl lsp-add public public1 \
-        -- lsp-set-addresses public1 unknown \
-        -- lsp-set-type public1 localnet \
-        -- lsp-set-options public1 network_name=phynet
-
-ovn-nbctl set logical_router_port rp-public options:prefix_delegation=true
-ovn-nbctl set logical_router_port rp-public options:prefix=true
-ovn-nbctl set logical_router_port rp-sw0 options:prefix=true
-ovn-nbctl set logical_router_port rp-sw1 options:prefix=true
-
-OVN_POPULATE_ARP
-
-ovn-nbctl --wait=hv sync
-
-cat > /etc/dhcp/dhcpd.conf <<EOF
-option dhcp-rebinding-time 15;
-option dhcp-renewal-time 10;
-option dhcp6.unicast 2001:1db8:3333::1;
-subnet6 2001:1db8:3333::/64 {
-    prefix6 2001:1db8:3333:100:: 2001:1db8:3333:111:: /96;
-}
-EOF
-rm -f /var/lib/dhcp/dhcpd6.leases
-touch /var/lib/dhcp/dhcpd6.leases
-chown root:dhcpd /var/lib/dhcp /var/lib/dhcp/dhcpd6.leases
-chmod 775 /var/lib/dhcp
-chmod 664 /var/lib/dhcp/dhcpd6.leases
-
-NETNS_DAEMONIZE([server], [dhcpd -6 -f s1 > dhcpd.log 2>&1], [dhcpd.pid])
-ovn-nbctl --wait=hv sync
-
-OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c4-15)" = ""])
-OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c4-15)" = ""])
-OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c4-15)" = ""])
-
-AT_CHECK([ovn-nbctl get logical_router_port rp-public ipv6_prefix | cut -c3-16], [0], [dnl
-[2001:1db8:3333]
-])
-AT_CHECK([ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16], [0], [dnl
-[2001:1db8:3333]
-])
-AT_CHECK([ovn-nbctl get logical_router_port rp-sw1 ipv6_prefix | cut -c3-16], [0], [dnl
-[2001:1db8:3333]
-])
-
-prefix=$(ovn-nbctl list logical_router_port rp-public | awk -F/ '/ipv6_prefix/{print substr($1,25,9)}' | sed 's/://g')
-ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
-ovn-nbctl set logical_router_port rp-sw1 options:prefix=false
-
-# Renew message
-NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x05 and ip6[[113:4]]=0x${prefix} > renew.pcap &])
-# Reply message with Status OK
-NS_CHECK_EXEC([server], [tcpdump -c 1 -nni s1 ip6[[48:1]]=0x07 and ip6[[81:4]]=0x${prefix} > reply.pcap &])
-
-OVS_WAIT_UNTIL([
-    total_pkts=$(cat renew.pcap | wc -l)
-    test "${total_pkts}" = "1"
-])
-
-OVS_WAIT_UNTIL([
-    total_pkts=$(cat reply.pcap | wc -l)
-    test "${total_pkts}" = "1"
-])
-
-kill $(pidof tcpdump)
-
-ovn-nbctl set logical_router_port rp-sw0 options:prefix=false
-ovn-nbctl clear logical_router_port rp-sw0 ipv6_prefix
-OVS_WAIT_WHILE([test "$(ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16)" = "[2001:1db8:3333]"])
-AT_CHECK([ovn-nbctl get logical_router_port rp-sw0 ipv6_prefix | cut -c3-16], [0], [dnl
-[]
+OVN_TEST_IPV6_PREFIX_DELEGATION(DGP)
+AT_CLEANUP
 ])
 
-kill $(pidof ovn-controller)
-
-as ovn-sb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as ovn-nb
-OVS_APP_EXIT_AND_WAIT([ovsdb-server])
-
-as northd
-OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([IPv6 prefix delegation - gw router])
+AT_SKIP_IF([test $HAVE_DHCPD = no])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+AT_KEYWORDS([ovn-ipv6-prefix_d])
 
-as
-OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
-/.*terminating with signal 15.*/d"])
+OVN_TEST_IPV6_PREFIX_DELEGATION(GR)
 AT_CLEANUP
 ])
 
@@ -8343,3 +8207,393 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
 
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([SNAT in gateway router mode])
+AT_KEYWORDS([ovnnat])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+check ovs-ofctl add-flow br0 action=normal
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ip link set br0 up
+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=provider:br0
+
+check ovn-nbctl ls-add ls1
+check ovn-nbctl lsp-add ls1 ls1p1
+check ovn-nbctl lsp-set-addresses ls1p1 "00:00:00:01:01:01 192.168.1.1 2001::1"
+check ovn-nbctl lsp-add ls1 ls1p2
+check ovn-nbctl lsp-set-addresses ls1p2 "00:00:00:01:01:02 192.168.1.2 2001::2"
+
+check ovn-nbctl lr-add lr1
+check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.1.254/24 2001::a/64
+check ovn-nbctl lsp-add ls1 ls1-lr1
+check ovn-nbctl lsp-set-addresses ls1-lr1 "00:00:00:00:00:01 192.168.1.254 2001::a"
+check ovn-nbctl lsp-set-type ls1-lr1 router
+check ovn-nbctl lsp-set-options ls1-lr1 router-port=lr1-ls1
+
+check ovn-nbctl set logical_router lr1 options:chassis=hv1
+
+check ovn-nbctl lrp-add lr1 lr1-pub 00:00:00:00:0f:01 172.16.1.254/24 1711::a/64
+check ovn-nbctl ls-add pub
+check ovn-nbctl lsp-add pub pub-lr1
+check ovn-nbctl lsp-set-type pub-lr1 router
+check ovn-nbctl lsp-set-options pub-lr1 router-port=lr1-pub
+check ovn-nbctl lsp-set-addresses pub-lr1 router
+
+check ovn-nbctl lsp-add pub ln -- lsp-set-options ln network_name=provider
+check ovn-nbctl lsp-set-type ln localnet
+check ovn-nbctl lsp-set-addresses ln unknown
+
+check ovn-nbctl lr-nat-add lr1 snat 172.16.1.10 192.168.1.0/24
+check ovn-nbctl lr-nat-add lr1 snat 1711::10 2001::/64
+
+NS_ADD_INT(ls1p1, ls1p1, br-int, "192.168.1.1/24", "00:00:00:01:01:01", "2001::1/64", "192.168.1.254", "2001::a" )
+NS_ADD_INT(ls1p2, ls1p2, br-int, "192.168.1.2/24", "00:00:00:01:01:02", "2001::2/64", "192.168.1.254", "2001::a" )
+
+ADD_NAMESPACES(ext1)
+ADD_INT(ext1, ext1, br0, 172.16.1.1/24, 1711::1/64)
+check ovn-nbctl --wait=hv sync
+wait_for_ports_up
+OVS_WAIT_UNTIL([test "$(ip netns exec ls1p1 ip a | grep 2001::1 | grep tentative)" = ""])
+OVS_WAIT_UNTIL([test "$(ip netns exec ls1p2 ip a | grep 2002::1 | grep tentative)" = ""])
+
+NS_CHECK_EXEC([ls1p1], [ping -q -c 3 -i 0.3 -w 2  172.16.1.1 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+NS_CHECK_EXEC([ls1p1], [ping6 -v -q -c 3 -i 0.3 -w 2 1711::1  | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/removing policing failed: No such device/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([mcast flow count])
+AT_KEYWORDS([ovnigmp IP-multicast])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl lsp-add ls vm1
+check ovn-nbctl lsp-set-addresses vm1 00:00:00:00:00:01
+check ovn-nbctl lsp-add ls vm2
+check ovn-nbctl lsp-set-addresses vm2 00:00:00:00:00:02
+check ovn-nbctl lsp-add ls vm3
+check ovn-nbctl lsp-set-addresses vm3 00:00:00:00:00:03
+
+check ovn-nbctl set logical_switch ls other_config:mcast_querier=false other_config:mcast_snoop=true other_config:mcast_query_interval=30 other_config:mcast_eth_src=00:00:00:00:00:05 other_config:mcast_ip4_src=42.42.42.5 other_config:mcast_ip6_src=fe80::1 other_config:mcast_idle_timeout=3000
+ovn-sbctl list ip_multicast
+
+wait_igmp_flows_installed()
+{
+    OVS_WAIT_UNTIL([ovs-ofctl dump-flows br-int table=31 | \
+    grep 'priority=90' | grep "nw_dst=$1"])
+}
+
+ADD_NAMESPACES(vm1)
+ADD_INT([vm1], [vm1], [br-int], [42.42.42.1/24])
+NS_CHECK_EXEC([vm1], [ip link set vm1 address 00:00:00:00:00:01], [0])
+NS_CHECK_EXEC([vm1], [ip route add default via 42.42.42.5], [0])
+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1
+
+ADD_NAMESPACES(vm2)
+ADD_INT([vm2], [vm2], [br-int], [42.42.42.2/24])
+NS_CHECK_EXEC([vm2], [ip link set vm2 address 00:00:00:00:00:02], [0])
+NS_CHECK_EXEC([vm2], [ip link set lo up], [0])
+check ovs-vsctl set Interface vm2 external_ids:iface-id=vm2
+
+ADD_NAMESPACES(vm3)
+NETNS_DAEMONIZE([vm3], [tcpdump -n -i any -nnleX > vm3.pcap 2>/dev/null], [tcpdump3.pid])
+
+ADD_INT([vm3], [vm3], [br-int], [42.42.42.3/24])
+NS_CHECK_EXEC([vm3], [ip link set vm3 address 00:00:00:00:00:03], [0])
+NS_CHECK_EXEC([vm3], [ip link set lo up], [0])
+NS_CHECK_EXEC([vm3], [ip route add default via 42.42.42.5], [0])
+check ovs-vsctl set Interface vm3 external_ids:iface-id=vm3
+
+NS_CHECK_EXEC([vm2], [sysctl -w net.ipv4.igmp_max_memberships=100], [ignore], [ignore])
+NS_CHECK_EXEC([vm3], [sysctl -w net.ipv4.igmp_max_memberships=100], [ignore], [ignore])
+wait_for_ports_up
+
+NS_CHECK_EXEC([vm3], [ip addr add 228.0.0.1 dev vm3 autojoin], [0])
+wait_igmp_flows_installed 228.0.0.1
+
+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 228.0.0.1], [ignore], [ignore])
+
+OVS_WAIT_UNTIL([
+    requests=`grep "ICMP echo request" -c vm3.pcap`
+    test "${requests}" -ge "3"
+])
+
+NETNS_DAEMONIZE([vm2], [tcpdump -n -i any -nnleX > vm2.pcap 2>/dev/null], [tcpdump2.pid])
+
+for i in `seq 1 40`;do
+    NS_CHECK_EXEC([vm2], [ip addr add 228.1.$i.1 dev vm2 autojoin &], [0])
+    NS_CHECK_EXEC([vm3], [ip addr add 229.1.$i.1 dev vm3 autojoin &], [0])
+    # Do not go too fast. If going fast, there is a higher chance of sb being busy, causing full recompute (engine has not run)
+    # In this test, we do not want too many recomputes as they might hide I+I related errors
+    sleep 0.2
+done
+
+for i in `seq 1 40`;do
+    wait_igmp_flows_installed 228.1.$i.1
+    wait_igmp_flows_installed 229.1.$i.1
+done
+ovn-sbctl list multicast_group
+
+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 228.1.1.1], [ignore], [ignore])
+
+OVS_WAIT_UNTIL([
+    requests=`grep "ICMP echo request" -c vm2.pcap`
+    test "${requests}" -ge "3"
+])
+
+# The test could succeed thanks to a lucky northd recompute...after hitting too any flows
+# Double check we never hit error condition
+AT_CHECK([grep -qE 'Too many active mcast flows' northd/ovn-northd.log], [1])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d
+/removing policing failed: No such device/d"])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DVR ping router port])
+AT_KEYWORDS([dvr])
+
+ovn_start
+
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+check ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+check ovs-vsctl set open . external_ids:ovn-bridge-mappings=phys:br-ext
+check ovs-vsctl set open . external-ids:ovn-chassis-mac-mappings="phys:ee:00:00:00:00:10"
+
+
+check ovn-nbctl ls-add internal
+
+check ovn-nbctl lsp-add internal ln_internal "" 100
+check ovn-nbctl lsp-set-addresses ln_internal unknown
+check ovn-nbctl lsp-set-type ln_internal localnet
+check ovn-nbctl lsp-set-options ln_internal network_name=phys
+
+check ovn-nbctl lsp-add internal internal-gw
+check ovn-nbctl lsp-set-type internal-gw router
+check ovn-nbctl lsp-set-addresses internal-gw router
+check ovn-nbctl lsp-set-options internal-gw router-port=gw-internal
+
+check ovn-nbctl lsp-add internal vif0
+# Set address as unknown so that LRP has to generate ARP request
+check ovn-nbctl lsp-set-addresses vif0 unknown
+
+check ovn-nbctl lr-add gw
+check ovn-nbctl lrp-add gw gw-internal 00:00:00:00:20:00 192.168.20.1/24
+
+ADD_NAMESPACES(vif0)
+ADD_VETH(vif0, vif0, br-int, "192.168.20.10/24", "00:00:00:00:20:10", "192.168.20.1")
+
+check ovn-nbctl --wait=sb sync
+check ovn-nbctl --wait=hv sync
+
+NS_CHECK_EXEC([vif0], [ping -q -c 3 -i 0.3 -w 1 192.168.20.1 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([SNAT in separate zone from DNAT])
+
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_NAT()
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+ADD_BR([br-int])
+
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+        -- set Open_vSwitch . external-ids:system-id=hv1 \
+        -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+        -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# The goal of this test is to ensure that when traffic is first DNATted
+# (by way of a load balancer), and then SNATted, the SNAT happens in a
+# separate conntrack zone from the DNAT.
+
+start_daemon ovn-controller
+
+check ovn-nbctl ls-add public
+
+check ovn-nbctl lr-add r1
+check ovn-nbctl lrp-add r1 r1_public 00:de:ad:ff:00:01 172.16.0.1/16
+check ovn-nbctl lrp-add r1 r1_s1 00:de:ad:fe:00:01 173.0.1.1/24
+check ovn-nbctl lrp-set-gateway-chassis r1_public hv1
+
+check ovn-nbctl lb-add r1_lb 30.0.0.1 172.16.0.102
+check ovn-nbctl lr-lb-add r1 r1_lb
+
+check ovn-nbctl ls-add s1
+check ovn-nbctl lsp-add s1 s1_r1
+check ovn-nbctl lsp-set-type s1_r1 router
+check ovn-nbctl lsp-set-addresses s1_r1 router
+check ovn-nbctl lsp-set-options s1_r1 router-port=r1_s1
+
+check ovn-nbctl lsp-add s1 vm1
+check ovn-nbctl lsp-set-addresses vm1 "00:de:ad:01:00:01 173.0.1.2"
+
+check ovn-nbctl lsp-add public public_r1
+check ovn-nbctl lsp-set-type public_r1 router
+check ovn-nbctl lsp-set-addresses public_r1 router
+check ovn-nbctl lsp-set-options public_r1 router-port=r1_public nat-addresses=router
+
+check ovn-nbctl lr-add r2
+check ovn-nbctl lrp-add r2 r2_public 00:de:ad:ff:00:02 172.16.0.2/16
+check ovn-nbctl lrp-add r2 r2_s2 00:de:ad:fe:00:02 173.0.2.1/24
+check ovn-nbctl lr-nat-add r2 dnat_and_snat 172.16.0.102 173.0.2.2
+check ovn-nbctl lrp-set-gateway-chassis r2_public hv1
+
+check ovn-nbctl ls-add s2
+check ovn-nbctl lsp-add s2 s2_r2
+check ovn-nbctl lsp-set-type s2_r2 router
+check ovn-nbctl lsp-set-addresses s2_r2 router
+check ovn-nbctl lsp-set-options s2_r2 router-port=r2_s2
+
+check ovn-nbctl lsp-add s2 vm2
+check ovn-nbctl lsp-set-addresses vm2 "00:de:ad:01:00:02 173.0.2.2"
+
+check ovn-nbctl lsp-add public public_r2
+check ovn-nbctl lsp-set-type public_r2 router
+check ovn-nbctl lsp-set-addresses public_r2 router
+check ovn-nbctl lsp-set-options public_r2 router-port=r2_public nat-addresses=router
+
+ADD_NAMESPACES(vm1)
+ADD_VETH(vm1, vm1, br-int, "173.0.1.2/24", "00:de:ad:01:00:01", \
+         "173.0.1.1")
+ADD_NAMESPACES(vm2)
+ADD_VETH(vm2, vm2, br-int, "173.0.2.2/24", "00:de:ad:01:00:02", \
+         "173.0.2.1")
+
+check ovn-nbctl lr-nat-add r1 dnat_and_snat 172.16.0.101 173.0.1.2 vm1 00:00:00:01:02:03
+check ovn-nbctl --wait=hv sync
+
+# Next, make sure that a ping works as expected
+NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 30.0.0.1 | FORMAT_PING], \
+[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+# Finally, make sure that conntrack shows two separate zones being used for
+# DNAT and SNAT
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.1) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=173.0.1.2,dst=30.0.0.1,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=173.0.1.2,id=<cleared>,type=0,code=0),zone=<cleared>,mark=2
+])
+
+# The final two entries appear identical here. That is because FORMAT_CT
+# scrubs the zone numbers. In actuality, the zone numbers are different,
+# which is why there are two entries.
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(172.16.0.102) | \
+sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
+icmp,orig=(src=172.16.0.101,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=173.0.2.2,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
+icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
+icmp,orig=(src=173.0.1.2,dst=172.16.0.102,id=<cleared>,type=8,code=0),reply=(src=172.16.0.102,dst=172.16.0.101,id=<cleared>,type=0,code=0),zone=<cleared>
+])
+
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
+/connection dropped.*/d"])
+AT_CLEANUP
+])