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 inport):
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
inport).
Fix:
Use the nf_learn_orig_inport() / nf_lookup_orig_inport() 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.
- Introduce a logical flag flags.inport_in_mc_unknown (bit 26)
that marks packets entering on an MC_UNKNOWN-member port (i.e. a
port with "unknown" in addresses and receive_multicast not
disabled, the only ports that can both originate and receive
unknown-unicast floods). ovn-controller sets it in the
physical-to-logical stage (OFTABLE_PHY_TO_LOG resubmits into a
new side table OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP), which is
populated for the local members of the per-datapath _MC_unknown
multicast group in SB. The flag is carried in MFF_LOG_FLAGS
into the egress pipeline.
- 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_inport()) for unicast IP packets that entered on
an MC_UNKNOWN-member port (flags.inport_in_mc_unknown == 1).
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.
* Priority 50 (overlay switches only, in ls_in_nf): learn on
the inport's ingress node when the ACL did not redirect
the packet (REGBIT_NF_ENABLED == 0), so the learn stays
co-located with the lookup even when the redirecting port and
the inport live on different nodes. VLAN-backed switches
do not need this: the post-NF packet is always sent back to
the inport's node.
- On the post-NF return path, run
REGBIT_NF_LOOKUP_HIT = nf_lookup_orig_inport() on both NF ports.
A priority-115 flow in ls_out_pre_acl runs the lookup on
input_port and skips already-traversed egress stages. A
priority-2 flow in ls_out_nf runs the lookup on output_port for
packets redirected from the ingress pipeline (for example, a
from-lport request that is re-flooded after NF processing and
re-enters egress).
- 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.
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]>
Assisted-by: Claude Opus 4.7, Cursor
---
TODO.rst | 10 ++
controller/lflow.h | 1 +
controller/physical.c | 39 +++++
include/ovn/logical-fields.h | 4 +
lib/logical-fields.c | 5 +
northd/northd.c | 151 +++++++++++++---
ovn-nb.xml | 13 ++
tests/ovn-macros.at | 1 +
tests/ovn-northd.at | 326 +++++++++++++++++++++++++++++------
tests/ovn.at | 113 ++++++++++++
10 files changed, 585 insertions(+), 78 deletions(-)
diff --git a/TODO.rst b/TODO.rst
index beca38daf..9e7807b58 100644
--- a/TODO.rst
+++ b/TODO.rst
@@ -177,6 +177,16 @@ OVN To-do List
* Geneve tunnel is used for supporting this feature for VLAN network.
Extend the support over VxLAN tunnel as well.
+ * Inline-NF loop prevention has limitations in topologies where
+ a post-NF flood copy may reach the same logical inport from a
+ different node than the packet's original ingress node. In that
+ case, the lookup runs on a different node than where the inport
+ was learned, so loopback copies are not identified.
+ Known example: OVN-IC L2 stretch with the NF and IC gateway on
+ different chassis. With ``unknown``-addressed ports, the post-NF
+ packet can be flooded across AZs and come back to the source AZ via
+ another node, so the lookup does not hit the original learn entry.
+
* CI
* ovn-kubernetes: Only a subset of the ovn-kubernetes features is currently
diff --git a/controller/lflow.h b/controller/lflow.h
index fa206392f..27104486f 100644
--- a/controller/lflow.h
+++ b/controller/lflow.h
@@ -106,6 +106,7 @@ struct uuid;
#define OFTABLE_LEARN_REMOTE_FDB 112
#define OFTABLE_EVPN_ARP_LOOKUP 113
#define OFTABLE_NF_ORIG_INPORT_LEARN 114
+#define OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP 115
/* Verify that table regions do not overlap. */
BUILD_ASSERT_DECL(OFTABLE_LOG_INGRESS_PIPELINE + LOG_PIPELINE_INGRESS_LEN
diff --git a/controller/physical.c b/controller/physical.c
index 452256e7a..3cd7b2195 100644
--- a/controller/physical.c
+++ b/controller/physical.c
@@ -1605,6 +1605,9 @@ load_logical_ingress_metadata(const struct
sbrec_port_binding *binding,
}
put_load(encap_id, MFF_LOG_ENCAP_ID, 16, 16, ofpacts_p);
}
+
+ /* Resubmit to OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table. */
+ put_resubmit(OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP, ofpacts_p);
}
static bool
@@ -2923,6 +2926,27 @@ mc_ofctrl_add_flow(const struct sbrec_multicast_group
*mc,
ofpbuf_clear(&ctx->ofpacts);
}
+static void
+add_inport_to_mc_unknown_table(const struct sbrec_multicast_group *mc,
+ const struct sbrec_port_binding *port,
+ struct ovn_desired_flow_table *flow_table)
+{
+ struct match match = MATCH_CATCHALL_INITIALIZER;
+ match_inport_dp_and_port_keys(&match, mc->datapath->tunnel_key,
+ port->tunnel_key);
+
+ uint64_t ofpacts_stub[16];
+ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
+ put_load(1, MFF_LOG_FLAGS, MLF_INPORT_IN_MC_UNKNOWN_BIT, 1, &ofpacts);
+
+ /* Install a flow in OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP that sets
+ * MLF_INPORT_IN_MC_UNKNOWN flag for packets ingressing on 'port'. */
+ ofctrl_add_flow(flow_table, OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP, 100,
+ mc->header_.uuid.parts[0], &match, &ofpacts,
+ &mc->header_.uuid);
+ ofpbuf_uninit(&ofpacts);
+}
+
static void
consider_mc_group(const struct physical_ctx *ctx,
const struct sbrec_multicast_group *mc,
@@ -3002,6 +3026,21 @@ consider_mc_group(const struct physical_ctx *ctx,
const char *lport_name = (port->parent_port && *port->parent_port) ?
port->parent_port : port->logical_port;
+ /* For MC_UNKNOWN local members, add a flow in
+ * OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table to set
+ * MLF_INPORT_IN_MC_UNKNOWN flag. */
+ if (mc->tunnel_key == OVN_MCAST_UNKNOWN_TUNNEL_KEY) {
+ bool is_local =
+ port == ldp->localnet_port
+ || ((port->chassis == ctx->chassis
+ || is_additional_chassis(port, ctx->chassis))
+ && local_binding_get_primary_pb(ctx->local_bindings,
+ lport_name));
+ if (is_local) {
+ add_inport_to_mc_unknown_table(mc, port, flow_table);
+ }
+ }
+
if (type == LP_PATCH) {
if (ldp->is_transit_switch) {
local_set_ct_zone_and_output_pb(port->tunnel_key, zone_id,
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
index e95dee316..dcc477c4c 100644
--- a/include/ovn/logical-fields.h
+++ b/include/ovn/logical-fields.h
@@ -141,6 +141,7 @@ enum mff_log_flags_bits {
MLF_RECIRC_BIT = 24,
MLF_EVPN_LOOKUP_BIT = 25,
MLF_POST_NF_LOOP_BACK_BIT = 26,
+ MLF_INPORT_IN_MC_UNKNOWN_BIT = 27,
MLF_NETWORK_ID_START_BIT = 28,
MLF_NETWORK_ID_END_BIT = 31,
};
@@ -223,6 +224,9 @@ enum mff_log_flags {
/* Set on a post-NF packet going back to the original ingress port. */
MLF_POST_NF_LOOP_BACK = (1 << MLF_POST_NF_LOOP_BACK_BIT),
+ /* Packet entered on an MC_UNKNOWN-member port. */
+ 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 35d9a2e22..8295d71f4 100644
--- a/lib/logical-fields.c
+++ b/lib/logical-fields.c
@@ -188,6 +188,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 1d1452791..55f368e61 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -176,6 +176,9 @@ 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 on a post-NF packet flowing back out its original ingress port;
+ * such packets 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. */
@@ -315,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*) | |
|
@@ -19391,6 +19396,68 @@ network_function_configure_fail_open_flows(struct
lflow_table *lflows,
ds_destroy(&match);
}
+static void
+build_nf_learn_orig_inport_flows(struct lflow_table *lflows,
+ const struct ovn_datapath *od,
+ const struct ovn_stage *stage,
+ uint16_t priority,
+ const char *match_suffix,
+ const char *action_suffix,
+ struct lflow_ref *lflow_ref)
+{
+ struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds action = DS_EMPTY_INITIALIZER;
+
+ /* Emit IPv4 nf_learn_orig_inport() flows for flagged IP packets. */
+ ds_put_cstr(&match, "ip4 && flags.inport_in_mc_unknown == 1");
+ if (match_suffix) {
+ ds_put_format(&match, " && %s", match_suffix);
+ }
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = false); %s",
+ action_suffix);
+ ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref);
+
+ ds_clear(&match);
+ ds_clear(&action);
+ /* Emit IPv6 nf_learn_orig_inport() flows for flagged IP packets. */
+ ds_put_cstr(&match, "ip6 && flags.inport_in_mc_unknown == 1");
+ if (match_suffix) {
+ ds_put_format(&match, " && %s", match_suffix);
+ }
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = true); %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
+build_nf_lookup_orig_inport_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;
+
+ /* Prepend nf_lookup_orig_inport() action for packets entering on
+ * 'port'. */
+ ds_put_format(&match, "inport == %s", port->json_key);
+ ds_put_format(&action,
+ REGBIT_NF_LOOKUP_HIT " = nf_lookup_orig_inport(); %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_inline(struct lflow_table *lflows,
const struct ovn_datapath *od,
@@ -19505,6 +19572,13 @@ consider_network_function_inline(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 in fwd_stage: same as the priority-99 redirect above,
+ * but learn the original inport first for flagged IP packets. */
+ build_nf_learn_orig_inport_flows(lflows, od, fwd_stage, 100,
+ ds_cstr(&match), ds_cstr(&action),
+ lflow_ref);
+
ds_clear(&match);
ds_clear(&action);
@@ -19542,53 +19616,57 @@ consider_network_function_inline(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);
+ /* Post-NF Processing: Resumes pipeline after ls_out_nf to bypass
+ * previously completed egress stages (e.g., conntrack). Uses priority
+ * 115 in out_pre_acl to take precedence over priority-110 conntrack
+ * skips, and calls nf_lookup_orig_inport() to identify loopback
+ * packets returning on their original inport. */
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_inport_flow(lflows, od, S_SWITCH_OUT_PRE_ACL, 115,
+ input_port, ds_cstr(&action),
+ lflow_ref);
+ ds_clear(&action);
+
+ /* Priority 2 in out_nf (output_port): post-NF packet re-entering the
+ * egress pipeline; run the lookup here so ls_out_check_port_sec drops
+ * a loopback copy. */
+ build_nf_lookup_orig_inport_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
@@ -19807,6 +19885,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
@@ -19834,14 +19913,15 @@ 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
- * 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,
- REGBIT_NF_ENABLED" == 1 && eth.mcast",
+ /* Ingress and Egress NF Table (Priority 110): skip the NF redirect for
+ * multicast/broadcast packets marked by the ACL, which would otherwise
+ * cause an L2 loop. Higher than the redirect/learn flows so they skip
+ * both. */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
+ "eth.mcast",
"next;", lflow_ref);
- ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
- REGBIT_NF_ENABLED" == 1 && eth.mcast",
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
+ "eth.mcast",
"next;", lflow_ref);
/* Ingress and Egress NF Table (Priority 0): Packets are forwarded to
@@ -19875,6 +19955,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);
}
@@ -19900,6 +19981,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);
@@ -19907,6 +19989,25 @@ build_network_function(const struct ovn_datapath *od,
}
}
}
+
+ if (has_nfg) {
+ /* Drop the loopback copy flagged by nf_lookup_orig_inport(). */
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_CHECK_PORT_SEC, 110,
+ REGBIT_NF_LOOKUP_HIT " == 1", debug_drop_action(),
+ lflow_ref);
+
+ /* Overlay only: always learn the inport when the packet enters
+ * on an MC_unknown-member port, so a post-NF loopback copy can
+ * be detected and dropped when the destination port has NF
+ * redirection enabled. */
+ if (!ls_has_localnet_port(od)) {
+ build_nf_learn_orig_inport_flows(
+ lflows, od, S_SWITCH_IN_NF, 50,
+ REGBIT_NF_ENABLED" == 0",
+ "next;", lflow_ref);
+ }
+ }
+
bitmap_free(nfg_ingress_bitmap);
bitmap_free(nfg_egress_bitmap);
}
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 68d4237c0..001a36f8b 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -6736,6 +6736,19 @@ or
as down, the fallback method is applied.
</p>
+ <p>
+ <b>Known limitation (inline mode)</b>: in topologies where a
+ post-NF flood copy may reach the same logical inport from a
+ different node than the packet's original ingress node. In that
+ case, the lookup runs on a different node than where the inport
+ was learned, so loopback copies are not identified.
+ Known example: OVN-IC L2 stretch with the NF and IC gateway on
+ different chassis. With <code>unknown</code>-addressed ports, the
+ post-NF packet can be flooded across AZs and come back to the
+ source AZ via another node, so the lookup does not hit the
+ original learn entry.
+ </p>
+
<column name="name">
Name of the <ref table="Network_Function_Group"/>. Name should be unique.
</column>
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 2a1faf677..530049821 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -1656,5 +1656,6 @@ m4_define([OFTABLE_GET_REMOTE_FDB], [111])
m4_define([OFTABLE_LEARN_REMOTE_FDB], [112])
m4_define([OFTABLE_EVPN_ARP_LOOKUP], [113])
m4_define([OFTABLE_NF_ORIG_INPORT_LEARN], [114])
+m4_define([OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP], [115])
m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)])
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index c58f731e9..9c9d14400 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -19966,15 +19966,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_inport(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_inport(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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=(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=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -19983,7 +19988,7 @@ AT_CHECK([grep "ls_in_l2_lkup" sw0flows |
ovn_strip_lflows | grep 'priority=100'
])
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_inport();
next(pipeline=egress, table=??);)
])
AT_CHECK(
@@ -20074,20 +20079,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_inport(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_inport(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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_inport(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_inport(ipv6 = true); outport =
"sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(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=(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=2 , match=(inport ==
"sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -20156,20 +20169,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_inport(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_inport(ipv6 = true); outport =
"sw1-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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_inport(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_inport(ipv6 = true); outport =
"sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=2 , match=(inport ==
"sw1-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw1-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -20204,6 +20225,176 @@ OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Network function -- inline post-NF re-flood loop prevention])
+
+dnl When an LS has ports with "unknown" addresses, a packet redirected
+dnl to an inline NF can be re-flooded back out its original inport after NF
+dnl processing. This is prevented by learning the original inport on the way
+dnl in, then on the post-NF return path looking it up and dropping any
+dnl copy headed back out that same port. This test verifies the
LEARN/LOOKUP/DROP
+dnl flows northd installs for loop prevention.
+
+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.
+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".
+check ovn-nbctl lsp-add sw0 sw0-vm2 -- \
+ lsp-set-addresses sw0-vm2 "00:00:00:00:00:02 10.0.0.11"
+
+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
+
+dnl Bind NF ports so nf_get_active() considers the NF healthy.
+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+chassis_uuid=$(fetch_column Chassis _uuid name=gw1)
+check ovn-sbctl set port_binding sw0-nf-p1 up=true chassis=$chassis_uuid
+check ovn-sbctl set port_binding sw0-nf-p2 up=true chassis=$chassis_uuid
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows sw0 > sw0flows
+AT_CAPTURE_FILE([sw0flows])
+
+dnl LEARN (redirect path): priority-100 LEARN+redirect flows in ls_in_nf
+dnl for IPv4 and IPv6, gated on flags.inport_in_mc_unknown.
+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_inport(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_inport(ipv6 = true); outport =
"sw0-nf-p1"; output;)
+])
+
+dnl LEARN (default path): the priority-50 LEARN is only emitted on overlay
+dnl switches. sw0 is VLAN-backed (has a localnet port), so it must NOT
+dnl have any priority-50 nf_learn flow.
+AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_learn | grep 'priority=50' | wc -l], [0], [0
+])
+
+dnl LOOKUP: nf_lookup_orig_inport() on both NF ports. input_port runs
+dnl in ls_out_pre_acl, output_port in ls_out_nf.
+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_inport();
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_inport(); 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 gets the default-path LEARN])
+
+dnl On an overlay switch the NF redirect may happen on a different node
+dnl than the inport. To keep the LEARN co-located with the LOOKUP,
+dnl northd emits a priority-50 default-path LEARN in ls_in_nf so the
+dnl inport is recorded on the same node the packet enters on.
+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-sbctl set port_binding sw1-nf-p1 up=true chassis=$chassis_uuid
+check ovn-sbctl set port_binding sw1-nf-p2 up=true chassis=$chassis_uuid
+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 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = true); next;)
+])
+
+AS_BOX([A second inline NFG on a to-lport ACL emits its own LEARN+LOOKUP])
+
+dnl A second NF pair attached via a to-lport ACL adds:
+dnl - priority-100 LEARN+redirect flows in ls_out_nf for that NFG, and
+dnl - LOOKUP flows on both of that NF's ports.
+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-sbctl set port_binding sw0-nf-p3 up=true chassis=$chassis_uuid
+check ovn-sbctl set port_binding sw0-nf-p4 up=true chassis=$chassis_uuid
+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_inport();
next(pipeline=egress, table=??);)
+ table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
"sw0-nf-p3"), action=(reg8[[24]] = nf_lookup_orig_inport();
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_inport(); next;)
+ table=??(ls_out_network_function), priority=2 , match=(inport ==
"sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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_inport(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_inport(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 clear logical_switch sw0 acls
+check as northd ovn-appctl -t ovn-northd inc-engine/recompute
+check ovn-nbctl --wait=sb sync
+ovn-sbctl dump-flows sw0 > sw0flows
+
+AT_CHECK([grep -E 'nf_(learn|lookup)_orig_inport' sw0flows | wc -l], [0], [0
+])
+AT_CHECK([grep -E 'reg8\[[24\]]' sw0flows | wc -l], [0], [0
+])
+
+OVN_CLEANUP_NORTHD
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Network function health check])
AT_KEYWORDS([ovn])
@@ -20301,16 +20492,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_inport(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_inport(ipv6 = true); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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_inport(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_inport(ipv6 = true); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=2 , match=(inport ==
"child-2"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -20342,16 +20540,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_inport(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_inport(ipv6 = true); outport =
"child-1"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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_inport(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_inport(ipv6 = true); outport =
"child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=2 , match=(inport ==
"child-2"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -20383,16 +20588,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_inport(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_inport(ipv6 = true); outport =
"child-3"; output;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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_inport(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_inport(ipv6 = true); outport =
"child-4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
+ table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ 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=2 , match=(inport ==
"child-4"), action=(reg8[[24]] = nf_lookup_orig_inport(); 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=??);)
])
@@ -20422,10 +20634,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=(eth.mcast),
action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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=(eth.mcast),
action=(next;)
])
AS_BOX([Configure NFG fallback method to fail-open])
@@ -20453,10 +20667,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=(eth.mcast),
action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(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=(eth.mcast),
action=(next;)
])
OVN_CLEANUP_NORTHD
@@ -21625,12 +21841,14 @@ AT_CHECK(
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=(drop;)
- table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport = "sw0-nf-p1";
output;}; 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=(outport ==
"sw0-nf-p1"), 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=(eth.mcast),
action=(next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport = "sw0-nf-p1";
reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
])
@@ -21723,14 +21941,16 @@ AT_CHECK(
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=(drop;)
table=??(ls_in_network_function), priority=100 , match=(inport ==
"sw0-nf-p3"), 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=(eth.mcast),
action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 &&
flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
action=(nf_learn_orig_inport(ipv6 = true); next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 2), action=(clone {outport = "sw0-nf-p3";
output;}; next;)
table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport = "sw0-nf-p1";
output;}; 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=(outport ==
"sw0-nf-p1"), action=(next;)
table=??(ls_out_network_function), priority=100 , match=(outport ==
"sw0-nf-p3"), 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=(eth.mcast),
action=(next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport = "sw0-nf-p1";
reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(clone {outport = "sw0-nf-p3";
reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
])
diff --git a/tests/ovn.at b/tests/ovn.at
index 47bf5f4ec..672dfb5f2 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -45034,6 +45034,119 @@ OVN_CLEANUP_NORTHD
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Network function -- inline post-NF re-flood loop prevention])
+AT_KEYWORDS([ovn])
+ovn_start
+
+# When an LS has ports with "unknown" addresses, a packet redirected to
+# an inline NF can be re-flooded back out its original inport after NF
+# processing. End-to-end check: sw0-p1 sends to an unknown MAC and the
+# post-NF re-flood copy heading back out sw0-p1 is dropped, 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
+
+# ovn-controller populates OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table with one
+# flow per local _MC_unknown member (sw0-p1, sw0-p2, and ln0).
+# Each flow sets MLF_INPORT_IN_MC_UNKNOWN flag.
+OVS_WAIT_UNTIL([
+ flow_count=`as hv1 ovs-ofctl dump-flows br-int
table=OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP | \
+ grep -c 'load:0x1->NXM_NX_REG10\[[27\]]'`
+ test $flow_count -eq 3
+])
+
+# Match the test packet in the pcaps (src IP 192.168.0.11 followed by dst IP
+# 192.168.0.12).
+icmp_pkt_filter=c0a8000bc0a8000c
+
+# 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);
+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
+])
+
+# The NF returns the packet on sw0-nf-p1 with an unknown dst MAC, triggering
+# a re-flood. Loop prevention drops the copy returning to sw0-p1 (original
+# inport), while sw0-p2 receives its copy.
+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 original inport 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
+])
+
+# The loop-prevention drop flow that has REGBIT_NF_LOOKUP_HIT (reg8=0x1000000)
+# bit set must have hit at least once.
+OVS_WAIT_UNTIL([
+ hits=`as hv1 ovs-ofctl dump-flows br-int | grep "reg8=0x1000000" | \
+ grep -v n_packets=0 | wc -l`
+ test $hits -ge 1
+])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
OVN_FOR_EACH_NORTHD([
AT_SETUP([Unicast ARP when proxy ARP is configured])
CHECK_SCAPY
--
2.43.5
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev