The commit introduced a regression when multicast traffic could
cause a storm due to the inport rewrite. When the multicast
traffic arrived over localnet with eth.src matching one of the
external ports, the new lflow would rewrite the inport so
the re-injected traffic would be flooded back into localnet
port. This was also happening with unknown unicast traffic
but ovs did prevent the loop because that packet didn't go
through pinctrl.

Add a test case that showcases the problem so we can avoid it
in the future.

This reverts commit 3bae90d9fe170b7f6dce3ad21031d2327232d2f1.

CC: JayGue Lee <[email protected]>
Fixes: 3bae90d9fe17 ("northd: Fix HM reply lflow for type=external backends on 
localnet LS.")
Reported-at: https://redhat.atlassian.net/browse/FDP-4157
Signed-off-by: Ales Musil <[email protected]>
---
 Documentation/ref/ovn-logical-flows.7.rst |   8 --
 NEWS                                      |  12 ---
 northd/northd.c                           |  97 +++++-------------
 tests/ovn-northd.at                       | 116 ----------------------
 tests/ovn.at                              | 115 +++++++++++++++++++--
 tests/scapy-server.py                     |   1 +
 6 files changed, 130 insertions(+), 219 deletions(-)

diff --git a/Documentation/ref/ovn-logical-flows.7.rst 
b/Documentation/ref/ovn-logical-flows.7.rst
index 2778b3633..f80826354 100644
--- a/Documentation/ref/ovn-logical-flows.7.rst
+++ b/Documentation/ref/ovn-logical-flows.7.rst
@@ -43,14 +43,6 @@ Ingress table 0 contains these logical flows:
   want to prevent duplicate replies and advertisements. This is achieved by a
   rule with priority 80 that sets ``REGBIT_PORT_SEC_DROP" = 1; next;"``.
 
-- For each ``type=external`` logical port on a switch that has a localnet port,
-  a priority 75 flow matches on ``inport == <localnet_port> && eth.src ==
-  <external_mac>`` and applies ``flags.localnet = 1; inport = <external_lsp>;
-  next;``.  This rewrites ``inport`` from the localnet port to the external LSP
-  so that all downstream stages observe the correct logical inport for traffic
-  originating from the baremetal member.
-
-
 - For each (enabled) vtep logical port, a priority 70 flow is added which
   matches on all packets and applies the action ``next(pipeline=ingress,
   table=S_SWITCH_IN_L3_LKUP) = 1;`` to skip most stages of ingress pipeline and
diff --git a/NEWS b/NEWS
index b233ee01a..84d7f605c 100644
--- a/NEWS
+++ b/NEWS
@@ -60,18 +60,6 @@ Post v26.03.0
      the "Route Administrative Distance" section of the ovn-northd manpage.
    - Windows support was broken since the split from OVS repository.
      Remaining bits of the build system and the documentation are now removed.
-   - Fixed Load_Balancer health check replies failing silently for
-     baremetal pool members whose backend LSP is type=external on a
-     Logical_Switch that has a localnet port.  ovn-northd now installs
-     an early inport-rewrite lflow at ls_in_check_port_sec that
-     substitutes MFF_LOG_INPORT from the localnet port to the external
-     LSP when eth.src matches the external port's MAC, so every
-     downstream pipeline stage (including the per-backend HM reply
-     lflow and the generic per-LS svc_monitor_mac lflow) observes
-     inport == <external_lsp> and pinctrl_find_svc_monitor() succeeds.
-     The DHCP and unbound-router ARP/ND drop lflows for external
-     ports were updated to key on the external LSP's inport
-     accordingly.
    - Removed deprecated "port-up-notif", "ct-no-masked-label" and
      "ovn-ct-lb-related" feature flags. At the same time mark
      "mac-binding-timestamp", "fdb-timestamp" and "ls-dpg-column" feature
diff --git a/northd/northd.c b/northd/northd.c
index 4ca9820d4..1d1452791 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -9046,7 +9046,8 @@ build_lb_health_check_response_lflows(
     const struct ovn_lb_datapaths *lb_dps,
     const struct ovn_datapaths *lr_datapaths,
     const struct shash *meter_groups,
-    struct ds *match)
+    struct ds *match,
+    struct ds *action)
 {
     /* For each LB backend that is monitored by a source_ip belonging
      * to a real LRP, install rule that punts service check replies to the
@@ -9093,6 +9094,7 @@ build_lb_health_check_response_lflows(
             }
 
             ds_clear(match);
+            ds_clear(action);
 
             /* icmp6 type 1 and icmp4 type 3 are included in the match, because
              * the controller is using them to detect unreachable ports. */
@@ -10296,13 +10298,6 @@ 
build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
 {
     struct ds match = DS_EMPTY_INITIALIZER;
 
-    /* With the early inport rewrite installed at
-     * S_SWITCH_IN_CHECK_PORT_SEC, packets from the external LSP arrive
-     * here with MFF_LOG_INPORT == op (the external LSP), not the
-     * localnet port (which was the value at table 0).  The match is
-     * therefore keyed on op->json_key.  The 'port' (localnet) argument
-     * is still used for incremental processing tagging through
-     * WITH_IO_PORT below. */
     for (size_t i = 0; i < op->n_lsp_addrs; i++) {
         struct ovn_port *rp;
         VECTOR_FOR_EACH (&op->od->router_ports, rp) {
@@ -10313,7 +10308,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct 
ovn_port *op,
                         &match, "inport == %s && eth.src == %s"
                         " && !is_chassis_resident(%s)"
                         " && arp.tpa == %s && arp.op == 1",
-                        op->json_key,
+                        port->json_key,
                         op->lsp_addrs[i].ea_s, op->json_key,
                         rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
                     ovn_lflow_add(lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT,
@@ -10329,7 +10324,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct 
ovn_port *op,
                         &match, "inport == %s && eth.src == %s"
                         " && !is_chassis_resident(%s)"
                         " && nd_ns && ip6.dst == {%s, %s} && nd.target == %s",
-                        op->json_key,
+                        port->json_key,
                         op->lsp_addrs[i].ea_s, op->json_key,
                         rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
                         rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
@@ -10347,7 +10342,7 @@ build_drop_arp_nd_flows_for_unbound_router_ports(struct 
ovn_port *op,
                     &match, "inport == %s && eth.src == %s"
                     " && eth.dst == %s"
                     " && !is_chassis_resident(%s)",
-                    op->json_key,
+                    port->json_key,
                     op->lsp_addrs[i].ea_s, rp->lsp_addrs[k].ea_s,
                     op->json_key);
                 ovn_lflow_add(lflows, op->od, S_SWITCH_IN_EXTERNAL_PORT, 100,
@@ -11117,20 +11112,24 @@ build_lswitch_dhcp_options_and_response(struct 
ovn_port *op,
     }
 
     for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-        /* For both regular VIF and type=external LSPs we pass the LSP
-         * itself (op) as the inport.  For external ports, the inport
-         * rewrite added in build_lswitch_external_lsp_inport_rewrite()
-         * at S_SWITCH_IN_CHECK_PORT_SEC has already substituted
-         * MFF_LOG_INPORT from the localnet port to the external LSP by
-         * the time we reach S_SWITCH_IN_DHCP_OPTIONS.  So a single set
-         * of DHCP lflows keyed on the external LSP is enough; we no
-         * longer need to enumerate every localnet port. */
-        build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op,
-                                   is_external, meter_groups,
-                                   lflows, op->lflow_ref);
-        build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op,
-                                   is_external, meter_groups,
-                                   lflows, op->lflow_ref);
+        if (is_external) {
+            struct ovn_port *lp;
+            VECTOR_FOR_EACH (&op->od->localnet_ports, lp) {
+                build_dhcpv4_options_flows(
+                    op, &op->lsp_addrs[i], lp, is_external,
+                    meter_groups, lflows, op->lflow_ref);
+                build_dhcpv6_options_flows(
+                    op, &op->lsp_addrs[i], lp, is_external,
+                    meter_groups, lflows, op->lflow_ref);
+            }
+        } else {
+            build_dhcpv4_options_flows(op, &op->lsp_addrs[i], op,
+                                       is_external, meter_groups,
+                                       lflows, op->lflow_ref);
+            build_dhcpv6_options_flows(op, &op->lsp_addrs[i], op,
+                                       is_external, meter_groups,
+                                       lflows, op->lflow_ref);
+        }
     }
 }
 
@@ -11209,51 +11208,6 @@ build_lswitch_external_port(struct ovn_port *op,
     }
 }
 
-/* For each external LSP on a switch with a localnet port, rewrite
- * MFF_LOG_INPORT from the localnet port to the external LSP when
- * eth.src matches one of the external port's MACs.  This makes
- * downstream stages observe inport == <external_lsp> for traffic
- * originating from that baremetal MAC.  Intentionally placed at
- * S_SWITCH_IN_CHECK_PORT_SEC priority 75 so it fires before the
- * existing priority-70 generic port-sec rules but does not collide
- * with the priority-100 disabled-port drop. */
-static void
-build_lswitch_external_lsp_inport_rewrite(struct ovn_port *op,
-                                          struct lflow_table *lflows,
-                                          struct ds *match,
-                                          struct ds *actions)
-{
-    ovs_assert(op->nbsp);
-    if (!lsp_is_external(op->nbsp)) {
-        return;
-    }
-    if (!ls_has_localnet_port(op->od)) {
-        return;
-    }
-    /* Also set flags.localnet here.  The existing S_SWITCH_IN_LOOKUP_FDB
-     * lflow generated by build_lswitch_learn_fdb_op() sets
-     * flags.localnet = 1 only when inport == <localnet> at that table;
-     * once we have rewritten inport to the external LSP, that match no
-     * longer fires.  Copy the assignment into our rewrite action so
-     * downstream stages keyed on flags.localnet == 1 continue to work
-     * for the external LSP case. */
-    struct ovn_port *lp;
-    VECTOR_FOR_EACH (&op->od->localnet_ports, lp) {
-        for (size_t i = 0; i < op->n_lsp_addrs; i++) {
-            ds_clear(match);
-            ds_clear(actions);
-            ds_put_format(match, "inport == %s && eth.src == %s",
-                          lp->json_key, op->lsp_addrs[i].ea_s);
-            ds_put_format(actions,
-                          "flags.localnet = 1; inport = %s; next;",
-                          op->json_key);
-            ovn_lflow_add(lflows, op->od, S_SWITCH_IN_CHECK_PORT_SEC, 75,
-                          ds_cstr(match), ds_cstr(actions),
-                          op->lflow_ref);
-        }
-    }
-}
-
 /* Ingress table 30: Destination lookup, broadcast and multicast handling
  * (priority 70 - 100). */
 static void
@@ -13994,7 +13948,7 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths 
*lb_dps,
 
         build_lb_health_check_response_lflows(
             lflows, lb, lb_vip, &lb->vips_nb[i], lb_dps, lr_datapaths,
-            meter_groups, match);
+            meter_groups, match, action);
 
         if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
             continue;
