On 8/20/26 6:09 PM, Naveen Yerramneni wrote: > 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. >
Hi Naveen, Thanks for the new revision! > 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 Something is a bit broken here. I rephrased this to: 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, specifically conntrack processing should be skipped. This flow also performs ``nf_lookup_orig_inport()`` before continuing so post-NF loopback copies can be detected and dropped. > +``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); This is quite hard to read. Same for the PG part below. I made the following incremental change: diff --git a/northd/northd.c b/northd/northd.c index 4e40bc8b4a..88e3ece884 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -20216,16 +20216,16 @@ build_network_function(const struct ovn_datapath *od, ? true : false; unsigned long *nfg_bitmap = ingress ? nfg_ingress_bitmap : nfg_egress_bitmap; - unsigned int nfg_id = acl->network_function_group->id; - if (bitmap_is_set(nfg_bitmap, nfg_id)) { + const struct nbrec_network_function_group *nfg = + acl->network_function_group; + if (bitmap_is_set(nfg_bitmap, nfg->id)) { /* Skip if same NF is used again. */ 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); + nfg_bitmap = bitmap_set1(nfg_bitmap, nfg->id); + if (!has_inline_nfg && !network_function_group_is_vtap_mode(nfg)) { + has_inline_nfg = true; + } consider_network_function(lflows, od, acl->network_function_group, ingress, lflow_ref); } @@ -20245,16 +20245,18 @@ build_network_function(const struct ovn_datapath *od, unsigned long *nfg_bitmap = ingress ? nfg_ingress_bitmap : nfg_egress_bitmap; - unsigned int nfg_id = acl->network_function_group->id; - if (bitmap_is_set(nfg_bitmap, nfg_id)) { + const struct nbrec_network_function_group *nfg = + acl->network_function_group; + if (bitmap_is_set(nfg_bitmap, nfg->id)) { /* Skip if same NF is used again. */ 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); + nfg_bitmap = bitmap_set1(nfg_bitmap, nfg->id); + + if (!has_inline_nfg && + !network_function_group_is_vtap_mode(nfg)) { + has_inline_nfg = true; + } consider_network_function(lflows, od, acl->network_function_group, ingress, lflow_ref); > 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 The sentence that ends here reads weird. I rephrased it to: <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, the lookup runs on a different node than where the inport was learned, so loopback copies are not identified. > + 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 s/Known/For > + 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]) It's weird that the two tests (one here and one in ovn.at) have the same name. I added the "-- lflows" suffix to this one here. > + > +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 Missing 'check'. > +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 Same missing 'check' for all of the above. > +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 The following reads a tiny bit better: src_ip=$(ip_to_hex 192 168 0 11) dst_ip=$(ip_to_hex 192 168 0 12) icmp_pkt_filter="${src_ip}${dst_ip}" > + > +# 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 I took care of the issues flagged above and applied this to main. Regards, Dumitru _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
