On 5/7/25 2:13 PM, Rukomoinikova Aleksandra wrote:
> Hi Dumitru, thanks for your time and review.

Hi Alexandra,

> I think the idea of ​​adding logical flows for each backend is extremely 
> inefficient with a large number of backends. Regarding the option: this 

That's my concern too with flows per backend.

> was our original approach, but what bothers me about this approach is 
> that what is the point of having a load balancer if there is a stateless 
> ACL if they won't work?
> 

They work if the traffic matched by stateless ACLs is different than the
traffic processed by load balancers.  A rather simple example (I guess
it can appear in a more complex form in real deployments too though):

VIF2 (42.42.42.2/24) --- LS --- VIF3 (42.42.42.3/24)

with:

stateless ACLs that allow all udp traffic except traffic for port 4242:

ovn-nbctl acl-add ls from-lport 1 'udp' allow-stateless
ovn-nbctl acl-add ls to-lport 2 'udp && udp.dst == 4242' drop
ovn-nbctl acl-add ls to-lport 1 'udp' allow-stateless

and a TCP LB:

ovn-nbctl lb-add lb-test 66.66.66.66:666 42.42.42.2:4242 tcp

If VIF3 sends UDP packets to 42.42.42.2:3131 these match the
allow-stateless ACLs (ingress and egress) and reach VIF2.