@@ -20105,7 +20059,6 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct 
ovn_port *op,
                                              meter_groups, actions, match);
     build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
     build_lswitch_external_port(op, lflows);
-    build_lswitch_external_lsp_inport_rewrite(op, lflows, match, actions);
     build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
     build_lswitch_ip_unicast_lookup(op, lflows, actions,
                                     match);
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 810e9f2d7..484642b7a 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -1806,122 +1806,6 @@ OVN_CLEANUP_NORTHD
 AT_CLEANUP
 ])
 
-OVN_FOR_EACH_NORTHD_NO_HV_PARALLELIZATION([
-AT_SETUP([Load balancer health check reply lflow for type=external backend on 
localnet LS])
-ovn_start
-
-# Topology:
-#
-#   lr0 --(lr0-sw0)-- sw0 (regular tenant LS, 10.0.0.0/24)
-#                      `-- vm-port (type="", regular VIF backend)
-#
-#   lr0 --(lr0-prov)-- prov (provider LS with localnet)
-#                       |-- prov-localnet (type=localnet)
-#                       `-- bm-port (type=external, baremetal pool member)
-#
-# A baremetal LB pool member's LSP is type=external; replies to HM probes
-# re-enter br-int via the localnet port, so MFF_LOG_INPORT carries the
-# localnet LSP's tunnel_key and the original
-#   "inport == <bm-port> && ... ; handle_svc_check(inport);"
-# reply lflow never matches.  pinctrl_find_svc_monitor() is keyed on
-# (dp_key, port_key) where port_key = backend LSP's tunnel_key, so
-# MFF_LOG_INPORT must hold that tunnel_key when the controller op fires.
-#
-# Fix: install an inport-rewrite lflow at S_SWITCH_IN_CHECK_PORT_SEC
-# priority 75 keyed on (inport == <localnet> && eth.src == <bm_mac>)
-# which assigns:
-#   flags.localnet = 1; inport = "<bm-port>"; next;
-# Once that fires, every downstream stage (including the original
-# per-backend handle_svc_check lflow at S_SWITCH_IN_L2_LKUP and the
-# generic per-LS svc_monitor_mac lflow) sees inport == <bm-port> and
-# works without further modification.
-
-check ovn-nbctl lr-add lr0
-check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:01:01 10.0.0.1/24
-check ovn-nbctl lrp-add lr0 lr0-prov 00:00:00:00:02:01 10.0.50.1/24
-
-check ovn-nbctl ls-add sw0
-check ovn-nbctl --wait=sb lsp-add sw0 sw0-lr0 \
-  -- lsp-set-type sw0-lr0 router \
-  -- lsp-set-options sw0-lr0 router-port=lr0-sw0 \
-  -- lsp-set-addresses sw0-lr0 router
-check ovn-nbctl --wait=sb lsp-add sw0 vm-port \
-  -- lsp-set-addresses vm-port "00:00:00:00:01:02 10.0.0.10"
-
-check ovn-nbctl ls-add prov
-check ovn-nbctl --wait=sb lsp-add prov prov-lr0 \
-  -- lsp-set-type prov-lr0 router \
-  -- lsp-set-options prov-lr0 router-port=lr0-prov \
-  -- lsp-set-addresses prov-lr0 router
-check ovn-nbctl --wait=sb lsp-add prov prov-localnet \
-  -- lsp-set-type prov-localnet localnet \
-  -- lsp-set-options prov-localnet network_name=physnet1 \
-  -- lsp-set-addresses prov-localnet unknown
-check ovn-nbctl --wait=sb lsp-add prov bm-port \
-  -- lsp-set-type bm-port external \
-  -- lsp-set-addresses bm-port "00:00:00:00:02:0a 10.0.50.10"
-
-check ovn-sbctl chassis-add hv1 geneve 127.0.0.1
-check ovn-sbctl lsp-bind vm-port hv1
-check ovn-sbctl lsp-bind bm-port hv1
-
-# LB has both a regular-VIF backend on sw0 and a type=external backend on prov.
-check ovn-nbctl lb-add lb1 192.168.0.10:80 10.0.0.10:80,10.0.50.10:80 tcp
-check ovn-nbctl --wait=sb set load_balancer lb1 \
-  ip_port_mappings:10.0.0.10=vm-port:10.0.0.1
-check ovn-nbctl --wait=sb set load_balancer lb1 \
-  ip_port_mappings:10.0.50.10=bm-port:10.0.50.1
-
-check_uuid ovn-nbctl --wait=sb -- --id=@hc create Load_Balancer_Health_Check \
-  vip="192.168.0.10\:80" -- add Load_Balancer lb1 health_check @hc
-
-check ovn-nbctl lr-lb-add lr0 lb1
-check ovn-nbctl ls-lb-add sw0 lb1
-check ovn-nbctl ls-lb-add prov lb1
-check ovn-nbctl --wait=sb sync
-
-# Regular backend on sw0: original "inport == <vm-port>" / 
"handle_svc_check(inport);"
-# behavior unchanged.
-AT_CAPTURE_FILE([sw0_lflows])
-ovn-sbctl dump-flows sw0 | grep ls_in_l2_lkup | grep handle_svc_check \
-  > sw0_lflows
-AT_CHECK([cat sw0_lflows | ovn_strip_lflows], [0], [dnl
-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == 
$svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
-  table=??(ls_in_l2_lkup      ), priority=110  , match=(inport == "vm-port" && 
ip4.dst == 10.0.0.1 && ip4.src == 10.0.0.10 && eth.dst == 00:00:00:00:01:01 && 
tcp.src == 80), action=(handle_svc_check(inport);)
-])
-
-# type=external backend on prov (localnet LS): the per-backend reply
-# lflow keeps the ORIGINAL inport-based match because the
-# inport-rewrite at S_SWITCH_IN_CHECK_PORT_SEC has already substituted
-# MFF_LOG_INPORT to <bm-port> by the time the packet reaches L2_LKUP.
-AT_CAPTURE_FILE([prov_lflows])
-ovn-sbctl dump-flows prov | grep ls_in_l2_lkup | grep handle_svc_check \
-  > prov_lflows
-AT_CHECK([cat prov_lflows | ovn_strip_lflows], [0], [dnl
-  table=??(ls_in_l2_lkup      ), priority=110  , match=(eth.dst == 
$svc_monitor_mac && (tcp || icmp || icmp6)), action=(handle_svc_check(inport);)
-  table=??(ls_in_l2_lkup      ), priority=110  , match=(inport == "bm-port" && 
ip4.dst == 10.0.50.1 && ip4.src == 10.0.50.10 && eth.dst == 00:00:00:00:02:01 
&& tcp.src == 80), action=(handle_svc_check(inport);)
-])
-
-# v6 inport-rewrite lflow at ls_in_check_port_sec priority 75: turns
-# (inport == <prov-localnet> && eth.src == <bm_mac>) into
-# (flags.localnet = 1; inport = "<bm-port>"; next;)
-AT_CAPTURE_FILE([prov_rewrite_lflows])
-ovn-sbctl dump-flows prov | grep ls_in_check_port_sec \
-  | grep 'priority=75 ' | grep 'inport = ' \
-  > prov_rewrite_lflows
-AT_CHECK([cat prov_rewrite_lflows | ovn_strip_lflows], [0], [dnl
-  table=??(ls_in_check_port_sec), priority=75   , match=(inport == 
"prov-localnet" && eth.src == 00:00:00:00:02:0a), action=(flags.localnet = 1; 
inport = "bm-port"; next;)
-])
-
-# Delete the localnet port and verify the inport-rewrite lflow disappears.
-check ovn-nbctl --wait=sb lsp-del prov-localnet
-AT_CHECK([ovn-sbctl dump-flows prov | grep ls_in_check_port_sec \
-  | grep 'priority=75 ' | grep 'inport = '], [1])
-
-OVN_CLEANUP_NORTHD
-AT_CLEANUP
-])
-
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([Load balancer VIP in NAT entries])
 AT_SKIP_IF([test $HAVE_PYTHON = no])
diff --git a/tests/ovn.at b/tests/ovn.at
index 3ff535227..a88ea9650 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -21451,10 +21451,6 @@ grep controller | grep tp_src=546 | grep \
 check ovn-nbctl --wait=hv lsp-add-localnet-port ls1 ln-public phys
 
 ln_public_key=$(fetch_column Port_Binding tunnel_key logical_port=ln-public)
-# DHCP lflows for external ports now match on the external LSP's inport
-# (after the inport-rewrite at table 0), so the OF flow's reg14 value is
-# the external LSP's tunnel_key, not the localnet's.
-lp_ext1_key=$(fetch_column Port_Binding tunnel_key logical_port=ls1-lp_ext1)
 
 # The ls1-lp_ext1 should be bound to hv1 as only hv1 is part of the
 # ha chassis group.
@@ -21466,13 +21462,13 @@ wait_for_ports_up ls1-lp_ext1
 (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows
 as hv1 ovs-ofctl dump-flows br-int > brintflows
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
-grep controller | grep "0a.00.00.06" | grep reg14=0x$lp_ext1_key | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
 wc -l], [0], [1
 ])
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
 grep controller | grep tp_src=546 | grep \
 "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$lp_ext1_key | wc -l], [0], [1
+grep reg14=0x$ln_public_key | wc -l], [0], [1
 ])
 
 # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
@@ -21716,16 +21712,15 @@ ovn-sbctl find port_binding logical_port=ls1-lp_ext1
 wait_row_count Port_Binding 1 logical_port=ls1-lp_ext1 chassis=$hv2_uuid
 wait_for_ports_up ls1-lp_ext1
 
-# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2.
-# reg14 carries the external LSP's tunnel_key (after inport rewrite).
+# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \
-grep controller | grep "0a.00.00.06" | grep reg14=0x$lp_ext1_key | \
+grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key | \
 wc -l], [0], [1
 ])
 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \
 grep controller | grep tp_src=546 | grep \
 "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$lp_ext1_key | wc -l], [0], [1
+grep reg14=0x$ln_public_key | wc -l], [0], [1
 ])
 
 # There should be no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
@@ -21735,7 +21730,7 @@ grep controller | grep "0a.00.00.06" | wc -l], [0], [0
 AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | \
 grep controller | grep tp_src=546 | grep \
 "ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
-grep reg14=0x$lp_ext1_key | wc -l], [0], [0
+grep reg14=0x$ln_public_key | wc -l], [0], [0
 ])
 
 # Send DHCPDISCOVER again for hv1/ext1. The DHCP response should come from
@@ -46641,3 +46636,101 @@ OVN_CHECK_PACKETS_CONTAIN([hv/br-phys_n1-tx.pcap], 
[expected_nd_ns])
 OVN_CLEANUP([hv])
 AT_CLEANUP
 ])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Traffic from external port not looped back to localnet])
