> On 11 Aug 2026, at 8:27 PM, Naveen Yerramneni <[email protected]>
> wrote:
>
>
>
>> On 10 Aug 2026, at 7:02 PM, Dumitru Ceara <[email protected]> wrote:
>>
>> !-------------------------------------------------------------------|
>> CAUTION: External Email
>>
>> |-------------------------------------------------------------------!
>>
>> Hi Naveen,
>
> Hi Dumitru,
>
>>
>> Thanks for v5, please see some review comments below. But before we go
>> to v6 I'd like to first agree on the right path forward about the new
>> flag this patch is adding, more details below inline.
>
> Thanks for the review!
>
>>
>> On 8/4/26 4:31 AM, 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.
>>>
>>> Following are the example packet flows.
>>>
>>> Example 1, VLAN switch (MAC flap):
>>>
>>> Topology
>>> VM1, VM2 and the NF are on the same logical switch (LS), which is
>>> VLAN-backed (localnet port on every node). VM1 is on N1, VM2 on
>>> N2 (its port has "unknown" in addresses and a to-lport ACL that
>>> redirects to the inline NF on N3).
>>>
>>> Flow
>>> 1. VM1 sends pkt to dst MAC X (not in FDB).
>>> 2. The LS floods the pkt; on N1 the copy exits the localnet port
>>> and the TOR floods it to N2.
>>> 3. On N2 the pkt ingresses on localnet; the LS floods it and the
>>> copy reaches VM2 (unknown-addr).
>>> 4. The ACL redirects the pkt to the NF on N3; the NF returns it
>>> to N2.
>>> 5. X is still not in the FDB, so the LS floods again; on N2 one
>>> copy exits the localnet port.
>>>
>>> Result
>>> The TOR now sees VM1's source MAC on N2's port, but it had just
>>> learned VM1's MAC on N1's port, so the MAC flaps on the TOR.
>>>
>>> Example 2, VLAN switch with two protected VMs (loop):
>>>
>>> Topology
>>> As above, plus VM3 (on N3, same LS) also has "unknown" in
>>> addresses and is also NF-protected.
>>>
>>> Flow
>>> 1-5 as above; in parallel, the original flood also reaches N3 via
>>> the TOR, where it is redirected to N3's NF, returns, and the
>>> LS re-floods it out N3's localnet port.
>>> 6. N3's re-flood reaches N2 via the TOR; the LS floods it on N2,
>>> the copy hits VM2 (unknown-addr), is redirected to the NF, and
>>> re-floods out N2's localnet port.
>>> 7. That re-flood reaches N3 via the TOR; the LS floods it on N3,
>>> the copy hits VM3 (also unknown-addr), is redirected to the
>>> NF, re-floods out localnet, ...
>>>
>>> Result
>>> The pkt keeps bouncing between N2 and N3 via the TOR, i.e. an
>>> L2 loop, on top of continuous MAC flaps. The loop persists as
>>> long as X stays unknown.
>>>
>>> Example 3, overlay switch (copy reflected to inport):
>>>
>>> Topology
>>> VM1, VM2 and the NF are on the same LS and all on N1. VM1 and
>>> VM2 both have "unknown" in addresses; VM2's to-lport ACL
>>> redirects to the inline NF.
>>>
>>> Flow
>>> 1. VM1 sends pkt to dst MAC X (not in FDB).
>>> 2. The LS floods to MC_UNKNOWN members on N1; one copy goes to
>>> VM2.
>>> 3. VM2's ACL redirects its copy to the NF; the NF returns it to
>>> N1.
>>> 4. X is still not in the FDB, so the LS floods to MC_UNKNOWN
>>> again; one copy is headed back out VM1's own port.
>>>
>>> Result
>>> VM1 receives a copy of the packet it just sent (reflected to the
>>> inport).
>>>
>>> Fix:
>>>
>>> Use the nf_learn_orig_inport() / nf_lookup_orig_inport() actions
>>> from the previous commit to remember the original ingress port and
>>> drop the copy if it is about to be sent back out of that port.
>>>
>>> - Introduce a logical flag flags.inport_in_mc_unknown (bit 26)
>>> that marks packets entering on an MC_UNKNOWN-member port (i.e. a
>>> port with "unknown" in addresses and receive_multicast not
>>> disabled, the only ports that can both originate and receive
>>> unknown-unicast floods). ovn-controller sets it in the
>>> physical-to-logical stage (OFTABLE_PHY_TO_LOG resubmits into a
>>> new side table OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP), which is
>>> populated for the local members of the per-datapath _MC_unknown
>>> multicast group in SB. The flag is carried in MFF_LOG_FLAGS
>>> into the egress pipeline.
>>>
>>> - In the NF redirect stage (ls_in_nf for a from-lport ACL,
>>> ls_out_nf for a to-lport ACL), on a switch with an inline NF
>>> group on an ACL, learn the original ingress port
>>> (nf_learn_orig_inport()) for unicast IP packets that entered on
>>> an MC_UNKNOWN-member port (flags.inport_in_mc_unknown == 1).
>>> Two flows per IP version:
>>> * Priority 100: learn, then redirect to the NF, when the ACL
>>> selected the packet for redirect. The existing NF-port and
>>> multicast skip flows move up to priority 110 so multicast is
>>> skipped before this flow.
>>> * Priority 50 (overlay switches only, in ls_in_nf): learn on
>>> the inport's ingress node when the ACL did not redirect
>>> the packet (REGBIT_NF_ENABLED == 0), so the learn stays
>>> co-located with the lookup even when the redirecting port and
>>> the inport live on different nodes. VLAN-backed switches
>>> do not need this: the post-NF packet is always sent back to
>>> the inport's node.
>>>
>>> - On the post-NF return path, run
>>> REGBIT_NF_LOOKUP_HIT = nf_lookup_orig_inport() on both NF ports.
>>> A priority-115 flow in ls_out_pre_acl runs the lookup on
>>> input_port and skips already-traversed egress stages. A
>>> priority-2 flow in ls_out_nf runs the lookup on output_port for
>>> packets redirected from the ingress pipeline (for example, a
>>> from-lport request that is re-flooded after NF processing and
>>> re-enters egress).
>>>
>>> - A new priority-110 flow in ls_out_check_port_sec drops packets
>>> with REGBIT_NF_LOOKUP_HIT == 1, i.e. the copies about to be
>>> sent back out of the port they originally arrived on.
>>>
>>> All new flows are gated on the switch having an inline NF group on
>>> an ACL.
>>>
>>> Signed-off-by: Naveen Yerramneni <[email protected]>
>>> Acked-by: Aditya Mehakare <[email protected]>
>>> Fixes: 8e2d6fa14804 ("northd, tests: Network Function insertion logical
>>> flow programming.")
>>> CC: Sragdhara Datta Chaudhuri <[email protected]>
>>> Assisted-by: Claude Opus 4.7, Cursor
>>
>> Same nit about your signed-off-by that should be last.
>
> Ack.
>
>>
>>> ---
>>> TODO.rst | 10 ++
>>> controller/lflow.h | 1 +
>>> controller/physical.c | 39 +++++
>>> include/ovn/logical-fields.h | 4 +
>>> lib/logical-fields.c | 5 +
>>> northd/northd.c | 151 +++++++++++++---
>>> ovn-nb.xml | 13 ++
>>> tests/ovn-macros.at | 1 +
>>> tests/ovn-northd.at | 326 +++++++++++++++++++++++++++++------
>>> tests/ovn.at | 113 ++++++++++++
>>> 10 files changed, 585 insertions(+), 78 deletions(-)
>>>
>>> diff --git a/TODO.rst b/TODO.rst
>>> index beca38daf..9e7807b58 100644
>>> --- a/TODO.rst
>>> +++ b/TODO.rst
>>> @@ -177,6 +177,16 @@ OVN To-do List
>>> * Geneve tunnel is used for supporting this feature for VLAN network.
>>> Extend the support over VxLAN tunnel as well.
>>>
>>> + * Inline-NF loop prevention has limitations in topologies where
>>> + a post-NF flood copy may reach the same logical inport from a
>>> + different node than the packet's original ingress node. In that
>>> + case, the lookup runs on a different node than where the inport
>>> + was learned, so loopback copies are not identified.
>>> + Known example: OVN-IC L2 stretch with the NF and IC gateway on
>>> + different chassis. With ``unknown``-addressed ports, the post-NF
>>> + packet can be flooded across AZs and come back to the source AZ via
>>> + another node, so the lookup does not hit the original learn entry.
>>> +
>>> * CI
>>>
>>> * ovn-kubernetes: Only a subset of the ovn-kubernetes features is currently
>>> diff --git a/controller/lflow.h b/controller/lflow.h
>>> index fa206392f..27104486f 100644
>>> --- a/controller/lflow.h
>>> +++ b/controller/lflow.h
>>> @@ -106,6 +106,7 @@ struct uuid;
>>> #define OFTABLE_LEARN_REMOTE_FDB 112
>>> #define OFTABLE_EVPN_ARP_LOOKUP 113
>>> #define OFTABLE_NF_ORIG_INPORT_LEARN 114
>>> +#define OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP 115
>>>
>>> /* Verify that table regions do not overlap. */
>>> BUILD_ASSERT_DECL(OFTABLE_LOG_INGRESS_PIPELINE + LOG_PIPELINE_INGRESS_LEN
>>> diff --git a/controller/physical.c b/controller/physical.c
>>> index 452256e7a..3cd7b2195 100644
>>> --- a/controller/physical.c
>>> +++ b/controller/physical.c
>>> @@ -1605,6 +1605,9 @@ load_logical_ingress_metadata(const struct
>>> sbrec_port_binding *binding,
>>> }
>>> put_load(encap_id, MFF_LOG_ENCAP_ID, 16, 16, ofpacts_p);
>>> }
>>> +
>>> + /* Resubmit to OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table. */
>>> + put_resubmit(OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP, ofpacts_p);
>>
>> So I've been thinking some more about this. On v4 (I think) I asked
>> whether it's not an option to just execute nf_learn_orig_inport() on all
>> IP packets in the NF logical stages.
>>
>> In the end it's just an optimization to run it only on packets received
>> from ports configured with "unknown" addresses. It wouldn't hurt if we
>> just ran it on all IP packets, right?
>>
>> That would remove the need for the new inport_in_mc_unknown flag all
>> together right?
>>
>> And it should just work fine for well configured clusters. It would
>> also solve the performance related concerns I have when looking at this
>> patch (I know I suggested the side table but):
>> - the resubmit will happen for all packets ingressing on all logical
>> switches
>> - the check in "consider_mc_group()" in physical.c is kind of ugly
>> because it singles out "unknown" multicast groups, it may also be
>> relatively costly as we need to manage these new flows as well
>> - the very specific flag name itself (inport_in_mc_unknown) is already a
>> sign that we're just working around a specific problem, ideally what
>> happens in the physical->logical stage should be generic enough, this is
>> not so much the case if we accept your patch.
>
>
> Yes, this is an optimization to learn entries only from ports configured
> with "unknown" addresses, so we add only the required entries to
> OFTABLE_NF_ORIG_INPORT_LEARN table.
>
> I also considered learning on the NF node before redirect. I did not use
> that approach because it would add more flows (all redirected flows) to
> OFTABLE_NF_ORIG_INPORT_LEARN table on the NF node. We would likely
> also need a new stage at the end of, or after, ingress to detect and drop
> loopback
> packets on the NF node before egress pipeline, since egress processing runs
> on the
> destination node.
>
>
Hi Dumitru,
If you prefer to always learn the inport, I will update the code and send v6.
Also, I am thinking to set the learned flows idle timeout to 10 secs instead of
30secs
to match default idle timeout of OVS data path offloaded flows.
>>
>>> }
>>>
>>> static bool
>>> @@ -2923,6 +2926,27 @@ mc_ofctrl_add_flow(const struct
>>> sbrec_multicast_group *mc,
>>> ofpbuf_clear(&ctx->ofpacts);
>>> }
>>>
>>> +static void
>>> +add_inport_to_mc_unknown_table(const struct sbrec_multicast_group *mc,
>>> + const struct sbrec_port_binding *port,
>>> + struct ovn_desired_flow_table *flow_table)
>>> +{
>>> + struct match match = MATCH_CATCHALL_INITIALIZER;
>>> + match_inport_dp_and_port_keys(&match, mc->datapath->tunnel_key,
>>> + port->tunnel_key);
>>> +
>>> + uint64_t ofpacts_stub[16];
>>> + struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub);
>>> + put_load(1, MFF_LOG_FLAGS, MLF_INPORT_IN_MC_UNKNOWN_BIT, 1, &ofpacts);
>>> +
>>> + /* Install a flow in OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP that sets
>>> + * MLF_INPORT_IN_MC_UNKNOWN flag for packets ingressing on 'port'. */
>>> + ofctrl_add_flow(flow_table, OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP, 100,
>>> + mc->header_.uuid.parts[0], &match, &ofpacts,
>>> + &mc->header_.uuid);
>>> + ofpbuf_uninit(&ofpacts);
>>> +}
>>> +
>>> static void
>>> consider_mc_group(const struct physical_ctx *ctx,
>>> const struct sbrec_multicast_group *mc,
>>> @@ -3002,6 +3026,21 @@ consider_mc_group(const struct physical_ctx *ctx,
>>> const char *lport_name = (port->parent_port && *port->parent_port) ?
>>> port->parent_port : port->logical_port;
>>>
>>> + /* For MC_UNKNOWN local members, add a flow in
>>> + * OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table to set
>>> + * MLF_INPORT_IN_MC_UNKNOWN flag. */
>>> + if (mc->tunnel_key == OVN_MCAST_UNKNOWN_TUNNEL_KEY) {
>>> + bool is_local =
>>> + port == ldp->localnet_port
>>> + || ((port->chassis == ctx->chassis
>>> + || is_additional_chassis(port, ctx->chassis))
>>> + && local_binding_get_primary_pb(ctx->local_bindings,
>>> + lport_name));
>>> + if (is_local) {
>>> + add_inport_to_mc_unknown_table(mc, port, flow_table);
>>> + }
>>> + }
>>> +
>>> if (type == LP_PATCH) {
>>> if (ldp->is_transit_switch) {
>>> local_set_ct_zone_and_output_pb(port->tunnel_key, zone_id,
>>> diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
>>> index e95dee316..dcc477c4c 100644
>>> --- a/include/ovn/logical-fields.h
>>> +++ b/include/ovn/logical-fields.h
>>> @@ -141,6 +141,7 @@ enum mff_log_flags_bits {
>>> MLF_RECIRC_BIT = 24,
>>> MLF_EVPN_LOOKUP_BIT = 25,
>>> MLF_POST_NF_LOOP_BACK_BIT = 26,
>>> + MLF_INPORT_IN_MC_UNKNOWN_BIT = 27,
>>> MLF_NETWORK_ID_START_BIT = 28,
>>> MLF_NETWORK_ID_END_BIT = 31,
>>> };
>>> @@ -223,6 +224,9 @@ enum mff_log_flags {
>>> /* Set on a post-NF packet going back to the original ingress port. */
>>> MLF_POST_NF_LOOP_BACK = (1 << MLF_POST_NF_LOOP_BACK_BIT),
>>>
>>> + /* Packet entered on an MC_UNKNOWN-member port. */
>>> + MLF_INPORT_IN_MC_UNKNOWN = (1 << MLF_INPORT_IN_MC_UNKNOWN_BIT),
>>> +
>>> /* Assign network ID to packet to choose correct network for snat when
>>> * lb_force_snat_ip=router_ip. */
>>> MLF_NETWORK_ID = (OVN_MAX_NETWORK_ID << MLF_NETWORK_ID_START_BIT),
>>> diff --git a/lib/logical-fields.c b/lib/logical-fields.c
>>> index 35d9a2e22..8295d71f4 100644
>>> --- a/lib/logical-fields.c
>>> +++ b/lib/logical-fields.c
>>> @@ -188,6 +188,11 @@ ovn_init_symtab(struct shash *symtab)
>>> snprintf(flags_str, sizeof flags_str, "flags[%d]", MLF_PKT_SAMPLED_BIT);
>>> expr_symtab_add_subfield(symtab, "flags.pkt_sampled", NULL, flags_str);
>>>
>>> + snprintf(flags_str, sizeof flags_str, "flags[%d]",
>>> + MLF_INPORT_IN_MC_UNKNOWN_BIT);
>>> + expr_symtab_add_subfield(symtab, "flags.inport_in_mc_unknown", NULL,
>>> + flags_str);
>>> +
>>> /* Connection tracking state. */
>>> expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false,
>>> WR_CT_COMMIT);
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index 1d1452791..55f368e61 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -176,6 +176,9 @@ static bool vxlan_mode;
>>> #define REGBIT_NF_ENABLED "reg8[21]"
>>> #define REGBIT_NF_ORIG_DIR "reg8[22]"
>>> #define REGBIT_NF_EGRESS_LOOPBACK "reg8[23]"
>>> +/* Set on a post-NF packet flowing back out its original ingress port;
>>> + * such packets are dropped. */
>>> +#define REGBIT_NF_LOOKUP_HIT "reg8[24]"
>>> /* Register to store the network function group id */
>>> #define REG_NF_GROUP_ID "reg0[22..29]"
>>> /* REG_NF_ID overrides REG_NF_GROUP_ID in the pre_network_function stage. */
>>> @@ -315,6 +318,8 @@ static const char *reg_ct_state[] = {
>>> * | | REGBIT_NF_{ENABLED/ORIG_DIR/ | G |
>>> |
>>> * | | EGRESS_LOOPBACK} | 4 |
>>> |
>>> * | | (>= ACL_EVAL* && <= NF*) | |
>>> |
>>> + * | | REGBIT_NF_LOOKUP_HIT | |
>>> |
>>> + * | | (>= OUT_PRE_ACL && <= OUT_CHECK_PORT_SEC)| |
>>> |
>>> * +----+----------------------------------------------+
>>> +-----------------------------------+
>>> * | R9 | OBS_POINT_ID_EST | |
>>> |
>>> * | | (>= ACL_EVAL* && <= ACL_ACTION*) | |
>>> |
>>> @@ -19391,6 +19396,68 @@ network_function_configure_fail_open_flows(struct
>>> lflow_table *lflows,
>>> ds_destroy(&match);
>>> }
>>>
>>> +static void
>>> +build_nf_learn_orig_inport_flows(struct lflow_table *lflows,
>>> + const struct ovn_datapath *od,
>>> + const struct ovn_stage *stage,
>>> + uint16_t priority,
>>> + const char *match_suffix,
>>> + const char *action_suffix,
>>> + struct lflow_ref *lflow_ref)
>>
>> Nit: indentation.
>>
>>> +{
>>> + struct ds match = DS_EMPTY_INITIALIZER;
>>> + struct ds action = DS_EMPTY_INITIALIZER;
>>> +
>>> + /* Emit IPv4 nf_learn_orig_inport() flows for flagged IP packets. */
>>> + ds_put_cstr(&match, "ip4 && flags.inport_in_mc_unknown == 1");
>>> + if (match_suffix) {
>>> + ds_put_format(&match, " && %s", match_suffix);
>>> + }
>>> + ds_put_format(&action, "nf_learn_orig_inport(ipv6 = false); %s",
>>> + action_suffix);
>>> + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
>>> + ds_cstr(&action), lflow_ref);
>>> +
>>> + ds_clear(&match);
>>> + ds_clear(&action);
>>> + /* Emit IPv6 nf_learn_orig_inport() flows for flagged IP packets. */
>>> + ds_put_cstr(&match, "ip6 && flags.inport_in_mc_unknown == 1");
>>> + if (match_suffix) {
>>> + ds_put_format(&match, " && %s", match_suffix);
>>> + }
>>> + ds_put_format(&action, "nf_learn_orig_inport(ipv6 = true); %s",
>>> + action_suffix);
>>> + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
>>> + ds_cstr(&action), lflow_ref);
>>> +
>>> + ds_destroy(&match);
>>> + ds_destroy(&action);
>>> +}
>>> +
>>> +static void
>>> +build_nf_lookup_orig_inport_flow(struct lflow_table *lflows,
>>> + const struct ovn_datapath *od,
>>> + const struct ovn_stage *stage,
>>> + uint16_t priority,
>>> + const struct ovn_port *port,
>>> + const char *action_suffix,
>>> + struct lflow_ref *lflow_ref)
>>> +{
>>> + struct ds match = DS_EMPTY_INITIALIZER;
>>> + struct ds action = DS_EMPTY_INITIALIZER;
>>> +
>>> + /* Prepend nf_lookup_orig_inport() action for packets entering on
>>> + * 'port'. */
>>> + ds_put_format(&match, "inport == %s", port->json_key);
>>> + ds_put_format(&action,
>>> + REGBIT_NF_LOOKUP_HIT " = nf_lookup_orig_inport(); %s",
>>> + action_suffix);
>>> + ovn_lflow_add(lflows, od, stage, priority, ds_cstr(&match),
>>> + ds_cstr(&action), lflow_ref);
>>> + ds_destroy(&match);
>>> + ds_destroy(&action);
>>> +}
>>> +
>>> static void
>>> consider_network_function_inline(struct lflow_table *lflows,
>>> const struct ovn_datapath *od,
>>> @@ -19505,6 +19572,13 @@ consider_network_function_inline(struct
>>> lflow_table *lflows,
>>> (uint8_t) nf->id);
>>> ovn_lflow_add(lflows, od, fwd_stage, 99, ds_cstr(&match),
>>> ds_cstr(&action), lflow_ref);
>>> +
>>> + /* Priority 100 in fwd_stage: same as the priority-99 redirect above,
>>> + * but learn the original inport first for flagged IP packets. */
>>> + build_nf_learn_orig_inport_flows(lflows, od, fwd_stage, 100,
>>> + ds_cstr(&match), ds_cstr(&action),
>>> + lflow_ref);
>>
>> Nit: indentation.
>>
>> Also, this means that the priority-99 flow will never be hit right? It
>> feels weird to have that flow there. See below, maybe we should just
>> use learn directly here.
>
> If we always learn inport then, we don’t need the additional flow here.
>
>>
>>> +
>>> ds_clear(&match);
>>> ds_clear(&action);
>>>
>>> @@ -19542,53 +19616,57 @@ consider_network_function_inline(struct
>>> lflow_table *lflows,
>>> ds_clear(&match);
>>> ds_clear(&action);
>>>
>>> - /* Priority 100 flow in in_nf:
>>> + /* Priority 110 flow in in_nf:
>>> * Allow packets to go through if coming from network-function port as
>>> * we don't want the packets to be redirected again based on from-lport
>>> * match.
>>> */
>>> ds_put_format(&match, "inport == %s", input_port->json_key);
>>> ds_put_format(&action, REG_TUN_OFPORT" = ct_label.tun_if_id; next;");
>>> - ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
>>> + ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
>>> ds_cstr(&match), ds_cstr(&action), lflow_ref);
>>> ds_clear(&match);
>>>
>>> ds_put_format(&match, "inport == %s", output_port->json_key);
>>> - ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
>>> + ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
>>> ds_cstr(&match), ds_cstr(&action), lflow_ref);
>>> ds_clear(&match);
>>> ds_clear(&action);
>>>
>>> - /* Priority 100 flow in out_nf:
>>> + /* Priority 110 flow in out_nf:
>>> * Allow packets to go through if outport is network-function port as
>>> * we don't want the packets to be redirected again based on to-lport
>>> * match.
>>> */
>>> ds_put_format(&match, "outport == %s", input_port->json_key);
>>> ds_put_format(&action, "next;");
>>> - ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
>>> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
>>> ds_cstr(&match), ds_cstr(&action), lflow_ref);
>>> ds_clear(&match);
>>>
>>> ds_put_format(&match, "outport == %s", output_port->json_key);
>>> - ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
>>> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
>>> ds_cstr(&match), ds_cstr(&action), lflow_ref);
>>> ds_clear(&match);
>>> ds_clear(&action);
>>>
>>> - /* For packets redirected from egress pipleline to the NF, when they
>>> come
>>> - * out from the other NF port, we don't want to process them again
>>> through
>>> - * egress stages they already went through, especially not again
>>> through
>>> - * conntrack as these packets are already accounted for there. Hence we
>>> - * need to skip the initial pipeline stages for such packets and
>>> directly
>>> - * start from the NF table. The packets that fall under this category
>>> are
>>> - * the response packets from NF for from-lport ACLs and request packets
>>> - * received from NF for to-lport ACLs. */
>>> - ds_put_format(&match, "inport == %s", input_port->json_key);
>>> + /* Post-NF Processing: Resumes pipeline after ls_out_nf to bypass
>>> + * previously completed egress stages (e.g., conntrack). Uses priority
>>> + * 115 in out_pre_acl to take precedence over priority-110 conntrack
>>> + * skips, and calls nf_lookup_orig_inport() to identify loopback
>>> + * packets returning on their original inport. */
>>> ds_put_format(&action, "next(pipeline=egress, table=%d);",
>>> - (ovn_stage_get_table(S_SWITCH_OUT_NF) + 1));
>>> - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, ds_cstr(&match),
>>> - ds_cstr(&action), lflow_ref);
>>> + ovn_stage_get_table(S_SWITCH_OUT_NF) + 1);
>>> + build_nf_lookup_orig_inport_flow(lflows, od, S_SWITCH_OUT_PRE_ACL, 115,
>>> + input_port, ds_cstr(&action),
>>> + lflow_ref);
>>> + ds_clear(&action);
>>> +
>>> + /* Priority 2 in out_nf (output_port): post-NF packet re-entering the
>>> + * egress pipeline; run the lookup here so ls_out_check_port_sec drops
>>> + * a loopback copy. */
>>> + build_nf_lookup_orig_inport_flow(lflows, od, S_SWITCH_OUT_NF, 2,
>>> + output_port, "next;", lflow_ref);
>>
>> I would just inline the two new logical flows (created by
>> build_nf_lookup_orig_inport_flow()) here, it makes it easier to read and
>> understand the pipeline if we have all the related flows in a single place.
>
> Ack.
>
>>
>>>
>>> /* Priority 120 flows in out_stateful:
>>> * If packet was received on a tunnel interface and being forwarded to a
>>> @@ -19807,6 +19885,7 @@ build_network_function(const struct ovn_datapath
>>> *od,
>>> {
>>> unsigned long *nfg_ingress_bitmap =
>>> bitmap_allocate(MAX_OVN_NF_GROUP_IDS);
>>> unsigned long *nfg_egress_bitmap = bitmap_allocate(MAX_OVN_NF_GROUP_IDS);
>>> + bool has_nfg = false;
>>>
>>> /* This flow matches packets injected from out_nf stage -
>>> * after it sets the outport - back to in_l2_lkup stage. This rule must
>>> be
>>> @@ -19834,14 +19913,15 @@ build_network_function(const struct ovn_datapath
>>> *od,
>>> REGBIT_NF_ENABLED" == 1 && " REGBIT_NF_ORIG_DIR" == 1",
>>> REG_NF_ID" = 0; next;", lflow_ref);
>>>
>>> - /* Ingress and Egress NF Table (Priority 100): ACL stage determined
>>> these
>>> - * packets should be redirected, but these are multicast/broadcast
>>> - * packets which can cause L2 loop if redirected to NF. */
>>> - ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
>>> - REGBIT_NF_ENABLED" == 1 && eth.mcast",
>>> + /* Ingress and Egress NF Table (Priority 110): skip the NF redirect for
>>> + * multicast/broadcast packets marked by the ACL, which would otherwise
>>> + * cause an L2 loop. Higher than the redirect/learn flows so they skip
>>> + * both. */
>>> + ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 110,
>>> + "eth.mcast",
>>> "next;", lflow_ref);
>>
>> We also have these flows for vtap mode:
>>
>> /* Priority 100 flow in in_network_function:
>> * Drop packets coming from network-function in vtap mode.
>> */
>> ds_put_format(&match, "inport == %s", input_port->json_key);
>> ds_put_format(&action, "drop;");
>> ovn_lflow_add(lflows, od, S_SWITCH_IN_NF, 100,
>> ds_cstr(&match), ds_cstr(&action), lflow_ref);
>>
>> If you now add a priority-110 flow for multicast, we'll bypass those and
>> essentially allow multicast packets entering from a network-function
>> that's in vtap mode which seems like a regression.
>
> Ack.
>
>>
>>> - ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 100,
>>> - REGBIT_NF_ENABLED" == 1 && eth.mcast",
>>> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_NF, 110,
>>> + "eth.mcast",
>>> "next;", lflow_ref);
>>>
>>> /* Ingress and Egress NF Table (Priority 0): Packets are forwarded to
>>> @@ -19875,6 +19955,7 @@ build_network_function(const struct ovn_datapath
>>> *od,
>>> continue;
>>> }
>>> nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
>>> + has_nfg = true;
>>> consider_network_function(lflows, od,
>>> acl->network_function_group,
>>> ingress, lflow_ref);
>>> }
>>> @@ -19900,6 +19981,7 @@ build_network_function(const struct ovn_datapath
>>> *od,
>>> continue;
>>> }
>>> nfg_bitmap = bitmap_set1(nfg_bitmap, nfg_id);
>>> + has_nfg = true;
>>> consider_network_function(lflows, od,
>>> acl->network_function_group,
>>> ingress, lflow_ref);
>>> @@ -19907,6 +19989,25 @@ build_network_function(const struct ovn_datapath
>>> *od,
>>> }
>>> }
>>> }
>>> +
>>> + if (has_nfg) {
>>> + /* Drop the loopback copy flagged by nf_lookup_orig_inport(). */
>>> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_CHECK_PORT_SEC, 110,
>>> + REGBIT_NF_LOOKUP_HIT " == 1", debug_drop_action(),
>>> + lflow_ref);
>>> +
>>> + /* Overlay only: always learn the inport when the packet enters
>>> + * on an MC_unknown-member port, so a post-NF loopback copy can
>>> + * be detected and dropped when the destination port has NF
>>> + * redirection enabled. */
>>
>> I'm confused about why doing this in "overlay only" switches? If we go
>> for my suggestion of unconditionally using the learn action then we can
>> just add it as part of the priority-99 flows installed in the NF_IN/OUT
>> stages right?
>
> This is still needed for overlay subnets. In VLAN-backed cases, post-NF return
> stays on the same node path where redirect-path learn already ran. In
> overlay, redirect may happen on the destination node, while post-NF flood
> may return toward the source node. Without the default-path learn, we
> cannot reliably catch loopback packets.
>
>>
>>> + if (!ls_has_localnet_port(od)) {
>>> + build_nf_learn_orig_inport_flows(
>>> + lflows, od, S_SWITCH_IN_NF, 50,
>>> + REGBIT_NF_ENABLED" == 0",
>>> + "next;", lflow_ref);
>>> + }
>>> + }
>>> +
>>> bitmap_free(nfg_ingress_bitmap);
>>> bitmap_free(nfg_egress_bitmap);
>>> }
>>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>>> index 68d4237c0..001a36f8b 100644
>>> --- a/ovn-nb.xml
>>> +++ b/ovn-nb.xml
>>> @@ -6736,6 +6736,19 @@ or
>>> as down, the fallback method is applied.
>>> </p>
>>>
>>> + <p>
>>> + <b>Known limitation (inline mode)</b>: in topologies where a
>>> + post-NF flood copy may reach the same logical inport from a
>>> + different node than the packet's original ingress node. In that
>>> + case, the lookup runs on a different node than where the inport
>>> + was learned, so loopback copies are not identified.
>>> + Known example: OVN-IC L2 stretch with the NF and IC gateway on
>>> + different chassis. With <code>unknown</code>-addressed ports, the
>>> + post-NF packet can be flooded across AZs and come back to the
>>> + source AZ via another node, so the lookup does not hit the
>>> + original learn entry.
>>> + </p>
>>> +
>>> <column name="name">
>>> Name of the <ref table="Network_Function_Group"/>. Name should be
>>> unique.
>>> </column>
>>> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
>>> index 2a1faf677..530049821 100644
>>> --- a/tests/ovn-macros.at
>>> +++ b/tests/ovn-macros.at
>>> @@ -1656,5 +1656,6 @@ m4_define([OFTABLE_GET_REMOTE_FDB], [111])
>>> m4_define([OFTABLE_LEARN_REMOTE_FDB], [112])
>>> m4_define([OFTABLE_EVPN_ARP_LOOKUP], [113])
>>> m4_define([OFTABLE_NF_ORIG_INPORT_LEARN], [114])
>>> +m4_define([OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP], [115])
>>>
>>> m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)])
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index c58f731e9..9c9d14400 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -19966,15 +19966,20 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows |
>>> sort], [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw0-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw0-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport =
>>> "sw0-nf-p1"; output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p2"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p2"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport =
>>> "sw0-nf-p2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>>
>>> @@ -19983,7 +19988,7 @@ AT_CHECK([grep "ls_in_l2_lkup" sw0flows |
>>> ovn_strip_lflows | grep 'priority=100'
>>> ])
>>>
>>> AT_CHECK([grep "ls_out_pre_acl" sw0flows | ovn_strip_lflows | grep
>>> 'sw0-nf-p1'], [0], [dnl
>>> - table=??(ls_out_pre_acl ), priority=110 , match=(inport ==
>>> "sw0-nf-p1"), action=(next(pipeline=egress, table=??);)
>>> + table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_inport();
>>> next(pipeline=egress, table=??);)
>>> ])
>>>
>>> AT_CHECK(
>>> @@ -20074,20 +20079,28 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' sw0flows | ovn_strip_lflows |
>>> sort], [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw0-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw0-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw0-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 102), action=(outport =
>>> "sw0-nf-p3"; output;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport =
>>> "sw0-nf-p1"; output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p2"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p3"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p4"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p2"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p3"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw0-nf-p4"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport =
>>> "sw0-nf-p2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 102), action=(outport =
>>> "sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>> @@ -20156,20 +20169,28 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' sw1flows | ovn_strip_lflows |
>>> sort], [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw1-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw1-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw1-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw1-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw1-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw1-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw1-nf-p1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw1-nf-p2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw1-nf-p3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "sw1-nf-p4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 102), action=(outport =
>>> "sw1-nf-p3"; output;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 101), action=(outport =
>>> "sw1-nf-p1"; output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw1-nf-p1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw1-nf-p2"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw1-nf-p3"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw1-nf-p4"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw1-nf-p1"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw1-nf-p2"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw1-nf-p3"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "sw1-nf-p4"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw1-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw1-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 101), action=(outport =
>>> "sw1-nf-p2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 102), action=(outport =
>>> "sw1-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>> @@ -20204,6 +20225,176 @@ OVN_CLEANUP_NORTHD
>>> AT_CLEANUP
>>> ])
>>>
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([Network function -- inline post-NF re-flood loop prevention])
>>> +
>>> +dnl When an LS has ports with "unknown" addresses, a packet redirected
>>> +dnl to an inline NF can be re-flooded back out its original inport after NF
>>> +dnl processing. This is prevented by learning the original inport on the
>>> way
>>> +dnl in, then on the post-NF return path looking it up and dropping any
>>> +dnl copy headed back out that same port. This test verifies the
>>> LEARN/LOOKUP/DROP
>>> +dnl flows northd installs for loop prevention.
>>> +
>>> +ovn_start
>>> +
>>> +check ovn-nbctl ls-add sw0
>>> +
>>> +dnl NF port-pair.
>>> +check ovn-nbctl lsp-add sw0 sw0-nf-p1
>>> +check ovn-nbctl lsp-add sw0 sw0-nf-p2
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p1 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p2
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p2 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p1
>>> +
>>> +dnl A VM port and a localnet port, both with "unknown" in addresses.
>>> +check ovn-nbctl lsp-add sw0 sw0-vm1 -- \
>>> + lsp-set-addresses sw0-vm1 "00:00:00:00:00:01 10.0.0.10" unknown
>>> +check ovn-nbctl lsp-add sw0 sw0-ln -- \
>>> + lsp-set-type sw0-ln localnet -- \
>>> + lsp-set-options sw0-ln network_name=phys -- \
>>> + lsp-set-addresses sw0-ln unknown
>>> +
>>> +dnl A second VM port without "unknown".
>>> +check ovn-nbctl lsp-add sw0 sw0-vm2 -- \
>>> + lsp-set-addresses sw0-vm2 "00:00:00:00:00:02 10.0.0.11"
>>> +
>>> +check ovn-nbctl nf-add nf0 101 sw0-nf-p1 sw0-nf-p2
>>> +check ovn-nbctl nfg-add nfg0 201 inline nf0
>>> +check ovn-nbctl acl-add sw0 from-lport 1234 'ip' allow-related nfg0
>>> +
>>> +dnl Bind NF ports so nf_get_active() considers the NF healthy.
>>> +check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
>>> +chassis_uuid=$(fetch_column Chassis _uuid name=gw1)
>>> +check ovn-sbctl set port_binding sw0-nf-p1 up=true chassis=$chassis_uuid
>>> +check ovn-sbctl set port_binding sw0-nf-p2 up=true chassis=$chassis_uuid
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +AT_CAPTURE_FILE([sw0flows])
>>> +
>>> +dnl LEARN (redirect path): priority-100 LEARN+redirect flows in ls_in_nf
>>> +dnl for IPv4 and IPv6, gated on flags.inport_in_mc_unknown.
>>> +AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
>>> + grep nf_learn | grep 'priority=100' | sort], [0], [dnl
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw0-nf-p1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 101), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw0-nf-p1"; output;)
>>> +])
>>> +
>>> +dnl LEARN (default path): the priority-50 LEARN is only emitted on overlay
>>> +dnl switches. sw0 is VLAN-backed (has a localnet port), so it must NOT
>>> +dnl have any priority-50 nf_learn flow.
>>> +AT_CHECK([grep 'ls_in_network_function' sw0flows | ovn_strip_lflows | \
>>> + grep nf_learn | grep 'priority=50' | wc -l], [0], [0
>>> +])
>>> +
>>> +dnl LOOKUP: nf_lookup_orig_inport() on both NF ports. input_port runs
>>> +dnl in ls_out_pre_acl, output_port in ls_out_nf.
>>> +AT_CHECK([grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | \
>>> + grep 'sw0-nf-p1'], [0], [dnl
>>> + table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_inport();
>>> next(pipeline=egress, table=??);)
>>> +])
>>> +AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
>>> + grep nf_lookup], [0], [dnl
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> +])
>>> +
>>> +dnl DROP: ls_out_check_port_sec drops on REGBIT_NF_LOOKUP_HIT == 1.
>>> +AT_CHECK([grep 'ls_out_check_port_sec' sw0flows | ovn_strip_lflows | \
>>> + grep 'reg8\[[24\]] == 1'], [0], [dnl
>>> + table=??(ls_out_check_port_sec), priority=110 , match=(reg8[[24]] ==
>>> 1), action=(drop;)
>>> +])
>>> +
>>> +AS_BOX([An overlay switch gets the default-path LEARN])
>>> +
>>> +dnl On an overlay switch the NF redirect may happen on a different node
>>> +dnl than the inport. To keep the LEARN co-located with the LOOKUP,
>>> +dnl northd emits a priority-50 default-path LEARN in ls_in_nf so the
>>> +dnl inport is recorded on the same node the packet enters on.
>>> +check ovn-nbctl ls-add sw1
>>> +check ovn-nbctl lsp-add sw1 sw1-nf-p1
>>> +check ovn-nbctl lsp-add sw1 sw1-nf-p2
>>> +check ovn-nbctl set logical_switch_port sw1-nf-p1 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw1-nf-p2
>>> +check ovn-nbctl set logical_switch_port sw1-nf-p2 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw1-nf-p1
>>> +check ovn-nbctl nf-add nf2 103 sw1-nf-p1 sw1-nf-p2
>>> +check ovn-nbctl nfg-add nfg2 203 inline nf2
>>> +check ovn-nbctl acl-add sw1 from-lport 1234 'ip' allow-related nfg2
>>> +
>>> +check ovn-sbctl set port_binding sw1-nf-p1 up=true chassis=$chassis_uuid
>>> +check ovn-sbctl set port_binding sw1-nf-p2 up=true chassis=$chassis_uuid
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +ovn-sbctl dump-flows sw1 > sw1flows
>>> +AT_CAPTURE_FILE([sw1flows])
>>> +
>>> +AT_CHECK([grep 'ls_in_network_function' sw1flows | ovn_strip_lflows | \
>>> + grep nf_learn | grep 'priority=50' | sort], [0], [dnl
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> +])
>>> +
>>> +AS_BOX([A second inline NFG on a to-lport ACL emits its own LEARN+LOOKUP])
>>> +
>>> +dnl A second NF pair attached via a to-lport ACL adds:
>>> +dnl - priority-100 LEARN+redirect flows in ls_out_nf for that NFG, and
>>> +dnl - LOOKUP flows on both of that NF's ports.
>>> +check ovn-nbctl lsp-add sw0 sw0-nf-p3
>>> +check ovn-nbctl lsp-add sw0 sw0-nf-p4
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p3 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p4
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p4 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p3
>>> +check ovn-nbctl nf-add nf1 102 sw0-nf-p3 sw0-nf-p4
>>> +check ovn-nbctl nfg-add nfg1 202 inline nf1
>>> +check ovn-nbctl acl-add sw0 to-lport 1234 'ip' allow-related nfg1
>>> +
>>> +check ovn-sbctl set port_binding sw0-nf-p3 up=true chassis=$chassis_uuid
>>> +check ovn-sbctl set port_binding sw0-nf-p4 up=true chassis=$chassis_uuid
>>> +check ovn-nbctl --wait=sb sync
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +
>>> +AT_CHECK([grep 'ls_out_pre_acl' sw0flows | ovn_strip_lflows | \
>>> + grep -E 'sw0-nf-p[[13]]' | sort], [0], [dnl
>>> + table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
>>> "sw0-nf-p1"), action=(reg8[[24]] = nf_lookup_orig_inport();
>>> next(pipeline=egress, table=??);)
>>> + table=??(ls_out_pre_acl ), priority=115 , match=(inport ==
>>> "sw0-nf-p3"), action=(reg8[[24]] = nf_lookup_orig_inport();
>>> next(pipeline=egress, table=??);)
>>> +])
>>> +AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
>>> + grep nf_lookup | sort], [0], [dnl
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "sw0-nf-p4"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> +])
>>> +
>>> +AT_CHECK([grep 'ls_out_network_function' sw0flows | ovn_strip_lflows | \
>>> + grep nf_learn | sort], [0], [dnl
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = false); outport
>>> = "sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 102), action=(nf_learn_orig_inport(ipv6 = true); outport
>>> = "sw0-nf-p4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> +])
>>> +
>>> +AS_BOX([Without any inline-mode NFG attached, no loop-prevention flows])
>>> +
>>> +check ovn-nbctl clear logical_switch sw0 acls
>>> +check as northd ovn-appctl -t ovn-northd inc-engine/recompute
>>> +check ovn-nbctl --wait=sb sync
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +
>>> +AT_CHECK([grep -E 'nf_(learn|lookup)_orig_inport' sw0flows | wc -l], [0],
>>> [0
>>> +])
>>> +AT_CHECK([grep -E 'reg8\[[24\]]' sw0flows | wc -l], [0], [0
>>> +])
>>> +
>>> +OVN_CLEANUP_NORTHD
>>> +AT_CLEANUP
>>> +])
>>> +
>>> OVN_FOR_EACH_NORTHD([
>>> AT_SETUP([Network function health check])
>>> AT_KEYWORDS([ovn])
>>> @@ -20301,16 +20492,23 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
>>> [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-1"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-1";
>>> output;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-1";
>>> output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-2"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-1"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-2"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "child-2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-2";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-2";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>> @@ -20342,16 +20540,23 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
>>> [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-1"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-1"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-1"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-2"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-1";
>>> output;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-1";
>>> output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-2"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 1), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-2"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-1"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-2"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "child-2"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(outport = "child-2";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(outport = "child-2";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>> @@ -20383,16 +20588,23 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
>>> [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "child-4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 2), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-3"; output;)
>>> + table=??(ls_in_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 2), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-3"; output;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-3"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(inport ==
>>> "child-4"), action=(reg5[[16..31]] = ct_label.tun_if_id; next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 2), action=(outport = "child-3";
>>> output;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(outport = "child-3";
>>> output;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-3"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "child-4"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 2), action=(nf_learn_orig_inport(ipv6 = false); outport =
>>> "child-4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=100 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 1 && reg8[[22]] == 1 &&
>>> reg0[[22..29]] == 2), action=(nf_learn_orig_inport(ipv6 = true); outport =
>>> "child-4"; reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-3"), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(outport ==
>>> "child-4"), action=(next;)
>>> + table=??(ls_out_network_function), priority=2 , match=(inport ==
>>> "child-4"), action=(reg8[[24]] = nf_lookup_orig_inport(); next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 2), action=(outport = "child-4";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(outport = "child-4";
>>> reg8[[23]] = 1; next(pipeline=ingress, table=??);)
>>> ])
>>> @@ -20422,10 +20634,12 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
>>> [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> ])
>>>
>>> AS_BOX([Configure NFG fallback method to fail-open])
>>> @@ -20453,10 +20667,12 @@ AT_CHECK(
>>> [grep -E 'ls_(in|out)_network_function' lflows | ovn_strip_lflows | sort],
>>> [0], [dnl
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> ])
>>>
>>> OVN_CLEANUP_NORTHD
>>> @@ -21625,12 +21841,14 @@ AT_CHECK(
>>> table=??(ls_in_network_function), priority=0 , match=(1), action=(next;)
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p1"), action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport =
>>> "sw0-nf-p1"; output;}; next;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport =
>>> "sw0-nf-p1"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
>>> ])
>>>
>>> @@ -21723,14 +21941,16 @@ AT_CHECK(
>>> table=??(ls_in_network_function), priority=1 , match=(reg8[[21]] == 1),
>>> action=(drop;)
>>> table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p1"), action=(drop;)
>>> table=??(ls_in_network_function), priority=100 , match=(inport ==
>>> "sw0-nf-p3"), action=(drop;)
>>> - table=??(ls_in_network_function), priority=100 , match=(reg8[[21]] == 1
>>> && eth.mcast), action=(next;)
>>> + table=??(ls_in_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip4 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = false); next;)
>>> + table=??(ls_in_network_function), priority=50 , match=(ip6 &&
>>> flags.inport_in_mc_unknown == 1 && reg8[[21]] == 0),
>>> action=(nf_learn_orig_inport(ipv6 = true); next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 2), action=(clone {outport =
>>> "sw0-nf-p3"; output;}; next;)
>>> table=??(ls_in_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 1), action=(clone {outport =
>>> "sw0-nf-p1"; output;}; next;)
>>> table=??(ls_out_network_function), priority=0 , match=(1),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=1 , match=(reg8[[21]] ==
>>> 1), action=(drop;)
>>> table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p1"), action=(next;)
>>> table=??(ls_out_network_function), priority=100 , match=(outport ==
>>> "sw0-nf-p3"), action=(next;)
>>> - table=??(ls_out_network_function), priority=100 , match=(reg8[[21]] ==
>>> 1 && eth.mcast), action=(next;)
>>> + table=??(ls_out_network_function), priority=110 , match=(eth.mcast),
>>> action=(next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 0 && ct_label.nf_id == 1), action=(clone {outport =
>>> "sw0-nf-p1"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
>>> table=??(ls_out_network_function), priority=99 , match=(reg8[[21]] == 1
>>> && reg8[[22]] == 1 && reg0[[22..29]] == 2), action=(clone {outport =
>>> "sw0-nf-p3"; reg8[[23]] = 1; next(pipeline=ingress, table=??);}; next;)
>>> ])
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index 47bf5f4ec..672dfb5f2 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -45034,6 +45034,119 @@ OVN_CLEANUP_NORTHD
>>> AT_CLEANUP
>>> ])
>>>
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Network function -- inline post-NF re-flood loop prevention])
>>> +AT_KEYWORDS([ovn])
>>> +ovn_start
>>> +
>>> +# When an LS has ports with "unknown" addresses, a packet redirected to
>>> +# an inline NF can be re-flooded back out its original inport after NF
>>> +# processing. End-to-end check: sw0-p1 sends to an unknown MAC and the
>>> +# post-NF re-flood copy heading back out sw0-p1 is dropped, while sw0-p2
>>> +# still receives it.
>>> +
>>> +check ovn-nbctl ls-add sw0
>>> +check ovn-nbctl lsp-add sw0 sw0-p1 -- \
>>> + lsp-set-addresses sw0-p1 "f0:00:00:00:00:01 192.168.0.11" "unknown"
>>> +check ovn-nbctl lsp-add sw0 sw0-p2 -- \
>>> + lsp-set-addresses sw0-p2 "f0:00:00:00:00:02 192.168.0.12" "unknown"
>>
>> These have FDB learning enabled.
>>
>>> +for i in 1 2; do
>>> + check ovn-nbctl lsp-add sw0 sw0-nf-p$i -- \
>>> + lsp-set-addresses sw0-nf-p$i "f0:00:00:00:01:0$i"
>>> +done
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p1 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p2
>>> +check ovn-nbctl set logical_switch_port sw0-nf-p2 \
>>> + options:receive_multicast=false options:lsp_learn_mac=false \
>>> + options:is-nf=true options:nf-linked-port=sw0-nf-p1
>>> +check ovn-nbctl nf-add nf0 1 sw0-nf-p1 sw0-nf-p2
>>> +check ovn-nbctl nfg-add nfg0 1 inline nf0
>>> +check ovn-nbctl acl-add sw0 to-lport 1002 'outport == "sw0-p2" && ip4' \
>>> + allow-related nfg0
>>> +
>>> +# VLAN-backed: add a localnet port.
>>> +check ovn-nbctl lsp-add-localnet-port sw0 ln0 phys
>>> +check ovn-nbctl set logical_switch_port ln0 tag_request=100
>>> +
>>> +net_add n
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
>>> +ovn_attach n br-phys 192.168.1.1
>>> +
>>> +ovs-vsctl add-port br-int vif1 -- \
>>> + set interface vif1 external-ids:iface-id=sw0-p1 \
>>> + options:tx_pcap=hv1/vif1-tx.pcap options:rxq_pcap=hv1/vif1-rx.pcap
>>> +ovs-vsctl add-port br-int vif2 -- \
>>> + set interface vif2 external-ids:iface-id=sw0-p2 \
>>> + options:tx_pcap=hv1/vif2-tx.pcap options:rxq_pcap=hv1/vif2-rx.pcap
>>> +for i in 1 2; do
>>> + ovs-vsctl add-port br-int vif-nf$i -- \
>>> + set interface vif-nf$i external-ids:iface-id=sw0-nf-p$i \
>>> + options:tx_pcap=hv1/vif-nf$i-tx.pcap \
>>> + options:rxq_pcap=hv1/vif-nf$i-rx.pcap
>>> +done
>>> +
>>> +OVN_POPULATE_ARP
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +# ovn-controller populates OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP table with
>>> one
>>> +# flow per local _MC_unknown member (sw0-p1, sw0-p2, and ln0).
>>> +# Each flow sets MLF_INPORT_IN_MC_UNKNOWN flag.
>>> +OVS_WAIT_UNTIL([
>>> + flow_count=`as hv1 ovs-ofctl dump-flows br-int
>>> table=OFTABLE_INPORT_IN_MC_UNKNOWN_LOOKUP | \
>>> + grep -c 'load:0x1->NXM_NX_REG10\[[27\]]'`
>>> + test $flow_count -eq 3
>>> +])
>>> +
>>> +# Match the test packet in the pcaps (src IP 192.168.0.11 followed by dst
>>> IP
>>> +# 192.168.0.12).
>>> +icmp_pkt_filter=c0a8000bc0a8000c
>>> +
>>> +# sw0-p1 sends to an unknown destination MAC. The flooded copy destined to
>>> +# sw0-p2 is redirected to the NF output port (sw0-nf-p2);
>>> +packet="inport==\"sw0-p1\" && eth.src==f0:00:00:00:00:01 &&
>>> + eth.dst==00:00:00:00:00:99 && ip.ttl==64 && ip4.src==192.168.0.11
>>> &&
>>> + ip4.dst==192.168.0.12 && icmp4.type==8 && icmp4.code==0"
>>> +check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
>>> +OVS_WAIT_UNTIL([
>>> + pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif-nf2-tx.pcap
>>> | grep ${icmp_pkt_filter} -c`
>>> + test $pkts -ge 1
>>> +])
>>> +
>>> +# The NF returns the packet on sw0-nf-p1 with an unknown dst MAC,
>>> triggering
>>> +# a re-flood. Loop prevention drops the copy returning to sw0-p1 (original
>>> +# inport), while sw0-p2 receives its copy.
>>> +packet="inport==\"sw0-nf-p1\" && eth.src==f0:00:00:00:00:01 &&
>>> + eth.dst==00:00:00:00:00:99 && ip.ttl==64 && ip4.src==192.168.0.11
>>> &&
>>> + ip4.dst==192.168.0.12 && icmp4.type==8 && icmp4.code==0"
>>> +check as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"
>>> +OVS_WAIT_UNTIL([
>>> + pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap |
>>> grep ${icmp_pkt_filter} -c`
>>> + test $pkts -ge 1
>>> +])
>>> +
>>> +# The original inport must not receive the looped-back copy.
>>> +AT_CHECK([
>>> + pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap |
>>> grep ${icmp_pkt_filter} -c`
>>> + test 0 -eq $pkts
>>> +])
>>> +
>>> +# The loop-prevention drop flow that has REGBIT_NF_LOOKUP_HIT
>>> (reg8=0x1000000)
>>> +# bit set must have hit at least once.
>>> +OVS_WAIT_UNTIL([
>>> + hits=`as hv1 ovs-ofctl dump-flows br-int | grep "reg8=0x1000000" | \
>>> + grep -v n_packets=0 | wc -l`
>>> + test $hits -ge 1
>>> +])
>>> +
>>> +OVN_CLEANUP([hv1])
>>
>> There's a cleanup race here as a new FDB entry might be learned in the
>> meantime (between flows are collected and recompute is issued and flows
>> collected again). Probably the easiest is to ignore FDB tables in this
>> case, i.e.:
>>
>> OVN_CLEANUP([hv1
>> ignored_tables=OFTABLE_GET_FDB,OFTABLE_LOOKUP_FDB])
>> AT_CLEANUP
>> ])
>
> Ack
>
>>
>>> +AT_CLEANUP
>>> +])
>>> +
>>> OVN_FOR_EACH_NORTHD([
>>> AT_SETUP([Unicast ARP when proxy ARP is configured])
>>> CHECK_SCAPY
>>
>> Regards,
>> Dumitru
>
>
> Thanks,
> Naveen
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev