diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h
index ba995ba..72af117 100644
--- a/lib/mcast-group-index.h
+++ b/lib/mcast-group-index.h
@@ -30,6 +30,7 @@ enum ovn_mcast_tunnel_keys {
     OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY,
     OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY,
     OVN_MCAST_STATIC_TUNNEL_KEY,
+    OVN_MCAST_FLOOD_L2_TUNNEL_KEY,
     OVN_MIN_IP_MULTICAST,
     OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST,
 };
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 9b96ce9..b37cecd 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1367,18 +1367,19 @@ output;
       </li>
 
       <li>
-        Priority-80 flows for each port connected to a logical router
-        matching self originated GARP/ARP request/ND packets. These packets
-        are flooded to the <code>MC_FLOOD</code> which contains all logical
-        ports.
+        Priority-80 flows for each IP address/VIP/NAT address owned by a
+        router port connected to the switch. These flows match ARP requests
+        and ND packets for the specific IP addresses.  Matched packets are
+        forwarded only to the router that owns the IP address and to the
+        <code>MC_FLOOD_L2</code> multicast group which contains all non-router
+        logical ports.
       </li>
 
       <li>
-        Priority-75 flows for each IP address/VIP/NAT address owned by a
-        router port connected to the switch. These flows match ARP requests
-        and ND packets for the specific IP addresses.  Matched packets are
-        forwarded only to the router that owns the IP address and, if
-        present, to the localnet port of the logical switch.
+        Priority-75 flows for each port connected to a logical router
+        matching self originated ARP request/ND packets.  These packets
+        are flooded to the <code>MC_FLOOD_L2</code> which contains all
+        non-router logical ports.
       </li>
 
       <li>
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 8800a3d..dbe5fa6 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -1559,6 +1559,12 @@ lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
 }
 
 static bool
+lsp_is_router(const struct nbrec_logical_switch_port *nbsp)
+{
+    return !strcmp(nbsp->type, "router");
+}
+
+static bool
 lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
 {
     return !lrport->enabled || *lrport->enabled;
@@ -2488,7 +2494,7 @@ join_logical_ports(struct northd_context *ctx,
      * to their peers. */
     struct ovn_port *op;
     HMAP_FOR_EACH (op, key_node, ports) {
-        if (op->nbsp && !strcmp(op->nbsp->type, "router") && !op->derived) {
+        if (op->nbsp && lsp_is_router(op->nbsp) && !op->derived) {
             const char *peer_name = smap_get(&op->nbsp->options, "router-port");
             if (!peer_name) {
                 continue;
@@ -3105,7 +3111,7 @@ ovn_port_update_sbrec(struct northd_context *ctx,
 
         sbrec_port_binding_set_nat_addresses(op->sb, NULL, 0);
     } else {
-        if (strcmp(op->nbsp->type, "router")) {
+        if (!lsp_is_router(op->nbsp)) {
             uint32_t queue_id = smap_get_int(
                     &op->sb->options, "qdisc_queue_id", 0);
             bool has_qos = port_has_qos_params(&op->nbsp->options);
@@ -3843,6 +3849,10 @@ static const struct multicast_group mc_static =
 static const struct multicast_group mc_unknown =
     { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
 
+#define MC_FLOOD_L2 "_MC_flood_l2"
+static const struct multicast_group mc_flood_l2 =
+    { MC_FLOOD_L2, OVN_MCAST_FLOOD_L2_TUNNEL_KEY };
+
 static bool
 multicast_group_equal(const struct multicast_group *a,
                       const struct multicast_group *b)
@@ -6394,12 +6404,11 @@ build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op,
         sset_add(&all_eth_addrs, nat->external_mac);
     }
 
-
-    /* Self originated (G)ARP requests/ND need to be flooded as usual.
-     * Determine that packets are self originated by also matching on
-     * source MAC. Matching on ingress port is not reliable in case this
-     * is a VLAN-backed network.
-     * Priority: 80.
+    /* Self originated ARP requests/ND need to be flooded to the L2 domain
+     * (except on router ports).  Determine that packets are self originated
+     * by also matching on source MAC. Matching on ingress port is not
+     * reliable in case this is a VLAN-backed network.
+     * Priority: 75.
      */
     const char *eth_addr;
 
@@ -6415,7 +6424,7 @@ build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op,
                   ds_cstr(&eth_src));
     ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
                   ds_cstr(&match),
-                  "outport = \""MC_FLOOD"\"; output;");
+                  "outport = \""MC_FLOOD_L2"\"; output;");
 
     sset_destroy(&all_eth_addrs);
     ds_destroy(&eth_src);
@@ -6461,14 +6470,16 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips,
     ds_chomp(&match, ',');
     ds_put_cstr(&match, "}");
 
-    /* Send a the packet only to the router pipeline and skip flooding it
-     * in the broadcast domain (except for the localnet port).
+    /* Send a the packet to the router pipeline.  If the switch has non-router
+     * ports then flood it there as well.
      */
-    for (size_t i = 0; i < od->n_localnet_ports; i++) {
-        ds_put_format(&actions, "clone { outport = %s; output; }; ",
-                      od->localnet_ports[i]->json_key);
+    if (od->n_router_ports != od->nbs->n_ports) {
+        ds_put_format(&actions, "clone {outport = %s; output; }; "
+                                "outport = \""MC_FLOOD_L2"\"; output;",
+                      patch_op->json_key);
+    } else {
+        ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
     }
-    ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
     ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
                             ds_cstr(&match), ds_cstr(&actions), stage_hint);
 
@@ -6498,14 +6509,9 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
         return;
     }
 
-    /* Self originated (G)ARP requests/ND need to be flooded as usual.
-     * Priority: 80.
-     */
-    build_lswitch_rport_arp_req_self_orig_flow(op, 80, sw_od, lflows);
-
     /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this
      * router port.
-     * Priority: 75.
+     * Priority: 80.
      */
     struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4);
     struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6);
@@ -6580,17 +6586,28 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op,
 
     if (!sset_is_empty(&all_ips_v4)) {
         build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v4, AF_INET, sw_op,
-                                                sw_od, 75, lflows,
+                                                sw_od, 80, lflows,
                                                 stage_hint);
     }
     if (!sset_is_empty(&all_ips_v6)) {
         build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v6, AF_INET6, sw_op,
-                                                sw_od, 75, lflows,
+                                                sw_od, 80, lflows,
                                                 stage_hint);
     }
 
     sset_destroy(&all_ips_v4);
     sset_destroy(&all_ips_v6);
+
+    /* Self originated ARP requests/ND need to be flooded as usual.
+     *
+     * However, if the switch doesn't have any non-router ports we shouldn't
+     * even try to flood.
+     *
+     * Priority: 75.
+     */
+    if (sw_od->n_router_ports != sw_od->nbs->n_ports) {
+        build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lflows);
+    }
 }
 
 static void
@@ -6930,7 +6947,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
              *  - port type is localport
              */
             if (check_lsp_is_up &&
-                !lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") &&
+                !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) &&
                 strcmp(op->nbsp->type, "localport")) {
                 continue;
             }
@@ -7005,8 +7022,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                             "flags.loopback = 1; "
                             "output; "
                             "};",
-                            !strcmp(op->nbsp->type, "router") ?
-                                "nd_na_router" : "nd_na",
+                            lsp_is_router(op->nbsp) ? "nd_na_router" : "nd_na",
                             op->lsp_addrs[i].ea_s,
                             op->lsp_addrs[i].ipv6_addrs[j].addr_s,
                             op->lsp_addrs[i].ipv6_addrs[j].addr_s,
@@ -7088,7 +7104,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
            continue;
         }
 
-        if (!lsp_is_enabled(op->nbsp) || !strcmp(op->nbsp->type, "router")) {
+        if (!lsp_is_enabled(op->nbsp) || lsp_is_router(op->nbsp)) {
             /* Don't add the DHCP flows if the port is not enabled or if the
              * port is a router port. */
             continue;
@@ -7347,7 +7363,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
          * broadcast flooding of ARP/ND requests in table 19. We direct the
          * requests only to the router port that owns the IP address.
          */
-        if (!strcmp(op->nbsp->type, "router")) {
+        if (lsp_is_router(op->nbsp)) {
             build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows,
                                               &op->nbsp->header_);
         }
@@ -10638,7 +10654,7 @@ build_arp_resolve_flows_for_lrouter_port(
          */
         build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true,
                                     lflows);
-    } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
+    } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
                && strcmp(op->nbsp->type, "virtual")) {
         /* This is a logical switch port that backs a VM or a container.
          * Extract its addresses. For each of the address, go through all
@@ -10722,7 +10738,7 @@ build_arp_resolve_flows_for_lrouter_port(
                 }
             }
         }
-    } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
+    } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp)
                && !strcmp(op->nbsp->type, "virtual")) {
         /* This is a virtual port. Add ARP replies for the virtual ip with
          * the mac of the present active virtual parent.
@@ -10826,7 +10842,7 @@ build_arp_resolve_flows_for_lrouter_port(
                 }
             }
         }
-    } else if (!strcmp(op->nbsp->type, "router")) {
+    } else if (lsp_is_router(op->nbsp)) {
         /* This is a logical switch port that connects to a router. */
 
         /* The peer of this switch port is the router port for which
@@ -11992,6 +12008,10 @@ build_mcast_groups(struct northd_context *ctx,
         } else if (op->nbsp && lsp_is_enabled(op->nbsp)) {
             ovn_multicast_add(mcast_groups, &mc_flood, op);
 
+            if (!lsp_is_router(op->nbsp)) {
+                ovn_multicast_add(mcast_groups, &mc_flood_l2, op);
+            }
+
             /* If this port is connected to a multicast router then add it
              * to the MC_MROUTER_FLOOD group.
              */
@@ -12435,7 +12455,7 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports,
             continue;
         }
 
-        bool up = (sb->chassis || !strcmp(op->nbsp->type, "router"));
+        bool up = (sb->chassis || lsp_is_router(op->nbsp));
         if (!op->nbsp->up || *op->nbsp->up != up) {
             nbrec_logical_switch_port_set_up(op->nbsp, &up, 1);
         }
diff --git a/tests/ovn.at b/tests/ovn.at
index 4be4840..434ae87 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -3593,7 +3593,7 @@ test_ip() {
     done
 }
 
-# test_arp INPORT SHA SPA TPA FLOOD [REPLY_HA]
+# test_arp INPORT SHA SPA TPA [REPLY_HA]
 #
 # Causes a packet to be received on INPORT.  The packet is an ARP
 # request with SHA, SPA, and TPA as specified.  If REPLY_HA is provided, then
@@ -3605,25 +3605,21 @@ test_ip() {
 # SPA and TPA are each 8 hex digits.
 test_arp() {
     echo "$@"
-    local inport=$1 sha=$2 spa=$3 tpa=$4 flood=$5 reply_ha=$6
+    local inport=$1 sha=$2 spa=$3 tpa=$4 reply_ha=$5
     local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
     hv=hv`vif_to_hv $inport`
     as $hv ovs-appctl netdev-dummy/receive vif$inport $request
     as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request > trace
 
     # Expect to receive the broadcast ARP on the other logical switch ports if
-    # IP address is not configured on the switch patch port or on the router
-    # port (i.e, $flood == 1).
+    # IP address is not configured to the switch patch port.
     local i=`vif_to_ls $inport`
     local j k
     for j in 1 2 3; do
         for k in 1 2 3; do
-            # Skip ingress port.
-            if test $i$j$k == $inport; then
-                continue
-            fi
-
-            if test X$flood == X1; then
+            # 192.168.33.254 is configured to the switch patch port for lrp33,
+            # so no ARP flooding expected for it.
+            if test $i$j$k != $inport && test $tpa != `ip_to_hex 192 168 33 254`; then
                 echo $request >> $i$j$k.expected
             fi
         done
@@ -3762,9 +3758,9 @@ for i in 1 2 3; do
       otherip=`ip_to_hex 192 168 $i$j 55` # Some other IP in subnet
       externalip=`ip_to_hex 1 2 3 4`      # Some other IP not in subnet
 
-      test_arp $i$j$k $smac $sip        $rip       0     $rmac       #4
-      test_arp $i$j$k $smac $otherip    $rip       0     $rmac       #5
-      test_arp $i$j$k $smac $sip        $otherip   1                 #6
+      test_arp $i$j$k $smac $sip        $rip        $rmac      #4
+      test_arp $i$j$k $smac $otherip    $rip        $rmac      #5
+      test_arp $i$j$k $smac $sip        $otherip               #6
 
       # When rip is 192.168.33.254, ARP request from externalip won't be
       # filtered, because 192.168.33.254 is configured to switch peer port
@@ -3773,7 +3769,7 @@ for i in 1 2 3; do
       if test $i = 3 && test $j = 3; then
         lrp33_rsp=$rmac
       fi
-      test_arp $i$j$k $smac $externalip $rip       0      $lrp33_rsp #7
+      test_arp $i$j$k $smac $externalip $rip        $lrp33_rsp #7
 
       # MAC binding should be learned from ARP request.
       host_mac_pretty=f0:00:00:00:0$i:$j$k
@@ -19819,7 +19815,7 @@ match_r1_metadata="metadata=0x${r1_dp_key}"
 send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 1)
 
 # Verify that the ARP request is sent only to rtr1.
-match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.1,arp_op=1"
+match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.1,arp_op=1"
 match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15"
 match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15"
 
@@ -19843,7 +19839,7 @@ dst_ipv6=00100000000000000000000000000001
 send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
-match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::1"
+match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::1"
 
 as hv1
 OVS_WAIT_UNTIL([
@@ -19875,7 +19871,7 @@ check ovn-nbctl --wait=hv sync
 send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 11)
 
 # Verify that the ARP request is sent only to rtr1.
-match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.11,arp_op=1"
+match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.11,arp_op=1"
 match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15"
 match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15"
 
@@ -19899,7 +19895,7 @@ dst_ipv6=00100000000000000000000000000011
 send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
-match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::11"
+match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::11"
 
 as hv1
 OVS_WAIT_UNTIL([
@@ -19943,7 +19939,7 @@ AT_CAPTURE_FILE([sbflows2])
 # - 10.0.0.22, 10::22 - LB VIPs.
 # - 10.0.0.222, 10::222 - DNAT IPs.
 as hv1
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl
 arp_tpa=10.0.0.1
 arp_tpa=10.0.0.11
 arp_tpa=10.0.0.111
@@ -19953,7 +19949,7 @@ arp_tpa=10.0.0.2
 arp_tpa=10.0.0.22
 arp_tpa=10.0.0.222
 ])
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl
 nd_target=10::1
 nd_target=10::11
 nd_target=10::111
@@ -19969,10 +19965,10 @@ nd_target=fe80::200:ff:fe00:200
 # For sw1-rtr1:
 # - 20.0.0.1, 20::1, fe80::200:1ff:fe00:0 - interface IPs.
 as hv1
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}" | grep -oE "arp_tpa=[[0-9.]]+" | sort], [0], [dnl
 arp_tpa=20.0.0.1
 ])
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}" | grep -oE "nd_target=[[0-9a-f:]]+" | sort], [0], [dnl
 nd_target=20::1
 nd_target=fe80::200:1ff:fe00:0
 ])
