> On 12 Aug 2026, at 3:35 PM, Dumitru Ceara <[email protected]> wrote: > > !-------------------------------------------------------------------| > CAUTION: External Email > > |-------------------------------------------------------------------! > > Hi Naveen,
Hi Dumitru, > > On 8/11/26 5:10 PM, Naveen Yerramneni wrote: >> >> >>> 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. > > At this point it seems like the simplest/safest approach to me so I'd > prefer always learning the inport if the packet is going to a NF. > > If in the future we realize we need to restrict this further (e.g., due > to too many flows being learned) we can revisit it and think of a better > way. As said above, one of the reasons I'm reluctant to accept the v5 > approach is because I'm not sure what impact it may have at scale on > non-NF deployments. Sure. I will send v6 with the changes. > >> 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. >> > > I'm not against it but I'm wondering what you're trying to achieve with > that. Idle flows will get evicted a bit early. It may not make significant difference. Setting below OVS data path offloaded idle timeout value may cause frequent up calls hence thought of setting it to 10 secs. > >>>> >>>>> } >>>>> >>>>> 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. >>> > > So does this mean we'll always hit the learn flows for these kinds of > switches (when they have NF applied) even if the packet is not going to > be sent out to a network function? Yes. > > But I guess I can have a closer look once v6 is out for review. > >>>> >>>>> + 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 >> >> > > Thanks, > Dumitru Thanks, Naveen _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
