On Thu, Nov 7, 2024 at 8:02 AM Dumitru Ceara <[email protected]> wrote:
>
> Hi Numan,
>
> Thanks for the fix!
>
> On 11/5/24 18:00, [email protected] wrote:
> > From: Numan Siddique <[email protected]>
> >
> > If we have a UDP load balancer - 10.0.0.10:80 = 10.0.0.3:8080, in order to
> > determine if the load balanced traffic needs to be hairpinned, the
> > vip - 10.0.0.10 and the vip port - 80 are stored in the registers before
> > the packet is load balanced using the below logical flow -
> >
> > table=6 (ls_in_pre_stateful ), priority=120  ,
> >   match=(reg0[2] == 1 && ip4.dst == 10.0.0.10 && tcp.dst == 80),
> >   action=(reg1 = 10.0.0.10; reg2[0..15] = 80; ct_lb_mark;)
> >
> > These registers are used in the later stages to check if the load balanced
> > packet needs to be hairpinned or not.
> >
> > However, if the packet is fragmented we may not be able to match on the
> > L4 fields (tcp, udp or sctp dest port) and this breaks the hairpin
> > traffic.
> >
> > This patch addressed this issue by making use of ct_nw_dst/ct_ip6_dst and
> > ct_tp_dst conntrack fields to determine the hairpin load balanced
> > traffic.
> >
>
> You mentioned it in the code comments but it's probably worth mentioning
> in the commit log too:
>
> Relying on conntrack to reassemble packets is not exactly correct, it
> only accidentaly works with the kernel datapath.  In our internal bug
> tracking system we have this issue to track this incorrect assumption:
>
> https://issues.redhat.com/browse/FDP-913
>
> > In order to not break hardware offload on certain smart nics, care is taken
> > to match on these fields only for fragmented packets.
> >
> > Reported-at: https://issues.redhat.com/browse/FDP-905
> > Fixes: 1139b655c996 ("Don't blindly save original dst IP and Port to avoid 
> > megaflow unwildcarding.")
> > CC: Han Zhou <[email protected]>
> > Suggested-by: Dumitru Ceara <[email protected]>
> > Signed-off-by: Numan Siddique <[email protected]>
> > ---
> >  controller/lflow.c           |   3 +
> >  controller/lflow.h           |   4 +
> >  controller/physical.c        |  37 +++++++++
> >  include/ovn/actions.h        |  14 +++-
> >  include/ovn/logical-fields.h |   4 +
> >  lib/actions.c                | 138 ++++++++++++++++++++++++++++----
> >  northd/northd.c              |  57 +++++++++++++-
> >  ovn-sb.xml                   |  27 +++++++
> >  tests/ovn-macros.at          |   3 +
> >  tests/ovn-northd.at          |  70 +++++++++-------
> >  tests/ovn.at                 |  56 ++++++++++++-
> >  tests/system-ovn-kmod.at     | 149 +++++++++++++++++++++++++++++++++++
> >  tests/test-ovn.c             |   3 +
> >  utilities/ovn-trace.c        |   6 ++
> >  14 files changed, 520 insertions(+), 51 deletions(-)
> >
> > diff --git a/controller/lflow.c b/controller/lflow.c
> > index 13c3a0d73e..76b4952fd4 100644
> > --- a/controller/lflow.c
> > +++ b/controller/lflow.c
> > @@ -886,6 +886,9 @@ add_matches_to_flow_table(const struct 
> > sbrec_logical_flow *lflow,
> >          .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
> >          .out_port_sec_ptable = OFTABLE_CHK_OUT_PORT_SEC,
> >          .mac_cache_use_table = OFTABLE_MAC_CACHE_USE,
> > +        .ct_nw_dst_load_table = OFTABLE_CT_ORIG_NW_DST_LOAD,
> > +        .ct_ip6_dst_load_table = OFTABLE_CT_ORIG_IP6_DST_LOAD,
> > +        .ct_tp_dst_load_table = OFTABLE_CT_ORIG_TP_DST_LOAD,
> >          .ctrl_meter_id = ctrl_meter_id,
> >          .common_nat_ct_zone = get_common_nat_zone(ldp),
> >      };
> > diff --git a/controller/lflow.h b/controller/lflow.h
> > index e95a016501..58a12ee0fe 100644
> > --- a/controller/lflow.h
> > +++ b/controller/lflow.h
> > @@ -95,6 +95,10 @@ struct uuid;
> >  #define OFTABLE_CHK_LB_AFFINITY          78
> >  #define OFTABLE_MAC_CACHE_USE            79
> >  #define OFTABLE_CT_ZONE_LOOKUP           80
> > +#define OFTABLE_CT_ORIG_NW_DST_LOAD      81
> > +#define OFTABLE_CT_ORIG_IP6_DST_LOAD     82
> > +#define OFTABLE_CT_ORIG_TP_DST_LOAD      83
> > +
> >
> >  struct lflow_ctx_in {
> >      struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath;
> > diff --git a/controller/physical.c b/controller/physical.c
> > index 2aaa16cbd0..855936ba4e 100644
> > --- a/controller/physical.c
> > +++ b/controller/physical.c
> > @@ -2770,5 +2770,42 @@ physical_run(struct physical_ctx *p_ctx,
> >       */
> >      add_default_drop_flow(p_ctx, OFTABLE_LOG_TO_PHY, flow_table);
> >
> > +    /* Table 81, 82 and 83
> > +     * Match on ct.trk and ct.dnat and store the ct_nw_dst, ct_ip6_dst and
> > +     * ct_tp_dst in the registers. */
> > +    uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
>
> I did some testing with this patch and these flows are actually only hit
> if the ct_state of the session is +est - that's fine.
>
> However, because ovn-northd generates flows that resubmit all IP
> fragments to these tables in the "in_lb" stage of the pipeline, before
> we could decide if this is part of a hairpin session or not (that
> happens later in "in_pre_hairpin"), it means that all IP fragments that
> are part of load balanced sessions will match on ct_state=+trk+dnat in
> the datapath - including those on sessions that are not hairpin.
>
> As you mentioned off-list yesterday, that breaks HW offload on certain
> hardware for fragmented load balanced traffic:
> https://github.com/ovn-org/ovn/commit/110e670
>
> I experimented a bit and it seems to me that we can just avoid the
> ct_state=+trk+dnat match all toghether.
>
> I changed this to +trk+est (established is a prerequisite for ct_nw/tp_*
> fields):
>
> uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_ESTABLISHED;
>
> and traffic seems to be natted properly for hairpin sessions with
> fragmented traffic too.

Hi Dumitru,

Thanks for review comments and for the suggestion.  This works.

I've addressed your comments and submitted v2.

Please take a look.

Numan

>
> > +    match_init_catchall(&match);
> > +    ofpbuf_clear(&ofpacts);
> > +
> > +    /* Add the flow:
> > +     * match = (ct.trk && ct.dnat), action = (reg8 = ct_tp_dst)
> > +     * table = 83
> > +     */
> > +    match_set_ct_state_masked(&match, ct_state, ct_state);
> > +    put_move(MFF_CT_TP_DST, 0,  MFF_LOG_CT_ORIG_TP_DST_PORT, 0, 16, 
> > &ofpacts);
> > +    ofctrl_add_flow(flow_table, OFTABLE_CT_ORIG_TP_DST_LOAD, 100, 0, 
> > &match,
> > +                    &ofpacts, hc_uuid);
> > +
> > +    /* Add the flow:
> > +     * match = (ct.trk && ct.dnat && ip4), action = (reg4 = ct_nw_dst)
> > +     * table = 81
> > +     */
> > +    ofpbuf_clear(&ofpacts);
> > +    match_set_dl_type(&match, htons(ETH_TYPE_IP));
> > +    put_move(MFF_CT_NW_DST, 0,  MFF_LOG_CT_ORIG_NW_DST_ADDR, 0, 32, 
> > &ofpacts);
> > +    ofctrl_add_flow(flow_table, OFTABLE_CT_ORIG_NW_DST_LOAD, 100, 0, 
> > &match,
> > +                    &ofpacts, hc_uuid);
> > +
> > +    /* Add the flow:
> > +     * match = (ct.trk && ct.dnat && ip6), action = (xxreg0 = ct_ip6_dst)
> > +     * table = 82
> > +     */
> > +    ofpbuf_clear(&ofpacts);
> > +    match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
> > +    put_move(MFF_CT_IPV6_DST, 0,  MFF_LOG_CT_ORIG_IP6_DST_ADDR, 0,
> > +             128, &ofpacts);
> > +    ofctrl_add_flow(flow_table, OFTABLE_CT_ORIG_IP6_DST_LOAD, 100, 0, 
> > &match,
> > +                    &ofpacts, hc_uuid);
> > +
> >      ofpbuf_uninit(&ofpacts);
> >  }
> > diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> > index 63a12a8821..91a6139b00 100644
> > --- a/include/ovn/actions.h
> > +++ b/include/ovn/actions.h
> > @@ -131,6 +131,9 @@ struct collector_set_ids;
> >      OVNACT(CHK_LB_AFF,        ovnact_result)          \
> >      OVNACT(SAMPLE,            ovnact_sample)          \
> >      OVNACT(MAC_CACHE_USE,     ovnact_null)            \
> > +    OVNACT(CT_ORIG_NW_DST,    ovnact_result)          \
> > +    OVNACT(CT_ORIG_IP6_DST,   ovnact_result)          \
> > +    OVNACT(CT_ORIG_TP_DST,    ovnact_result)          \
> >
> >  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
> >  enum OVS_PACKED_ENUM ovnact_type {
> > @@ -416,10 +419,11 @@ struct ovnact_set_queue {
> >      uint16_t queue_id;
> >  };
> >
> > -/* OVNACT_DNS_LOOKUP, OVNACT_CHK_LB_HAIRPIN, OVNACT_CHK_LB_HAIRPIN_REPLY. 
> > */
> > +/* OVNACT_DNS_LOOKUP, OVNACT_CHK_LB_HAIRPIN, OVNACT_CHK_LB_HAIRPIN_REPLY,
> > + * OVNACT_CT_ORIG_NW_DST, CT_ORIG_IP6_DST, CT_ORIG_TP_DST */
> >  struct ovnact_result {
> >      struct ovnact ovnact;
> > -    struct expr_field dst;      /* 1-bit destination field. */
> > +    struct expr_field dst;      /* destination field. */
> >  };
> >
> >  /* OVNACT_LOG. */
> > @@ -935,6 +939,12 @@ struct ovnact_encode_params {
> >                                      this determines which CT zone to use */
> >      uint32_t mac_cache_use_table; /* OpenFlow table for 'mac_cache_use'
> >                                     * to resubmit. */
> > +    uint32_t ct_nw_dst_load_table; /* OpenFlow table for 'ct_nw_dst'
> > +                                   *  to resubmit. */
> > +    uint32_t ct_ip6_dst_load_table; /* OpenFlow table for 'ct_ip6_dst'
> > +                                   *  to resubmit. */
> > +    uint32_t ct_tp_dst_load_table; /* OpenFlow table for 'ct_tp_dst'
> > +                                   *  to resubmit. */
> >  };
> >
> >  void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
> > diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
> > index d6c4a9b6b3..d563e044cb 100644
> > --- a/include/ovn/logical-fields.h
> > +++ b/include/ovn/logical-fields.h
> > @@ -60,6 +60,10 @@ enum ovn_controller_event {
> >  #define MFF_LOG_LB_AFF_MATCH_LR_IP6_ADDR    MFF_XXREG1
> >  #define MFF_LOG_LB_AFF_MATCH_PORT           MFF_REG8
> >
> > +#define MFF_LOG_CT_ORIG_NW_DST_ADDR         MFF_REG4
> > +#define MFF_LOG_CT_ORIG_IP6_DST_ADDR        MFF_XXREG0
> > +#define MFF_LOG_CT_ORIG_TP_DST_PORT         MFF_REG8
> > +
> >  void ovn_init_symtab(struct shash *symtab);
> >
> >  /* MFF_LOG_FLAGS_REG bit assignments */
> > diff --git a/lib/actions.c b/lib/actions.c
> > index c5bde996b7..d5fc30b27a 100644
> > --- a/lib/actions.c
> > +++ b/lib/actions.c
> > @@ -3280,9 +3280,10 @@ ovnact_set_queue_free(struct ovnact_set_queue *a 
> > OVS_UNUSED)
> >  }
> >
> >  static void
> > -parse_ovnact_result(struct action_context *ctx, const char *name,
> > -                    const char *prereq, const struct expr_field *dst,
> > -                    struct ovnact_result *res)
> > +parse_ovnact_result__(struct action_context *ctx, const char *name,
> > +                      const char *prereq, const struct expr_field *dst,
> > +                      struct ovnact_result *res,
> > +                      int n_bits)
> >  {
> >      lexer_get(ctx->lexer); /* Skip action name. */
> >      lexer_get(ctx->lexer); /* Skip '('. */
> > @@ -3290,8 +3291,8 @@ parse_ovnact_result(struct action_context *ctx, const 
> > char *name,
> >          lexer_error(ctx->lexer, "%s doesn't take any parameters", name);
> >          return;
> >      }
> > -    /* Validate that the destination is a 1-bit, modifiable field. */
> > -    char *error = expr_type_check(dst, 1, true, ctx->scope);
> > +    /* Validate that the destination is n_bits, modifiable field. */
> > +    char *error = expr_type_check(dst, n_bits, true, ctx->scope);
> >      if (error) {
> >          lexer_error(ctx->lexer, "%s", error);
> >          free(error);
> > @@ -3304,6 +3305,14 @@ parse_ovnact_result(struct action_context *ctx, 
> > const char *name,
> >      }
> >  }
> >
> > +static void
> > +parse_ovnact_result(struct action_context *ctx, const char *name,
> > +                    const char *prereq, const struct expr_field *dst,
> > +                    struct ovnact_result *res)
> > +{
> > +    parse_ovnact_result__(ctx, name, prereq, dst, res, 1);
> > +}
> > +
> >  static void
> >  parse_dns_lookup(struct action_context *ctx, const struct expr_field *dst,
> >                   struct ovnact_result *dl)
> > @@ -4299,22 +4308,40 @@ format_CHK_LB_HAIRPIN_REPLY(const struct 
> > ovnact_result *res, struct ds *s)
> >      ds_put_cstr(s, " = chk_lb_hairpin_reply();");
> >  }
> >
> > +static void
> > +encode_result_action___(const struct ovnact_result *res,
> > +                        uint8_t resubmit_table,
> > +                        enum mf_field_id dst,
> > +                        int ofs, int n_bits,
> > +                        struct ofpbuf *ofpacts)
> > +{
> > +    ovs_assert(n_bits <= 128);
> > +
> > +    struct mf_subfield res_dst = expr_resolve_field(&res->dst);
> > +    ovs_assert(res_dst.field);
> > +
> > +    put_load(0, dst, ofs, n_bits < 64 ? n_bits : 64, ofpacts);
> > +    if (n_bits > 64) {
> > +        put_load(0, dst, ofs + 64, n_bits - 64, ofpacts);
> > +    }
> > +
> > +    emit_resubmit(ofpacts, resubmit_table);
> > +
> > +    struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
> > +    orm->dst = res_dst;
> > +    orm->src.field = mf_from_id(dst);
> > +    orm->src.ofs = ofs;
> > +    orm->src.n_bits = n_bits;
> > +}
> > +
> >  static void
> >  encode_result_action__(const struct ovnact_result *res,
> >                         uint8_t resubmit_table,
> >                         int log_flags_result_bit,
> >                         struct ofpbuf *ofpacts)
> >  {
> > -    struct mf_subfield dst = expr_resolve_field(&res->dst);
> > -    ovs_assert(dst.field);
> > -    put_load(0, MFF_LOG_FLAGS, log_flags_result_bit, 1, ofpacts);
> > -    emit_resubmit(ofpacts, resubmit_table);
> > -
> > -    struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
> > -    orm->dst = dst;
> > -    orm->src.field = mf_from_id(MFF_LOG_FLAGS);
> > -    orm->src.ofs = log_flags_result_bit;
> > -    orm->src.n_bits = 1;
> > +    encode_result_action___(res, resubmit_table, MFF_LOG_FLAGS,
> > +                            log_flags_result_bit, 1, ofpacts);
> >  }
> >
> >  static void
> > @@ -5435,6 +5462,75 @@ encode_MAC_CACHE_USE(const struct ovnact_null *null 
> > OVS_UNUSED,
> >      emit_resubmit(ofpacts, ep->mac_cache_use_table);
> >  }
> >
> > +static void
> > +encode_CT_ORIG_NW_DST(const struct ovnact_result *res,
> > +                      const struct ovnact_encode_params *ep,
> > +                      struct ofpbuf *ofpacts)
> > +{
> > +    encode_result_action___(res, ep->ct_nw_dst_load_table,
> > +                            MFF_LOG_CT_ORIG_NW_DST_ADDR, 0, 32, ofpacts);
> > +}
> > +
> > +static void
> > +parse_CT_ORIG_NW_DST(struct action_context *ctx, const struct expr_field 
> > *dst,
> > +                     struct ovnact_result *res)
> > +{
> > +    parse_ovnact_result__(ctx, "ct_nw_dst", NULL, dst, res, 32);
> > +}
> > +
> > +static void
> > +format_CT_ORIG_NW_DST(const struct ovnact_result *res, struct ds *s)
> > +{
> > +    expr_field_format(&res->dst, s);
> > +    ds_put_cstr(s, " = ct_nw_dst();");
> > +}
> > +
> > +static void
> > +encode_CT_ORIG_IP6_DST(const struct ovnact_result *res,
> > +                       const struct ovnact_encode_params *ep,
> > +                       struct ofpbuf *ofpacts)
> > +{
> > +    encode_result_action___(res, ep->ct_ip6_dst_load_table,
> > +                            MFF_LOG_CT_ORIG_IP6_DST_ADDR, 0, 128, ofpacts);
> > +}
> > +
> > +static void
> > +parse_CT_ORIG_IP6_DST(struct action_context *ctx, const struct expr_field 
> > *dst,
> > +                     struct ovnact_result *res)
> > +{
> > +    parse_ovnact_result__(ctx, "ct_ip6_dst", NULL, dst, res, 128);
> > +}
> > +
> > +static void
> > +format_CT_ORIG_IP6_DST(const struct ovnact_result *res, struct ds *s)
> > +{
> > +    expr_field_format(&res->dst, s);
> > +    ds_put_cstr(s, " = ct_ip6_dst();");
> > +}
> > +
> > +static void
> > +encode_CT_ORIG_TP_DST(const struct ovnact_result *res,
> > +                      const struct ovnact_encode_params *ep OVS_UNUSED,
> > +                      struct ofpbuf *ofpacts)
> > +{
> > +    encode_result_action___(res, ep->ct_tp_dst_load_table,
> > +                            MFF_LOG_CT_ORIG_TP_DST_PORT, 0, 16, ofpacts);
> > +}
> > +
> > +static void
> > +parse_CT_ORIG_TP_DST(struct action_context *ctx, const struct expr_field 
> > *dst,
> > +                     struct ovnact_result *res)
> > +{
> > +    parse_ovnact_result__(ctx, "ct_tp_dst", NULL, dst, res, 16);
> > +}
> > +
> > +static void
> > +format_CT_ORIG_TP_DST(const struct ovnact_result *res, struct ds *s)
> > +{
> > +    expr_field_format(&res->dst, s);
> > +    ds_put_cstr(s, " = ct_tp_dst();");
> > +}
> > +
> >  /* Parses an assignment or exchange or put_dhcp_opts action. */
> >  static void
> >  parse_set_action(struct action_context *ctx)
> > @@ -5529,6 +5625,18 @@ parse_set_action(struct action_context *ctx)
> >          } else if (lexer_match_id(ctx->lexer, "dhcp_relay_resp_chk")) {
> >              parse_dhcp_relay_chk(
> >                  ctx, &lhs, ovnact_put_DHCPV4_RELAY_RESP_CHK(ctx->ovnacts));
> > +        } else if (!strcmp(ctx->lexer->token.s, "ct_nw_dst") &&
> > +                   lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
> > +            parse_CT_ORIG_NW_DST(ctx, &lhs,
> > +                                 ovnact_put_CT_ORIG_NW_DST(ctx->ovnacts));
> > +        } else if (!strcmp(ctx->lexer->token.s, "ct_ip6_dst") &&
> > +                   lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
> > +            parse_CT_ORIG_IP6_DST(ctx, &lhs,
> > +                                  
> > ovnact_put_CT_ORIG_IP6_DST(ctx->ovnacts));
> > +        } else if (!strcmp(ctx->lexer->token.s, "ct_tp_dst") &&
> > +                   lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
> > +            parse_CT_ORIG_TP_DST(ctx, &lhs,
> > +                                 ovnact_put_CT_ORIG_TP_DST(ctx->ovnacts));
> >          } else {
> >              parse_assignment_action(ctx, false, &lhs);
> >          }
> > diff --git a/northd/northd.c b/northd/northd.c
> > index 3037ce0b55..985283a98e 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -124,6 +124,7 @@ static bool vxlan_mode;
> >  #define REGBIT_ACL_STATELESS      "reg0[16]"
> >  #define REGBIT_ACL_HINT_ALLOW_REL "reg0[17]"
> >  #define REGBIT_FROM_ROUTER_PORT   "reg0[18]"
> > +#define REGBIT_IP_FRAG            "reg0[19]"
> >
> >  #define REG_ORIG_DIP_IPV4         "reg1"
> >  #define REG_ORIG_DIP_IPV6         "xxreg1"
> > @@ -6398,6 +6399,13 @@ build_pre_stateful(struct ovn_datapath *od,
> >
> >      /* Note: priority-120 flows are added in 
> > build_lb_rules_pre_stateful(). */
> >
> > +    /* If the packet is fragmented, set the REGBIT_IP_FRAG reg bit to 1
> > +     * as ip.is_frag will not be preserved after conntrack recirculation. 
> > */
> > +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 115,
> > +                  REGBIT_CONNTRACK_NAT" == 1 && ip.is_frag",
> > +                  REGBIT_IP_FRAG" = 1; ct_lb_mark;",
> > +                  lflow_ref);
> > +
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 110,
> >                    REGBIT_CONNTRACK_NAT" == 1", "ct_lb_mark;",
> >                    lflow_ref);
> > @@ -8240,13 +8248,32 @@ build_lb_rules(struct lflow_table *lflows, struct 
> > ovn_lb_datapaths *lb_dps,
> >          struct ovn_lb_vip *lb_vip = &lb->vips[i];
> >          struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
> >          const char *ip_match = NULL;
> > +
> > +        ds_clear(action);
> > +
> > +        /* Store the original destination IP to be used when generating
> > +         * hairpin flows.
> > +         * If the packet is fragmented, then the flow which saves the
> > +         * original destination IP (and port) in the "ls_in_pre_stateful"
> > +         * stage will not be hit.
> > +         */
> >          if (lb_vip->address_family == AF_INET) {
> >              ip_match = "ip4";
> > +            ds_put_format(action, REG_ORIG_DIP_IPV4 " = %s; ",
> > +                          lb_vip->vip_str);
> >          } else {
> >              ip_match = "ip6";
> > +            ds_put_format(action, REG_ORIG_DIP_IPV6 " = %s; ",
> > +                          lb_vip->vip_str);
> >          }
> >
> > -        ds_clear(action);
> > +        if (lb_vip->port_str) {
> > +            /* Store the original destination port to be used when 
> > generating
> > +             * hairpin flows.
> > +             */
> > +            ds_put_format(action, REG_ORIG_TP_DPORT " = %s; ",
> > +                          lb_vip->port_str);
> > +        }
> >          ds_clear(match);
> >
> >          /* New connections in Ingress table. */
> > @@ -8378,8 +8405,32 @@ build_lb_hairpin(const struct ls_stateful_record 
> > *ls_stateful_rec,
> >                    lflow_ref);
> >
> >      if (ls_stateful_rec->has_lb_vip) {
> > -        /* Check if the packet needs to be hairpinned.
> > -         * Set REGBIT_HAIRPIN in the original direction and
> > +        /* Check if the packet needs to be hairpinned. */
> > +
> > +        /* In order to check if the fragmented packets needs to be
> > +         * hairpinned we need to save the ct tuple original IPv4/v6
> > +         * destination and L4 destination port in the registers after
> > +         * the conntrack recirculation.
> > +         *
> > +         * Note: We are assuming that sending the packets to conntrack
> > +         * will reassemble the packet and L4 fields will be available.
> > +         * It is a risky assumption as ovs-vswitchd doesn't guarantee it
> > +         * and userspace datapath doesn't reassemble the fragmented packets
> > +         * after conntrack.  It is the kernel datapath conntrack behavior.
> > +         * We need to find a better way to handle the fragmented packets.
> > +         * */
> > +        ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 110,
> > +                      "ct.trk && !ct.rpl && "REGBIT_IP_FRAG" == 1 && ip4",
>
> Why do we do this only for packets in the original direction.  Don't we
> need to be able to detect hairpin on replies too?  I removed the !ct.rpl
> condition and things seem to work fine.
>
> > +                      REG_ORIG_DIP_IPV4 " = ct_nw_dst(); "
> > +                      REG_ORIG_TP_DPORT " = ct_tp_dst(); next;",
> > +                      lflow_ref);
> > +        ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 110,
> > +                      "ct.trk && !ct.rpl && "REGBIT_IP_FRAG" == 1 && ip6",
> > +                      REG_ORIG_DIP_IPV6 " = ct_ip6_dst(); "
> > +                      REG_ORIG_TP_DPORT " = ct_tp_dst(); next;",
> > +                      lflow_ref);
> > +
> > +        /* Set REGBIT_HAIRPIN in the original direction and
> >           * REGBIT_HAIRPIN_REPLY in the reply direction.
> >           */
> >          ovn_lflow_add_with_hint(
> > diff --git a/ovn-sb.xml b/ovn-sb.xml
> > index 479d3e2851..ea4adc1c34 100644
> > --- a/ovn-sb.xml
> > +++ b/ovn-sb.xml
> > @@ -2766,6 +2766,33 @@ tcp.flags = RST;
> >            </p>
> >          </dd>
> >
> > +        <dt><code><var>R</var> = ct_nw_dst();</code></dt>
> > +        <dd>
> > +          <p>
> > +            This action checks if the packet is tracked and stores the
> > +            conntrack original destination IPv4 address in the register
> > +            <var>R</var> of 32-bit size.
> > +          </p>
> > +        </dd>
> > +
> > +        <dt><code><var>R</var> = ct_ip6_dst();</code></dt>
> > +        <dd>
> > +          <p>
> > +            This action checks if the packet is tracked and stores the
> > +            conntrack original destination IPv6 address in the register
> > +            <var>R</var> of 128-bit size.
> > +          </p>
> > +        </dd>
> > +
> > +        <dt><code><var>R</var> = ct_tp_dst();</code></dt>
> > +        <dd>
> > +          <p>
> > +            This action checks if the packet is tracked and stores the
> > +            conntrack original L4 destination port in the register
> > +            <var>R</var> of 16-bit size.
> > +          </p>
> > +        </dd>
> > +
> >          <dt><code>sample(probability=<var>packets</var>, ...)</code></dt>
> >          <dd>
> >            <p>
> > diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> > index 77d1515f6f..a88556aa99 100644
> > --- a/tests/ovn-macros.at
> > +++ b/tests/ovn-macros.at
> > @@ -1217,5 +1217,8 @@ m4_define([OFTABLE_ECMP_NH], [77])
> >  m4_define([OFTABLE_CHK_LB_AFFINITY], [78])
> >  m4_define([OFTABLE_MAC_CACHE_USE], [79])
> >  m4_define([OFTABLE_CT_ZONE_LOOKUP], [80])
> > +m4_define([OFTABLE_CT_ORIG_NW_DST_LOAD], [81])
> > +m4_define([OFTABLE_CT_ORIG_IP6_DST_LOAD], [82])
> > +m4_define([OFTABLE_CT_ORIG_TP_DST_LOAD], [83])
> >
> >  m4_define([OFTABLE_SAVE_INPORT_HEX], [m4_eval(OFTABLE_SAVE_INPORT, 16)])
> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > index d1f7f105c0..e1386323a7 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -1415,7 +1415,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> >  AT_CAPTURE_FILE([sbflows])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' 
> > | ovn_strip_lflows], 0, [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >
> >  # disabled LSPs should not be a backend of Load Balancer
> > @@ -1424,7 +1424,7 @@ check ovn-nbctl lsp-set-enabled sw0-p1 disabled
> >  AT_CAPTURE_FILE([sbflows])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' 
> > | ovn_strip_lflows], 0, [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=20.0.0.3:80);)
> >  ])
> >  wait_row_count Service_Monitor 1
> >
> > @@ -1433,7 +1433,7 @@ check ovn-nbctl lsp-set-enabled sw0-p1 enabled
> >  AT_CAPTURE_FILE([sbflows])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' 
> > | ovn_strip_lflows], 0, [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >  wait_row_count Service_Monitor 2
> >
> > @@ -1444,7 +1444,7 @@ wait_row_count Service_Monitor 0
> >  AT_CAPTURE_FILE([sbflows2])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*backends' 
> > | ovn_strip_lflows], [0],
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Create the Load_Balancer_Health_Check again.])
> > @@ -1456,7 +1456,7 @@ check ovn-nbctl --wait=sb sync
> >
> >  ovn-sbctl dump-flows sw0 | grep backends | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt | ovn_strip_lflows], [0], [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Get the uuid of both the service_monitor])
> > @@ -1466,7 +1466,7 @@ sm_sw1_p1=$(fetch_column Service_Monitor _uuid 
> > logical_port=sw1-p1)
> >  AT_CAPTURE_FILE([sbflows3])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 
> > 'priority=120.*backends' | ovn_strip_lflows], [0],
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Set the service monitor for sw1-p1 to offline])
> > @@ -1477,7 +1477,7 @@ check ovn-nbctl --wait=sb sync
> >  AT_CAPTURE_FILE([sbflows4])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*backends' 
> > | ovn_strip_lflows], [0],
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.3:80);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Set the service monitor for sw0-p1 to offline])
> > @@ -1506,7 +1506,7 @@ check ovn-nbctl --wait=sb sync
> >  AT_CAPTURE_FILE([sbflows7])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep backends | grep 
> > priority=120 | ovn_strip_lflows], 0,
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Set the service monitor for sw1-p1 to error])
> > @@ -1517,7 +1517,7 @@ check ovn-nbctl --wait=sb sync
> >  ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" \
> >  | grep priority=120 > lflows.txt
> >  AT_CHECK([cat lflows.txt | grep ls_in_lb | ovn_strip_lflows], [0], [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Add one more vip to lb1])
> > @@ -1543,8 +1543,8 @@ AT_CAPTURE_FILE([sbflows9])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep backends | grep 
> > priority=120 | ovn_strip_lflows],
> >    0,
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.3:80);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), 
> > action=(ct_lb_mark(backends=10.0.0.3:1000);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 
> > 1000; ct_lb_mark(backends=10.0.0.3:1000);)
> >  ])
> >
> >  AS_BOX([Set the service monitor for sw1-p1 to online])
> > @@ -1557,8 +1557,8 @@ AT_CAPTURE_FILE([sbflows10])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep backends | grep 
> > priority=120 | ovn_strip_lflows],
> >    0,
> > -[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), 
> > action=(ct_lb_mark(backends=10.0.0.3:1000,20.0.0.3:80);)
> > +[  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 
> > 1000; ct_lb_mark(backends=10.0.0.3:1000,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Associate lb1 to sw1])
> > @@ -1567,8 +1567,8 @@ AT_CAPTURE_FILE([sbflows11])
> >  OVS_WAIT_FOR_OUTPUT(
> >    [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep backends | grep 
> > priority=120 | ovn_strip_lflows],
> >    0, [dnl
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), 
> > action=(ct_lb_mark(backends=10.0.0.3:1000,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.40 && tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 
> > 1000; ct_lb_mark(backends=10.0.0.3:1000,20.0.0.3:80);)
> >  ])
> >
> >  AS_BOX([Now create lb2 same as lb1 but udp protocol.])
> > @@ -4653,14 +4653,17 @@ check_stateful_flows() {
> >    table=??(ls_in_pre_stateful ), priority=0    , match=(1), action=(next;)
> >    table=??(ls_in_pre_stateful ), priority=100  , match=(reg0[[0]] == 1), 
> > action=(ct_next;)
> >    table=??(ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), 
> > action=(ct_lb_mark;)
> > +  table=??(ls_in_pre_stateful ), priority=115  , match=(reg0[[2]] == 1 && 
> > ip.is_frag), action=(reg0[[19]] = 1; ct_lb_mark;)
> >    table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
> > ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; 
> > reg2[[0..15]] = 80; ct_lb_mark;)
> >    table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
> > ip4.dst == 10.0.0.20 && tcp.dst == 80), action=(reg1 = 10.0.0.20; 
> > reg2[[0..15]] = 80; ct_lb_mark;)
> >  ])
> >
> >      AT_CHECK([grep "ls_in_lb " sw0flows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.4:8080);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.20 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.40:8080);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.4:8080);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 10.0.0.20 && tcp.dst == 80), action=(reg1 = 10.0.0.20; reg2[[0..15]] = 
> > 80; ct_lb_mark(backends=10.0.0.40:8080);)
> >  ])
> >
> >      AT_CHECK([grep "ls_in_stateful" sw0flows | ovn_strip_lflows], [0], [dnl
> > @@ -4724,6 +4727,7 @@ AT_CHECK([grep "ls_in_pre_stateful" sw0flows | 
> > ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_pre_stateful ), priority=0    , match=(1), action=(next;)
> >    table=??(ls_in_pre_stateful ), priority=100  , match=(reg0[[0]] == 1), 
> > action=(ct_next;)
> >    table=??(ls_in_pre_stateful ), priority=110  , match=(reg0[[2]] == 1), 
> > action=(ct_lb_mark;)
> > +  table=??(ls_in_pre_stateful ), priority=115  , match=(reg0[[2]] == 1 && 
> > ip.is_frag), action=(reg0[[19]] = 1; ct_lb_mark;)
> >  ])
> >
> >  AT_CHECK([grep "ls_in_lb " sw0flows | ovn_strip_lflows], [0], [dnl
> > @@ -4764,6 +4768,8 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
> >  AT_CHECK([ovn-sbctl dump-flows sw0 | grep "ls_in_lb " | ovn_strip_lflows 
> > ], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> >    table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.20), action=(drop;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> >  ])
> >
> >  AT_CLEANUP
> > @@ -7763,7 +7769,9 @@ AT_CHECK([grep -e "ls_in_acl.*eval" -e 
> > "ls_in_acl_hint" lsflows | ovn_strip_lflo
> >
> >  AT_CHECK([grep -e "ls_in_lb " lsflows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(reg1 = 10.0.0.2; ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> >  ])
> >
> >  AT_CHECK([grep -e "ls_in_stateful" lsflows | ovn_strip_lflows], [0], [dnl
> > @@ -7818,7 +7826,9 @@ AT_CHECK([grep -e "ls_in_acl.*eval" -e 
> > "ls_in_acl_hint" lsflows | ovn_strip_lflo
> >
> >  AT_CHECK([grep -e "ls_in_lb " lsflows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(reg1 = 10.0.0.2; ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> >  ])
> >
> >  AT_CHECK([grep -e "ls_in_stateful" lsflows | ovn_strip_lflows], [0], [dnl
> > @@ -7873,7 +7883,9 @@ AT_CHECK([grep -e "ls_in_acl.*eval" -e 
> > "ls_in_acl_hint" lsflows | ovn_strip_lflo
> >
> >  AT_CHECK([grep -e "ls_in_lb " lsflows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 10.0.0.2), action=(reg1 = 10.0.0.2; ct_lb_mark(backends=10.0.0.10);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> >  ])
> >
> >  AT_CHECK([grep -e "ls_in_stateful" lsflows | ovn_strip_lflows], [0], [dnl
> > @@ -9258,13 +9270,13 @@ AT_CAPTURE_FILE([S1flows])
> >
> >  AT_CHECK([grep "ls_in_lb " S0flows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.2:80);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.11 && tcp.dst == 8080), 
> > action=(ct_lb_mark(backends=10.0.0.2:8080);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), action=(reg1 = 172.16.0.10; reg2[[0..15]] 
> > = 80; ct_lb_mark(backends=10.0.0.2:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.11 && tcp.dst == 8080), action=(reg1 = 172.16.0.11; 
> > reg2[[0..15]] = 8080; ct_lb_mark(backends=10.0.0.2:8080);)
> >  ])
> >  AT_CHECK([grep "ls_in_lb " S1flows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), action=(ct_lb_mark(backends=10.0.0.2:80);)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.11 && tcp.dst == 8080), 
> > action=(ct_lb_mark(backends=10.0.0.2:8080);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), action=(reg1 = 172.16.0.10; reg2[[0..15]] 
> > = 80; ct_lb_mark(backends=10.0.0.2:80);)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.11 && tcp.dst == 8080), action=(reg1 = 172.16.0.11; 
> > reg2[[0..15]] = 8080; ct_lb_mark(backends=10.0.0.2:8080);)
> >  ])
> >
> >  ovn-sbctl get datapath S0 _uuid > dp_uuids
> > @@ -9394,7 +9406,9 @@ AT_CHECK([grep "ls_in_lb_aff_check" S0flows | 
> > ovn_strip_lflows], [0], [dnl
> >  ])
> >  AT_CHECK([grep "ls_in_lb " S0flows | ovn_strip_lflows], [0], [dnl
> >    table=??(ls_in_lb           ), priority=0    , match=(1), action=(next;)
> > -  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip4), action=(reg1 = ct_nw_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.trk && !ct.rpl 
> > && reg0[[19]] == 1 && ip6), action=(xxreg1 = ct_ip6_dst(); reg2[[0..15]] = 
> > ct_tp_dst(); next;)
> > +  table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst 
> > == 172.16.0.10 && tcp.dst == 80), action=(reg1 = 172.16.0.10; reg2[[0..15]] 
> > = 80; ct_lb_mark(backends=10.0.0.2:80,20.0.0.2:80);)
> >    table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && 
> > ct.new && ip4.dst == 172.16.0.10 && reg4 == 10.0.0.2 && reg8[[0..15]] == 
> > 80), action=(reg1 = 172.16.0.10; reg2[[0..15]] = 80; 
> > ct_lb_mark(backends=10.0.0.2:80);)
> >    table=??(ls_in_lb           ), priority=150  , match=(reg9[[6]] == 1 && 
> > ct.new && ip4.dst == 172.16.0.10 && reg4 == 20.0.0.2 && reg8[[0..15]] == 
> > 80), action=(reg1 = 172.16.0.10; reg2[[0..15]] = 80; 
> > ct_lb_mark(backends=20.0.0.2:80);)
> >  ])
> > @@ -13976,7 +13990,7 @@ AT_CHECK([grep "ls_in_pre_stateful" s1flows | 
> > ovn_strip_lflows | grep "30.0.0.1"
> >    table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
> > ip4.dst == 30.0.0.1), action=(reg1 = 30.0.0.1; ct_lb_mark;)
> >  ])
> >  AT_CHECK([grep "ls_in_lb" s1flows | ovn_strip_lflows | grep "30.0.0.1"], 
> > [0], [dnl
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 30.0.0.1), 
> > action=(ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 30.0.0.1), action=(reg1 = 30.0.0.1; 
> > ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
> >  ])
> >
> >  # Associate load balancer to lr1 with DGP
> > @@ -14090,7 +14104,7 @@ AT_CHECK([grep "ls_in_pre_stateful" s1flows | 
> > ovn_strip_lflows | grep "2001:db8:
> >    table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
> > ip6.dst == 2001:db8:cccc::1), action=(xxreg1 = 2001:db8:cccc::1; 
> > ct_lb_mark;)
> >  ])
> >  AT_CHECK([grep "ls_in_lb" s1flows | ovn_strip_lflows | grep 
> > "2001:db8:cccc::1"], [0], [dnl
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip6.dst 
> > == 2001:db8:cccc::1), 
> > action=(ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip6.dst 
> > == 2001:db8:cccc::1), action=(xxreg1 = 2001:db8:cccc::1; 
> > ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);)
> >  ])
> >
> >  # Associate load balancer to lr1 with DGP
> > @@ -14201,7 +14215,7 @@ AT_CHECK([grep "ls_in_pre_stateful" s1flows | 
> > ovn_strip_lflows | grep "30.0.0.1"
> >    table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
> > ip4.dst == 30.0.0.1), action=(reg1 = 30.0.0.1; ct_lb_mark;)
> >  ])
> >  AT_CHECK([grep "ls_in_lb" s1flows | ovn_strip_lflows | grep "30.0.0.1"], 
> > [0], [dnl
> > -  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 30.0.0.1), 
> > action=(ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
> > +  table=??(ls_in_lb           ), priority=110  , match=(ct.new && ip4.dst 
> > == 30.0.0.1), action=(reg1 = 30.0.0.1; 
> > ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
> >  ])
> >
> >  # Associate load balancer to lr1 with DGP
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index 10cd7a79b9..af3133f868 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -2418,6 +2418,54 @@ sample(probability=10, obs_point=ct_label);
> >  mac_cache_use;
> >      encodes as resubmit(,OFTABLE_MAC_CACHE_USE)
> >
> > +# ct_nw_dst()
> > +reg1 = ct_nw_dst();
> > +    encodes as 
> > set_field:0->reg4,resubmit(,OFTABLE_CT_ORIG_NW_DST_LOAD),move:NXM_NX_REG4[[]]->NXM_NX_XXREG0[[64..95]]
> > +
> > +xreg1[[3..34]] = ct_nw_dst();
> > +    encodes as 
> > set_field:0->reg4,resubmit(,OFTABLE_CT_ORIG_NW_DST_LOAD),move:NXM_NX_REG4[[]]->NXM_NX_XXREG0[[3..34]]
> > +
> > +reg1[[3..34]] = ct_nw_dst();
> > +    Cannot select bits 3 to 34 of 32-bit field reg1.
> > +
> > +reg1[[3..35]] = ct_nw_dst();
> > +    Cannot select bits 3 to 35 of 32-bit field reg1.
> > +
> > +reg1[[1]] = ct_nw_dst();
> > +    Cannot use 1-bit field reg1[[1..1]] where 32-bit field is required.
> > +
> > +ct_nw_dst;
> > +    Syntax error at `ct_nw_dst' expecting action.
> > +
> > +ct_nw_dst();
> > +    Syntax error at `ct_nw_dst' expecting action.
> > +
> > +# ct_ip6_dst()
> > +xxreg1 = ct_ip6_dst();
> > +    encodes as 
> > set_field:0/0xffffffffffffffff->xxreg0,set_field:0/0xffffffffffffffff0000000000000000->xxreg0,resubmit(,OFTABLE_CT_ORIG_IP6_DST_LOAD),move:NXM_NX_XXREG0[[]]->NXM_NX_XXREG1[[]]
> > +
> > +reg1 = ct_ip6_dst();
> > +    Cannot use 32-bit field reg1[[0..31]] where 128-bit field is required.
> > +
> > +ct_ip6_dst;
> > +    Syntax error at `ct_ip6_dst' expecting action.
> > +
> > +ct_ip6_dst();
> > +    Syntax error at `ct_ip6_dst' expecting action.
> > +
> > +# ct_tp_dst()
> > +reg1[[0..15]] = ct_tp_dst();
> > +    encodes as 
> > set_field:0/0xffff->reg8,resubmit(,OFTABLE_CT_ORIG_TP_DST_LOAD),move:NXM_NX_REG8[[0..15]]->NXM_NX_XXREG0[[64..79]]
> > +
> > +reg1 = ct_tp_dst();
> > +    Cannot use 32-bit field reg1[[0..31]] where 16-bit field is required.
> > +
> > +ct_tp_dst;
> > +    Syntax error at `ct_tp_dst' expecting action.
> > +
> > +ct_tp_dst();
> > +    Syntax error at `ct_tp_dst' expecting action.
> > +
> >  # Miscellaneous negative tests.
> >  ;
> >      Syntax error at `;'.
> > @@ -25632,7 +25680,7 @@ OVS_WAIT_FOR_OUTPUT(
> >     ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 
> > 's/table=..//'], 0,
> >    [dnl
> >    (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip4.dst 
> > == 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 
> > 80; ct_lb_mark;)
> > -  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 
> > 10.0.0.10 && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80; 
> > hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> > +  (ls_in_lb           ), priority=120  , match=(ct.new && ip4.dst == 
> > 10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; 
> > ct_lb_mark(backends=10.0.0.3:80,20.0.0.3:80; 
> > hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> >  ])
> >
> >  AT_CAPTURE_FILE([sbflows2])
> > @@ -25831,7 +25879,7 @@ OVS_WAIT_FOR_OUTPUT(
> >     ovn-sbctl dump-flows sw0 | grep ct_lb_mark | grep priority=120 | sed 
> > 's/table=..//'], 0,
> >    [dnl
> >    (ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && ip6.dst 
> > == 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; 
> > ct_lb_mark;)
> > -  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 
> > 2001::a && tcp.dst == 80), 
> > action=(ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; 
> > hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> > +  (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 
> > 2001::a && tcp.dst == 80), action=(xxreg1 = 2001::a; reg2[[0..15]] = 80; 
> > ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; 
> > hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
> >  ])
> >
> >  AT_CAPTURE_FILE([sbflows2])
> > @@ -35663,7 +35711,9 @@ check_default_flows() {
> >      for table in $(grep -oP "table=\K\d*, " oflows | tr -d ',' | sort -n | 
> > uniq); do
> >          # Tables 68 and 70 are part of the chk_lb_hairpin and 
> > ct_snat_to_vip actions
> >          # respectively and it's OK if they don't have a default action.
> > -        if test ${table} -eq 68 -o ${table} -eq 70; then
> > +        # Tables 81, 82 and 83 are part of ct_nw_dst(), ct_ip6_dst() and 
> > ct_tp_dst()
> > +        # actions respectively and its OK for them to not have default 
> > flows.
> > +        if test ${table} -eq 68 -o ${table} -eq 70 -o ${table} -eq 81 -o 
> > ${table} -eq 82 -o ${table} -eq 83; then
> >              continue;
> >          fi
> >          AT_CHECK([grep -qe "table=$table.* priority=0\(,metadata=0x\w*\)\? 
> > actions" oflows], [0], [ignore], [ignore], [echo "Table $table does not 
> > contain a default action"])
> > diff --git a/tests/system-ovn-kmod.at b/tests/system-ovn-kmod.at
> > index 75ecdadebe..b17ec53f43 100644
> > --- a/tests/system-ovn-kmod.at
> > +++ b/tests/system-ovn-kmod.at
> > @@ -1027,3 +1027,152 @@ OVS_TRAFFIC_VSWITCHD_STOP(["
> >  "])
> >  AT_CLEANUP
> >  ])
> > +
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Load Balancer LS hairpin IPv4 UDP - larger than MTU])
> > +AT_SKIP_IF([test $HAVE_NC = no])
> > +AT_SKIP_IF([test $HAVE_TCPDUMP = no])
>
> We don't use tcpdump in the test.
>
> > +AT_KEYWORDS([lb])
> > +
> > +ovn_start
> > +
> > +OVS_TRAFFIC_VSWITCHD_START()
> > +ADD_BR([br-int])
> > +
> > +# Set external-ids in br-int needed for ovn-controller
> > +ovs-vsctl \
> > +        -- set Open_vSwitch . external-ids:system-id=hv1 \
> > +        -- set Open_vSwitch . 
> > external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> > +        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> > +        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> > +        -- set bridge br-int fail-mode=secure 
> > other-config:disable-in-band=true
> > +
> > +# Start ovn-controller
> > +start_daemon ovn-controller
> > +
> > +# Logical network:
> > +# One logical switch with IPv4 load balancers that hairpin the traffic.
> > +ovn-nbctl ls-add sw
>
> Please add "check" in all places where it's applicable.
>
> > +ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
> > +ovn-nbctl lb-add lb-ipv4-tcp     88.88.88.88:8080 42.42.42.1:4041 tcp
>
> We don't use this load balancer.
>
> > +ovn-nbctl lb-add lb-ipv4-udp     88.88.88.88:4040 42.42.42.1:2021 udp
> > +ovn-nbctl ls-lb-add sw lb-ipv4-tcp
> > +ovn-nbctl ls-lb-add sw lb-ipv4-udp
> > +
> > +ovn-nbctl lr-add rtr
> > +ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24
> > +ovn-nbctl lsp-add sw sw-rtr                       \
> > +    -- lsp-set-type sw-rtr router                 \
> > +    -- lsp-set-addresses sw-rtr 00:00:00:00:01:00 \
> > +    -- lsp-set-options sw-rtr router-port=rtr-sw
> > +
> > +ADD_NAMESPACES(lsp)
> > +ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
> > +         "42.42.42.254")
> > +
> > +ovn-nbctl --wait=hv -t 3 sync
> > +
>
> No timeout needed.
>
> > +yes 1 | head -n 10000 | tr '\n' ' ' | dd of=datafile bs=7373 count=1
> > +cat datafile datafile datafile datafile > frag_test.expected
> > +
>
> Maybe:
>
> dnl Generate a datafile that's 8KB long.
> printf %8s > datafile
>
> > +# Start IPv4 UDP server on lsp.
> > +NETNS_DAEMONIZE([lsp], [nc -l -u 42.42.42.1 2021 -o udp_frag_test.rcvd], 
> > [lsp0_udp.pid])
> > +
> > +NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -p 20000 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -p 20000 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -p 20000 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -p 20000 < datafile], [0], 
> > [ignore], [ignore])
> > +
>
> This doesn't reply to UDP packets so the session doesn't move to
> established.
>
> Maybe we can try something like:
>
> NETNS_DAEMONIZE([lsp], [nc -e /bin/cat -u -v -l -o udp_frag_test.rcvd
> 2021], [lsp-nc.pid])
> NS_CHECK_EXEC([lsp], [(cat datafile; sleep 10) | nc -u 88.88.88.88 4040])
>
> This will make the server echo the incoming data.  It will also dump all
> session data (incoming and outgoing) so we need to change the
> frag_test.expected contents (duplicate them).
>
> I added the ugly sleep 10 to avoid the client closing the socket too
> early - the kernel would generate ICMP port unreachable for the replies
> in that case.  Maybe that can be done in a nicer way though.
>
> > +AT_CHECK([cmp frag_test.expected udp_frag_test.rcvd], [0], [ignore], 
> > [ignore])
> This could be:
> check cmp frag_test.expected udp_frag_test.rcvd
>
> Most of the system test comments above also apply to the IPv6 test below.
>
> > +
> > +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> > +
> > +as ovn-sb
> > +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > +
> > +as ovn-nb
> > +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > +
> > +as northd
> > +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> > +
> > +as
> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> > +/connection dropped.*/d"])
> > +AT_CLEANUP
> > +])
> > +
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Load Balancer LS hairpin IPv6 UDP - larger than MTU])
> > +AT_SKIP_IF([test $HAVE_NC = no])
> > +AT_KEYWORDS([lb])
> > +
> > +ovn_start
> > +
> > +OVS_TRAFFIC_VSWITCHD_START()
> > +ADD_BR([br-int])
> > +
> > +# Set external-ids in br-int needed for ovn-controller
> > +ovs-vsctl \
> > +        -- set Open_vSwitch . external-ids:system-id=hv1 \
> > +        -- set Open_vSwitch . 
> > external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
> > +        -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
> > +        -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
> > +        -- set bridge br-int fail-mode=secure 
> > other-config:disable-in-band=true
> > +
> > +# Start ovn-controller
> > +start_daemon ovn-controller
> > +
> > +# Logical network:
> > +# One logical switch with IPv6 load balancers that hairpin the traffic.
> > +ovn-nbctl ls-add sw
> > +ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
> > +ovn-nbctl lb-add lb-ipv6-tcp     [[8800::0088]]:8080 [[4200::1]]:4041 tcp
> > +ovn-nbctl lb-add lb-ipv6-udp     [[8800::0088]]:4040 [[4200::1]]:2021 udp
> > +ovn-nbctl ls-lb-add sw lb-ipv6-tcp
> > +ovn-nbctl ls-lb-add sw lb-ipv6-udp
> > +
> > +ovn-nbctl lr-add rtr
> > +ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64
> > +ovn-nbctl lsp-add sw sw-rtr                       \
> > +    -- lsp-set-type sw-rtr router                 \
> > +    -- lsp-set-addresses sw-rtr 00:00:00:00:01:00 \
> > +    -- lsp-set-options sw-rtr router-port=rtr-sw
> > +
> > +ovn-nbctl --wait=hv sync
> > +
> > +ADD_NAMESPACES(lsp)
> > +ADD_VETH(lsp, lsp, br-int, "4200::1/64", "00:00:00:00:00:01", 
> > "4200::00ff", "nodad")
> > +ovn-nbctl --wait=hv -t 3 sync
>
>
>
> > +
> > +yes 1 | head -n 10000 | tr '\n' ' ' | dd of=datafile bs=7373 count=1
> > +cat datafile datafile datafile datafile > frag_test.expected
> > +
> > +# Start IPv6 UDP server on lsp.
> > +NETNS_DAEMONIZE([lsp], [nc -l -u 4200::1 2021 -o udp_frag_test.rcvd], 
> > [lsp0_udp.pid])
> > +
> > +yes 1 | head -n 10000 | tr '\n' ' ' | dd of=datafile bs=7373 count=1
> > +NS_CHECK_EXEC([lsp], [nc -6 -u 8800::0088 4040 -p 20001 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -6 -u 8800::0088 4040 -p 20001 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -6 -u 8800::0088 4040 -p 20001 < datafile], [0], 
> > [ignore], [ignore])
> > +NS_CHECK_EXEC([lsp], [nc -6 -u 8800::0088 4040 -p 20001 < datafile], [0], 
> > [ignore], [ignore])
> > +
> > +cat datafile datafile datafile datafile > udp_frag_test.expected
> > +AT_CHECK([cmp udp_frag_test.expected udp_frag_test.rcvd], [0], [ignore], 
> > [ignore])
> > +
> > +OVS_APP_EXIT_AND_WAIT([ovn-controller])
> > +
> > +as ovn-sb
> > +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > +
> > +as ovn-nb
> > +OVS_APP_EXIT_AND_WAIT([ovsdb-server])
> > +
> > +as northd
> > +OVS_APP_EXIT_AND_WAIT([ovn-northd])
> > +
> > +as
> > +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
> > +/connection dropped.*/d"])
> > +AT_CLEANUP
> > +])
> > diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> > index 2ea68f2124..7954bb98ac 100644
> > --- a/tests/test-ovn.c
> > +++ b/tests/test-ovn.c
> > @@ -1376,6 +1376,9 @@ test_parse_actions(struct ovs_cmdl_context *ctx 
> > OVS_UNUSED)
> >                  .in_port_sec_ptable = OFTABLE_CHK_IN_PORT_SEC,
> >                  .out_port_sec_ptable = OFTABLE_CHK_OUT_PORT_SEC,
> >                  .mac_cache_use_table = OFTABLE_MAC_CACHE_USE,
> > +                .ct_nw_dst_load_table = OFTABLE_CT_ORIG_NW_DST_LOAD,
> > +                .ct_ip6_dst_load_table = OFTABLE_CT_ORIG_IP6_DST_LOAD,
> > +                .ct_tp_dst_load_table = OFTABLE_CT_ORIG_TP_DST_LOAD,
> >                  .lflow_uuid.parts =
> >                      { 0xaaaaaaaa, 0xbbbbbbbb, 0xcccccccc, 0xdddddddd},
> >                  .dp_key = 0xabcdef,
> > diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> > index f9cc2463b7..806bdf3d9a 100644
> > --- a/utilities/ovn-trace.c
> > +++ b/utilities/ovn-trace.c
> > @@ -3447,6 +3447,12 @@ trace_actions(const struct ovnact *ovnacts, size_t 
> > ovnacts_len,
> >              break;
> >          case OVNACT_MAC_CACHE_USE:
> >              break;
> > +        case OVNACT_CT_ORIG_NW_DST:
> > +            break;
> > +        case OVNACT_CT_ORIG_IP6_DST:
> > +            break;
> > +        case OVNACT_CT_ORIG_TP_DST:
> > +            break;
> >          }
> >      }
> >      ofpbuf_uninit(&stack);
>
> Thanks,
> Dumitru
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to