@@ -19984,13 +19980,13 @@ nd_target=fe80::200:1ff:fe00:0
 # - 00:00:00:01:00:00 - dnat_and_snat external MAC.
 # - 00:00:00:02:00:00 - dnat_and_snat external MAC.
 as hv1
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
 dl_src=00:00:00:00:01:00
 dl_src=00:00:00:00:02:00
 dl_src=00:00:00:01:00:00
 dl_src=00:00:00:02:00:00
 ])
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw_metadata}.*icmp_type=135" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw_metadata}.*icmp_type=135" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
 dl_src=00:00:00:00:01:00
 dl_src=00:00:00:00:02:00
 dl_src=00:00:00:01:00:00
@@ -20000,7 +19996,7 @@ dl_src=00:00:00:02:00:00
 # Self originated ARP/NS with SMACs owned by rtr1-sw1 should be flooded:
 # - 00:00:01:00:00:00 - interface MAC.
 as hv1
-AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=80,.*${match_sw1_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
+AT_CHECK([ovs-ofctl dump-flows br-int | grep -E "priority=75,.*${match_sw1_metadata}.*arp_op=1" | grep -oE "dl_src=[[0-9a-f:]]+" | sort], [0], [dnl
 dl_src=00:00:01:00:00:00
 ])
 
@@ -20008,7 +20004,7 @@ dl_src=00:00:01:00:00:00
 send_arp_request 1 0 ${src_mac} $(ip_to_hex 10 0 0 254) $(ip_to_hex 10 0 0 111)
 
 # Verify that the ARP request is sent only to rtr1.
-match_arp_req="priority=75.*${match_sw_metadata}.*arp_tpa=10.0.0.111,arp_op=1"
+match_arp_req="priority=80.*${match_sw_metadata}.*arp_tpa=10.0.0.111,arp_op=1"
 match_send_rtr1="load:0x${r1_tnl_key}->NXM_NX_REG15"
 match_send_rtr2="load:0x${r2_tnl_key}->NXM_NX_REG15"
 
@@ -20072,7 +20068,7 @@ dst_ipv6=00100000000000000000000000000111
 send_nd_ns 1 0 ${src_mac} ${src_ipv6} ${dst_ipv6} 751d
 
 # Verify that the ND_NS is sent only to rtr1.
-match_nd_ns="priority=75.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::111"
+match_nd_ns="priority=80.*${match_sw_metadata}.*icmp_type=135.*nd_target=10::111"
 
 as hv1
 OVS_WAIT_UNTIL([