If VIF3 initiates a TCP connection to LB VIP 66.66.66.66:666, DNAT to
42.42.42.2:4242 happens and replies are properly sent to conntrack (they
don't match allow-stateless ACLs).

Regards,
Dumitru

> 
> On 07.05.2025 13:55, Dumitru Ceara wrote:
>> On 4/28/25 8:32 PM, Alexandra Rukomoinikova wrote:
>>> [0] Removed support for using load balancers in conjunction with stateless 
>>> ACL.
>>> This commit removes the ability to use load balancers alongside stateless 
>>> ACL.
>>> If a load balancer is created, the datapath is no longer fully stateless.
>>> Therefore, to avoid traffic being directed to the contract, it is 
>>> recommended
>>> to refrain from creating a load balancer entirely.
>>>
>>> Commit [0] ensures the separation of stateful and stateless scenarios
>>> in the absence of load balancers, without altering the functionality
>>> of load balancers themselves.
>>>
>>> When a logical switch is configured with stateless ACL and a load balancer,
>>> the check for the `REGBIT_CONNTRACK_NAT` flag in the `pre_lb` stage of
>>> the ingress pipeline becomes redundant. Traffic directed to the load 
>>> balancer
>>> must be processed through the conntrack.
>>>
>>> To ensure proper load balancer operation, a rule must be added to match
>>> the load balancer's VIP address and its protocol (if applicable). This rule
>>> is added to the datapath group and does not negatively impact performance.
>>> Packets matching this rule would still be directed to the contract via
>>> lower-priority rules in the absence of stateless ACL. However, with 
>>> stateless ACL,
>>> this rule enables load balancing when the client balances traffic to itself.
>>>
>>> In the egress pipeline, the stateless register should only be set if no
>>> load balancers are present on the datapath. This maintains a clear 
>>> separation
>>> between Stateful and Stateless modes when using ACL.
>>> If a user creates a load balancer on a logical switch, they should be aware
>>> that the traffic will no longer be fully stateless.
>>>
>>> Also in case of lb configured with stateless ACLs we no longer take into 
>>> account
>>> ct.inv packets in egress. They will be dropped further, at the hypervisor 
>>> level.
>>>
>>> [0] - ovn-org@a0f82ef.
>>>
>>> Signed-off-by: Alexandra Rukomoinikova <arukomoinikova@k2.cloud>
>>> ---
>>> v3 --> v4: fixed Dumitu's comments.
>>>
>> Hi Alexandra,
>>
>> Thanks for the patch and sorry for the slowness in reviews.
>>
>>> ---
>>>   lib/lb.c                |   1 +
>>>   lib/lb.h                |   2 +
>>>   northd/en-ls-stateful.c |   4 +
>>>   northd/en-ls-stateful.h |   1 +
>>>   northd/lb.c             |  17 ++++-
>>>   northd/northd.c         |  71 +++++++++++++++---
>>>   northd/ovn-northd.8.xml |  11 ++-
>>>   tests/ovn-northd.at     | 143 +++++++++++++++++++++++++++++++++---
>>>   tests/ovn.at            |   8 +-
>>>   tests/system-ovn.at     | 157 ++++++++++++++++++++++++++++++++++++----
>>>   10 files changed, 372 insertions(+), 43 deletions(-)
>>>
>>> diff --git a/lib/lb.c b/lib/lb.c
>>> index 6e7a4e296..f12373321 100644
>>> --- a/lib/lb.c
>>> +++ b/lib/lb.c
>>> @@ -304,6 +304,7 @@ ovn_lb_vip_destroy(struct ovn_lb_vip *vip)
>>>   {
>>>       free(vip->vip_str);
>>>       free(vip->port_str);
>>> +    free(vip->hairpin_snat_ip);
>>>       ovn_lb_backends_destroy(vip);
>>>   }
>>>   
>>> diff --git a/lib/lb.h b/lib/lb.h
>>> index b1a89d63c..c629e1efd 100644
>>> --- a/lib/lb.h
>>> +++ b/lib/lb.h
>>> @@ -42,6 +42,8 @@ struct ovn_lb_vip {
>>>   
>>>       bool empty_backend_rej;
>>>       int address_family;
>>> +
>>> +    char *hairpin_snat_ip;
>>>   };
>>>   
>>>   struct ovn_lb_backend {
>>> diff --git a/northd/en-ls-stateful.c b/northd/en-ls-stateful.c
>>> index 69cda5008..6240c1ea8 100644
>>> --- a/northd/en-ls-stateful.c
>>> +++ b/northd/en-ls-stateful.c
>>> @@ -452,6 +452,10 @@ ls_stateful_record_set_acl_flags_(struct 
>>> ls_stateful_record *ls_stateful_rec,
>>>                   && !strcmp(acl->action, "allow-related")) {
>>>               ls_stateful_rec->has_stateful_acl = true;
>>>           }
>>> +        if (!ls_stateful_rec->has_stateless_acl
>>> +                && !strcmp(acl->action, "allow-stateless")) {
>>> +            ls_stateful_rec->has_stateless_acl = true;
>>> +        }
>>>           if (ls_stateful_rec->has_stateful_acl &&
>>>               ls_acl_tiers_are_maxed_out(
>>>                   &ls_stateful_rec->max_acl_tier,
>>> diff --git a/northd/en-ls-stateful.h b/northd/en-ls-stateful.h
>>> index 18a7398a6..8516996c5 100644
>>> --- a/northd/en-ls-stateful.h
>>> +++ b/northd/en-ls-stateful.h
>>> @@ -50,6 +50,7 @@ struct ls_stateful_record {
>>>       size_t ls_index;
>>>   
>>>       bool has_stateful_acl;
>>> +    bool has_stateless_acl;
>>>       bool has_lb_vip;
>>>       bool has_acls;
>>>       struct acl_tier max_acl_tier;
>>> diff --git a/northd/lb.c b/northd/lb.c
>>> index af0c92954..3279a8fcd 100644
>>> --- a/northd/lb.c
>>> +++ b/northd/lb.c
>>> @@ -256,6 +256,14 @@ ovn_lb_get_health_check(const struct 
>>> nbrec_load_balancer *nbrec_lb,
>>>       return NULL;
>>>   }
>>>   
>>> +static bool
>>> +validate_snap_ip_address(const char *snat_ip)
>>> +{
>>> +    ovs_be32 ip;
>>> +
>>> +    return ip_parse(snat_ip, &ip);
>>> +}
>>> +
>>>   static void
>>>   ovn_northd_lb_init(struct ovn_northd_lb *lb,
>>>                      const struct nbrec_load_balancer *nbrec_lb)
>>> @@ -338,7 +346,14 @@ ovn_northd_lb_init(struct ovn_northd_lb *lb,
>>>           if (lb_vip_nb->lb_health_check) {
>>>               ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
>>>           }
>>> -    }
>>> +
>>> +        const char *snat_ip = smap_get(&lb->nlb->options,
>>> +                                       "hairpin_snat_ip");
>>> +
>>> +        if (snat_ip && validate_snap_ip_address(snat_ip)) {
>>> +            lb_vip->hairpin_snat_ip = xstrdup(snat_ip);
>>> +        }
>>> +   }
>>>   
>>>       /* It's possible that parsing VIPs fails.  Update the lb->n_vips to 
>>> the
>>>        * correct value.
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index 74792e38b..b9f733bd8 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -5926,7 +5926,8 @@ skip_port_from_conntrack(const struct ovn_datapath 
>>> *od, struct ovn_port *op,
>>>   }
>>>   
>>>   static void
>>> -build_stateless_filter(const struct ovn_datapath *od,
>>> +build_stateless_filter(const struct ls_stateful_record *ls_stateful_rec,
>>> +                       const struct ovn_datapath *od,
>>>                          const struct nbrec_acl *acl,
>>>                          struct lflow_table *lflows,
>>>                          struct lflow_ref *lflow_ref)
>>> @@ -5939,7 +5940,11 @@ build_stateless_filter(const struct ovn_datapath *od,
>>>                                   action,
>>>                                   &acl->header_,
>>>                                   lflow_ref);
>>> -    } else {
>>> +    } else if (!ls_stateful_rec->has_lb_vip) {
>>> +        /* For cases when we have statefull ACLs but no load
>> Uhm, I think you mean "For cases when we have stateless ACLs but no load
>> balancer".
>>
>>> +           balancer configured on logical switch - we should
>>> +           completely bypass conntrack on egress, otherwise
>>> +           it is necessary to check the balanced traffic. */
>>>           ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL,
>>>                                   acl->priority + OVN_ACL_PRI_OFFSET,
>>>                                   acl->match,
>> However, I've been testing this patch some more and, as I was trying to
>> say on earlier versions, I don't think we can accept it as is.  Let's
>> assume we have an user that has the following configuration:
>>
>> VIF2 (42.42.42.2/24) --- LS --- VIF3 (42.42.42.3/24)
>>
>> And a to-lport (egress) stateless ACL:
>>
>> to-lport, prio 1, if "ip4.dst == 42.42.42.0/24", then allow-stateless
>>
>> When there's no load balancer configured all traffic from VIF3
>> (42.42.42.3) to VIF2 (42.42.42.2) is forwarded without ever being sent
>> to conntrack, in the datapath ct_state=-trk, which is fine.
>>
>> Now let's assume we also have VIFs 4 and 5:
>>
>> VIF2 (42.42.42.2/24) --- LS --- VIF3 (42.42.42.3/24)
>>                           |
>> VIF4 (42.42.42.4/24) ----+----- VIF5 (42.42.42.5/24)
>>
>> And a TCP load balancer:
>> - VIP: 66.66.66.66:666
>> - backends: 42.42.42.4:4242 (VIF4)
>>
>> Before your patch:
>>
>> a. traffic from VIF5 to LB VIP (66.66.66.66:666) is not DNATed, LB
>> doesn't work
>>
>> b. all traffic from VIF3 (42.42.42.3) to VIF2 (42.42.42.2) is forwarded
>> without ever being sent to conntrack, in the datapath ct_state=-trk.
>>
>> According to Han and Venu [0], [1], that's as designed:
>> [0]
>> https://github.com/ovn-org/ovn/commit/a0f82efdd9dfd3ef2d9606c1890e353df1097a51
>> [1] https://mail.openvswitch.org/pipermail/ovs-dev/2022-November/399224.html
>>
>> Now with your patch:
>>
>> a. traffic from VIF5 to LB VIP (66.66.66.66:666) works.
>>
>> BUT
>>
>> b. all traffic between VIF3 (42.42.42.3) and VIF2 (42.42.42.2) is
>> forwarded either on datapath flows that match on:
>> ct_state(+new-est-rel-rpl-inv+trk)
>>
>> OR on flows that match on:
>> ct_state(-new-est-rel-rpl+inv+trk)
>>
>> That means the to-lport allow-stateless ACL has no stateless semantics
>> anymore.  It also breaks HW offload for some NVIDIA NICs and it also
>> means (useless?) recirculations in the datapath.
>>
>> I understand that for your use case that's acceptable but other use
>> cases as above get broken.
>>
>> I think we have two options:
>>
>> A. add flows that match on reply traffic that's destined to load
>> balancer backends.  This comes with a control plane performance cost
>> because we need to add a flow per backend.  It also means we'd have to
>> add the flows from ovn-controller if we want to support template load
>> balancers (for which the backend IPs are potentially not known in
>> ovn-northd).
>>
>> B. add a per switch option to enable the load balancer functionality
>> when stateless ACLs are configured on the switch.  The documentation for
>> this option should clearly note that when the option is set
>> allow-stateless ACLs in the egress direction are not stateless anymore
>> if LBs are present.  The option should be false by default so we don't
>> break any existing deployments.
>>
>> Something like NB.Logical_Switch.other_config:enable-stateless-acl-lb.
>>
>> Then change the code above to bypass the stateless filter in the egress
>> pipeline only if the option is set to True for the given switch.
>>
>> We'd also need to make sure we change the ct.inv/!ct.inv lflow matches
>> only if the option is set to true.
>>
>> This new option would also need a NEWS file entry.
>>
>> Knobs are not great but in this situation I'm out of ideas so I vote for
>> option B.
>>
>> Please see some more minor comments below.
>>
>>> @@ -5950,15 +5955,17 @@ build_stateless_filter(const struct ovn_datapath 
>>> *od,
>>>   }
>>>   
>>>   static void
>>> -build_stateless_filters(const struct ovn_datapath *od,
>>> +build_stateless_filters(const struct ls_stateful_record *ls_stateful_rec,
>>>                           const struct ls_port_group_table *ls_port_groups,
>>> +                        const struct ovn_datapath *od,
>>>                           struct lflow_table *lflows,
>>>                           struct lflow_ref *lflow_ref)
>>>   {
>>>       for (size_t i = 0; i < od->nbs->n_acls; i++) {
>>>           const struct nbrec_acl *acl = od->nbs->acls[i];
>>>           if (!strcmp(acl->action, "allow-stateless")) {
>>> -            build_stateless_filter(od, acl, lflows, lflow_ref);
>>> +            build_stateless_filter(ls_stateful_rec, od, acl, lflows,
>>> +                                   lflow_ref);
>>>           }
>>>       }
>>>   
>>> @@ -5974,7 +5981,8 @@ build_stateless_filters(const struct ovn_datapath *od,
>>>               const struct nbrec_acl *acl = ls_pg_rec->nb_pg->acls[i];
>>>   
>>>               if (!strcmp(acl->action, "allow-stateless")) {
>>> -                build_stateless_filter(od, acl, lflows, lflow_ref);
>>> +                build_stateless_filter(ls_stateful_rec, od, acl, lflows,
>>> +                                       lflow_ref);
>>>               }
>>>           }
>>>       }
>>> @@ -6029,7 +6037,8 @@ build_ls_stateful_rec_pre_acls(
>>>           }
>>>   
>>>           /* stateless filters always take precedence over stateful ACLs. */
>>> -        build_stateless_filters(od, ls_port_groups, lflows, lflow_ref);
>>> +        build_stateless_filters(ls_stateful_rec, ls_port_groups, od, 
>>> lflows,
>>> +                                lflow_ref);
>>>   
>>>           /* Ingress and Egress Pre-ACL Table (Priority 110).
>>>            *
>>> @@ -6074,7 +6083,8 @@ build_ls_stateful_rec_pre_acls(
>>>       } else if (ls_stateful_rec->has_lb_vip) {
>>>           /* We'll build stateless filters if there are LB rules so that
>>>            * the stateless flows are not tracked in pre-lb. */
>>> -         build_stateless_filters(od, ls_port_groups, lflows, lflow_ref);
>>> +         build_stateless_filters(ls_stateful_rec, ls_port_groups, od, 
>>> lflows,
>>> +                                 lflow_ref);
>>>       }
>>>   }
>>>   
>>> @@ -7400,6 +7410,23 @@ choose_max_acl_tier(const struct ls_stateful_record 
>>> *ls_stateful_rec,
>>>       }
>>>   }
>>>   
>>> +/* In the case of stateless ACLs and load balancers, all traffic
>>> + * is passed to conntrack during egress pipeline. Conntrack will
>>> + * mark the packets as 'invalid,' but since stateless systems do
>>> + * not rely on conntrack state, these invalid packets will be
>>> + * discarded during processing on the hypervisor level.
>>> + */
>>> +static bool
>>> +stateless_inv_match(const struct ls_stateful_record *ls_stateful_rec)
>>> +{
>>> +    if (ls_stateful_rec->has_lb_vip
>>> +            && ls_stateful_rec->has_stateless_acl) {
>> Nit: these fit on the same line.
>>
>>> +        return false;
>>> +    }
>>> +
>>> +    return true;
>>> +}
>>> +
>>>   static void
>>>   build_acls(const struct ls_stateful_record *ls_stateful_rec,
>>>              const struct ovn_datapath *od,
>>> @@ -7502,8 +7529,14 @@ build_acls(const struct ls_stateful_record 
>>> *ls_stateful_rec,
>>>            *
>>>            * This is enforced at a higher priority than ACLs can be 
>>> defined. */
>>>           ds_clear(&match);
>>> -        ds_put_format(&match, "%s(ct.est && ct.rpl && ct_mark.blocked == 
>>> 1)",
>>> -                      use_ct_inv_match ? "ct.inv || " : "");
>>> +
>>> +        if (use_ct_inv_match && stateless_inv_match(ls_stateful_rec)) {
>>> +            ds_put_cstr(&match, "ct.inv || (ct.est && ct.rpl && "
>>> +                                "ct_mark.blocked == 1)");
>>> +        } else {
>>> +            ds_put_cstr(&match, "ct.est && ct.rpl && ct_mark.blocked == 
>>> 1");
>>> +        }
>>> +
>>>           ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX - 3,
>>>                         ds_cstr(&match), REGBIT_ACL_VERDICT_DROP " = 1; 
>>> next;",
>>>                         lflow_ref);
>>> @@ -7797,8 +7830,7 @@ build_lb_rules_pre_stateful(struct lflow_table 
>>> *lflows,
>>>           }
>>>           ds_put_cstr(action, "ct_lb_mark;");
>>>   
>>> -        ds_put_format(match, REGBIT_CONNTRACK_NAT" == 1 && %s.dst == %s",
>>> -                      ip_match, lb_vip->vip_str);
>>> +        ds_put_format(match, "%s.dst == %s", ip_match, lb_vip->vip_str);
>>>           if (lb_vip->port_str) {
>>>               ds_put_format(match, " && %s.dst == %s", lb->proto,
>>>                             lb_vip->port_str);
>>> @@ -7808,6 +7840,23 @@ build_lb_rules_pre_stateful(struct lflow_table 
>>> *lflows,
>>>               lflows, lb_dps->nb_ls_map, ods_size(ls_datapaths),
>>>               S_SWITCH_IN_PRE_STATEFUL, 120, ds_cstr(match), 
>>> ds_cstr(action),
>>>               &lb->nlb->header_, lb_dps->lflow_ref);
>>> +
>>> +        /* Pass all VIP traffic to the conntrack to support load balancers
>>> +           in the case of stateless acl. */
>>> +        if (lb_vip->hairpin_snat_ip || lb_vip->port_str) {
>>> +            ds_clear(action);
>>> +            ds_clear(match);
>>> +
>>> +            ds_put_format(match, "%s && %s.dst == %s", lb->proto, ip_match,
>>> +                          lb_vip->hairpin_snat_ip ? lb_vip->hairpin_snat_ip
>>> +                          : lb_vip->vip_str);
>>> +            ds_put_cstr(action, "ct_lb_mark;");
>>> +
>>> +            ovn_lflow_add_with_dp_group(
>>> +                lflows, lb_dps->nb_ls_map, ods_size(ls_datapaths),
>>> +                S_SWITCH_IN_PRE_STATEFUL, 105, ds_cstr(match), 
>>> ds_cstr(action),
>>> +                &lb->nlb->header_, lb_dps->lflow_ref);
>>> +        }
>>>       }
>>>   }
>>>   
>>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
>>> index e087b6f59..08803b4c7 100644
>>> --- a/northd/ovn-northd.8.xml
>>> +++ b/northd/ovn-northd.8.xml
>>> @@ -507,7 +507,7 @@
>>>         <ref table="Logical_Switch_Port" db="OVN_Northbound"/>.  Multicast, 
>>> IPv6
>>>         Neighbor Discovery and MLD traffic also skips stateful ACLs. For
>>>         "allow-stateless" ACLs, a flow is added to bypass setting the hint 
>>> for
>>> -      connection tracker processing when there are stateful ACLs or LB 
>>> rules;
>>> +      connection tracker processing when there are stateful ACLs without 
>>> LB;
>>>         <code>REGBIT_ACL_STATELESS</code> is set for traffic matching 
>>> stateless
>>>         ACL flows.
>>>       </p>
>>> @@ -624,6 +624,12 @@
>>>            <code>ct_lb_mark;</code> action.
>>>         </li>
>>>   
>>> +      <li>
>>> +         A priority-105 flow sends all packet directed to VIP that don't
>>> +         match the above flows to connection tracker. This allows load
>>> +         balancers to work in case of stateless ACLs.
>>> +      </li>
>>> +
>>>         <li>
>>>            A priority-100 flow sends the packets to connection tracker based
>>>            on a hint provided by the previous tables
>>> @@ -770,7 +776,8 @@
>>>         </li>
>>>         <li>
>>>           <code>allow-stateless</code> ACLs translate into logical flows 
>>> that set
>>> -        the allow bit and advance to the next table.
>>> +        the allow bit and advance to the next table. In case of load 
>>> balancers
>>> +        with stateless ACLs in this table we allow ct.inv packets go 
>>> further.
>>>         </li>
>>>         <li>
>>>           <code>reject</code> ACLs translate into logical flows with that 
>>> set the
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index 82dfe92fd..63e1fe330 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -3747,10 +3747,18 @@ for direction in from to; do
>>>   done
>>>   check ovn-nbctl --wait=sb sync
>>>   
>>> -# TCP packets should not go to conntrack for load balancing.
>>> +# TCP packets should go to conntrack for load balancing.
>>>   flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
>>>   AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --minimal ls "${flow}"], 
>>> [0], [dnl
>>> -output("lsp2");
>>> +ct_lb_mark {
>>> +    ct_lb_mark {
>>> +        reg0[[6]] = 0;
>>> +        reg0[[12]] = 0;
>>> +        ct_lb_mark /* default (use --ct to customize) */ {
>>> +            output("lsp2");
>>> +        };
>>> +    };
>>> +};
>>>   ])
>>>   
>>>   # UDP packets still go to conntrack.
>>> @@ -3883,10 +3891,18 @@ for direction in from to; do
>>>   done
>>>   check ovn-nbctl --wait=sb sync
>>>   
>>> -# TCP packets should not go to conntrack for load balancing.
>>> +# TCP packets should go to conntrack for load balancing.
>>>   flow="inport == \"lsp1\" && ${flow_eth} && ${flow_ip} && ${flow_tcp}"
>>>   AT_CHECK_UNQUOTED([ovn_trace --ct new --ct new --minimal ls "${flow}"], 
>>> [0], [dnl
>>> -output("lsp2");
>>> +ct_lb_mark {
>>> +    ct_lb_mark {
>>> +        reg0[[6]] = 0;
>>> +        reg0[[12]] = 0;
>>> +        ct_lb_mark /* default (use --ct to customize) */ {
>>> +            output("lsp2");
>>> +        };
>>> +    };
>>> +};
>>>   ])
>>>   
>>>   # UDP packets still go to conntrack.
>>> @@ -4775,10 +4791,12 @@ check_stateful_flows() {
>>>       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=105  , match=(tcp && ip4.dst == 
>>> 10.0.0.10), action=(ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=105  , match=(tcp && ip4.dst == 
>>> 10.0.0.20), action=(ct_lb_mark;)
>>>     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=(reg4 = 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=(reg4 = 10.0.0.20; 
>>> reg2[[0..15]] = 80; ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 10.0.0.10 && tcp.dst == 80), action=(reg4 = 10.0.0.10; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 10.0.0.20 && tcp.dst == 80), action=(reg4 = 10.0.0.20; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>>   ])
>>>   
>>>       AT_CHECK([grep "ls_in_lb " sw0flows | ovn_strip_lflows], [0], [dnl
>>> @@ -5072,16 +5090,16 @@ AT_CAPTURE_FILE([sw0flows])
>>>   
>>>   AT_CHECK([grep -w "ls_in_acl_eval" sw0flows | grep 6553 | 
>>> ovn_strip_lflows], [0], [dnl
>>>     table=??(ls_in_acl_eval     ), priority=65532, match=(!ct.est && ct.rel 
>>> && !ct.new && ct_mark.blocked == 0), action=(reg0[[17]] = 1; reg8[[16]] = 
>>> 1; ct_commit_nat;)
>>> -  table=??(ls_in_acl_eval     ), priority=65532, match=((ct.est && ct.rpl 
>>> && ct_mark.blocked == 1)), action=(reg8[[17]] = 1; next;)
>>>     table=??(ls_in_acl_eval     ), priority=65532, match=(ct.est && !ct.rel 
>>> && !ct.new && ct.rpl && ct_mark.blocked == 0), action=(reg0[[9]] = 0; 
>>> reg0[[10]] = 0; reg0[[17]] = 1; reg8[[16]] = 1; next;)
>>> +  table=??(ls_in_acl_eval     ), priority=65532, match=(ct.est && ct.rpl 
>>> && ct_mark.blocked == 1), action=(reg8[[17]] = 1; next;)
>>>     table=??(ls_in_acl_eval     ), priority=65532, match=(ct.est && 
>>> ct_mark.allow_established == 1), action=(reg0[[21]] = 1; reg8[[16]] = 1; 
>>> next;)
>>>     table=??(ls_in_acl_eval     ), priority=65532, match=(nd || nd_ra || 
>>> nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
>>>   ])
>>>   
>>>   AT_CHECK([grep -w "ls_out_acl_eval" sw0flows | grep 6553 | 
>>> ovn_strip_lflows], [0], [dnl
>>>     table=??(ls_out_acl_eval    ), priority=65532, match=(!ct.est && ct.rel 
>>> && !ct.new && ct_mark.blocked == 0), action=(reg8[[16]] = 1; ct_commit_nat;)
>>> -  table=??(ls_out_acl_eval    ), priority=65532, match=((ct.est && ct.rpl 
>>> && ct_mark.blocked == 1)), action=(reg8[[17]] = 1; next;)
>>>     table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && !ct.rel 
>>> && !ct.new && ct.rpl && ct_mark.blocked == 0), action=(reg8[[16]] = 1; 
>>> next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && ct.rpl 
>>> && ct_mark.blocked == 1), action=(reg8[[17]] = 1; next;)
>>>     table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && 
>>> ct_mark.allow_established == 1), action=(reg8[[16]] = 1; next;)
>>>     table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || 
>>> nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
>>>   ])
>>> @@ -14398,7 +14416,7 @@ ovn-sbctl dump-flows s1 > s1flows
>>>   AT_CAPTURE_FILE([s1flows])
>>>   
>>>   AT_CHECK([grep "ls_in_pre_stateful" s1flows | ovn_strip_lflows | grep 
>>> "30.0.0.1"], [0], [dnl
>>> -  table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
>>> ip4.dst == 30.0.0.1), action=(reg4 = 30.0.0.1; ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 30.0.0.1), action=(reg4 = 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=(reg4 = 30.0.0.1; 
>>> ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
>>> @@ -14512,7 +14530,7 @@ ovn-sbctl dump-flows s1 > s1flows
>>>   AT_CAPTURE_FILE([s1flows])
>>>   
>>>   AT_CHECK([grep "ls_in_pre_stateful" s1flows | ovn_strip_lflows | grep 
>>> "2001:db8:cccc::1"], [0], [dnl
>>> -  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;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(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=(xxreg1 = 2001:db8:cccc::1; 
>>> ct_lb_mark(backends=2001:db8:aaaa:3::103,2001:db8:aaaa:3::102,2001:db8:aaaa:3::101);)
>>> @@ -14623,7 +14641,7 @@ ovn-sbctl dump-flows s1 > s1flows
>>>   AT_CAPTURE_FILE([s1flows])
>>>   
>>>   AT_CHECK([grep "ls_in_pre_stateful" s1flows | ovn_strip_lflows | grep 
>>> "30.0.0.1"], [0], [dnl
>>> -  table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
>>> ip4.dst == 30.0.0.1), action=(reg4 = 30.0.0.1; ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 30.0.0.1), action=(reg4 = 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=(reg4 = 30.0.0.1; 
>>> ct_lb_mark(backends=172.16.0.103,172.16.0.102,172.16.0.101);)
>>> @@ -16960,5 +16978,108 @@ AT_CHECK([ovn_strip_lflows < lrflows | grep 
>>> priority=105 | grep -c "flags.force_
>>>   1
>>>   ])
>>>   
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([Load Balancers with stateless ACLs])
>>> +ovn_start ovn-northd
>>> +
>>> +AS_BOX([create logical switches and ports])
>>> +check ovn-nbctl ls-add sw0
>>> +check ovn-nbctl --wait=sb lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \
>>> +"00:00:00:00:00:02 10.0.0.2"
>>> +
>>> +check ovn-nbctl --wait=sb lsp-add sw0 sw0-p2 -- lsp-set-addresses sw0-p2 \
>>> +"00:00:00:00:00:03 10.0.0.3"
>>> +
>>> +AS_BOX([create stateless ACLs])
>>> +check ovn-nbctl acl-add sw0 from-lport 1001 "ip" allow-stateless
>>> +check ovn-nbctl acl-add sw0 to-lport 1001 "ip" allow-stateless
>>> +
>>> +AS_BOX([create stateful ACLs])
>>> +# check if allow-stateless acls have higher priority we skip conntrack.
>>> +check ovn-nbctl acl-add sw0 from-lport 1000 "ip" allow-related
>>> +check ovn-nbctl acl-add sw0 to-lport 1000 "ip" allow-related
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +
>>> +AT_CHECK(
>>> +  [grep -E 'ls_(in|out)_pre_acl' sw0flows | grep reg0 | ovn_strip_lflows], 
>>> [0], [dnl
>>> +  table=??(ls_in_pre_acl      ), priority=100  , match=(ip), 
>>> action=(reg0[[0]] = 1; next;)
>>> +  table=??(ls_in_pre_acl      ), priority=2001 , match=(ip), 
>>> action=(reg0[[16]] = 1; next;)
>>> +  table=??(ls_out_pre_acl     ), priority=100  , match=(ip), 
>>> action=(reg0[[0]] = 1; next;)
>>> +  table=??(ls_out_pre_acl     ), priority=2001 , match=(ip), 
>>> action=(reg0[[16]] = 1; next;)
>>> +])
>>> +
>>> +AT_CHECK(
>>> +  [grep -E 'ls_out_acl_eval' sw0flows | grep 65532 | ovn_strip_lflows], 
>>> [0], [dnl
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(!ct.est && ct.rel 
>>> && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg8[[16]] = 1; 
>>> ct_commit_nat;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && !ct.rel 
>>> && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), 
>>> action=(reg8[[16]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && 
>>> ct_mark.allow_established == 1), action=(reg8[[16]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.inv || (ct.est 
>>> && ct.rpl && ct_mark.blocked == 1)), action=(reg8[[17]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || 
>>> nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
>>> +])
>>> +
>>> +AS_BOX([create Load Balancer])
>>> +check ovn-nbctl lb-add lb1 10.0.0.4:80 10.0.0.2:80,10.0.0.3:80
>>> +check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +
>>> +AT_CHECK([grep -E 'ls_(in|out)_pre_acl' sw0flows | grep reg0 | 
>>> ovn_strip_lflows], [0], [dnl
>>> +  table=??(ls_in_pre_acl      ), priority=100  , match=(ip), 
>>> action=(reg0[[0]] = 1; next;)
>>> +  table=??(ls_in_pre_acl      ), priority=2001 , match=(ip), 
>>> action=(reg0[[16]] = 1; next;)
>>> +  table=??(ls_out_pre_acl     ), priority=100  , match=(ip), 
>>> action=(reg0[[0]] = 1; next;)
>>> +])
>>> +
>>> +# We do not match conntrack invalide packets in case of load balancers 
>>> with stateless ACLs.
>>> +AT_CHECK(
>>> +  [grep -E 'ls_out_acl_eval' sw0flows | grep 65532 | ovn_strip_lflows], 
>>> [0], [dnl
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(!ct.est && ct.rel 
>>> && !ct.new && !ct.inv && ct_mark.blocked == 0), action=(reg8[[16]] = 1; 
>>> ct_commit_nat;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && !ct.rel 
>>> && !ct.new && !ct.inv && ct.rpl && ct_mark.blocked == 0), 
>>> action=(reg8[[16]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && ct.rpl 
>>> && ct_mark.blocked == 1), action=(reg8[[17]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(ct.est && 
>>> ct_mark.allow_established == 1), action=(reg8[[16]] = 1; next;)
>>> +  table=??(ls_out_acl_eval    ), priority=65532, match=(nd || nd_ra || 
>>> nd_rs || mldv1 || mldv2), action=(reg8[[16]] = 1; next;)
>>> +])
>>> +
>>> +AT_CHECK([grep -E '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=105  , match=(tcp && ip4.dst == 
>>> 10.0.0.4), action=(ct_lb_mark;)
>>> +  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=(ip4.dst == 
>>> 10.0.0.4 && tcp.dst == 80), action=(reg4 = 10.0.0.4; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>> +])
>>> +
>>> +AS_BOX([create Load Balancer without port])
>>> +check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
>>> +check ovn-nbctl lb-add lb2 10.0.0.5 10.0.0.2,10.0.0.3
>>> +check ovn-nbctl --wait=sb ls-lb-add sw0 lb2
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +
>>> +AT_CHECK([grep -E '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;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 10.0.0.5), action=(reg4 = 10.0.0.5; ct_lb_mark;)
>>> +])
>>> +
>>> +AS_BOX([set hairpin_snat_ip to Load Balancer without port])
>>> +check ovn-nbctl --wait=sb set load_balancer lb2 
>>> options:hairpin_snat_ip="10.0.0.6"
>>> +
>>> +ovn-sbctl dump-flows sw0 > sw0flows
>>> +AT_CHECK([grep -E '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=105  , match=(tcp && ip4.dst == 
>>> 10.0.0.6), action=(ct_lb_mark;)
>>> +  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=(ip4.dst == 
>>> 10.0.0.5), action=(reg4 = 10.0.0.5; ct_lb_mark;)
>>> +])
>>> +
>>> +
>>>   AT_CLEANUP
>>>   ])
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index 88c9cabfe..ea5682e00 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -26388,7 +26388,7 @@ OVS_WAIT_FOR_OUTPUT(
>>>     [ovn-sbctl dump-flows > sbflows
>>>      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=(reg4 = 10.0.0.10; reg2[[0..15]] = 
>>> 80; ct_lb_mark;)
>>> +  (ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 10.0.0.10 && 
>>> tcp.dst == 80), action=(reg4 = 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=(reg4 = 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");)
>>>   ])
>>>   
>>> @@ -26433,7 +26433,7 @@ AT_CHECK(
>>>     [grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" sbflows3 | 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=(drop;)
>>> -  table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
>>> ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg4 = 10.0.0.10; 
>>> reg2[[0..15]] = 80; ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip4.dst == 
>>> 10.0.0.10 && tcp.dst == 80), action=(reg4 = 10.0.0.10; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>>   ])
>>>   
>>>   AT_CAPTURE_FILE([sbflows4])
>>> @@ -26594,7 +26594,7 @@ OVS_WAIT_FOR_OUTPUT(
>>>     [ovn-sbctl dump-flows > sbflows
>>>      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 
>>> == 2002::a && tcp.dst == 80), action=(xxreg1 = 2002::a; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>> +  (ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2002::a && 
>>> tcp.dst == 80), action=(xxreg1 = 2002::a; reg2[[0..15]] = 80; ct_lb_mark;)
>>>     (ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst == 
>>> 2002::a && tcp.dst == 80), action=(xxreg1 = 2002::a; reg2[[0..15]] = 80; 
>>> ct_lb_mark(backends=[[2001::3]]:80,[[2002::3]]:80; 
>>> hash_fields="ipv6_dst,ipv6_src,tcp_dst,tcp_src");)
>>>   ])
>>>   
>>> @@ -26638,7 +26638,7 @@ AT_CHECK(
>>>     [grep "ip6.dst == 2002::a && tcp.dst == 80" sbflows3 | grep 
>>> priority=120 |\
>>>      ovn_strip_lflows], [0], [dnl
>>>     table=??(ls_in_lb           ), priority=120  , match=(ct.new && ip6.dst 
>>> == 2002::a && tcp.dst == 80), action=(drop;)
>>> -  table=??(ls_in_pre_stateful ), priority=120  , match=(reg0[[2]] == 1 && 
>>> ip6.dst == 2002::a && tcp.dst == 80), action=(xxreg1 = 2002::a; 
>>> reg2[[0..15]] = 80; ct_lb_mark;)
>>> +  table=??(ls_in_pre_stateful ), priority=120  , match=(ip6.dst == 2002::a 
>>> && tcp.dst == 80), action=(xxreg1 = 2002::a; reg2[[0..15]] = 80; 
>>> ct_lb_mark;)
>>>   ])
>>>   
>>>   AT_CAPTURE_FILE([sbflows4])
>>> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
>>> index 5fa740cfb..59f6fdefa 100644
>>> --- a/tests/system-ovn.at
>>> +++ b/tests/system-ovn.at
>>> @@ -5184,6 +5184,88 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port 
>>> patch-.*/d
>>>   AT_CLEANUP
>>>   ])
>>>   
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Load Balancer LS hairpin IPv4 with stateless ACLs])
>>> +
>>> +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.
>>> +check ovn-nbctl ls-add sw
>>> +check ovn-nbctl lsp-add sw lsp1 -- lsp-set-addresses lsp1 00:00:00:00:00:01
>>> +check ovn-nbctl lsp-add sw lsp2 -- lsp-set-addresses lsp2 00:00:00:00:00:02
>>> +check ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
>>> +check ovn-nbctl ls-lb-add sw lb-ipv4-tcp
>>> +
>>> +check ovn-nbctl lr-add rtr
>>> +check ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24
>>> +check 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(lsp1)
>>> +ADD_VETH(lsp1, lsp1, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \
>>> +         "42.42.42.254")
>>> +
>>> +ADD_NAMESPACES(lsp2)
>>> +ADD_VETH(lsp2, lsp2, br-int, "42.42.42.2/24", "00:00:00:00:00:02", \
>>> +         "42.42.42.254")
>>> +
>>> +check ovn-nbctl --wait=hv -t 3 sync
>>> +
>>
>> We shouldn't reduce the default timeout (-t 3), a plain "check ovn-nbctl
>> --wait=hv sync" is enough.
>>
>> We probably want a "wait_for_ports_up" as well here.
>>
>>> +# Start IPv4 TCP server on lsp1.
>>> +NETNS_DAEMONIZE([lsp1], [nc -l -k 42.42.42.1 4041], [lsp1.pid])
>>> +
>>> +# Send the packet to VIP.
>>> +NS_CHECK_EXEC([lsp1], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +NS_CHECK_EXEC([lsp2], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +
>>> +check ovn-nbctl acl-add sw to-lport 2000 'ip' allow-stateless
>>> +check ovn-nbctl acl-add sw from-lport 2000 'ip' allow-stateless
>>> +
>> This is racy, we need a "--wait=hv sync" here.
>>
>>> +# Send the packet to VIP after add stateless acl.
>>> +NS_CHECK_EXEC([lsp1], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +NS_CHECK_EXEC([lsp2], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +
>>> +check ovn-nbctl acl-add sw to-lport 2001 'ip' allow-related
>>> +check ovn-nbctl acl-add sw from-lport 2001 'ip' allow-related
>>> +
>> Here too.
>>
>>> +# Send the packet to VIP after add related acls.
>>> +NS_CHECK_EXEC([lsp1], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +NS_CHECK_EXEC([lsp2], [nc -z 88.88.88.88 8080], [0], [ignore], [ignore])
>>> +
>>> +OVN_CLEANUP_CONTROLLER([hv1])
>>> +
>>> +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])
>>>   AT_SKIP_IF([test $HAVE_NC = no])
>>> @@ -10071,7 +10153,7 @@ 
>>> tcp,orig=(src=192.168.1.2,dst=30.30.30.30,sport=<cleared>,dport=<cleared>),reply
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>   
>>> -# remove lb
>>> +# Remove Load Balancer.
>>>   check ovn-nbctl ls-lb-del foo lb1
>>>   
>>>   # add stateless acl
>>> @@ -10088,7 +10170,29 @@ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], 
>>> [dnl
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>   
>>> -# add lb back
>>> +# Delete ACLs.
>>> +check ovn-nbctl acl-del foo
>>> +
>>> +# Add stateful ACLs.
>>> +check ovn-nbctl acl-add foo from-lport 1 1 allow-related
>>> +check ovn-nbctl --wait=hv acl-add foo to-lport 1 1 allow-related
>>> +
>>> +# Add stateless ACLs.
>>> +check ovn-nbctl acl-add foo from-lport 2 1 allow-stateless
>>> +check ovn-nbctl --wait=hv acl-add foo to-lport 2 1 allow-stateless
>>> +
>>> +AT_CHECK([ip netns exec foo1 wget   192.168.2.2 -t 1 -T 1], [0], [ignore], 
>>> [ignore])
>>> +
>>> +# Check conntrack zone has no entry.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
>>> +FORMAT_CT(192.168.2.2) | \
>>> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +])
>>> +
>>> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> +
>>> +# When lb configured: pass conntrack for vip traffic.
>>> +# Add Load Balancer back.
>>>   check ovn-nbctl ls-lb-add foo lb1
>>>   
>>>   # Wait for ovn-controller to catch up.
>>> @@ -10097,13 +10201,14 @@ check ovn-nbctl --wait=hv sync
>>>   OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
>>>   grep 'nat(dst=192.168.2.2:80)'])
>>>   
>>> -# should not dnat so will not be able to connect
>>> -AT_CHECK([ip netns exec foo1 curl 30.30.30.30 --retry 3 --max-time 1], 
>>> [28], [ignore], [ignore])
>>> +# Now check with VIP.
>>> +AT_CHECK([ip netns exec foo1 wget   30.30.30.30  -t 3 -T 1], [0], 
>>> [ignore], [ignore])
>>>   
>>> -# check conntrack zone has no tcp entry
>>> +# Check conntrack zone has tcp entry.
>>>   AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
>>>   FORMAT_CT(30.30.30.30) | \
>>>   sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +tcp,orig=(src=192.168.1.2,dst=30.30.30.30,sport=<cleared>,dport=<cleared>),reply=(src=192.168.2.2,dst=192.168.1.2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
>>>   ])
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> @@ -10203,7 +10308,7 @@ 
>>> tcp,orig=(src=fd11::2,dst=fd12::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>   
>>> -# now check with VIP
>>> +# Now check with VIP.
>>>   AT_CHECK([ip netns exec foo1 wget  http://[[fd30::2]]  -t 3 -T 1], [0], 
>>> [ignore], [ignore])
>>>   
>>>   # check conntrack zone has tcp entry
>>> @@ -10215,16 +10320,39 @@ 
>>> tcp,orig=(src=fd11::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>   
>>> -# remove lb
>>> +# Remove Load_Balancer.
>>>   check ovn-nbctl ls-lb-del foo lb1
>>>   
>>> -# add stateless acl
>>> +# Delete ACLs.
>>> +check ovn-nbctl acl-del foo
>>> +
>>> +# Add stateful ACLs.
>>> +check ovn-nbctl acl-add foo from-lport 1 1 allow-related
>>> +check ovn-nbctl --wait=hv acl-add foo to-lport 1 1 allow-related
>>> +
>>> +# Add stateless ACLs.
>>> +check ovn-nbctl acl-add foo from-lport 2 1 allow-stateless
>>> +check ovn-nbctl --wait=hv acl-add foo to-lport 2 1 allow-stateless
>>> +
>>> +AT_CHECK([ip netns exec foo1  wget http://[[fd12::2]] -t 1 -T 1], [0], 
>>> [ignore], [ignore])
>>> +
>>> +# Check conntrack zone has no tcp entry.
>>> +AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
>>> +FORMAT_CT(fd12::2) |  grep -v fe80 | \
>>> +sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +])
>>> +
>>> +AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>> +
>>> +check ovn-nbctl acl-del foo
>>> +
>>> +# Add stateless ACLs.
>>>   check ovn-nbctl acl-add foo from-lport 1 1 allow-stateless
>>>   check ovn-nbctl --wait=hv acl-add foo to-lport 1 1 allow-stateless
>>>   
>>>   AT_CHECK([ip netns exec foo1  wget http://[[fd12::2]] -t 3 -T 1], [0], 
>>> [ignore], [ignore])
>>>   
>>> -# check conntrack zone has no tcp entry
>>> +# Check conntrack zone has no tcp entry.
>>>   AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
>>>   FORMAT_CT(fd12::2) |  grep -v fe80 | \
>>>   sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> @@ -10232,7 +10360,7 @@ sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>>>   
>>> -# add lb back
>>> +# Add Load Balancer back.
>>>   check ovn-nbctl ls-lb-add foo lb1
>>>   
>>>   # Wait for ovn-controller to catch up.
>>> @@ -10241,13 +10369,14 @@ check ovn-nbctl --wait=hv sync
>>>   OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-groups br-int | \
>>>   grep 'nat(dst=\[[fd12::2\]]:80)'])
>>>   
>>> -# should not dnat so will not be able to connect
>>> -AT_CHECK([ip netns exec foo1 curl http://[[fd30::2]] --retry 3 --max-time 
>>> 1], [28], [ignore], [ignore])
>>> -#
>>> -# check conntrack zone has no tcp entry
>>> +# Now check with VIP.
>>> +AT_CHECK([ip netns exec foo1 wget  http://[[fd30::2]]  -t 3 -T 1], [0], 
>>> [ignore], [ignore])
>>> +
>>> +# Check conntrack zone has tcp entry.
>>>   AT_CHECK([ovs-appctl dpctl/dump-conntrack zone=$zone_id | \
>>>   FORMAT_CT(fd30::2) | grep -v fe80 | \
>>>   sed -e 's/zone=[[0-9]]*/zone=<cleared>/'], [0], [dnl
>>> +tcp,orig=(src=fd11::2,dst=fd30::2,sport=<cleared>,dport=<cleared>),reply=(src=fd12::2,dst=fd11::2,sport=<cleared>,dport=<cleared>),zone=<cleared>,mark=2,protoinfo=(state=<cleared>)
>>>   ])
>>>   
>>>   AT_CHECK([ovs-appctl dpctl/flush-conntrack])
>> Regards,
>> Dumitru
>>
> 

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to