Hi,

Thanks.
Yes, I can send the backport patches to 25.09 and 25.03.

Regards,
Lucas

Em sex., 16 de jan. de 2026 às 12:11, Dumitru Ceara <[email protected]>
escreveu:

> On 11/14/25 7:54 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,
>
> Sorry for the very long delay in reviewing this patch.
>
> It looks good to me aside for some very small comments I shared below.
> In my opinion the implementation is way more clear now with these changes.
>
> Therefore, I rebased the patch on the current main branch, fixed up the
> nits below and then pushed it to main.
>
> I guess we should backport this to 25.09 and 25.03.  Would you mind
> sending backport patches if you have time?  Some of the refactors that
> went in since on the main branch make a cherry pick impossible as is.
>
> Regards,
> Dumitru
>
>
> >  northd/lb.c         |   3 +
> >  northd/lb.h         |   1 +
> >  northd/northd.c     | 298 +++++++++++++++++++++++++++++++-------------
> >  tests/multinode.at  |  99 +++++++++------
> >  tests/ovn-northd.at | 217 ++++++++++++++++++++++++++++++--
> >  5 files changed, 481 insertions(+), 137 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 cdf12ec86..4defff1cc 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]"
> > @@ -12439,6 +12440,155 @@ 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
>
> Typo: selecs
>
> > +     * REG_IDX_LB_STATELESS. It works to multi-chassis and avoid to
> > +     * sync conntrack betweem them.
>
> Typo: betweem
>
> > +     */
> > +    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);
> > +    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);
> > +    }
> > +
> > +    ds_destroy(&new_match_stateless_nat);
> > +    ds_destroy(&new_action_stateless_nat);
> > +
> > +    if (vector_is_empty(&ctx->lb_vip->backends)) {
> > +        return;
> > +    }
> > +
> > +    size_t undnat_match_len = ctx->undnat_match->length;
> > +    struct ds snat_action = DS_EMPTY_INITIALIZER;
> > +
> > +
> > +    /* 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);
> > +
> > +    /* 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);
> > +
> > +
>
> Nit: one newline too many.
>
> > +    /* undnat_action: Just follows the pipeline in the lr_out_undenat
> NAT rule.
> > +     */
> > +    ovn_lflow_add_with_hint(ctx->lflows, od, S_ROUTER_OUT_UNDNAT, 120,
> > +                            ds_cstr(ctx->undnat_match),
> > +                            "next;", &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.
>
> I think I'd say "without being SNATed" instead.
>
> > +     */
> > +    if (ctx->lb_vip->port_str) {
> > +        ds_put_format(&snat_action, "%s.src = %s; %s.src = %s; next;",
> > +                      ip_match, ctx->lb_vip->vip_str, ctx->lb->proto,
> > +                      ctx->lb_vip->port_str);
> > +    } else {
> > +        ds_put_format(&snat_action, "%s.src = %s; next;",
> > +                      ip_match, 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(&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,
> > @@ -12447,66 +12597,39 @@ 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;
> >      }
> >
> >      struct ds undnat_action = DS_EMPTY_INITIALIZER;
> > -    struct ds snat_action = DS_EMPTY_INITIALIZER;
> >
> >      switch (type) {
> >      case LROUTER_NAT_LB_FLOW_FORCE_SNAT:
> > @@ -12523,13 +12646,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.
> >       */
> > @@ -12546,53 +12662,16 @@ build_distr_lrouter_nat_flows_for_lb(struct
> lrouter_nat_lb_flows_ctx *ctx,
> >      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);
> > -    /* 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);
> > -    }
> > +                        " && is_chassis_resident(%s)",
> > +                        dgp->json_key, dgp->json_key,
> > +                        dgp->cr_port->json_key);
> >      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);
> >  }
> >
> >  static void
> > @@ -12749,8 +12828,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 *);
> > @@ -12883,13 +12961,45 @@ build_lswitch_flows_for_lb(struct
> ovn_lb_datapaths *lb_dps,
> >      build_lb_rules_for_stateless_acl(lflows, lb_dps);
> >  }
> >
> > +static void
> > +build_lrouter_defrag_flows_for_lb_stateless(const struct ovn_northd_lb
> *lb,
> > +                                            const struct ovn_lb_vip
> *lb_vip,
> > +                                            struct ds *action)
> > +{
> > +    ovs_assert(lb && lb_vip && action);
> > +
> > +    if (vector_len(&lb_vip->backends) > 1) {
> > +        ds_put_format(action, REG_IDX_LB_STATELESS" = select(");
> > +        if (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->selection_fields) {
> > +            ds_put_format(action, "); hash_fields=\"%s\"",
> > +                          lb->selection_fields);
> > +        }
> > +        ds_put_format(action,");");
>
> Nit: missing space after "action,".
>
> > +    } else {
> > +        ds_put_format(action, "next;");
> > +    }
> > +}
> > +
> >  /* If there are any load balancing rules, we should send the packet to
> > - * conntrack for defragmentation and tracking.  This helps with two
> things.
> > + * conntrack for defragmentation and tracking, unless LB is stateless.
> > + * This helps with two things.
> >   *
> >   * 1. With tracking, we can send only new connections to pick a DNAT ip
> address
> >   *    from a group.
> >   * 2. If there are L4 ports in load balancing rules, we need the
> >   *    defragmentation to match on L4 ports.
> > + *
> > + * If load balancer is stateless, conntrack must not be used.
> > + * Here, basicaly, it's calculated a hash to select the backend.
>
> Nit: I'd rephrase it to "A hash is calculated then to select the backend.".
>
> >   */
> >  static void
> >  build_lrouter_defrag_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
> > @@ -12900,21 +13010,31 @@ 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;
> > +            build_lrouter_defrag_flows_for_lb_stateless(lb_dps->lb,
> lb_vip,
> > +                                                        &action);
> > +        } else {
>
> Nit: I'd move "stage = S_ROUTER_IN_DEFRAG" here to make it more explicit.
>
> > +            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 2f74487c8..5b001994d 100644
> > --- a/tests/multinode.at
> > +++ b/tests/multinode.at
> > @@ -1514,6 +1514,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])
> > @@ -1545,14 +1546,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>)
> >  ])
> >
> > @@ -1583,7 +1582,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>)
> >  ])
> >
> > @@ -1649,7 +1647,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>)
> >  ])
> >
> > @@ -1705,7 +1702,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>)
> >  ])
> >
> > @@ -1771,7 +1767,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>)
> >  ])
> >
> > @@ -1858,7 +1853,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
> > @@ -1874,7 +1868,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
> > @@ -1907,14 +1900,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)
> >  #                     |
>            |
> >  #
>  +.............................+.............................+
> >  #                                                   |
> > @@ -1968,14 +1961,15 @@ check multinode_nbctl lrp-add lr0 lr0-sw0
> 00:00:00:00:ff:01 10.0.2.1/24 1000::a/
> >  check multinode_nbctl lsp-add-router-port sw0 sw0-lr0 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-localnet-port public ln-public-1 public1
> > +check multinode_nbctl lsp-add-localnet-port public_right ln-public-1
> public1
> >
> >  # DGP public2
> > -check multinode_nbctl lsp-add-localnet-port public ln-public-2 public2
> > +check multinode_nbctl lsp-add-localnet-port public_left ln-public-2
> public2
> >
> >  # Attach DGP public1 to GW-1 public1 (overlay connectivity)
> >  m_as ovn-gw-1 ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=public1:br-ex
> > @@ -1983,19 +1977,19 @@ 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-router-port public public-lr0-p1
> lr0-public-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-router-port public_right public-lr0-p1
> lr0-public-p1
> >  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-router-port public public-lr0-p2
> lr0-public-p2
> > +check multinode_nbctl lsp-add-router-port public_left public-lr0-p2
> lr0-public-p2
> >  check multinode_nbctl lrp-set-gateway-chassis lr0-public-p2 ovn-gw-2 10
> >
> >  M_NS_CHECK_EXEC([ovn-chassis-2], [sw0p2], [ping -q -c 3 -i 0.3 -w 2
> 20.0.2.2 | FORMAT_PING], \
> > @@ -2010,13 +2004,13 @@ check multinode_nbctl lsp-add-router-port sw1
> sw1-lr1 lr1-sw1
> >
> >  # create external connection for N/S traffic
> >  # DGP public3
> > -check multinode_nbctl lsp-add-localnet-port public ln-public-3 public3
> > +check multinode_nbctl lsp-add-localnet-port public_left ln-public-3
> 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-router-port public public-lr1-p3
> lr1-public-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-router-port public_left public-lr1-p3
> lr1-public-p3
> >  check multinode_nbctl lrp-set-gateway-chassis lr1-public-p3 ovn-gw-3 10
> >
> >  M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2
> 40.0.2.1 | FORMAT_PING], \
> > @@ -2024,21 +2018,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
> >  ])
> > @@ -2050,13 +2039,13 @@ 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-localnet-port public ln-public-4 public4
> > +check multinode_nbctl lsp-add-localnet-port public_right ln-public-4
> public4
> >
> >  # Attach DGP public4 to GW-2 public4 (overlay connectivity)
> >  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-router-port public public-lr1-p4
> lr1-public-p4
> > +check multinode_nbctl lsp-add-router-port public_right public-lr1-p4
> lr1-public-p4
> >  check multinode_nbctl lrp-set-gateway-chassis lr1-public-p4 ovn-gw-4 10
> >
> >  M_NS_CHECK_EXEC([ovn-chassis-3], [sw1p1], [ping -q -c 3 -i 0.3 -w 2
> 20.0.2.4 | FORMAT_PING], \
> > @@ -2064,9 +2053,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
> > @@ -2200,36 +2186,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 448bc66ae..1551df17f 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -14300,14 +14300,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
>
> Nit: missing period at the end of the sentence (in multiple places in
> these test changes).
>
> > +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;)
> > @@ -14315,7 +14367,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;)
> > @@ -14324,6 +14376,50 @@ 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
> >  ])
> >
> > @@ -14405,14 +14501,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;)
> > @@ -14420,7 +14569,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;)
> > @@ -14429,6 +14578,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
> >  ])
> >
>
>

-- 




_‘Esta mensagem é direcionada apenas para os endereços constantes no 
cabeçalho inicial. Se você não está listado nos endereços constantes no 
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa 
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas estão 
imediatamente anuladas e proibidas’._


* **‘Apesar do Magazine Luiza tomar 
todas as precauções razoáveis para assegurar que nenhum vírus esteja 
presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por 
quaisquer perdas ou danos causados por esse e-mail ou por seus anexos’.*



_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to