+
+ovn_start
+CHECK_SCAPY()
+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 set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
+# Topology:
+# A logical switch with mcast_snoop enabled, a localnet port (sw-ln),
+# a regular VIF port (sw-p1) to make the datapath local, and an
+# external port (sw-ext) representing a baremetal node.
+
+check ovn-nbctl ls-add sw              \
+    -- lsp-add sw sw-ln                \
+    -- lsp-set-type sw-ln localnet     \
+    -- lsp-set-addresses sw-ln unknown \
+    -- lsp-set-options sw-ln network_name=phys mcast_flood_reports=true
+
+check ovn-nbctl lsp-add sw sw-p1 \
+    -- lsp-set-addresses sw-p1 "00:00:00:00:00:10 10.0.0.10"
+
+check ovn-nbctl lsp-add sw sw-ext   \
+    -- lsp-set-type sw-ext external \
+    -- lsp-set-addresses sw-ext "50:54:00:00:00:01"
+
+check ovn-nbctl set logical_switch sw \
+    other_config:mcast_snoop=true     \
+    other_config:mcast_querier=false
+
+check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+    set interface hv1-vif1 external-ids:iface-id=sw-p1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap
+
+check ovn-sbctl lsp-bind sw-ext hv1
+
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([grep pinctrl hv1/ovn-controller.log | grep -c connected])
+
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+
+# Unicast unknown.  The destination MAC is not known to any LSP so
+# the packet hits S_SWITCH_IN_L2_UNKNOWN and gets flooded to
+# MC_UNKNOWN (which includes the localnet port).
+packet=$(fmt_pkt \
+    "Ether(dst='aa:bb:cc:dd:ee:ff', src='50:54:00:00:00:01') / \
+     IP(src='10.0.0.1', dst='10.0.0.99') / \
+     UDP(sport=1234, dport=5678) / Raw(b'test')")
+check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
+
+# MLDv2 report.  Simulates a baremetal node sending an MLD report
+# during PXE boot via the provisioning network.
+packet=$(fmt_pkt \
+    "Ether(dst='33:33:00:00:00:16', src='50:54:00:00:00:01') / \
+     IPv6(src='fe80::1', dst='ff02::16', hlim=1) / \
+     IPv6ExtHdrHopByHop(options=RouterAlert(value=0)) / \
+     ICMPv6MLReport2(records=ICMPv6MLDMultAddrRec(rtype=4, \
+         dst='ff0a:dead:beef::1'))")
+check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
+
+# IGMPv3 report.  Same scenario but with IPv4 multicast.
+packet=$(fmt_pkt \
+    "Ether(dst='01:00:5e:00:00:16', src='50:54:00:00:00:01') / \
+     IP(src='10.0.0.1', dst='224.0.0.22', ttl=1, \
+        options=IPOption_Router_Alert()) / \
+     IGMPv3(type=0x22) / \
+     IGMPv3mr(records=IGMPv3gr(rtype=4, maddr='239.1.2.3'))")
+check as hv1 ovs-appctl netdev-dummy/receive br-phys_n1 $packet
+
+# Wait for pinctrl to fully process the IGMP/MLD reports and sync them
+# to the SB database.  This guarantees that pinctrl has also re-injected
+# the reports for flooding to mrouters (where the loopback would occur).
+# The unicast unknown packet (sent first) was processed synchronously in
+# the datapath, so it is certainly done by now.
+wait_row_count IGMP_Group 1 address=239.1.2.3
+wait_row_count IGMP_Group 1 address='"ff0a:dead:beef::1"'
+
+# None of the packets must have been sent back to the localnet port.
+AT_CHECK([$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" \
+    hv1/br-phys_n1-tx.pcap > tx_packets.txt])
+AT_CHECK([test ! -s tx_packets.txt])
+
+# Check if ovs did prevent any loop.
+AT_CHECK([grep "skipping output to input port" \
+    hv1/ovs-vswitchd.log], [1])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
diff --git a/tests/scapy-server.py b/tests/scapy-server.py
index e9f2429ee..004a49265 100755
--- a/tests/scapy-server.py
+++ b/tests/scapy-server.py
@@ -11,6 +11,7 @@ import binascii
 from socket import *  # noqa: F401,F403
 from scapy.all import *  # noqa: F401,F403
 from scapy.all import raw
+from scapy.contrib.igmpv3 import *  # noqa: F401,F403
 
 
 vlog = ovs.vlog.Vlog("scapy-server")
-- 
2.55.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to