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.
- In the NF redirect stage (ls_in_nf for a from-lport ACL,
ls_out_nf for a to-lport ACL), existing priority-99 redirect
flows are updated to learn the original ingress port
(nf_learn_orig_inport()) before redirecting unicast IP packets
to the NF.
- 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.
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
Signed-off-by: Naveen Yerramneni <[email protected]>
---
Documentation/ref/ovn-logical-flows.7.rst | 61 ++++--
TODO.rst | 10 +
northd/northd.c | 121 +++++++++---
ovn-nb.xml | 13 ++
tests/ovn-northd.at | 229 ++++++++++++++++++++--
tests/ovn.at | 105 ++++++++++
6 files changed, 486 insertions(+), 53 deletions(-)
diff --git a/Documentation/ref/ovn-logical-flows.7.rst
b/Documentation/ref/ovn-logical-flows.7.rst
index 0ec69e39e..6718c7fa8 100644
--- a/Documentation/ref/ovn-logical-flows.7.rst
+++ b/Documentation/ref/ovn-logical-flows.7.rst
@@ -946,11 +946,15 @@ refer to either the parent or child ports as applicable
to this logical switch.
table.
- In inline: For each active network function with *id* that is referenced in a
- network function group, a priority-99 flow matches ``reg8[21] == 1 &&
- reg8[22] == 1 && reg0[22..29] == id`` and sets ``outport=P; output;`` where
- *P* is the ``inport`` of that network function. This redirects request
- packets for flows matching ``from-lport`` ACLs with network_function_group
- to the specific network function selected by the Pre Network Function stage.
+ network function group, priority-99 flows match ``ip4 && reg8[21] == 1 &&
+ reg8[22] == 1 && reg0[22..29] == id`` and ``ip6 && reg8[21] == 1 &&
+ reg8[22] == 1 && reg0[22..29] == id`` and set
+ ``nf_learn_orig_inport(ipv6 = false|true); outport=P; output;``
+ where *P* is the ``inport`` of that network function. This redirects request
+ packets for flows matching ``from-lport`` ACLs with
+ network_function_group to the specific network function selected by the Pre
+ Network Function stage. It also learns the original inport before
+ redirection.
- In vtap mode: For each active network function with *id*, a priority-99
forward flow matches ``reg8[21] == 1 && reg8[22] == 1 && reg0[22..29] == id``
@@ -965,6 +969,12 @@ refer to either the parent or child ports as applicable to
this logical switch.
This redirects response and related packets for ``to-lport`` ACLs to the
same network function that handled the request.
+- In inline (overlay logical switches only), two priority-50 flows match
+ ``ip4 && reg8[21] == 0`` and ``ip6 && reg8[21] == 0`` and set
+ ``nf_learn_orig_inport(ipv6 = false|true); next;``. This learns on the
+ packet's ingress node even when redirection happens on a different node,
+ so the learn stays co-located with the lookup.
+
- In vtap mode: A priority-99 reverse flow matches ``reg8[21] == 1 && reg8[22]
== 0 && ct_label.nf_id == id`` and sets ``clone { outport = P; output; };
next;`` to mirror response/related packets to the same NF.
@@ -1645,13 +1655,17 @@ peer of a logical router port. This flow is added to
skip the connection
tracking of packets which will be entering logical router datapath from logical
switch datapath for routing.
-This table also has a priority-110 flow for each network_function ``inport``
*P*
-that matches ``inport == P``. The action is to skip all the egress tables up to
-the :ref:`Network Function <ls-out-13>` table and advance the packet directly
to
-the table after that. This is for the case where packet redirection happens in
-egress :ref:`Network Function <ls-out-13>` table. The same packet when it comes
-out of the other port of network function, they should not be processed again
by
-the same egress stages, specially they should skip the conntrack processing.
+This table also has a priority-115 flow for each network_function ``inport``
+*P*
+that matches ``inport == P`` and applies
+``reg8[24] = nf_lookup_orig_inport(); next(pipeline=egress, table=T);`` where
+*T* is the table immediately following :ref:`Network Function <ls-out-13>`.
+This is for the case where packet redirection happens in egress
+:ref:`Network Function <ls-out-13>` table. When the packet comes back from the
+other NF port, it should not be processed again by the same egress stages.
+, specially they should skip the conntrack processing. This flow also performs
+``nf_lookup_orig_inport()`` before continuing so post-NF loopback copies can
+be detected and dropped.
.. _ls-out-3:
@@ -1886,13 +1900,15 @@ in ``ct_label.nf_id`` during request processing.
table.
- In inline: For each active network function with *id* that is referenced in a
- network function group, a priority-99 flow matches ``reg8[21] == 1 &&
- reg8[22] == 1 && reg0[22..29] == id`` and sets ``outport=P; reg8[23] = 1;
- next(pipeline=ingress, table=T)`` where *P* is the ``outport`` of
- that network function and *T* is the ingress table :ref:`Destination Lookup
- <ls-in-33>`. This redirects request packets matching ``to-lport`` ACLs with
- network_function_group to the specific network function selected by the Pre
- Network Function stage.
+ network function group, priority-99 flows match ``ip4 && reg8[21] == 1 &&
+ reg8[22] == 1 && reg0[22..29] == id`` and ``ip6 && reg8[21] == 1 &&
+ reg8[22] == 1 && reg0[22..29] == id`` and set
+ ``nf_learn_orig_inport(ipv6 = false|true); outport=P; reg8[23] = 1;
+ next(pipeline=ingress, table=T);`` where *P* is the NF outport and *T* is
+ the ingress :ref:`Destination Lookup <ls-in-33>` table. This redirects
+ request packets matching ``to-lport`` ACLs with network_function_group to the
+ specific network function selected by the Pre Network Function stage, and
+ learns the original inport before redirection.
- In vtap mode: For each active network function with *id*, a priority-99
forward flow matches ``reg8[21] == 1 && reg8[22] == 1 && reg0[22..29] == id``
@@ -1922,6 +1938,10 @@ in ``ct_label.nf_id`` during request processing.
port) and advances to the next table so packets to the NF are not mirrored
again.
+- In inline: A priority-2 flow matches ``inport == P`` where *P* is the NF
+ ``outport`` and sets ``reg8[24] = nf_lookup_orig_inport(); next;``. This
+ captures post-NF packets that re-enter egress from ingress recirculation.
+
- In vtap mode: In egress Pre ACL table, a priority-110 flow matches ``ip &&
outport == P`` with action ``ct_clear; next;`` for the vtap NF port so
packets toward the NF are not committed to conntrack.
@@ -1944,6 +1964,9 @@ This is similar to the port security logic in table
:ref:`Ingress Port Security
check <ls-in-0>` except that action ``check_out_port_sec`` is used to check the
port security rules. This table adds the below logical flows.
+- A priority-110 flow matching ``reg8[24] == 1`` with action ``drop;``. This
+ drops post-NF loopback copies detected by ``nf_lookup_orig_inport()``.
+
- A priority 100 flow which matches on the multicast traffic and applies the
action ``REGBIT_PORT_SEC_DROP" = 0; next;"`` to skip the out port security
checks.
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/northd/northd.c b/northd/northd.c
index 9f33b50cf..4e40bc8b4 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*) | |
|
@@ -19776,35 +19781,64 @@ consider_network_function_inline(struct lflow_table
*lflows,
/* Add forward flows for redirection:
* Flows to handle request packets for new or existing connections.
+ * These flows learn the original inport before redirection.
*
* from-lport ACL in_nf priority 99:
* in_acl_eval has already categorized it and populated nf_enabled,
* direction and nfg_id registers. in_pre_nf sets the active network
- * function id in nf_id register. Here this rule sets the outport to the
- * NF port and does output action to skip the rest of the ingress pipeline.
+ * function id in nf_id register. Here this rule learns original inport,
+ * sets the outport to the NF port and does output action to skip the
+ * rest of the ingress pipeline.
*
* to-lport ACL out_nf priority 99:
* out_acl_eval, and out_pre_nf set the nf related registers. Then the
- * out_nf stage sets the outport to NF port and submits the
- * packet back to ingress pipeline l2_lkup table. The l2_lkup would skip
- * mac based lookup as the NF_EGRESS_LOOPBACK is set.
+ * out_nf stage learns original inport, sets the outport to NF port and
+ * submits the packet back to ingress pipeline l2_lkup table. The l2_lkup
+ * would skip mac based lookup as the NF_EGRESS_LOOPBACK is set.
*/
+ ds_put_format(&match, REGBIT_NF_ENABLED" == 1 && "
+ REGBIT_NF_ORIG_DIR" == 1 && "
+ REG_NF_ID " == %"PRIu8,
+ (uint8_t) nf->id);
+ size_t fwd_match_len = match.length;
+
+ /* IPv4 redirect flow: learn original inport before redirect. */
+ ds_put_format(&match, " && ip4");
+ ds_clear(&action);
if (ingress) {
- ds_put_format(&action, "outport = %s; output;",
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = false); "
+ "outport = %s; output;",
redirect_port->json_key);
} else {
- ds_put_format(&action, "outport = %s; "
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = false); "
+ "outport = %s; "
REGBIT_NF_EGRESS_LOOPBACK" = 1; "
"next(pipeline=ingress, table=%d);",
redirect_port->json_key,
ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));
}
- ds_put_format(&match, REGBIT_NF_ENABLED" == 1 && "
- REGBIT_NF_ORIG_DIR" == 1 && "
- REG_NF_ID " == %"PRIu8,
- (uint8_t) nf->id);
ovn_lflow_add(lflows, od, fwd_stage, 99, ds_cstr(&match),
ds_cstr(&action), lflow_ref);
+ ds_truncate(&match, fwd_match_len);
+
+ /* IPv6 redirect flow: learn original inport before redirect. */
+ ds_put_format(&match, " && ip6");
+ ds_clear(&action);
+ if (ingress) {
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = true); "
+ "outport = %s; output;",
+ redirect_port->json_key);
+ } else {
+ ds_put_format(&action, "nf_learn_orig_inport(ipv6 = true); "
+ "outport = %s; "
+ REGBIT_NF_EGRESS_LOOPBACK" = 1; "
+ "next(pipeline=ingress, table=%d);",
+ redirect_port->json_key,
+ ovn_stage_get_table(S_SWITCH_IN_L2_LKUP));
+ }
+ ovn_lflow_add(lflows, od, fwd_stage, 99, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref);
+
ds_clear(&match);
ds_clear(&action);
@@ -19876,19 +19910,31 @@ consider_network_function_inline(struct lflow_table
*lflows,
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. */
+ /* 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
+ * skip flows, and calls nf_lookup_orig_inport() to identify loopback
+ * packets returning on their original inport. */
ds_put_format(&match, "inport == %s", input_port->json_key);
- 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_put_format(&action,
+ REGBIT_NF_LOOKUP_HIT " = nf_lookup_orig_inport(); "
+ "next(pipeline=egress, table=%d);",
+ ovn_stage_get_table(S_SWITCH_OUT_NF) + 1);
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 115, ds_cstr(&match),
ds_cstr(&action), lflow_ref);
+ ds_clear(&match);
+ 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. */
+ ds_put_format(&match, "inport == %s", output_port->json_key);
+ ds_put_format(&action,
+ REGBIT_NF_LOOKUP_HIT " = nf_lookup_orig_inport(); next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 2, ds_cstr(&match),
+ ds_cstr(&action), lflow_ref);
+ ds_clear(&match);
+ ds_clear(&action);
/* Priority 120 flows in out_stateful:
* If packet was received on a tunnel interface and being forwarded to a
@@ -20107,6 +20153,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_inline_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
@@ -20175,6 +20222,10 @@ build_network_function(const struct ovn_datapath *od,
continue;
}
nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
+ has_inline_nfg =
+ has_inline_nfg ||
+ !network_function_group_is_vtap_mode(
+ acl->network_function_group);
consider_network_function(lflows, od, acl->network_function_group,
ingress, lflow_ref);
}
@@ -20200,6 +20251,10 @@ build_network_function(const struct ovn_datapath *od,
continue;
}
nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
+ has_inline_nfg =
+ has_inline_nfg ||
+ !network_function_group_is_vtap_mode(
+ acl->network_function_group);
consider_network_function(lflows, od,
acl->network_function_group,
ingress, lflow_ref);
@@ -20207,6 +20262,28 @@ build_network_function(const struct ovn_datapath *od,
}
}
}
+
+ if (has_inline_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 so a post-NF loopback copy
+ * can be detected and dropped when redirection happens on a different
+ * node than where the packet originally entered the LS. */
+ if (!ls_has_localnet_port(od)) {
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 50,
+ "ip4 && " REGBIT_NF_ENABLED" == 0",
+ "nf_learn_orig_inport(ipv6 = false); next;",
+ lflow_ref);
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 50,
+ "ip6 && " REGBIT_NF_ENABLED" == 0",
+ "nf_learn_orig_inport(ipv6 = true); next;",
+ lflow_ref);
+ }
+ }
+
bitmap_free(nfg_ingress_bitmap);
bitmap_free(nfg_egress_bitmap);
}
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 8a9d19fa9..7c1d5004a 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -6760,6 +6760,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-northd.at b/tests/ovn-northd.at
index 2b9e95d7f..f814f4707 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -21758,12 +21758,16 @@ AT_CHECK(
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=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport = "sw0-nf-p1";
output;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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=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=??);)
])
@@ -21772,7 +21776,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(
@@ -21868,8 +21872,11 @@ AT_CHECK(
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=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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;)
@@ -21877,8 +21884,11 @@ AT_CHECK(
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=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=??);)
+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 102 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw0-nf-p4"; 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 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); outport = "sw0-nf-p4"; reg8[[23]] =
1; next(pipeline=ingress, table=??);)
])
# ICMP packets to sw0-p1 should be redirected to sw0-nf-p4.
@@ -21950,8 +21960,11 @@ AT_CHECK(
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=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw1-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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;)
@@ -21959,8 +21972,11 @@ AT_CHECK(
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=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=??);)
+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 102 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw1-nf-p4"; 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 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); outport = "sw1-nf-p4"; reg8[[23]] =
1; next(pipeline=ingress, table=??);)
])
# ICMP packets from sw1-p1 should be redirected to sw1-nf-p1.
@@ -21993,6 +22009,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-99 LEARN+redirect flows in ls_in_nf
+dnl for IPv4 and IPv6.
+AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
+ grep nf_learn | grep 'priority=99' | sort], [0], [dnl
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw0-nf-p1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 101 && ip6),
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 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 102 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "sw0-nf-p4"; 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 && ip6),
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])
@@ -22093,15 +22279,20 @@ AT_CHECK(
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=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "child-1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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=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=??);)
+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); 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 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); outport = "child-2"; reg8[[23]] = 1;
next(pipeline=ingress, table=??);)
])
# Set the service monitor for nf0 to online and nf1 to online
@@ -22134,15 +22325,20 @@ AT_CHECK(
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=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "child-1"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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=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=??);)
+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 1 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); 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 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); outport = "child-2"; reg8[[23]] = 1;
next(pipeline=ingress, table=??);)
])
# Set the service monitor for nf0 to offline and nf1 to online
@@ -22175,15 +22371,20 @@ AT_CHECK(
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=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); outport = "child-3"; output;)
+ table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); 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=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=??);)
+ table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1 &&
reg8[[22]] == 1 && reg0[[22..29]] == 2 && ip4),
action=(nf_learn_orig_inport(ipv6 = false); 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 && ip6),
action=(nf_learn_orig_inport(ipv6 = true); outport = "child-4"; reg8[[23]] = 1;
next(pipeline=ingress, table=??);)
])
# Set the service monitor for nf0 to offline and nf1 to offline
@@ -22212,6 +22413,8 @@ 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=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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;)
@@ -22243,6 +22446,8 @@ 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=(reg8[[21]] == 1 &&
eth.mcast), action=(next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip4 && reg8[[21]]
== 0), action=(nf_learn_orig_inport(ipv6 = false); next;)
+ table=??(ls_in_network_function), priority=50 , match=(ip6 && 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;)
diff --git a/tests/ovn.at b/tests/ovn.at
index 52fb75286..ea20a7d8d 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -45165,6 +45165,111 @@ 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
+
+# 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
+ignored_tables=OFTABLE_GET_FDB,OFTABLE_LOOKUP_FDB])
+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