A Network Function (NF) in inline mode redirects matched traffic
through a service VM. When the redirected traffic is IP
unknown-unicast (destination MAC not yet in the FDB), the packet
coming back from the NF is re-flooded by the switch, because the
destination MAC is still unknown. That re-flood produces a copy on
the same port the packet originally arrived on, causing MAC flaps and
potential L2 loops.
Following are the example packet flows.
Example 1, VLAN switch (MAC flap):
Topology
VM1, VM2 and the NF are on the same logical switch (LS), which is
VLAN-backed (localnet port on every node). VM1 is on N1, VM2 on
N2 (its port has "unknown" in addresses and a to-lport ACL that
redirects to the inline NF on N3).
Flow
1. VM1 sends pkt to dst MAC X (not in FDB).
2. The LS floods the pkt; on N1 the copy exits the localnet port
and the TOR floods it to N2.
3. On N2 the pkt ingresses on localnet; the LS floods it and the
copy reaches VM2 (unknown-addr).
4. The ACL redirects the pkt to the NF on N3; the NF returns it
to N2.
5. X is still not in the FDB, so the LS floods again; on N2 one
copy exits the localnet port.
Result
The TOR now sees VM1's source MAC on N2's port, but it had just
learned VM1's MAC on N1's port, so the MAC flaps on the TOR.
Example 2, VLAN switch with two protected VMs (loop):
Topology
As above, plus VM3 (on N3, same LS) also has "unknown" in
addresses and is also NF-protected.
Flow
1-5 as above; in parallel, the original flood also reaches N3 via
the TOR, where it is redirected to N3's NF, returns, and the
LS re-floods it out N3's localnet port.
6. N3's re-flood reaches N2 via the TOR; the LS floods it on N2,
the copy hits VM2 (unknown-addr), is redirected to the NF, and
re-floods out N2's localnet port.
7. That re-flood reaches N3 via the TOR; the LS floods it on N3,
the copy hits VM3 (also unknown-addr), is redirected to the
NF, re-floods out localnet, ...
Result
The pkt keeps bouncing between N2 and N3 via the TOR, i.e. an
L2 loop, on top of continuous MAC flaps. The loop persists as
long as X stays unknown.
Example 3, overlay switch (copy reflected to source port):
Topology
VM1, VM2 and the NF are on the same LS and all on N1. VM1 and
VM2 both have "unknown" in addresses; VM2's to-lport ACL
redirects to the inline NF.
Flow
1. VM1 sends pkt to dst MAC X (not in FDB).
2. The LS floods to MC_UNKNOWN members on N1; one copy goes to
VM2.
3. VM2's ACL redirects its copy to the NF; the NF returns it to
N1.
4. X is still not in the FDB, so the LS floods to MC_UNKNOWN
again; one copy is headed back out VM1's own port.
Result
VM1 receives a copy of the packet it just sent (reflected to the
source port).
Fix:
Use the nf_learn_orig_src_port() / nf_lookup_orig_src_port() actions
from the previous commit to remember the original ingress port and
drop the copy if it is about to be sent back out of that port.
- Add a logical flag flags.inport_in_mc_unknown (bit 26) that
marks packets entering on an MC_UNKNOWN-member port (i.e. one
with "unknown" in addresses and receive_multicast not disabled,
the only ports that can both originate and receive
unknown-unicast floods). northd sets it in ls_in_lookup_fdb,
piggybacking on the existing FDB-learn or flags.localnet flow
where possible, and emits a dedicated priority-50 flow for the
remaining MC_UNKNOWN members (ex: ports where
lsp_learn_fdb=false).
- In the NF redirect stage (ls_in_nf for a from-lport ACL,
ls_out_nf for a to-lport ACL), on a switch with an inline NF group
on an ACL, learn the original ingress port
(nf_learn_orig_src_port()) for unicast IP packets that entered on
an MC_UNKNOWN-member port (flags.inport_in_mc_unknown set). The
flag is set in ls_in_lookup_fdb and carried in MFF_LOG_FLAGS into
the egress pipeline, so it is still available in ls_out_nf. Two
flows per IP version:
* Priority 100: learn, then redirect to the NF, when the ACL
selected the packet for redirect. The existing NF-port and
multicast skip flows move up to priority 110 so multicast is
skipped before this flow, which therefore needs no !eth.mcast
match.
* Priority 50 (overlay switches only, in ls_in_nf): learn, then
continue to the next stage, when the ACL did not redirect the
packet (REGBIT_NF_ENABLED == 0). On an overlay switch the
redirecting port and the source port can be on different nodes,
so the priority-100 learn would land on a different node than
the lookup; learning here, on the source port's own ingress
node, keeps them co-located. VLAN-backed switches do not need
this: the re-flood returns to the redirecting node, where the
priority-100 learn already ran.
- On the post-NF return path run
REGBIT_NF_LOOKUP_HIT = nf_lookup_orig_src_port() on both NF
ports. The existing ls_out_pre_acl skip-stages flow does it for
the input_port (priority bumped 110->115 to win over the
priority-110 conntrack skip flow); a new priority-2 flow in
ls_out_nf does it for the output_port (packets redirected from
the ingress pipeline, e.g. a from-lport request re-flooded after
the NF, doing a fresh egress after conntrack).
- A new priority-110 flow in ls_out_check_port_sec drops packets
with REGBIT_NF_LOOKUP_HIT == 1, i.e. the copies about to be
sent back out of the port they originally arrived on.
On VLAN-backed switches the re-flood returns to the redirecting node
and egresses the shared localnet port, so the learn and the lookup
always run on that node: dropping the copy headed back out that port
removes both the MAC flap and the underlay L2 loop.
On overlay switches the copy headed back to the source port is always
dropped, as in example 3: the learn runs on the source port's ingress
and the lookup on its egress, both on the source port's node.
All new flows are gated on the switch having an inline NF group on
an ACL.
Signed-off-by: Naveen Yerramneni <[email protected]>
Acked-by: Aditya Mehakare <[email protected]>
Fixes: 8e2d6fa14804 ("northd, tests: Network Function insertion logical flow
programming.")
CC: Sragdhara Datta Chaudhuri <[email protected]>
---
NEWS | 3 +
include/ovn/logical-fields.h | 9 +
lib/logical-fields.c | 5 +
northd/northd.c | 257 ++++++++++++++++++-----
northd/northd.h | 19 ++
ovn-sb.xml | 8 +
tests/ovn-northd.at | 393 +++++++++++++++++++++++++++++------
tests/ovn.at | 114 +++++++++-
8 files changed, 690 insertions(+), 118 deletions(-)
diff --git a/NEWS b/NEWS
index 748ae30eb..8d930d49d 100644
--- a/NEWS
+++ b/NEWS
@@ -33,6 +33,9 @@ Post v26.03.0
The DHCP and unbound-router ARP/ND drop lflows for external
ports were updated to key on the external LSP's inport
accordingly.
+ - Fixed MAC flaps and L2 loops caused by inline Network_Function
+ redirection of unknown-unicast IP traffic, where the copy returning
+ from the NF could be re-flooded out of the original ingress port.
OVN v26.03.0 - xxx xx xxxx
--------------------------
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index 54465bc2e..3ca49f808 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -150,6 +150,7 @@ enum mff_log_flags_bits {
MLF_PKT_SAMPLED_BIT = 23,
MLF_RECIRC_BIT = 24,
MLF_NF_LOOKUP_HIT_BIT = 25,
+ MLF_INPORT_IN_MC_UNKNOWN_BIT = 26,
MLF_NETWORK_ID_START_BIT = 28,
MLF_NETWORK_ID_END_BIT = 31,
};
@@ -232,6 +233,14 @@ enum mff_log_flags {
* MAC flaps / L2 loops after network function redirection. */
MLF_NF_LOOKUP_HIT = (1 << MLF_NF_LOOKUP_HIT_BIT),
+ /* Indicate that the packet entered the logical switch on a port that
+ * is a member of MC_UNKNOWN (i.e. the port has "unknown" in its
+ * addresses and "receive_multicast" is not disabled, so it can both
+ * receive and originate unknown-unicast floods). Used by the inline
+ * network function loop prevention to learn the original source port
+ * before redirection. */
+ MLF_INPORT_IN_MC_UNKNOWN = (1 << MLF_INPORT_IN_MC_UNKNOWN_BIT),
+
/* Assign network ID to packet to choose correct network for snat when
* lb_force_snat_ip=router_ip. */
MLF_NETWORK_ID = (OVN_MAX_NETWORK_ID << MLF_NETWORK_ID_START_BIT),
diff --git a/lib/logical-fields.c b/lib/logical-fields.c
index 807bb4db4..a2c835b1c 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -181,6 +181,11 @@ ovn_init_symtab(struct shash *symtab)
snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_PKT_SAMPLED_BIT);
expr_symtab_add_subfield(symtab, "flags.pkt_sampled", NULL, flags_str);
+ snprintf(flags_str, sizeof flags_str, "flags[%d]",
+ MLF_INPORT_IN_MC_UNKNOWN_BIT);
+ expr_symtab_add_subfield(symtab, "flags.inport_in_mc_unknown", NULL,
+ flags_str);
+
/* Connection tracking state. */
expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
WR_CT_COMMIT);
diff --git a/northd/northd.c b/northd/northd.c
index f5aa5cca3..03484fb3f 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -176,6 +176,10 @@ static bool vxlan_mode;
#define REGBIT_NF_ENABLED "reg8[21]"
#define REGBIT_NF_ORIG_DIR "reg8[22]"
#define REGBIT_NF_EGRESS_LOOPBACK "reg8[23]"
+/* Set when a packet returning from an inline network function is about to
+ * be sent back out of the port it originally arrived on; such loopback
+ * copies are dropped. */
+#define REGBIT_NF_LOOKUP_HIT "reg8[24]"
/* Register to store the network function group id */
#define REG_NF_GROUP_ID "reg0[22..29]"
/* REG_NF_ID overrides REG_NF_GROUP_ID in the pre_network_function stage. */
@@ -314,6 +318,8 @@ static const char *reg_ct_state[] = {
* | | REGBIT_NF_{ENABLED/ORIG_DIR/ | G |
|
* | | EGRESS_LOOPBACK} | 4 |
|
* | | (>= ACL_EVAL* && <= NF*) | |
|
+ * | | REGBIT_NF_LOOKUP_HIT | |
|
+ * | | (>= OUT_PRE_ACL && <= OUT_CHECK_PORT_SEC)| |
|
* +----+----------------------------------------------+
+-----------------------------------+
* | R9 | OBS_POINT_ID_EST | |
|
* | | (>= ACL_EVAL* && <= ACL_ACTION*) | |
|
@@ -6306,6 +6312,19 @@ build_lswitch_port_sec_op(struct ovn_port *op, struct
lflow_table *lflows,
}
}
+/* True if 'op' emits the FDB-learn (LOOKUP_FDB / PUT_FDB) flows. */
+static bool
+lsp_emits_fdb_learn_lflow(const struct ovn_port *op)
+{
+ if (op->lsp_has_port_sec || !op->has_unknown) {
+ return false;
+ }
+ return lsp_is_remote(op->nbsp)
+ || (!strcmp(op->nbsp->type, "") && lsp_can_learn_mac(op->nbsp))
+ || lsp_is_switch(op->nbsp)
+ || (lsp_is_localnet(op->nbsp) && localnet_can_learn_mac(op->nbsp));
+}
+
static void
build_lswitch_learn_fdb_op(
struct ovn_port *op, struct lflow_table *lflows,
@@ -6313,36 +6332,35 @@ build_lswitch_learn_fdb_op(
{
ovs_assert(op->nbsp);
- if (op->lsp_has_port_sec || !op->has_unknown) {
+ if (!lsp_emits_fdb_learn_lflow(op)) {
return;
}
bool remote = lsp_is_remote(op->nbsp);
- if (remote || (!strcmp(op->nbsp->type, "") && lsp_can_learn_mac(op->nbsp))
- || lsp_is_switch(op->nbsp)
- || (lsp_is_localnet(op->nbsp) && localnet_can_learn_mac(op->nbsp))) {
- ds_clear(match);
- ds_clear(actions);
- ds_put_format(match, "inport == %s", op->json_key);
- if (lsp_is_localnet(op->nbsp)) {
- ds_put_cstr(actions, "flags.localnet = 1; ");
- }
- ds_put_format(actions, REGBIT_LKUP_FDB
- " = lookup_fdb(inport, eth.src); next;");
- ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_LOOKUP_FDB
- : S_SWITCH_IN_LOOKUP_FDB,
- 100, ds_cstr(match), ds_cstr(actions), op->lflow_ref,
- WITH_IO_PORT(op->key), WITH_HINT(&op->nbsp->header_));
-
- ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
- ds_clear(actions);
- ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
- ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_PUT_FDB
- : S_SWITCH_IN_PUT_FDB,
- 100, ds_cstr(match), ds_cstr(actions), op->lflow_ref,
- WITH_IO_PORT(op->key), WITH_HINT(&op->nbsp->header_));
+ ds_clear(match);
+ ds_clear(actions);
+ ds_put_format(match, "inport == %s", op->json_key);
+ if (lsp_is_localnet(op->nbsp)) {
+ ds_put_cstr(actions, "flags.localnet = 1; ");
}
+ if (!remote && lsp_is_mc_unknown_ingress_member(op)) {
+ ds_put_cstr(actions, "flags.inport_in_mc_unknown = 1; ");
+ }
+ ds_put_format(actions, REGBIT_LKUP_FDB
+ " = lookup_fdb(inport, eth.src); next;");
+ ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_LOOKUP_FDB
+ : S_SWITCH_IN_LOOKUP_FDB,
+ 100, ds_cstr(match), ds_cstr(actions), op->lflow_ref,
+ WITH_IO_PORT(op->key), WITH_HINT(&op->nbsp->header_));
+
+ ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
+ ds_clear(actions);
+ ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
+ ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_PUT_FDB
+ : S_SWITCH_IN_PUT_FDB,
+ 100, ds_cstr(match), ds_cstr(actions), op->lflow_ref,
+ WITH_IO_PORT(op->key), WITH_HINT(&op->nbsp->header_));
}
static void
@@ -10354,20 +10372,52 @@ build_arp_nd_service_monitor_lflow(const char
*svc_monitor_mac,
/* Ingress table: Lookup FDB. Set flags.localnet for packets arriving from
* localnet ports so that downstream stages (e.g., ARP/ND responder) can
- * condition their behavior on whether the packet came from localnet. */
+ * condition their behavior on whether the packet came from localnet. Also
+ * set flags.inport_in_mc_unknown for MC_UNKNOWN-member localnet ports. */
static void
build_lswitch_from_localnet_op(struct ovn_port *op,
struct lflow_table *lflows,
- struct ds *match)
+ struct ds *actions, struct ds *match)
{
ovs_assert(op->nbsp);
if (!lsp_is_localnet(op->nbsp)) {
return;
}
ds_clear(match);
+ ds_clear(actions);
ds_put_format(match, "inport == %s", op->json_key);
+ ds_put_cstr(actions, "flags.localnet = 1; ");
+ if (lsp_is_mc_unknown_ingress_member(op)) {
+ ds_put_cstr(actions, "flags.inport_in_mc_unknown = 1; ");
+ }
+ ds_put_cstr(actions, "next;");
ovn_lflow_add(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 50,
- ds_cstr(match), "flags.localnet = 1; next;",
+ ds_cstr(match), ds_cstr(actions),
+ op->lflow_ref, WITH_IO_PORT(op->key),
+ WITH_HINT(&op->nbsp->header_));
+}
+
+/* Ingress table: Lookup FDB. Set flags.inport_in_mc_unknown for
+ * MC_UNKNOWN-member ports not already covered by the FDB-learn or
+ * localnet flows. */
+static void
+build_lswitch_set_inport_in_mc_unknown_op(struct ovn_port *op,
+ struct lflow_table *lflows,
+ struct ds *match)
+{
+ ovs_assert(op->nbsp);
+ if (!lsp_is_mc_unknown_ingress_member(op)) {
+ return;
+ }
+ if (lsp_emits_fdb_learn_lflow(op) || lsp_is_localnet(op->nbsp)) {
+ return;
+ }
+
+ ds_clear(match);
+ ds_put_format(match, "inport == %s", op->json_key);
+ ovn_lflow_add(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 50,
+ ds_cstr(match),
+ "flags.inport_in_mc_unknown = 1; next;",
op->lflow_ref, WITH_IO_PORT(op->key),
WITH_HINT(&op->nbsp->header_));
}
@@ -19167,6 +19217,61 @@ network_function_configure_fail_open_flows(struct
lflow_table *lflows,
ds_destroy(&match);
}
+/* Emit IPv4 and IPv6 nf_learn_orig_src_port() flows for flagged IP packets.
+ * 'extra_match' is ANDed onto the match if non-NULL; 'action_suffix' runs
+ * after the learn. */
+static void
+build_nf_learn_orig_src_port_flows(struct lflow_table *lflows,
+ const struct ovn_datapath *od,
+ const struct ovn_stage *stage,
+ uint16_t priority,
+ const char *extra_match,
+ const char *action_suffix,
+ struct lflow_ref *lflow_ref)
+{
+ for (int ipv6 = 0; ipv6 <= 1; ipv6++) {
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds action = DS_EMPTY_INITIALIZER;
+
+ ds_put_cstr(&match, ipv6 ? "ip6" : "ip4");
+ ds_put_cstr(&match, " && flags.inport_in_mc_unknown == 1");
+ if (extra_match) {
+ ds_put_format(&match, " && %s", extra_match);
+ }
+ ds_put_format(&action, "nf_learn_orig_src_port(ipv6 = %s); %s",
+ ipv6 ? "true" : "false", action_suffix);
+ ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref);
+ ds_destroy(&match);
+ ds_destroy(&action);
+ }
+}
+
+/* Emit a post-NF nf_lookup_orig_src_port() flow for packets re-entering on
+ * 'port'. A hit sets REGBIT_NF_LOOKUP_HIT, which ls_out_check_port_sec
+ * drops. 'action_suffix' runs after the lookup. */
+static void
+build_nf_lookup_orig_src_port_flow(struct lflow_table *lflows,
+ const struct ovn_datapath *od,
+ const struct ovn_stage *stage,
+ uint16_t priority,
+ const struct ovn_port *port,
+ const char *action_suffix,
+ struct lflow_ref *lflow_ref)
+{
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds action = DS_EMPTY_INITIALIZER;
+
+ ds_put_format(&match, "inport == %s", port->json_key);
+ ds_put_format(&action,
+ REGBIT_NF_LOOKUP_HIT " = nf_lookup_orig_src_port(); %s",
+ action_suffix);
+ ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref);
+ ds_destroy(&match);
+ ds_destroy(&action);
+}
+
static void
consider_network_function(struct lflow_table *lflows,
const struct ovn_datapath *od,
@@ -19272,6 +19377,15 @@ consider_network_function(struct lflow_table *lflows,
(uint8_t) nf->id);
ovn_lflow_add(lflows, od, fwd_stage, 99, ds_cstr(&match),
ds_cstr(&action), lflow_ref);
+
+ /* Priority 100 flows in fwd_stage:
+ * Same as the priority-99 redirect above, but learn the original source
+ * port first (for flagged IP packets). Multicast is already skipped by
+ * the higher-priority (110) flow below, so it is not matched here. */
+ build_nf_learn_orig_src_port_flows(lflows, od, fwd_stage, 100,
+ ds_cstr(&match), ds_cstr(&action),
+ lflow_ref);
+
ds_clear(&match);
ds_clear(&action);
@@ -19309,53 +19423,62 @@ consider_network_function(struct lflow_table *lflows,
ds_clear(&match);
ds_clear(&action);
- /* Priority 100 flow in in_nf:
+ /* Priority 110 flow in in_nf:
* Allow packets to go through if coming from network-function port as
* we don't want the packets to be redirected again based on from-lport
* match.
*/
ds_put_format(&match, "inport == %s", input_port->json_key);
ds_put_format(&action, REG_TUN_OFPORT" = ct_label.tun_if_id; next;");
- ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
ds_cstr(&match), ds_cstr(&action), lflow_ref);
ds_clear(&match);
ds_put_format(&match, "inport == %s", output_port->json_key);
- ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
ds_cstr(&match), ds_cstr(&action), lflow_ref);
ds_clear(&match);
ds_clear(&action);
- /* Priority 100 flow in out_nf:
+ /* Priority 110 flow in out_nf:
* Allow packets to go through if outport is network-function port as
* we don't want the packets to be redirected again based on to-lport
* match.
*/
ds_put_format(&match, "outport == %s", input_port->json_key);
ds_put_format(&action, "next;");
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
ds_cstr(&match), ds_cstr(&action), lflow_ref);
ds_clear(&match);
ds_put_format(&match, "outport == %s", output_port->json_key);
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
ds_cstr(&match), ds_cstr(&action), lflow_ref);
ds_clear(&match);
ds_clear(&action);
- /* For packets redirected from egress pipleline to the NF, when they come
- * out from the other NF port, we don't want to process them again through
- * egress stages they already went through, especially not again through
- * conntrack as these packets are already accounted for there. Hence we
- * need to skip the initial pipeline stages for such packets and directly
- * start from the NF table. The packets that fall under this category are
- * the response packets from NF for from-lport ACLs and request packets
- * received from NF for to-lport ACLs. */
- ds_put_format(&match, "inport == %s", input_port->json_key);
+ /* Priority 115 flow in out_pre_acl (input_port):
+ * Extend the existing input_port skip-stages flow (skip the NF
+ * table to avoid re-running conntrack) so the same flow also runs
+ * nf_lookup_orig_src_port(). On hit, ls_out_check_port_sec drops
+ * the copy heading back to its source port.
+ *
+ * Priority 115 to take precedence over the priority-110 conntrack
+ * skip flow. */
ds_put_format(&action, "next(pipeline=egress, table=%d);",
- (ovn_stage_get_table(S_SWITCH_OUT_NF) + 1));
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, ds_cstr(&match),
- ds_cstr(&action), lflow_ref);
+ ovn_stage_get_table(S_SWITCH_OUT_NF) + 1);
+ build_nf_lookup_orig_src_port_flow(lflows, od, S_SWITCH_OUT_PRE_ACL, 115,
+ input_port, ds_cstr(&action),
+ lflow_ref);
+ ds_clear(&action);
+
+ /* Priority 2 flow in out_nf (output_port):
+ * A post-NF packet re-entering here is doing a fresh egress and has not
+ * run the egress stages, so run the lookup here. A hit means it is
+ * heading back out the port it arrived on, and ls_out_check_port_sec
+ * drops it. */
+ build_nf_lookup_orig_src_port_flow(lflows, od, S_SWITCH_OUT_NF, 2,
+ output_port, "next;", lflow_ref);
/* Priority 120 flows in out_stateful:
* If packet was received on a tunnel interface and being forwarded to a
@@ -19376,6 +19499,7 @@ build_network_function(const struct ovn_datapath *od,
{
unsigned long *nfg_ingress_bitmap = bitmap_allocate(MAX_OVN_NF_GROUP_IDS);
unsigned long *nfg_egress_bitmap = bitmap_allocate(MAX_OVN_NF_GROUP_IDS);
+ bool has_nfg = false;
/* This flow matches packets injected from out_nf stage -
* after it sets the outport - back to in_l2_lkup stage. This rule must be
@@ -19403,13 +19527,14 @@ build_network_function(const struct ovn_datapath *od,
REGBIT_NF_ENABLED" == 1 && " REGBIT_NF_ORIG_DIR" == 1",
REG_NF_ID" = 0; next;", lflow_ref);
- /* Ingress and Egress NF Table (Priority 100): ACL stage determined these
+ /* Ingress and Egress NF Table (Priority 110): ACL stage determined these
* packets should be redirected, but these are multicast/broadcast
- * packets which can cause L2 loop if redirected to NF. */
- ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
+ * packets which can cause L2 loop if redirected to NF. Higher priority
+ * than the redirect/learn flows so they skip both. */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
REGBIT_NF_ENABLED" == 1 && eth.mcast",
"next;", lflow_ref);
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
REGBIT_NF_ENABLED" == 1 && eth.mcast",
"next;", lflow_ref);
@@ -19444,6 +19569,7 @@ build_network_function(const struct ovn_datapath *od,
continue;
}
nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
+ has_nfg = true;
consider_network_function(lflows, od, acl->network_function_group,
ingress, lflow_ref);
}
@@ -19469,6 +19595,7 @@ build_network_function(const struct ovn_datapath *od,
continue;
}
nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
+ has_nfg = true;
consider_network_function(lflows, od,
acl->network_function_group,
ingress, lflow_ref);
@@ -19476,6 +19603,35 @@ build_network_function(const struct ovn_datapath *od,
}
}
}
+
+ if (has_nfg) {
+ /* Drop flow for loopback duplicates that the post-NF lookup
+ * marked via REGBIT_NF_LOOKUP_HIT. The lookup runs on both NF
+ * return ports (ls_out_pre_acl for input_port, ls_out_nf for
+ * output_port) and is installed by consider_network_function(). */
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_CHECK_PORT_SEC, 110,
+ REGBIT_NF_LOOKUP_HIT " == 1", debug_drop_action(),
+ lflow_ref);
+
+ /* Priority 50 flows in in_nf, overlay switches only:
+ * Learn the ingress port of a flagged unicast IP packet this ACL did
+ * not redirect to an NF (REGBIT_NF_ENABLED == 0), so a post-NF copy
+ * reflected back to that port can be dropped. On overlay switches
+ * the redirect may run on a different node than the source port, so
+ * the priority-100 redirect-path learn would land on the wrong node;
+ * learning here keeps it on the source port's node, co-located with
+ * the lookup. VLAN-backed switches don't need this: the re-flood
+ * returns to the redirecting node, where the 100 learn already ran.
+ * Unlike the redirect-path learn, no higher-priority flow skips
+ * multicast here, so exclude it in the match. */
+ if (!ls_has_localnet_port(od)) {
+ build_nf_learn_orig_src_port_flows(
+ lflows, od, S_SWITCH_IN_NF, 50,
+ "!eth.mcast && " REGBIT_NF_ENABLED" == 0",
+ "next;", lflow_ref);
+ }
+ }
+
bitmap_free(nfg_ingress_bitmap);
bitmap_free(nfg_egress_bitmap);
}
@@ -19623,7 +19779,8 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct
ovn_port *op,
build_mirror_lflows(op, ls_ports, lflows);
build_lswitch_port_sec_op(op, lflows, actions, match);
build_lswitch_learn_fdb_op(op, lflows, actions, match);
- build_lswitch_from_localnet_op(op, lflows, match);
+ build_lswitch_from_localnet_op(op, lflows, actions, match);
+ build_lswitch_set_inport_in_mc_unknown_op(op, lflows, match);
build_lswitch_arp_nd_responder_known_ips(op, lflows, ls_ports,
meter_groups, actions, match);
build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
diff --git a/northd/northd.h b/northd/northd.h
index 726a416e4..20dac8ea7 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -1120,6 +1120,25 @@ lsp_can_learn_mac(const struct nbrec_logical_switch_port
*nbsp)
return smap_get_bool( ->options, "lsp_learn_fdb", true);
}
+/* True if 'op' can receive unknown-unicast floods, i.e. it has 'unknown'
+ * among its addresses and "receive_multicast" is not disabled. */
+static inline bool
+lsp_can_receive_unknown_flood(const struct ovn_port *op)
+{
+ return op->nbsp
+ && op->has_unknown
+ && lsp_can_receive_multicast(op->nbsp);
+}
+
+/* True if 'op' is a local MC_UNKNOWN-member ingress port (excludes
+ * remote ports). Used to set flags.inport_in_mc_unknown. */
+static inline bool
+lsp_is_mc_unknown_ingress_member(const struct ovn_port *op)
+{
+ return lsp_can_receive_unknown_flood(op)
+ && strcmp(op->nbsp->type, "remote");
+}
+
const char *lrp_find_member_ip(const struct ovn_port *op, const char *ip_s);
/* This function returns true if 'op' is a gateway router port.
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 526699740..64d08b0d6 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -1786,6 +1786,14 @@
otherwise cause MAC address flaps or L2 loops.
</p>
+ <p>
+ northd emits the learn action only for packets entering on a port
+ that is a member of the <code>MC_UNKNOWN</code> multicast group
+ (gated by <code>flags.inport_in_mc_unknown</code>, set in the
+ <code>ls_in_lookup_fdb</code> stage), since only such packets can
+ be re-flooded by the NF.
+ </p>
+
<p>
<b>Example:</b>
<code>nf_learn_orig_src_port(ipv6 = false);</code>
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 7f4a88d4e..adac72729 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -7875,7 +7875,7 @@ AT_CAPTURE_FILE([S1flows])
#Verify the flows for default port type (VM port)
AT_CHECK([grep -e "ls_in_l2_lkup.*S1-vm1" S1flows | grep -e "match=(eth.dst ==
50:54:00:00:00:01)"], [1], [])
AT_CHECK([grep -e "ls_in_.*_fdb.*S1-vm1" S1flows | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-vm1"),
action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-vm1"),
action=(flags.inport_in_mc_unknown = 1; reg0[[11]] = lookup_fdb(inport,
eth.src); next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport == "S1-vm1" &&
reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
])
@@ -7951,15 +7951,19 @@ AT_CAPTURE_FILE([S1flows])
dnl Check that S2-vm address is not known on S1 and the forwarding to
dnl _MC_unknown group is configured.
-AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown" S1flows | ovn_strip_lflows],
[0], [dnl
+AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|ls_in_l2_unknown" S1flows |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst ==
50:54:00:00:00:01), action=(outport = "S1-vm"; output;)
table=??(ls_in_l2_unknown ), priority=0 , match=(1), action=(output;)
table=??(ls_in_l2_unknown ), priority=50 , match=(outport == "none"),
action=(outport = "_MC_unknown"; output;)
])
dnl Check that FDB learning is enabled for the switch port.
+dnl The port type "switch" implicitly carries unknown addresses and thus
+dnl can receive unknown-unicast floods, so flags.inport_in_mc_unknown is
+dnl also set (consumed by inline NF redirection in ls_in_nf; harmless
+dnl otherwise).
AT_CHECK([grep -E "ls_.*fdb.*S1-" S1flows | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-S2"),
action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-S2"),
action=(flags.inport_in_mc_unknown = 1; reg0[[11]] = lookup_fdb(inport,
eth.src); next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport == "S1-S2" &&
reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
])
@@ -7968,7 +7972,7 @@ AT_CAPTURE_FILE([S2flows])
dnl Check that S1-vm address is not known on S2 and the forwarding to
dnl _MC_unknown group is configured.
-AT_CHECK([grep -E "ls_in_l2_lkup.*S2-|unknown" S2flows | ovn_strip_lflows],
[0], [dnl
+AT_CHECK([grep -E "ls_in_l2_lkup.*S2-|ls_in_l2_unknown" S2flows |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst ==
50:54:00:00:00:02), action=(outport = "S2-vm"; output;)
table=??(ls_in_l2_unknown ), priority=0 , match=(1), action=(output;)
table=??(ls_in_l2_unknown ), priority=50 , match=(outport == "none"),
action=(outport = "_MC_unknown"; output;)
@@ -7976,7 +7980,7 @@ AT_CHECK([grep -E "ls_in_l2_lkup.*S2-|unknown" S2flows |
ovn_strip_lflows], [0],
dnl Check that FDB learning is enabled for the switch port.
AT_CHECK([grep -E "ls_.*fdb.*S2-" S2flows | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S2-S1"),
action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S2-S1"),
action=(flags.inport_in_mc_unknown = 1; reg0[[11]] = lookup_fdb(inport,
eth.src); next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport == "S2-S1" &&
reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
])
@@ -7988,7 +7992,7 @@ AT_CAPTURE_FILE([S1flows2])
dnl Check that that address is now known on S1, but forwarding to _MC_unknown
dnl group is still configured for other potential addresses.
-AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown" S1flows2 | ovn_strip_lflows],
[0], [dnl
+AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|ls_in_l2_unknown" S1flows2 |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst ==
50:54:00:00:00:01), action=(outport = "S1-vm"; output;)
table=??(ls_in_l2_lkup ), priority=50 , match=(eth.dst ==
50:54:00:00:00:02), action=(outport = "S1-S2"; output;)
table=??(ls_in_l2_unknown ), priority=0 , match=(1), action=(output;)
@@ -7997,7 +8001,7 @@ AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown" S1flows2 |
ovn_strip_lflows], [0]
dnl Check that FDB learning is still enabled for the switch port.
AT_CHECK([grep -E "ls_.*fdb.*S1-" S1flows | ovn_strip_lflows], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-S2"),
action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "S1-S2"),
action=(flags.inport_in_mc_unknown = 1; reg0[[11]] = lookup_fdb(inport,
eth.src); next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport == "S1-S2" &&
reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
])
@@ -10188,10 +10192,14 @@ AT_CHECK([ovn-nbctl ls-add ls0])
AT_CHECK([ovn-nbctl lsp-add-localnet-port ls0 ln_port phys])
AT_CHECK([ovn-nbctl --wait=sb sync])
-# Check MAC learning flows with 'localnet_learn_fdb' default (false)
+# Check MAC learning flows with 'localnet_learn_fdb' default (false).
+# Because lsp-add-localnet-port defaults addresses to "unknown", the
+# localnet port can receive unknown-unicast floods and gets
+# flags.inport_in_mc_unknown set (consumed by inline NF redirection
+# in ls_in_nf; harmless otherwise).
AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
])
@@ -10199,8 +10207,8 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e
'ls_in_\(put\|lookup\)_fdb' | ovn_s
AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port localnet_learn_fdb=true])
AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln_port"),
action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln_port"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; reg0[[11]] =
lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport == "ln_port" &&
reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
])
@@ -10209,7 +10217,7 @@ AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e
'ls_in_\(put\|lookup\)_fdb' | ovn_s
AT_CHECK([ovn-nbctl --wait=sb lsp-set-options ln_port
localnet_learn_fdb=false])
AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln_port"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
table=??(ls_in_put_fdb ), priority=0 , match=(1), action=(next;)
])
@@ -10582,7 +10590,7 @@ dnl ls1: ls_in_lookup_fdb should have priority 0
default +
dnl priority 50 flags.localnet.
AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
])
dnl ls1: ls_in_arp_rsp should include flags.localnet condition for
@@ -10622,8 +10630,8 @@ dnl ls1: ls_in_lookup_fdb should have priority 100 FDB +
dnl priority 50 fallback.
AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln1"),
action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "ln1"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; reg0[[11]] =
lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
])
dnl ls1: ls_in_arp_rsp should be unchanged.
@@ -10644,7 +10652,7 @@ check ovn-nbctl --wait=sb lsp-set-options ln1
localnet_learn_fdb=false
AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , match=(1), action=(next;)
- table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "ln1"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
])
AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_arp_rsp' |
ovn_strip_lflows], [0], [dnl
@@ -19657,15 +19665,20 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport = "sw0-nf-p1";
output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p1"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p2"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p1"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p2"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport = "sw0-nf-p2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -19673,8 +19686,11 @@ AT_CHECK([grep "ls_in_l2_lkup" sw0flows |
ovn_strip_lflows | grep 'priority=100'
table=??(ls_in_l2_lkup ), priority=100 , match=(reg8[[23]] == 1),
action=(output;)
])
+dnl LOOKUP runs on both NF return ports: input_port in ls_out_pre_acl (also
+dnl skipping the already-traversed egress stages) and output_port in
+dnl ls_out_nf (fresh egress, after conntrack).
AT_CHECK([grep "ls_out_pre_acl" sw0flows | ovn_strip_lflows | grep
'sw0-nf-p1'], [0], [dnl
- table=??(ls_out_pre_acl ), priority=110 , match=(inport ==
"sw0-nf-p1"), action=(next(pipeline=egress, table=??);)
+ table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
"sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_src_port();
next(pipeline=egress, table=??);)
])
AT_CHECK(
@@ -19765,20 +19781,28 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows |
sort], [0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw0-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 102), action=(outport = "sw0-nf-p3";
output;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport = "sw0-nf-p1";
output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p1"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p2"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p3"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p4"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p1"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p2"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p3"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw0-nf-p4"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport = "sw0-nf-p2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 102), action=(outport = "sw0-nf-p4";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -19847,20 +19871,28 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' sw1flows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw1-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw1-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw1-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw1-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw1-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw1-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw1-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw1-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw1-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"sw1-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 102), action=(outport = "sw1-nf-p3";
output;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport = "sw1-nf-p1";
output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw1-nf-p1"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw1-nf-p2"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw1-nf-p3"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw1-nf-p4"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw1-nf-p1"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw1-nf-p2"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw1-nf-p3"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"sw1-nf-p4"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw1-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw1-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport = "sw1-nf-p2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 102), action=(outport = "sw1-nf-p4";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -19895,6 +19927,210 @@ OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Network function -- inline post-NF re-flood loop prevention])
+
+dnl Verifies that with an inline-mode Network_Function_Group attached to an
+dnl ACL, northd installs:
+dnl - flags.inport_in_mc_unknown = 1 in ls_in_lookup_fdb for every port
+dnl that can receive an unknown-unicast flood,
+dnl - per-NFG nf_learn_orig_src_port() redirect-path flows (priority 101)
+dnl in ls_in_nf (or ls_out_nf for to-lport ACLs) gated on that flag, and,
+dnl on overlay switches only, a per-LS default-path flow (priority 50) in
+dnl ls_in_nf,
+dnl - nf_lookup_orig_src_port() flows on both NF ports: input_port in
+dnl ls_out_pre_acl (also skipping the already-traversed egress stages)
+dnl and output_port in ls_out_nf (fresh egress, after conntrack), and
+dnl - the ls_out_check_port_sec drop on REGBIT_NF_LOOKUP_HIT.
+
+ovn_start
+
+check ovn-nbctl ls-add sw0
+
+dnl NF port-pair.
+check ovn-nbctl lsp-add sw0 sw0-nf-p1
+check ovn-nbctl lsp-add sw0 sw0-nf-p2
+check ovn-nbctl set logical_switch_port sw0-nf-p1 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p2
+check ovn-nbctl set logical_switch_port sw0-nf-p2 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p1
+
+dnl A VM port and a localnet port, both with "unknown" in addresses: each
+dnl can receive an unknown-unicast flood, so each must get the flag.
+check ovn-nbctl lsp-add sw0 sw0-vm1 -- \
+ lsp-set-addresses sw0-vm1 "00:00:00:00:00:01 10.0.0.10" unknown
+check ovn-nbctl lsp-add sw0 sw0-ln -- \
+ lsp-set-type sw0-ln localnet -- \
+ lsp-set-options sw0-ln network_name=phys -- \
+ lsp-set-addresses sw0-ln unknown
+
+dnl A second VM port without "unknown" must NOT get the flag set.
+check ovn-nbctl lsp-add sw0 sw0-vm2 -- \
+ lsp-set-addresses sw0-vm2 "00:00:00:00:00:02 10.0.0.11"
+
+dnl A third port with "unknown" but with receive_multicast=false must NOT
+dnl get the flag set (it would never be a flood target).
+check ovn-nbctl lsp-add sw0 sw0-vm3 -- \
+ lsp-set-addresses sw0-vm3 "00:00:00:00:00:03 10.0.0.12" unknown -- \
+ lsp-set-options sw0-vm3 receive_multicast=false
+
+check ovn-nbctl nf-add nf0 101 sw0-nf-p1 sw0-nf-p2
+check ovn-nbctl nfg-add nfg0 201 inline nf0
+check ovn-nbctl acl-add sw0 from-lport 1234 'ip' allow-related nfg0
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows sw0 > sw0flows
+AT_CAPTURE_FILE([sw0flows])
+
+dnl FLAG: every port that can receive an unknown-unicast flood gets
+dnl flags.inport_in_mc_unknown = 1 set in ls_in_lookup_fdb. The VIF with
+dnl "unknown" gets the action appended to its priority-100 FDB-learn flow;
+dnl the localnet (without localnet_learn_fdb) gets it appended to its
+dnl priority-50 flags.localnet flow.
+AT_CHECK([grep 'ls_in_lookup_fdb' sw0flows | ovn_strip_lflows | \
+ grep inport_in_mc_unknown | sort], [0], [dnl
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport == "sw0-vm1"),
action=(flags.inport_in_mc_unknown = 1; reg0[[11]] = lookup_fdb(inport,
eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=50 , match=(inport == "sw0-ln"),
action=(flags.localnet = 1; flags.inport_in_mc_unknown = 1; next;)
+])
+
+dnl FLAG must not be set for NF ports (receive_multicast=false), plain VM
+dnl ports without "unknown", or ports with receive_multicast=false.
+AT_CHECK([grep 'ls_in_lookup_fdb' sw0flows | ovn_strip_lflows | \
+ grep inport_in_mc_unknown | \
+ grep -E 'sw0-nf-p[[12]]|sw0-vm2|sw0-vm3' | wc -l], [0], [0
+])
+
+dnl LEARN (redirect path): for the inline-mode NFG attached via from-lport
+dnl ACL, ls_in_nf has priority-100 LEARN+redirect flows for IPv4 and IPv6,
+dnl gated on the flag. Multicast is skipped by the higher-priority (110)
+dnl flow, so it is not matched here. The existing priority-99 redirect flow
+dnl remains as a catch-all.
+AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_learn | grep 'priority=100' | sort], [0], [dnl
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 101), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+])
+
+dnl LEARN (default path): the per-LS priority-50 default-path LEARN is only
+dnl emitted on overlay switches. sw0 has a localnet port (VLAN-backed), where
+dnl the redirect-path learn already co-locates with the lookup, so it must NOT
+dnl have any priority-50 nf_learn flow. (Overlay coverage is below.)
+AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_learn | grep 'priority=50' | wc -l], [0], [0
+])
+
+dnl LOOKUP: both NF ports run nf_lookup_orig_src_port() and stash the result
+dnl in REGBIT_NF_LOOKUP_HIT. input_port runs in ls_out_pre_acl and also
+dnl skips the already-traversed egress stages; output_port (fresh egress,
+dnl e.g. a re-flooded request) runs in ls_out_nf, after conntrack.
+AT_CHECK([grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | \
+ grep 'sw0-nf-p1'], [0], [dnl
+ table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
"sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_src_port();
next(pipeline=egress, table=??);)
+])
+AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_lookup], [0], [dnl
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
+])
+
+dnl DROP: ls_out_check_port_sec drops on REGBIT_NF_LOOKUP_HIT == 1.
+AT_CHECK([grep 'ls_out_check_port_sec' sw0flows | ovn_strip_lflows | \
+ grep 'reg8\[[24\]] == 1'], [0], [dnl
+ table=??(ls_out_check_port_sec), priority=110 , match=(reg8[[24]] == 1),
action=(drop;)
+])
+
+AS_BOX([An overlay switch (no localnet) gets the default-path LEARN])
+
+dnl On an overlay switch the redirect can run on a different node than the
+dnl source port, so the redirect-path learn would land on the wrong node.
+dnl northd therefore also emits the per-LS priority-50 default-path LEARN in
+dnl ls_in_nf, recording the source port on its own ingress node.
+check ovn-nbctl ls-add sw1
+check ovn-nbctl lsp-add sw1 sw1-nf-p1
+check ovn-nbctl lsp-add sw1 sw1-nf-p2
+check ovn-nbctl set logical_switch_port sw1-nf-p1 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p2
+check ovn-nbctl set logical_switch_port sw1-nf-p2 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw1-nf-p1
+check ovn-nbctl nf-add nf2 103 sw1-nf-p1 sw1-nf-p2
+check ovn-nbctl nfg-add nfg2 203 inline nf2
+check ovn-nbctl acl-add sw1 from-lport 1234 'ip' allow-related nfg2
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows sw1 > sw1flows
+AT_CAPTURE_FILE([sw1flows])
+
+AT_CHECK([grep 'ls_in_network_function' sw1flows | ovn_strip_lflows | \
+ grep nf_learn | grep 'priority=50' | sort], [0], [dnl
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
+])
+
+AS_BOX([A second inline NFG on a to-lport ACL emits its own LEARN+LOOKUP])
+
+dnl Adding a second NF pair and attaching it via a to-lport ACL must yield:
+dnl - additional priority-100 LEARN+redirect flows in ls_out_nf for that
+dnl NFG (gated on the flag),
+dnl - LOOKUP flows keyed on both of that NF's ports too.
+dnl Flag-set flows in ls_in_lookup_fdb are LS-wide and are not duplicated by
+dnl the second NFG.
+check ovn-nbctl lsp-add sw0 sw0-nf-p3
+check ovn-nbctl lsp-add sw0 sw0-nf-p4
+check ovn-nbctl set logical_switch_port sw0-nf-p3 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p4
+check ovn-nbctl set logical_switch_port sw0-nf-p4 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p3
+check ovn-nbctl nf-add nf1 102 sw0-nf-p3 sw0-nf-p4
+check ovn-nbctl nfg-add nfg1 202 inline nf1
+check ovn-nbctl acl-add sw0 to-lport 1234 'ip' allow-related nfg1
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows sw0 > sw0flows
+
+AT_CHECK([grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | \
+ grep -E 'sw0-nf-p[[13]]' | sort], [0], [dnl
+ table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
"sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_src_port();
next(pipeline=egress, table=??);)
+ table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
"sw0-nf-p3"), action=(reg8[[24]] = nf_lookup_orig_src_port();
next(pipeline=egress, table=??);)
+])
+AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_lookup | sort], [0], [dnl
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
+])
+
+AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_learn | sort], [0], [dnl
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 102), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+])
+
+AS_BOX([Without any inline-mode NFG attached, no loop-prevention flows])
+
+check ovn-nbctl acl-del sw0
+check ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows sw0 > sw0flows
+
+AT_CHECK([grep -E 'nf_(learn|lookup)_orig_src_port' sw0flows | wc -l], [0], [0
+])
+AT_CHECK([grep -E 'reg8\[[24\]]' sw0flows | wc -l], [0], [0
+])
+
+dnl The ls_in_lookup_fdb flag-set flows persist even with no inline NFG
+dnl attached: they are per-port and harmless (nothing consumes the flag
+dnl without an inline NFG).
+AT_CHECK([grep 'ls_in_lookup_fdb' sw0flows | ovn_strip_lflows | \
+ grep inport_in_mc_unknown | wc -l], [0], [2
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Network function health check])
AT_KEYWORDS([ovn])
@@ -19986,16 +20222,23 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-1"; output;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-1"; output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-1"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-2"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-1"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-2"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"child-2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -20027,16 +20270,23 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-1"; output;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-1"; output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-1"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-2"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 1), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-1"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-2"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"child-2"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-2";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -20068,16 +20318,23 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(inport ==
"child-4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 2), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-3"; output;)
+ table=??(ls_in_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 2), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-3"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(inport ==
"child-4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 2), action=(outport = "child-3"; output;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(outport = "child-3"; output;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-3"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(outport ==
"child-4"), action=(next;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=100 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 2), action=(nf_learn_orig_src_port(ipv6 = false); outport =
"child-4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=100 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
reg0[[22..29]] == 2), action=(nf_learn_orig_src_port(ipv6 = true); outport =
"child-4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-3"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(outport ==
"child-4"), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"child-4"), action=(reg8[[24]] = nf_lookup_orig_src_port(); next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 2), action=(outport = "child-4";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(outport = "child-4";
reg8[[23]] = 1; next(pipeline=ingress, table=??);)
])
@@ -20107,10 +20364,12 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
])
AS_BOX([Configure NFG fallback method to fail-open])
@@ -20138,10 +20397,12 @@ AT_CHECK(
[grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
[0], [dnl
table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && !eth.mcast && reg8[[21]] == 0),
action=(nf_learn_orig_src_port(ipv6 = true); next;)
table=??(ls_out_network_function), priority=0 , match=(1), action=(next;)
table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] == 1),
action=(drop;)
- table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_out_network_function), priority=110 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
])
OVN_CLEANUP_NORTHD
@@ -20610,7 +20871,7 @@ AT_CHECK([ovn-sbctl dump-flows ls1 > ls1flows])
AT_CAPTURE_FILE([ls1flows])
AT_CHECK([grep "ls1-to-spine" ls1flows | ovn_strip_lflows | sort], [0], [dnl
- table=??(ls_in_lookup_fdb ), priority=100 , match=(inport ==
"ls1-to-spine"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
+ table=??(ls_in_lookup_fdb ), priority=100 , match=(inport ==
"ls1-to-spine"), action=(flags.inport_in_mc_unknown = 1; reg0[[11]] =
lookup_fdb(inport, eth.src); next;)
table=??(ls_in_pre_acl ), priority=110 , match=(ip && inport ==
"ls1-to-spine"), action=(next;)
table=??(ls_in_pre_lb ), priority=110 , match=(ip && inport ==
"ls1-to-spine"), action=(next;)
table=??(ls_in_put_fdb ), priority=100 , match=(inport ==
"ls1-to-spine" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
diff --git a/tests/ovn.at b/tests/ovn.at
index eaf56f3ed..80937b99c 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -33587,9 +33587,9 @@ AT_CHECK([grep "ls_in_lookup_fdb" sw0flows |
ovn_strip_lflows], [0], [dnl
table=??(ls_in_lookup_fdb ), priority=0 , dnl
match=(1), action=(next;)
table=??(ls_in_lookup_fdb ), priority=100 , dnl
-match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
next;)
+match=(inport == "sw0-p1"), action=(flags.inport_in_mc_unknown = 1; reg0[[11]]
= lookup_fdb(inport, eth.src); next;)
table=??(ls_in_lookup_fdb ), priority=100 , dnl
-match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
next;)
+match=(inport == "sw0-p3"), action=(flags.inport_in_mc_unknown = 1; reg0[[11]]
= lookup_fdb(inport, eth.src); next;)
])
AT_CHECK([grep "ls_in_put_fdb" sw0flows | ovn_strip_lflows], [0], [dnl
@@ -44896,6 +44896,116 @@ OVN_CLEANUP([hv1],[hv2],[hv3])
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Network function -- post-NF re-flood copy to source port is dropped])
+AT_KEYWORDS([ovn])
+ovn_start
+
+# Single-node, VLAN-backed (localnet) switch. sw0-p1 and sw0-p2 carry
+# "unknown" addresses, so they flood unknown-unicast; a to-lport ACL redirects
+# traffic destined to sw0-p2 to the inline NF.
+#
+# sw0-p1 sends to an unknown destination MAC. The flood copy to sw0-p2 is
+# redirected to the NF, where sw0-p1 is learned as the source port. When the
+# NF returns the packet, the still-unknown MAC makes the switch re-flood; the
+# copy headed back out sw0-p1 is dropped by the loop-prevention, while sw0-p2
+# still receives it.
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl lsp-add sw0 sw0-p1 -- \
+ lsp-set-addresses sw0-p1 "f0:00:00:00:00:01 192.168.0.11" "unknown"
+check ovn-nbctl lsp-add sw0 sw0-p2 -- \
+ lsp-set-addresses sw0-p2 "f0:00:00:00:00:02 192.168.0.12" "unknown"
+for i in 1 2; do
+ check ovn-nbctl lsp-add sw0 sw0-nf-p$i -- \
+ lsp-set-addresses sw0-nf-p$i "f0:00:00:00:01:0$i"
+done
+check ovn-nbctl set logical_switch_port sw0-nf-p1 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p2
+check ovn-nbctl set logical_switch_port sw0-nf-p2 \
+ options:receive_multicast=false options:lsp_learn_mac=false \
+ options:is-nf=true options:nf-linked-port=sw0-nf-p1
+check ovn-nbctl nf-add nf0 1 sw0-nf-p1 sw0-nf-p2
+check ovn-nbctl nfg-add nfg0 1 inline nf0
+check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p2" && ip4' \
+ allow-related nfg0
+
+# VLAN-backed: add a localnet port.
+check ovn-nbctl lsp-add-localnet-port sw0 ln0 phys
+check ovn-nbctl set logical_switch_port ln0 tag_request=100
+
+net_add n
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ovn_attach n br-phys 192.168.1.1
+
+ovs-vsctl add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=sw0-p1 \
+ options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
+ovs-vsctl add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=sw0-p2 \
+ options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap
+for i in 1 2; do
+ ovs-vsctl add-port br-int vif-nf$i -- \
+ set interface vif-nf$i external-ids:iface-id=sw0-nf-p$i \
+ options:tx_pcap=hv1/vif-nf$i-tx.pcap \
+ options:rxq_pcap=hv1/vif-nf$i-rx.pcap
+done
+
+OVN_POPULATE_ARP
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+# Match the test packet in the pcaps (src IP 192.168.0.11 followed by dst IP
+# 192.168.0.12, consecutive only in an IP header).
+icmp_pkt_filter=c0a8000bc0a8000c
+
+# 1. sw0-p1 sends to an unknown destination MAC. The flooded copy destined to
+# sw0-p2 is redirected to the NF output port (sw0-nf-p2); the learn of the
+# original source port (sw0-p1) happens on this redirect.
+packet="inport==\"sw0-p1\" && eth.src==f0:00:00:00:00:01 &&
+ eth.dst==00:00:00:00:00:99 && ip.ttl==64 && ip4.src==192.168.0.11 &&
+ ip4.dst==192.168.0.12 && icmp4.type==8 && icmp4.code==0"
+check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
+OVS_WAIT_UNTIL([
+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif-nf2-tx.pcap |
grep ${icmp_pkt_filter} -c`
+ test $pkts -ge 1
+])
+
+# 2. The NF returns the packet on its input port (sw0-nf-p1). The destination
+# MAC is still unknown, so the switch re-floods. The copy headed back out
+# sw0-p1 (the original ingress port) must be dropped by the loop-prevention;
+# the sibling copy to sw0-p2 is delivered, confirming the re-flood happened.
+packet="inport==\"sw0-nf-p1\" && eth.src==f0:00:00:00:00:01 &&
+ eth.dst==00:00:00:00:00:99 && ip.ttl==64 && ip4.src==192.168.0.11 &&
+ ip4.dst==192.168.0.12 && icmp4.type==8 && icmp4.code==0"
+check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
+OVS_WAIT_UNTIL([
+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | grep
${icmp_pkt_filter} -c`
+ test $pkts -ge 1
+])
+
+# The loop-prevention drop flow (ls_out_check_port_sec, REGBIT_NF_LOOKUP_HIT,
+# rendered as reg8=0x1000000 -> drop) must have dropped exactly the one
+# looped-back copy.
+OVS_WAIT_UNTIL([
+ hits=`as hv1 ovs-ofctl dump-flows br-int | grep "reg8=0x1000000" | sed -n
's/.*n_packets=\([[0-9]]*\).*/\1/p'`
+ test 1 -eq $hits
+])
+
+# The source port must not receive the looped-back copy.
+AT_CHECK([
+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap | grep
${icmp_pkt_filter} -c`
+ test 0 -eq $pkts
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Unicast ARP when proxy ARP is configured])
AT_SKIP_IF([test $HAVE_SCAPY = no])
--
2.43.5
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev