On 11/13/25 8:39 PM, Lucas Vargas Dias wrote: > Hi all, > Hi Lucas,
> > I tested with the following two lines before AT_CLEANUP, and the leaks were > detected. > > as northd > OVS_APP_EXIT_AND_WAIT([ovn-northd]) > Ah, good catch, we're not stopping any of the daemons (NB/SB/ovn-northd) in the ovn-northd.at testsuite tests. Let me see if I can figure out a patch to do that. I'll CC you on it once I have it. Thanks, Dumitru > > Regards, > Lucas > > Em seg., 10 de nov. de 2025 às 10:22, Lucas Vargas Dias < > [email protected]> escreveu: > >> Hi Dumitru >> >> Em seg., 10 de nov. de 2025 às 10:18, Dumitru Ceara <[email protected]> >> escreveu: >> >>> On 11/10/25 2:11 PM, Lucas Vargas Dias wrote: >>>> Hi, >>> >>> Hi Lucas, >>> >>>> Thanks for review >>>> >>>> Em seg., 10 de nov. de 2025 às 09:50, Dumitru Ceara <[email protected]> >>>> escreveu: >>>> >>>>> On 10/22/25 4:05 PM, Lucas Vargas Dias via dev wrote: >>>>>> FIN or FIN-ACK packets from client was dropping because the client >>>>>> side was using conntrack. Connection is in SYN_SENT state because the >>>>>> response from backends bypass the conntrack, when client sends a FIN >>>>>> or FIN-ACK, the conntrack is invalid and packet is dropped. >>>>>> To fix it, remove the client side from conntrack, calculating the hash >>>>>> from packet to choice the backend. >>>>>> REG_IDX_LB_STATELESS is used to store the index from backend. >>>>>> >>>>>> Signed-off-by: Lucas Vargas Dias <[email protected]> >>>>>> --- >>>>> >>>>> Hi Lucas, >>>>> >>>>> Thanks for the patch! >>>>> >>>>>> northd/lb.c | 3 + >>>>>> northd/lb.h | 1 + >>>>>> northd/northd.c | 278 >>> +++++++++++++++++++++++++++++++------------- >>>>>> tests/multinode.at | 99 ++++++++++------ >>>>>> tests/ovn-northd.at | 218 ++++++++++++++++++++++++++++++++-- >>>>>> 5 files changed, 467 insertions(+), 132 deletions(-) >>>>>> >>>>>> diff --git a/northd/lb.c b/northd/lb.c >>>>>> index 919557ec4..2266d76e8 100644 >>>>>> --- a/northd/lb.c >>>>>> +++ b/northd/lb.c >>>>>> @@ -358,6 +358,9 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb, >>>>>> } >>>>>> lb->affinity_timeout = affinity_timeout; >>>>>> >>>>>> + lb->use_stateless_nat = smap_get_bool(&nbrec_lb->options, >>>>>> + "use_stateless_nat", >>> false); >>>>>> + >>>>>> const char *snat_ip = smap_get(&nbrec_lb->options, >>>>> "hairpin_snat_ip"); >>>>>> >>>>>> if (snat_ip && validate_snap_ip_address(snat_ip)) { >>>>>> diff --git a/northd/lb.h b/northd/lb.h >>>>>> index 43a8a1850..53dc4abf0 100644 >>>>>> --- a/northd/lb.h >>>>>> +++ b/northd/lb.h >>>>>> @@ -75,6 +75,7 @@ struct ovn_northd_lb { >>>>>> bool health_checks; >>>>>> >>>>>> char *hairpin_snat_ip; >>>>>> + bool use_stateless_nat; >>>>>> }; >>>>>> >>>>>> /* ovn-northd specific backend information. */ >>>>>> diff --git a/northd/northd.c b/northd/northd.c >>>>>> index 55e31659f..9da995cea 100644 >>>>>> --- a/northd/northd.c >>>>>> +++ b/northd/northd.c >>>>>> @@ -157,6 +157,7 @@ static bool vxlan_mode; >>>>>> #define REG_LB_PORT "reg2[0..15]" >>>>>> #define REG_CT_TP_DST "reg1[0..15]" >>>>>> #define REG_CT_PROTO "reg1[16..23]" >>>>>> +#define REG_IDX_LB_STATELESS "reg1[0..15]" >>>>>> >>>>>> /* Registers for ACL evaluation */ >>>>>> #define REGBIT_ACL_VERDICT_ALLOW "reg8[16]" >>>>>> @@ -12416,6 +12417,159 @@ lrouter_use_common_zone(const struct >>>>> ovn_datapath *od) >>>>>> return !od->is_gw_router && use_common_zone; >>>>>> } >>>>>> >>>>>> +static void >>>>>> +build_lrouter_flows_for_lb_stateless(struct lrouter_nat_lb_flows_ctx >>>>> *ctx, >>>>>> + struct ovn_datapath *od, >>>>>> + struct lflow_ref *lflow_ref, >>>>>> + struct ovn_port *dgp, >>>>>> + const char *meter) >>>>>> +{ >>>>>> + /* (NOTE) dnat_action: Add a new rule lr_in_dnat with backend IP >>>>>> + * and port action to skip conntrack completely. It is based on >>>>>> + * REG_IDX_LB_STATELESS which was calculated in lr_in_ct_extract. >>>>>> + * So, if the packet has VIP IP destination and port >>>>>> + * (if port was configured), it selecs a backend based on >>>>>> + * REG_IDX_LB_STATELESS. It works to multi-chassis and avoid to >>>>>> + * sync conntrack betweem them. >>>>>> + */ >>>>>> + struct ds new_action_stateless_nat = DS_EMPTY_INITIALIZER; >>>>>> + struct ds new_match_stateless_nat = DS_EMPTY_INITIALIZER; >>>>>> + if (!vector_is_empty(&ctx->lb_vip->backends) || >>>>>> + !ctx->lb_vip->empty_backend_rej) { >>>>>> + ds_put_format(&new_match_stateless_nat, >>>>> "is_chassis_resident(%s)", >>>>>> + dgp->cr_port->json_key); >>>>>> + } >>>>>> + >>>>>> + bool ipv4 = ctx->lb_vip->address_family == AF_INET; >>>>>> + const char *ip_match = ipv4 ? "ip4" : "ip6"; >>>>>> + ds_put_format(&new_match_stateless_nat, " && %s && %s.dst == %s", >>>>>> + ip_match, ip_match, ctx->lb_vip->vip_str); >>>>> >>>>> Nit: should be aligned one space to the left, under '&'. >>>>> >>>> >>>> I agree. >>>> >>>> >>>>> >>>>>> + if (ctx->lb_vip->port_str) { >>>>>> + ds_put_format(&new_match_stateless_nat, >>>>>> + " && %s && %s.dst == %s", >>>>>> + ctx->lb->proto, ctx->lb->proto, >>>>>> + ctx->lb_vip->port_str); >>>>>> + } >>>>>> + >>>>>> + const struct ovn_lb_backend *backend; >>>>>> + if (vector_len(&ctx->lb_vip->backends) == 1) { >>>>>> + backend = vector_get_ptr(&ctx->lb_vip->backends, 0); >>>>>> + ds_put_format(&new_action_stateless_nat, "%s.dst = %s; ", >>>>>> + ip_match, backend->ip_str); >>>>>> + if (ctx->lb_vip->port_str) { >>>>>> + ds_put_format(&new_action_stateless_nat, "%s.dst = %s; ", >>>>>> + ctx->lb->proto, backend->port_str); >>>>>> + } >>>>>> + ds_put_format(&new_action_stateless_nat, "next;"); >>>>>> + ovn_lflow_add_with_hint__(ctx->lflows, od, S_ROUTER_IN_DNAT, >>>>>> + ctx->prio, >>>>>> + ds_cstr(&new_match_stateless_nat), >>>>>> + ds_cstr(&new_action_stateless_nat), >>>>>> + NULL, meter, >>> &ctx->lb->nlb->header_, >>>>>> + lflow_ref); >>>>>> + } >>>>>> + size_t match_len = new_match_stateless_nat.length; >>>>>> + size_t i = 0; >>>>>> + VECTOR_FOR_EACH_PTR (&ctx->lb_vip->backends, backend) { >>>>>> + if (vector_len(&ctx->lb_vip->backends) <= 1) { >>>>>> + break; >>>>>> + } >>>>>> + ds_put_format(&new_match_stateless_nat, " && " >>>>>> + REG_IDX_LB_STATELESS" == ""%"PRIuSIZE, i++); >>>>>> + ds_put_format(&new_action_stateless_nat, "%s.dst = %s; ", >>>>>> + ip_match, backend->ip_str); >>>>>> + if (ctx->lb_vip->port_str) { >>>>>> + ds_put_format(&new_action_stateless_nat, "%s.dst = %s; ", >>>>>> + ctx->lb->proto, backend->port_str); >>>>>> + } >>>>>> + ds_put_format(&new_action_stateless_nat, "next;"); >>>>>> + ovn_lflow_add_with_hint__(ctx->lflows, od, S_ROUTER_IN_DNAT, >>>>>> + ctx->prio, >>>>>> + ds_cstr(&new_match_stateless_nat), >>>>>> + ds_cstr(&new_action_stateless_nat), >>>>>> + NULL, meter, >>> &ctx->lb->nlb->header_, >>>>>> + lflow_ref); >>>>>> + ds_clear(&new_action_stateless_nat); >>>>>> + ds_truncate(&new_match_stateless_nat, match_len); >>>>>> + } >>>>>> + >>>>>> + >>>>> >>>>> We leak 'new_match_stateless_nat' and 'new_action_stateless_nat' here. >>>>> As the system and unit tests were green it means we're not testing this >>>>> in non-multinode tests. Is that right? Can we add a small >>>>> ovn-northd.at test for this scenario too? >>>>> >>>> >>>> Actually, we are testing in "Load balancer with Distributed Gateway >>> Ports >>>> (LB + DGP + NAT Stateless". >>>> >>> >>> Hmm, that's weird, we run tests with address sanitizer enabled and that >>> should detect the memory leak. And unless I completely misread the code >>> there is a leak here in all cases. >>> >> >> You're right, there's a leak. I didn't understand why it didn't find the >> leak. >> >> >>>>> >>>>>> + if (vector_is_empty(&ctx->lb_vip->backends)) { >>>>>> + return; >>>>>> + } >>>>>> + >>>>>> + size_t undnat_match_len = ctx->undnat_match->length; >>>>>> + struct ds undnat_action = DS_EMPTY_INITIALIZER; >>>>>> + struct ds snat_action = DS_EMPTY_INITIALIZER; >>>>>> + >>>>>> + >>>>>> + /* undnat_action: Just follows the pipeline in the lr_out_undenat >>>>> NAT rule. >>>>>> + */ >>>>>> + ds_put_format(&undnat_action, "next;"); >>>>>> + >>>>>> + /* We need to centralize the LB traffic to properly perform >>>>>> + * the undnat stage. >>>>>> + */ >>>>>> + ds_put_format(ctx->undnat_match, ") && outport == %s", >>>>> dgp->json_key); >>>>>> + ds_clear(ctx->gw_redir_action); >>>>>> + ds_put_format(ctx->gw_redir_action, "outport = %s; next;", >>>>>> + dgp->cr_port->json_key); >>>>>> + >>>>>> + ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_IN_GW_REDIRECT, >>>>>> + 200, ds_cstr(ctx->undnat_match), >>>>>> + ds_cstr(ctx->gw_redir_action), >>>>>> + &ctx->lb->nlb->header_, >>>>>> + lflow_ref); >>>>>> + ds_truncate(ctx->undnat_match, undnat_match_len); >>>>>> + >>>>>> + ds_put_format(ctx->undnat_match, ") && (inport == %s || outport >>> == >>>>> %s)" >>>>>> + " && is_chassis_resident(%s)", dgp->json_key, >>>>> dgp->json_key, >>>>>> + dgp->cr_port->json_key); >>>>> >>>>> Nit: this reads a bit weird to me, I'd write it as: >>>>> >>>>> ds_put_format(ctx->undnat_match, ") && (inport == %s || outport == >>> %s)" >>>>> " && is_chassis_resident(%s)", >>>>> dgp->json_key, dgp->json_key, >>>>> dgp->cr_port->json_key); >>>>> >>>>> >>>> I agree. >>>> >>>> >>>>>> + >>>>>> + /* Use the LB protocol as matching criteria for out undnat and >>> snat >>>>> when >>>>>> + * creating LBs with stateless NAT. */ >>>>>> + ds_put_format(ctx->undnat_match, " && %s", ctx->lb->proto); >>>>>> + >>>>>> + ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_UNDNAT, >>> 120, >>>>>> + ds_cstr(ctx->undnat_match), >>>>>> + ds_cstr(&undnat_action), >>>>> &ctx->lb->nlb->header_, >>>>>> + lflow_ref); >>>>>> + >>>>>> + /* (NOTE) snat_action: Add a new rule lr_out_snat with LB VIP as >>>>> source >>>>>> + * IP action to perform stateless NAT pipeline completely when >>> the >>>>>> + * outgoing packet is redirected to a chassis that does not have >>> an >>>>>> + * active conntrack entry. Otherwise, it will not be SNATed by >>> the >>>>>> + * ct_lb action because it does not refer to a valid created >>> flow. >>>>> The >>>>>> + * use case for responding to a packet in different chassis is >>>>> multipath >>>>>> + * via ECMP. So, the LB lr_out_snat is created with a lower >>>>> priority than >>>>>> + * the other router pipeline entries, in this case, if the packet >>>>> is not >>>>>> + * SNATed by ct_lb (conntrack lost), it will be SNATed by the LB >>>>>> + * stateless NAT rule. Also, SNAT is performed only when the >>> packet >>>>>> + * matches the configured LB backend IPs, ports and protocols. >>>>> Otherwise, >>>>>> + * the packet will be forwarded without SNAted interference. >>>>>> + */ >>>>>> + if (ctx->lb_vip->port_str) { >>>>>> + ds_put_format(&snat_action, "%s.src = %s; %s.src = %s; >>> next;", >>>>>> + ctx->lb_vip->address_family == AF_INET6 ? >>>>>> + "ip6" : "ip4", >>>>> >>>>> We have the 'bool ipv4' variable, set earlier in the function. This >>> could >>>>> be: >>>>> >>>>> ipv4 ? "ip4" : "ip6", >>>>> >>>>> I agree. >>>> >>>> >>>>>> + ctx->lb_vip->vip_str, ctx->lb->proto, >>>>>> + ctx->lb_vip->port_str); >>>>>> + } else { >>>>>> + ds_put_format(&snat_action, "%s.src = %s; next;", >>>>>> + ctx->lb_vip->address_family == AF_INET6 ? >>>>>> + "ip6" : "ip4", >>>>> >>>>> Same here. >>>>> >>>>> I agree. >>>> >>>> >>>>>> + ctx->lb_vip->vip_str); >>>>>> + } >>>>>> + ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_SNAT, 160, >>>>>> + ds_cstr(ctx->undnat_match), >>>>>> + ds_cstr(&snat_action), >>>>> &ctx->lb->nlb->header_, >>>>>> + lflow_ref); >>>>>> + >>>>>> + ds_truncate(ctx->undnat_match, undnat_match_len); >>>>>> + ds_destroy(&undnat_action); >>>>>> + ds_destroy(&snat_action); >>>>>> +} >>>>>> + >>>>>> static void >>>>>> build_distr_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx >>>>> *ctx, >>>>>> enum lrouter_nat_lb_flow_type >>> type, >>>>>> @@ -12424,60 +12578,34 @@ build_distr_lrouter_nat_flows_for_lb(struct >>>>> lrouter_nat_lb_flows_ctx *ctx, >>>>>> struct ovn_port *dgp, >>>>>> bool stateless_nat) >>>>>> { >>>>>> - struct ds dnat_action = DS_EMPTY_INITIALIZER; >>>>>> - >>>>>> /* Store the match lengths, so we can reuse the ds buffer. */ >>>>>> size_t new_match_len = ctx->new_match->length; >>>>>> size_t undnat_match_len = ctx->undnat_match->length; >>>>>> >>>>>> - /* (NOTE) dnat_action: Add the first LB backend IP as a >>> destination >>>>>> - * action of the lr_in_dnat NAT rule. Including the backend IP is >>>>> useful >>>>>> - * for accepting packets coming from a chassis that does not have >>>>>> - * previously established conntrack entries. This means that the >>>>> actions >>>>>> - * (ip4.dst + ct_lb_mark) are executed in addition and ip4.dst is >>>>> not >>>>>> - * useful when traffic passes through the same chassis for >>>>> ingress/egress >>>>>> - * packets. However, the actions are complementary in cases where >>>>> traffic >>>>>> - * enters from one chassis, the ack response comes from another >>>>> chassis, >>>>>> - * and the final ack step of the TCP handshake comes from the >>> first >>>>>> - * chassis used. Without using stateless NAT, the connection will >>>>> not be >>>>>> - * established because the return packet followed a path through >>>>> another >>>>>> - * chassis and only ct_lb_mark will not be able to receive the >>> ack >>>>> and >>>>>> - * forward it to the right backend. With using stateless NAT, the >>>>> packet >>>>>> - * will be accepted and forwarded to the same backend that >>>>> corresponds to >>>>>> - * the previous conntrack entry that is in the SYN_SENT state >>>>>> - * (created by ct_lb_mark for the first rcv packet in this flow). >>>>>> - */ >>>>>> - if (stateless_nat) { >>>>>> - if (!vector_is_empty(&ctx->lb_vip->backends)) { >>>>>> - const struct ovn_lb_backend *backend = >>>>>> - vector_get_ptr(&ctx->lb_vip->backends, 0); >>>>>> - bool ipv6 = !IN6_IS_ADDR_V4MAPPED(&backend->ip); >>>>>> - ds_put_format(&dnat_action, "%s.dst = %s; ", ipv6 ? >>> "ip6" : >>>>> "ip4", >>>>>> - backend->ip_str); >>>>>> - } >>>>>> - } >>>>>> - ds_put_format(&dnat_action, "%s", ctx->new_action[type]); >>>>>> - >>>>>> const char *meter = NULL; >>>>>> >>>>>> if (ctx->reject) { >>>>>> meter = copp_meter_get(COPP_REJECT, od->nbr->copp, >>>>> ctx->meter_groups); >>>>>> } >>>>>> >>>>>> + if (stateless_nat) { >>>>>> + return build_lrouter_flows_for_lb_stateless(ctx, od, >>> lflow_ref, >>>>>> + dgp, meter); >>>>>> + } >>>>>> + >>>>>> if (!vector_is_empty(&ctx->lb_vip->backends) || >>>>>> !ctx->lb_vip->empty_backend_rej) { >>>>>> ds_put_format(ctx->new_match, " && is_chassis_resident(%s)", >>>>>> dgp->cr_port->json_key); >>>>>> } >>>>>> >>>>>> - ovn_lflow_add_with_hint__(ctx->lflows, od, S_ROUTER_IN_DNAT, >>>>> ctx->prio, >>>>>> - ds_cstr(ctx->new_match), >>>>> ds_cstr(&dnat_action), >>>>>> - NULL, meter, &ctx->lb->nlb->header_, >>>>>> - lflow_ref); >>>>>> + ovn_lflow_add_with_hint__(ctx->lflows, od, S_ROUTER_IN_DNAT, >>>>>> + ctx->prio, ds_cstr(ctx->new_match), >>>>>> + ctx->new_action[type], NULL, meter, >>>>>> + &ctx->lb->nlb->header_, lflow_ref); >>>>>> >>>>>> ds_truncate(ctx->new_match, new_match_len); >>>>>> >>>>>> - ds_destroy(&dnat_action); >>>>>> if (vector_is_empty(&ctx->lb_vip->backends)) { >>>>>> return; >>>>>> } >>>>>> @@ -12500,13 +12628,6 @@ build_distr_lrouter_nat_flows_for_lb(struct >>>>> lrouter_nat_lb_flows_ctx *ctx, >>>>>> break; >>>>>> } >>>>>> >>>>>> - /* undnat_action: Remove the ct action from the lr_out_undenat >>> NAT >>>>> rule. >>>>>> - */ >>>>>> - if (stateless_nat) { >>>>>> - ds_clear(&undnat_action); >>>>>> - ds_put_format(&undnat_action, "next;"); >>>>>> - } >>>>>> - >>>>>> /* We need to centralize the LB traffic to properly perform >>>>>> * the undnat stage. >>>>>> */ >>>>>> @@ -12525,48 +12646,11 @@ build_distr_lrouter_nat_flows_for_lb(struct >>>>> lrouter_nat_lb_flows_ctx *ctx, >>>>>> ds_put_format(ctx->undnat_match, ") && (inport == %s || outport >>> == >>>>> %s)" >>>>>> " && is_chassis_resident(%s)", dgp->json_key, >>>>> dgp->json_key, >>>>>> dgp->cr_port->json_key); >>>>>> - /* Use the LB protocol as matching criteria for out undnat and >>> snat >>>>> when >>>>>> - * creating LBs with stateless NAT. */ >>>>>> - if (stateless_nat) { >>>>>> - ds_put_format(ctx->undnat_match, " && %s", ctx->lb->proto); >>>>>> - } >>>>>> ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_UNDNAT, >>> 120, >>>>>> ds_cstr(ctx->undnat_match), >>>>>> ds_cstr(&undnat_action), >>>>> &ctx->lb->nlb->header_, >>>>>> lflow_ref); >>>>>> >>>>>> - /* (NOTE) snat_action: Add a new rule lr_out_snat with LB VIP as >>>>> source >>>>>> - * IP action to perform stateless NAT pipeline completely when >>> the >>>>>> - * outgoing packet is redirected to a chassis that does not have >>> an >>>>>> - * active conntrack entry. Otherwise, it will not be SNATed by >>> the >>>>>> - * ct_lb action because it does not refer to a valid created >>> flow. >>>>> The >>>>>> - * use case for responding to a packet in different chassis is >>>>> multipath >>>>>> - * via ECMP. So, the LB lr_out_snat is created with a lower >>>>> priority than >>>>>> - * the other router pipeline entries, in this case, if the packet >>>>> is not >>>>>> - * SNATed by ct_lb (conntrack lost), it will be SNATed by the LB >>>>>> - * stateless NAT rule. Also, SNAT is performed only when the >>> packet >>>>>> - * matches the configured LB backend IPs, ports and protocols. >>>>> Otherwise, >>>>>> - * the packet will be forwarded without SNAted interference. >>>>>> - */ >>>>>> - if (stateless_nat) { >>>>>> - if (ctx->lb_vip->port_str) { >>>>>> - ds_put_format(&snat_action, "%s.src = %s; %s.src = %s; >>>>> next;", >>>>>> - ctx->lb_vip->address_family == AF_INET6 ? >>>>>> - "ip6" : "ip4", >>>>>> - ctx->lb_vip->vip_str, ctx->lb->proto, >>>>>> - ctx->lb_vip->port_str); >>>>>> - } else { >>>>>> - ds_put_format(&snat_action, "%s.src = %s; next;", >>>>>> - ctx->lb_vip->address_family == AF_INET6 ? >>>>>> - "ip6" : "ip4", >>>>>> - ctx->lb_vip->vip_str); >>>>>> - } >>>>>> - ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_SNAT, >>> 160, >>>>>> - ds_cstr(ctx->undnat_match), >>>>>> - ds_cstr(&snat_action), >>>>> &ctx->lb->nlb->header_, >>>>>> - lflow_ref); >>>>>> - } >>>>>> - >>>>>> ds_truncate(ctx->undnat_match, undnat_match_len); >>>>>> ds_destroy(&undnat_action); >>>>>> ds_destroy(&snat_action); >>>>>> @@ -12726,8 +12810,7 @@ build_lrouter_nat_flows_for_lb( >>>>>> * lflow generation for them. >>>>>> */ >>>>>> size_t index; >>>>>> - bool use_stateless_nat = smap_get_bool(&lb->nlb->options, >>>>>> - "use_stateless_nat", >>> false); >>>>>> + bool use_stateless_nat = lb->use_stateless_nat; >>>>>> DYNAMIC_BITMAP_FOR_EACH_1 (index, &lb_dps->nb_lr_map) { >>>>>> struct ovn_datapath *od = vector_get(&lr_datapaths->dps, >>> index, >>>>>> struct ovn_datapath *); >>>>>> @@ -12876,21 +12959,48 @@ build_lrouter_defrag_flows_for_lb(struct >>>>> ovn_lb_datapaths *lb_dps, >>>>>> if (dynamic_bitmap_is_empty(&lb_dps->nb_lr_map)) { >>>>>> return; >>>>>> } >>>>>> + struct ds action = DS_EMPTY_INITIALIZER; >>>>>> >>>>>> for (size_t i = 0; i < lb_dps->lb->n_vips; i++) { >>>>>> struct ovn_lb_vip *lb_vip = &lb_dps->lb->vips[i]; >>>>>> bool ipv6 = lb_vip->address_family == AF_INET6; >>>>>> int prio = 100; >>>>>> - >>>>>> + enum ovn_stage stage = S_ROUTER_IN_DEFRAG; >>>>>> ds_clear(match); >>>>>> ds_put_format(match, "ip && ip%c.dst == %s", ipv6 ? '6' : >>> '4', >>>>>> lb_vip->vip_str); >>>>>> - >>>>>> + if (lb_dps->lb->use_stateless_nat) { >>>>>> + stage = S_ROUTER_IN_CT_EXTRACT; >>>>>> + prio = 120; >>>>>> + if (vector_len(&lb_vip->backends) > 1) { >>>>>> + ds_put_format(&action, REG_IDX_LB_STATELESS" = >>>>> select("); >>>>>> + if (lb_dps->lb->selection_fields) { >>>>>> + ds_put_format(&action, "values=("); >>>>>> + } >>>>>> + for (size_t idx_backend = 0; idx_backend < >>>>>> + vector_len(&lb_vip->backends); >>>>>> + idx_backend++) { >>>>>> + ds_put_format(&action, "%"PRIuSIZE",", >>> idx_backend); >>>>>> + } >>>>>> + ds_truncate(&action, action.length - 1); >>>>>> + if (lb_dps->lb->selection_fields) { >>>>>> + ds_put_format(&action, "); hash_fields=\"%s\"", >>>>>> + lb_dps->lb->selection_fields); >>>>>> + } >>>>>> + ds_put_format(&action,");"); >>>>>> + } else { >>>>>> + ds_put_format(&action, "next;"); >>>>>> + } >>>>> >>>>> Maybe extract this part into a function for stateless-nat LBs like >>>>> we do with the other functions that build logical flows for such >>>>> load balancers? >>>>> >>>>> I agree. >>>> >>>> >>>>>> + } else { >>>>>> + ds_put_format(&action, "ct_dnat;"); >>>>>> + } >>>>>> ovn_lflow_add_with_dp_group( >>>>>> lflows, lb_dps->nb_lr_map.map, ods_size(lr_datapaths), >>>>>> - S_ROUTER_IN_DEFRAG, prio, ds_cstr(match), "ct_dnat;", >>>>>> + stage, prio, ds_cstr(match), ds_cstr(&action), >>>>>> &lb_dps->lb->nlb->header_, lb_dps->lflow_ref); >>>>>> + ds_clear(&action); >>>>>> } >>>>>> + ds_destroy(&action); >>>>>> } >>>>>> >>>>>> static void >>>>>> diff --git a/tests/multinode.at b/tests/multinode.at >>>>>> index a2c8e3f55..c4ec71481 100644 >>>>>> --- a/tests/multinode.at >>>>>> +++ b/tests/multinode.at >>>>>> @@ -1643,6 +1643,7 @@ check multinode_nbctl ls-lb-add sw0 lb0 >>>>>> >>>>>> # Set use_stateless_nat to true >>>>>> check multinode_nbctl set load_balancer lb0 >>>>> options:use_stateless_nat=true >>>>>> +check multinode_nbctl set load_balancer lb0 >>>>> selection_fields="ip_src,tp_src,ip_dst,tp_dst" >>>>>> >>>>>> # Start backend http services >>>>>> M_NS_DAEMONIZE([ovn-chassis-1], [sw0p1], [python3 -m http.server >>> --bind >>>>> 10.0.1.3 80 >/dev/null 2>&1], [http1.pid]) >>>>>> @@ -1674,14 +1675,12 @@ m_as ovn-gw-2 ovs-appctl dpctl/flush-conntrack >>>>>> M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c 'curl -v >>> 172.16.0.100:80 >>>>> --retry 0 --connect-timeout 1 --max-time 1 --local-port 59001']) >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | >>>>> M_FORMAT_CT(20.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59001,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59001),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59001,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59001),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c 'curl -v >>> 172.16.0.100:80 >>>>> --retry 0 --connect-timeout 1 --max-time 1 --local-port 59000']) >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | >>>>> M_FORMAT_CT(30.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59000,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59000),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59000,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59000),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> @@ -1712,7 +1711,6 @@ Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> # Check if we have only one backend for the same connection - orig + >>>>> dest ports >>>>>> OVS_WAIT_FOR_OUTPUT([echo -e $gw1_ct | M_FORMAT_CT(20.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59004,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59004),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59004,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59004),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> @@ -1778,7 +1776,6 @@ Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> # Check if we have only one backend for the same connection - orig + >>>>> dest ports >>>>>> OVS_WAIT_FOR_OUTPUT([echo -e $gw1_ct | M_FORMAT_CT(20.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59005,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59005),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59005,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59005),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> @@ -1834,7 +1831,6 @@ Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> # Check if we have only one backend for the same connection - orig + >>>>> dest ports >>>>>> OVS_WAIT_FOR_OUTPUT([echo -e $gw2_ct | M_FORMAT_CT(30.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59006,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59006),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59006,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59006),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> @@ -1900,7 +1896,6 @@ Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> # Check if we have only one backend for the same connection - orig + >>>>> dest ports >>>>>> OVS_WAIT_FOR_OUTPUT([echo -e $gw2_ct | M_FORMAT_CT(30.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> -tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59007,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59007),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59007,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59007),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> @@ -1987,7 +1982,6 @@ M_NS_EXEC([ovn-chassis-3], [publicp1], [sh -c >>>>> 'curl -v -O 172.16.0.100:9000/down >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-1 ovs-appctl dpctl/dump-conntrack | >>>>> M_FORMAT_CT(20.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59008,dport=80),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59008),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> -tcp,orig=(src=20.0.1.3,dst=<cleared>,sport=59008,dport=9000),reply=(src=<cleared>,dst=20.0.1.3,sport=80,dport=59008),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [9000])], [0], [dnl >>>>>> @@ -2003,7 +1997,6 @@ M_NS_EXEC([ovn-chassis-4], [publicp2], [sh -c >>>>> 'curl -v -O 172.16.0.100:9000/down >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-gw-2 ovs-appctl dpctl/dump-conntrack | >>>>> M_FORMAT_CT(30.0.1.3) | \ >>>>>> grep tcp | sed -E -e 's/10.0.1.3|10.0.1.4/<cleared>/g' | sort], [0], >>>>> [dnl >>>>>> >>>>> >>> tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59008,dport=80),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59008),zone=<cleared>,protoinfo=(state=<cleared>) >>>>>> >>>>> >>> -tcp,orig=(src=30.0.1.3,dst=<cleared>,sport=59008,dport=9000),reply=(src=<cleared>,dst=30.0.1.3,sport=80,dport=59008),zone=<cleared>,mark=<cleared>,protoinfo=(state=<cleared>) >>>>>> ]) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [9000])], [0], [dnl >>>>>> @@ -2036,14 +2029,14 @@ done >>>>>> # | >>>>>> # >>>>> +.............................|.............................+ >>>>>> # | >>>>> | >>>>>> -# DGP publicp3 (ovn-gw-3) (20.0.2.3/24) DGP >>>>> publicp4 (ovn-gw-4) (20.0.2.4/24) >>>>>> +# DGP publicp3 (ovn-gw-3) (20.0.1.2/24) DGP >>>>> publicp4 (ovn-gw-4) (20.0.2.4/24) >>>>>> # | >>>>> | >>>>>> # >>>>> +.............................+.............................+ >>>>>> -# | >>>>>> -# | (overlay) >>>>>> +# | >>>>> | >>>>>> +# | (public_right) >>>>> (public_left)| >>>>>> # >>>>> +.............................+.............................+ >>>>>> # | >>>>> | >>>>>> -# DGP public1 (ovn-gw-1) (20.0.2.1/24) DGP >>>>> public2 (ovn-gw-2) (20.0.2.2/24) >>>>>> +# DGP public1 (ovn-gw-1) (20.0.1.1/24) DGP >>>>> public2 (ovn-gw-2) (20.0.2.2/24) >>>>>> # | >>>>> | >>>>>> # >>>>> +.............................+.............................+ >>>>>> # | >>>>>> @@ -2100,17 +2093,18 @@ check multinode_nbctl lsp-set-addresses >>> sw0-lr0 >>>>> router >>>>>> check multinode_nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 >>>>>> >>>>>> # create external connection for N/S traffic using multiple DGPs >>>>>> -check multinode_nbctl ls-add public >>>>>> +check multinode_nbctl ls-add public_right >>>>>> +check multinode_nbctl ls-add public_left >>>>>> >>>>>> # create external connection for N/S traffic >>>>>> # DGP public1 >>>>>> -check multinode_nbctl lsp-add public ln-public-1 >>>>>> +check multinode_nbctl lsp-add public_right ln-public-1 >>>>> >>>>> This file needs a small rebase now that the patch adding the new >>>>> "lsp-add-localnet-port" nbctl helper has been merged to main. >>>>> >>>>> It should be relatively straightforward to rebase though. >>>>> >>>>>> check multinode_nbctl lsp-set-type ln-public-1 localnet >>>>>> check multinode_nbctl lsp-set-addresses ln-public-1 unknown >>>>>> check multinode_nbctl lsp-set-options ln-public-1 >>> network_name=public1 >>>>>> >>>>>> # DGP public2 >>>>>> -check multinode_nbctl lsp-add public ln-public-2 >>>>>> +check multinode_nbctl lsp-add public_left ln-public-2 >>>>>> check multinode_nbctl lsp-set-type ln-public-2 localnet >>>>>> check multinode_nbctl lsp-set-addresses ln-public-2 unknown >>>>>> check multinode_nbctl lsp-set-options ln-public-2 >>> network_name=public2 >>>>>> @@ -2121,8 +2115,8 @@ m_as ovn-gw-1 ovs-vsctl set open . >>>>> external-ids:ovn-bridge-mappings=public1:br-e >>>>>> # Attach DGP public2 to GW-2 public2 (overlay connectivity) >>>>>> m_as ovn-gw-2 ovs-vsctl set open . >>>>> external-ids:ovn-bridge-mappings=public2:br-ex >>>>>> >>>>>> -check multinode_nbctl lrp-add lr0 lr0-public-p1 40:54:00:00:00:01 >>>>> 20.0.2.1/24 2000::1/64 >>>>>> -check multinode_nbctl lsp-add public public-lr0-p1 >>>>>> +check multinode_nbctl lrp-add lr0 lr0-public-p1 40:54:00:00:00:01 >>>>> 20.0.1.1/24 2000::1/64 >>>>>> +check multinode_nbctl lsp-add public_right public-lr0-p1 >>>>>> check multinode_nbctl lsp-set-type public-lr0-p1 router >>>>>> check multinode_nbctl lsp-set-addresses public-lr0-p1 router >>>>>> check multinode_nbctl lsp-set-options public-lr0-p1 >>>>> router-port=lr0-public-p1 >>>>>> @@ -2130,13 +2124,13 @@ check multinode_nbctl lrp-set-gateway-chassis >>>>> lr0-public-p1 ovn-gw-1 10 >>>>>> >>>>>> m_wait_for_ports_up >>>>>> >>>>>> -M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.2.1 | FORMAT_PING], \ >>>>>> +M_NS_CHECK_EXEC([ovn-chassis-1], [sw0p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.1.1 | FORMAT_PING], \ >>>>>> [0], [dnl >>>>>> 3 packets transmitted, 3 received, 0% packet loss, time 0ms >>>>>> ]) >>>>>> >>>>>> check multinode_nbctl lrp-add lr0 lr0-public-p2 40:54:00:00:00:02 >>>>> 20.0.2.2/24 2000::2/64 >>>>>> -check multinode_nbctl lsp-add public public-lr0-p2 >>>>>> +check multinode_nbctl lsp-add public_left public-lr0-p2 >>>>>> check multinode_nbctl lsp-set-type public-lr0-p2 router >>>>>> check multinode_nbctl lsp-set-addresses public-lr0-p2 router >>>>>> check multinode_nbctl lsp-set-options public-lr0-p2 >>>>> router-port=lr0-public-p2 >>>>>> @@ -2157,7 +2151,7 @@ check multinode_nbctl lsp-set-options sw1-lr1 >>>>> router-port=lr1-sw1 >>>>>> >>>>>> # create external connection for N/S traffic >>>>>> # DGP public3 >>>>>> -check multinode_nbctl lsp-add public ln-public-3 >>>>>> +check multinode_nbctl lsp-add public_right ln-public-3 >>>>>> check multinode_nbctl lsp-set-type ln-public-3 localnet >>>>>> check multinode_nbctl lsp-set-addresses ln-public-3 unknown >>>>>> check multinode_nbctl lsp-set-options ln-public-3 >>> network_name=public3 >>>>>> @@ -2165,8 +2159,8 @@ check multinode_nbctl lsp-set-options >>> ln-public-3 >>>>> network_name=public3 >>>>>> # Attach DGP public3 to GW-3 public3 (overlay connectivity) >>>>>> m_as ovn-gw-3 ovs-vsctl set open . >>>>> external-ids:ovn-bridge-mappings=public3:br-ex >>>>>> >>>>>> -check multinode_nbctl lrp-add lr1 lr1-public-p3 40:54:00:00:00:03 >>>>> 20.0.2.3/24 2000::3/64 >>>>>> -check multinode_nbctl lsp-add public public-lr1-p3 >>>>>> +check multinode_nbctl lrp-add lr1 lr1-public-p3 40:54:00:00:00:03 >>>>> 20.0.1.2/24 2000::3/64 >>>>>> +check multinode_nbctl lsp-add public_right public-lr1-p3 >>>>>> check multinode_nbctl lsp-set-type public-lr1-p3 router >>>>>> check multinode_nbctl lsp-set-addresses public-lr1-p3 router >>>>>> check multinode_nbctl lsp-set-options public-lr1-p3 >>>>> router-port=lr1-public-p3 >>>>>> @@ -2177,21 +2171,16 @@ M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], >>> [ping >>>>> -q -c 3 -i 0.3 -w 2 40.0.2.1 | F >>>>>> 3 packets transmitted, 3 received, 0% packet loss, time 0ms >>>>>> ]) >>>>>> >>>>>> -M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.2.3 | FORMAT_PING], \ >>>>>> +M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.1.2 | FORMAT_PING], \ >>>>>> [0], [dnl >>>>>> 3 packets transmitted, 3 received, 0% packet loss, time 0ms >>>>>> ]) >>>>>> >>>>>> # Add a default route for multiple DGPs using ECMP - first step >>>>>> -check multinode_nbctl --ecmp lr-route-add lr0 0.0.0.0/0 20.0.2.3 >>>>>> -check multinode_nbctl --ecmp lr-route-add lr1 0.0.0.0/0 20.0.2.1 >>>>>> - >>>>>> -# Add SNAT rules using gateway-port >>>>>> -check multinode_nbctl --gateway-port lr0-public-p1 lr-nat-add lr0 >>> snat >>>>> 20.0.2.1 10.0.2.0/24 >>>>>> -check multinode_nbctl --gateway-port lr0-public-p2 lr-nat-add lr0 >>> snat >>>>> 20.0.2.2 10.0.2.0/24 >>>>>> -check multinode_nbctl --gateway-port lr1-public-p3 lr-nat-add lr1 >>> snat >>>>> 20.0.2.3 40.0.2.0/24 >>>>>> +check multinode_nbctl --ecmp lr-route-add lr0 0.0.0.0/0 20.0.1.2 >>>>>> +check multinode_nbctl --ecmp lr-route-add lr1 0.0.0.0/0 20.0.1.1 >>>>>> >>>>>> -M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.2.1 | FORMAT_PING], \ >>>>>> +M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2 >>>>> 20.0.1.1 | FORMAT_PING], \ >>>>>> [0], [dnl >>>>>> 3 packets transmitted, 3 received, 0% packet loss, time 0ms >>>>>> ]) >>>>>> @@ -2203,7 +2192,7 @@ M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping >>> -q >>>>> -c 3 -i 0.3 -w 2 20.0.2.2 | F >>>>>> >>>>>> # Configure the second DGP for the lr1 >>>>>> # DGP public4 >>>>>> -check multinode_nbctl lsp-add public ln-public-4 >>>>>> +check multinode_nbctl lsp-add public_left ln-public-4 >>>>>> check multinode_nbctl lsp-set-type ln-public-4 localnet >>>>>> check multinode_nbctl lsp-set-addresses ln-public-4 unknown >>>>>> check multinode_nbctl lsp-set-options ln-public-4 >>> network_name=public4 >>>>>> @@ -2212,7 +2201,7 @@ check multinode_nbctl lsp-set-options >>> ln-public-4 >>>>> network_name=public4 >>>>>> m_as ovn-gw-4 ovs-vsctl set open . >>>>> external-ids:ovn-bridge-mappings=public4:br-ex >>>>>> >>>>>> check multinode_nbctl lrp-add lr1 lr1-public-p4 40:54:00:00:00:04 >>>>> 20.0.2.4/24 2000::4/64 >>>>>> -check multinode_nbctl lsp-add public public-lr1-p4 >>>>>> +check multinode_nbctl lsp-add public_left public-lr1-p4 >>>>>> check multinode_nbctl lsp-set-type public-lr1-p4 router >>>>>> check multinode_nbctl lsp-set-addresses public-lr1-p4 router >>>>>> check multinode_nbctl lsp-set-options public-lr1-p4 >>>>> router-port=lr1-public-p4 >>>>>> @@ -2223,9 +2212,6 @@ M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping >>> -q >>>>> -c 3 -i 0.3 -w 2 20.0.2.4 | F >>>>>> 3 packets transmitted, 3 received, 0% packet loss, time 0ms >>>>>> ]) >>>>>> >>>>>> -# Add SNAT rules using gateway-port >>>>>> -check multinode_nbctl --gateway-port lr1-public-p4 lr-nat-add lr1 >>> snat >>>>> 20.0.2.4 40.0.2.0/24 >>>>>> - >>>>>> # Add a default route for multiple DGPs using ECMP - second step >>>>> (multipath) >>>>>> check multinode_nbctl --ecmp lr-route-add lr0 0.0.0.0/0 20.0.2.4 >>>>>> check multinode_nbctl --ecmp lr-route-add lr1 0.0.0.0/0 20.0.2.2 >>>>>> @@ -2359,36 +2345,77 @@ fi >>>>>> # Set use_stateless_nat to true >>>>>> # Now, if the traffic passes through both gateways (GW-1 and GW-2) it >>>>> will be forwarded successfully >>>>>> check multinode_nbctl set load_balancer lb0 >>>>> options:use_stateless_nat=true >>>>>> +check multinode_nbctl --wait=sb set load_balancer lb0 >>>>> selection_fields="ip_src,tp_src,ip_dst,tp_dst" >>>>>> >>>>>> # Check the flows again for the LB VIP - always needs to be >>> successful >>>>> regardless of the datapath (one or two gw chassis) >>>>>> M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O >>>>> 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 >>> --max-time 1 >>>>> 2>curl.out']) >>>>>> +M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >>>> connections.out']) >>>>>> +M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >>>> connections.out']) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl >>>>>> Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> 200 OK >>>>>> ]) >>>>>> >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-1 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O >>>>> 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 >>> --max-time 1 >>>>> 2>curl.out']) >>>>>> +M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >>>> connections.out']) >>>>>> +M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >>>> connections.out']) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl >>>>>> Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> 200 OK >>>>>> ]) >>>>>> >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-1 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O >>>>> 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 >>> --max-time 1 >>>>> 2>curl.out']) >>>>>> +M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >>>> connections.out']) >>>>>> +M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >>>> connections.out']) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl >>>>>> Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> 200 OK >>>>>> ]) >>>>>> >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-1 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O >>>>> 172.16.0.100:80/download_file --retry 0 --connect-timeout 1 >>> --max-time 1 >>>>> 2>curl.out']) >>>>>> +M_NS_EXEC([ovn-chassis-1], [sw0p1], [sh -c 'ss -nn >>>> connections.out']) >>>>>> +M_NS_EXEC([ovn-chassis-2], [sw0p2], [sh -c 'ss -nn >>>> connections.out']) >>>>>> >>>>>> OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-3 cat -v curl.out | >>>>> M_FORMAT_CURL([172.16.0.100], [80])], [0], [dnl >>>>>> Connected to 172.16.0.100 (172.16.0.100) port 80 >>>>>> 200 OK >>>>>> ]) >>>>>> >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-1 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> +OVS_WAIT_FOR_OUTPUT([m_as ovn-chassis-2 cat connections.out | grep >>>>> "FIN-WAIT-2" | wc -l], [0], [dnl >>>>>> +0 >>>>>> +]) >>>>>> + >>>>>> # Direct backend traffic using the same LB ports needs to be dropped >>>>>> M_NS_EXEC([ovn-chassis-3], [sw1p1], [sh -c 'curl -v -O >>>>> 10.0.2.3:80/download_file --retry 0 --connect-timeout 1 --max-time 1 >>>>> 2>curl.out']) >>>>>> >>>>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at >>>>>> index fd572a023..5c9cc5cca 100644 >>>>>> --- a/tests/ovn-northd.at >>>>>> +++ b/tests/ovn-northd.at >>>>>> @@ -14533,14 +14533,66 @@ ovn-sbctl dump-flows lr1 > lr1flows >>>>>> AT_CAPTURE_FILE([lr1flows]) >>>>>> >>>>>> # Check stateless NAT rules for load balancer with multiple DGP >>>>>> -# 1. Check if the backend IPs are in the ipX.dst action >>>>>> +# 1. Check if the reg1[0..15] will select one of backends >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip4.dst >>>>> == 30.0.0.1), action=(reg1[[0..15]] = select(0,1,2);) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip4 && ip4.dst == 30.0.0.1 && >>>>> is_chassis_resident("cr-lr1-ts1")), action=(ip4.dst = 172.16.0.103; >>>>> ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);) >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip4 && ip4.dst == 30.0.0.1 && >>>>> is_chassis_resident("cr-lr1-ts2")), action=(ip4.dst = 172.16.0.103; >>>>> ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);) >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip4 && ip4.dst == 30.0.0.1 && >>>>> is_chassis_resident("cr-lr1_public")), action=(ip4.dst = 172.16.0.103; >>>>> ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> +AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> + table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1-ts1" || outport == "lr1-ts1") && >>>>> is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1-ts2" || outport == "lr1-ts2") && >>>>> is_chassis_resident("cr-lr1-ts2") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1_public" || outport == "lr1_public") >>> && >>>>> is_chassis_resident("cr-lr1_public") && tcp), action=(next;) >>>>>> ]) >>>>>> >>>>>> -# 2. Check if the DGP ports are in the match with action next >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> +AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> + table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1-ts1" || outport == "lr1-ts1") && >>>>> is_chassis_resident("cr-lr1-ts1") && tcp), action=(ip4.src = 30.0.0.1; >>>>> next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1-ts2" || outport == "lr1-ts2") && >>>>> is_chassis_resident("cr-lr1-ts2") && tcp), action=(ip4.src = 30.0.0.1; >>>>> next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1_public" || outport == "lr1_public") >>> && >>>>> is_chassis_resident("cr-lr1_public") && tcp), action=(ip4.src = >>> 30.0.0.1; >>>>> next;) >>>>>> +]) >>>>>> + >>>>>> +# Set selection fields >>>>>> +check ovn-nbctl --wait=sb set load_balancer lb1 >>>>> selection_fields="ip_src,tp_src,ip_dst,tp_dst" >>>>>> + >>>>>> +ovn-sbctl dump-flows lr1 > lr1flows >>>>>> +AT_CAPTURE_FILE([lr1flows]) >>>>>> + >>>>>> +# 1. Check if the reg1[0..15] will select one of backends using >>>>> hash_fields >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip4.dst >>>>> == 30.0.0.1), action=(reg1[[0..15]] = select(values=(0,1,2); >>>>> hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> +AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == 30.0.0.1 >>> && >>>>> reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 0), action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 1), action=(ip4.dst = 172.16.0.102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1 >>>>> && reg1[[0..15]] == 2), action=(ip4.dst = 172.16.0.101; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1-ts1" || outport == "lr1-ts1") && >>>>> is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> @@ -14548,7 +14600,7 @@ AT_CHECK([grep "lr_out_undnat" lr1flows | >>>>> ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1_public" || outport == "lr1_public") >>> && >>>>> is_chassis_resident("cr-lr1_public") && tcp), action=(next;) >>>>>> ]) >>>>>> >>>>>> -# 3. Check if the VIP IP is in the ipX.src action >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> @@ -14557,6 +14609,51 @@ AT_CHECK([grep "lr_out_snat" lr1flows | >>>>> ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103) || (ip4.src == 172.16.0.102) || (ip4.src == >>>>> 172.16.0.101)) && (inport == "lr1_public" || outport == "lr1_public") >>> && >>>>> is_chassis_resident("cr-lr1_public") && tcp), action=(ip4.src = >>> 30.0.0.1; >>>>> next;) >>>>>> ]) >>>>>> >>>>>> +# Delete LB and create with one backend >>>>>> +check ovn-nbctl --wait=sb lb-del lb1 >>>>>> +check ovn-nbctl --wait=sb lb-add lb1 "30.0.0.1" "172.16.0.103" >>>>>> + >>>>>> +# Set use_stateless_nat to true >>>>>> +check ovn-nbctl --wait=sb set load_balancer lb1 >>>>> options:use_stateless_nat=true >>>>>> + >>>>>> +# Associate load balancer to s1 >>>>>> +check ovn-nbctl ls-lb-add s1 lb1 >>>>>> +check ovn-nbctl lr-lb-add lr1 lb1 >>>>>> +check ovn-nbctl --wait=sb sync >>>>>> + >>>>>> +ovn-sbctl dump-flows lr1 > lr1flows >>>>>> +AT_CAPTURE_FILE([lr1flows]) >>>>>> + >>>>>> +# 1. Check if the reg1[0..15] will select one of backends using >>>>> hash_fields >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip4.dst >>>>> == 30.0.0.1), action=(next;) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> +AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "30.0.0.1"], [0], [dnl >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip4 && ip4.dst == >>> 30.0.0.1), >>>>> action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip4 && ip4.dst == >>> 30.0.0.1), >>>>> action=(ip4.dst = 172.16.0.103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip4 && ip4.dst == >>> 30.0.0.1), >>>>> action=(ip4.dst = 172.16.0.103; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> +AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> + table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1-ts1" || outport == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1-ts2" || outport == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1_public" || outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(next;) >>>>>> +]) >>>>>> + >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> +AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> + table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1-ts1" || outport == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), >>> action=(ip4.src = >>>>> 30.0.0.1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1-ts2" || outport == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), >>> action=(ip4.src = >>>>> 30.0.0.1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip4 && >>>>> ((ip4.src == 172.16.0.103)) && (inport == "lr1_public" || outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(ip4.src = 30.0.0.1; next;) >>>>>> +]) >>>>>> + >>>>>> + >>>>>> AT_CLEANUP >>>>>> ]) >>>>>> >>>>>> @@ -14647,14 +14744,67 @@ ovn-sbctl dump-flows lr1 > lr1flows >>>>>> AT_CAPTURE_FILE([lr1flows]) >>>>>> >>>>>> # Check stateless NAT rules for load balancer with multiple DGP >>>>>> -# 1. Check if the backend IPs are in the ipX.dst action >>>>>> +# 1. Check if the reg1[0..15] will select one of backends >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip6.dst >>>>> == 2001:db8:cccc::1), action=(reg1[[0..15]] = select(0,1,2);) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip6 && ip6.dst == 2001:db8:cccc::1 && >>>>> is_chassis_resident("cr-lr1-ts1")), action=(ip6.dst = >>> 2001:db8:aaaa:3::103; >>>>> >>> ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);) >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip6 && ip6.dst == 2001:db8:cccc::1 && >>>>> is_chassis_resident("cr-lr1-ts2")), action=(ip6.dst = >>> 2001:db8:aaaa:3::103; >>>>> >>> ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);) >>>>>> - table=??(lr_in_dnat ), priority=110 , match=(ct.new && >>>>> !ct.rel && ip6 && ip6.dst == 2001:db8:cccc::1 && >>>>> is_chassis_resident("cr-lr1_public")), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; >>>>> >>> ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> +AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> + table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1-ts1" || outport >>> == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1-ts2" || outport >>> == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1_public" || >>> outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(next;) >>>>>> ]) >>>>>> >>>>>> -# 2. Check if the DGP ports are in the match with action next >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> +AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> + table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1-ts1" || outport >>> == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), >>> action=(ip6.src = >>>>> 2001:db8:cccc::1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1-ts2" || outport >>> == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), >>> action=(ip6.src = >>>>> 2001:db8:cccc::1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1_public" || >>> outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(ip6.src = 2001:db8:cccc::1; next;) >>>>>> +]) >>>>>> + >>>>>> +# Set selection fields >>>>>> +check ovn-nbctl --wait=sb set load_balancer lb1 >>>>> selection_fields="ip_src,tp_src,ip_dst,tp_dst" >>>>>> + >>>>>> +ovn-sbctl dump-flows lr1 > lr1flows >>>>>> +AT_CAPTURE_FILE([lr1flows]) >>>>>> + >>>>>> +# Check stateless NAT rules for load balancer with multiple DGP >>>>>> +# 1. Check if the reg1[0..15] will select one of backends >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip6.dst >>>>> == 2001:db8:cccc::1), action=(reg1[[0..15]] = select(values=(0,1,2); >>>>> hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> +AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 0), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 1), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::102; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1 && reg1[[0..15]] == 2), action=(ip6.dst = >>>>> 2001:db8:aaaa:3::101; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1-ts1" || outport >>> == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> @@ -14662,7 +14812,7 @@ AT_CHECK([grep "lr_out_undnat" lr1flows | >>>>> ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1_public" || >>> outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(next;) >>>>>> ]) >>>>>> >>>>>> -# 3. Check if the VIP IP is in the ipX.src action >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> @@ -14671,6 +14821,50 @@ AT_CHECK([grep "lr_out_snat" lr1flows | >>>>> ovn_strip_lflows], [0], [dnl >>>>>> table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103) || (ip6.src == >>> 2001:db8:aaaa:3::102) || >>>>> (ip6.src == 2001:db8:aaaa:3::101)) && (inport == "lr1_public" || >>> outport == >>>>> "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(ip6.src = 2001:db8:cccc::1; next;) >>>>>> ]) >>>>>> >>>>>> +# Delete LB and create with one backend >>>>>> +check ovn-nbctl --wait=sb lb-del lb1 >>>>>> +check ovn-nbctl --wait=sb lb-add lb1 "2001:db8:cccc::1" >>>>> "2001:db8:aaaa:3::103" >>>>>> + >>>>>> +# Set use_stateless_nat to true >>>>>> +check ovn-nbctl --wait=sb set load_balancer lb1 >>>>> options:use_stateless_nat=true >>>>>> + >>>>>> +# Associate load balancer to s1 >>>>>> +check ovn-nbctl ls-lb-add s1 lb1 >>>>>> +check ovn-nbctl lr-lb-add lr1 lb1 >>>>>> +check ovn-nbctl --wait=sb sync >>>>>> + >>>>>> +ovn-sbctl dump-flows lr1 > lr1flows >>>>>> +AT_CAPTURE_FILE([lr1flows]) >>>>>> +# Check stateless NAT rules for load balancer with multiple DGP >>>>>> +# 1. Check if the reg1[0..15] will select one of backends >>>>>> +AT_CHECK([grep "lr_in_ct_extract" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> + table=??(lr_in_ct_extract ), priority=120 , match=(ip && ip6.dst >>>>> == 2001:db8:cccc::1), action=(next;) >>>>>> +]) >>>>>> + >>>>>> +# 2. Check if the backend IPs are in the ipX.dst action >>>>>> +AT_CHECK([grep "lr_in_dnat" lr1flows | ovn_strip_lflows | grep >>>>> "2001:db8:cccc::1"], [0], [dnl >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts1") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1), action=(ip6.dst = 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1-ts2") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1), action=(ip6.dst = 2001:db8:aaaa:3::103; next;) >>>>>> + table=??(lr_in_dnat ), priority=110 , >>>>> match=(is_chassis_resident("cr-lr1_public") && ip6 && ip6.dst == >>>>> 2001:db8:cccc::1), action=(ip6.dst = 2001:db8:aaaa:3::103; next;) >>>>>> +]) >>>>>> + >>>>>> +# 3. Check if the DGP ports are in the match with action next >>>>>> +AT_CHECK([grep "lr_out_undnat" lr1flows | ovn_strip_lflows], [0], >>> [dnl >>>>>> + table=??(lr_out_undnat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1-ts1" || outport >>> == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1-ts2" || outport >>> == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), action=(next;) >>>>>> + table=??(lr_out_undnat ), priority=120 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1_public" || >>> outport >>>>> == "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(next;) >>>>>> +]) >>>>>> + >>>>>> +# 4. Check if the VIP IP is in the ipX.src action >>>>>> +AT_CHECK([grep "lr_out_snat" lr1flows | ovn_strip_lflows], [0], [dnl >>>>>> + table=??(lr_out_snat ), priority=0 , match=(1), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=120 , match=(nd_ns), >>>>> action=(next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1-ts1" || outport >>> == >>>>> "lr1-ts1") && is_chassis_resident("cr-lr1-ts1") && tcp), >>> action=(ip6.src = >>>>> 2001:db8:cccc::1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1-ts2" || outport >>> == >>>>> "lr1-ts2") && is_chassis_resident("cr-lr1-ts2") && tcp), >>> action=(ip6.src = >>>>> 2001:db8:cccc::1; next;) >>>>>> + table=??(lr_out_snat ), priority=160 , match=(ip6 && >>>>> ((ip6.src == 2001:db8:aaaa:3::103)) && (inport == "lr1_public" || >>> outport >>>>> == "lr1_public") && is_chassis_resident("cr-lr1_public") && tcp), >>>>> action=(ip6.src = 2001:db8:cccc::1; next;) >>>>>> +]) >>>>>> + >>>>>> AT_CLEANUP >>>>>> ]) >>>>>> >>>>> >>>>> Regards, >>>>> Dumitru >>>>> >>>>> >>>>> >>>> Regards, >>>> Lucas >>>> >>> >>> Thanks, >>> Dumitru >>> >>> > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
