diff mbox series

[ovs-dev,v2,09/18] northd: Add a new node ls_lbacls.

Message ID 20231026181557.3367139-1-numans@ovn.org
State Changes Requested
Headers show
Series northd lflow incremental processing | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Numan Siddique Oct. 26, 2023, 6:15 p.m. UTC
From: Numan Siddique <numans@ovn.org>

This new engine now maintains the load balancer and ACL data of a
logical switch which was earlier part of northd engine node data.
The main inputs to this engine are:
    - northd node
    - NB logical switch node
    - Port group node

A record for each logical switch is maintained in the 'ls_lbacls'
hmap table and this record stores the below data which was earlier
part of 'struct ovn_datapath'.

    - bool has_stateful_acl;
    - bool has_lb_vip;
    - bool has_acls;
    - uint64_t max_acl_tier;

This engine node becomes an input to 'lflow' node.

Signed-off-by: Numan Siddique <numans@ovn.org>
---
 lib/stopwatch-names.h      |   1 +
 northd/automake.mk         |   2 +
 northd/en-lflow.c          |   4 +
 northd/en-lr-lb-nat-data.c |   8 +-
 northd/en-lr-lb-nat-data.h |   2 +
 northd/en-ls-lb-acls.c     | 527 +++++++++++++++++++++++++++++++++++++
 northd/en-ls-lb-acls.h     |  88 +++++++
 northd/en-port-group.h     |   3 +
 northd/inc-proc-northd.c   |   9 +
 northd/northd.c            | 271 +++++++++----------
 northd/northd.h            |   7 +-
 11 files changed, 776 insertions(+), 146 deletions(-)
 create mode 100644 northd/en-ls-lb-acls.c
 create mode 100644 northd/en-ls-lb-acls.h

Comments

Han Zhou Nov. 15, 2023, 6:54 a.m. UTC | #1
On Thu, Oct 26, 2023 at 11:17 AM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> This new engine now maintains the load balancer and ACL data of a
> logical switch which was earlier part of northd engine node data.
> The main inputs to this engine are:
>     - northd node
>     - NB logical switch node
>     - Port group node
>
> A record for each logical switch is maintained in the 'ls_lbacls'
> hmap table and this record stores the below data which was earlier
> part of 'struct ovn_datapath'.
>
>     - bool has_stateful_acl;
>     - bool has_lb_vip;
>     - bool has_acls;
>     - uint64_t max_acl_tier;

The node name indicates it contains LBs and ACLs of each LS, but it is
actually about the stateful related flags. So I think it is better to be
renamed to avoid confusion.

>
> This engine node becomes an input to 'lflow' node.
>
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---
>  lib/stopwatch-names.h      |   1 +
>  northd/automake.mk         |   2 +
>  northd/en-lflow.c          |   4 +
>  northd/en-lr-lb-nat-data.c |   8 +-
>  northd/en-lr-lb-nat-data.h |   2 +
>  northd/en-ls-lb-acls.c     | 527 +++++++++++++++++++++++++++++++++++++
>  northd/en-ls-lb-acls.h     |  88 +++++++
>  northd/en-port-group.h     |   3 +
>  northd/inc-proc-northd.c   |   9 +
>  northd/northd.c            | 271 +++++++++----------
>  northd/northd.h            |   7 +-
>  11 files changed, 776 insertions(+), 146 deletions(-)
>  create mode 100644 northd/en-ls-lb-acls.c
>  create mode 100644 northd/en-ls-lb-acls.h
>
> diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> index 7d85acdaea..8b0018a593 100644
> --- a/lib/stopwatch-names.h
> +++ b/lib/stopwatch-names.h
> @@ -34,5 +34,6 @@
>  #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
>  #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
>  #define LR_LB_NAT_DATA_RUN_STOPWATCH_NAME "lr_lb_nat_data"
> +#define LS_LBACLS_RUN_STOPWATCH_NAME "lr_lb_acls"
>
>  #endif
> diff --git a/northd/automake.mk b/northd/automake.mk
> index 4116c487df..4593654726 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -28,6 +28,8 @@ northd_ovn_northd_SOURCES = \
>         northd/en-lr-nat.h \
>         northd/en-lr-lb-nat-data.c \
>         northd/en-lr-lb-nat-data.h \
> +       northd/en-ls-lb-acls.c \
> +       northd/en-ls-lb-acls.h \
>         northd/inc-proc-northd.c \
>         northd/inc-proc-northd.h \
>         northd/ipam.c \
> diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> index 229f4be1d0..648a477916 100644
> --- a/northd/en-lflow.c
> +++ b/northd/en-lflow.c
> @@ -21,6 +21,7 @@
>  #include "en-lflow.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-lb-nat-data.h"
> +#include "en-ls-lb-acls.h"
>  #include "en-northd.h"
>  #include "en-meters.h"
>
> @@ -44,6 +45,8 @@ lflow_get_input_data(struct engine_node *node,
>          engine_get_input_data("sync_meters", node);
>      struct ed_type_lr_lb_nat_data *lr_lb_nat_data =
>          engine_get_input_data("lr_lb_nat_data", node);
> +    struct ed_type_ls_lbacls *ls_lbacls_data =
> +        engine_get_input_data("ls_lbacls", node);
>
>      lflow_input->nbrec_bfd_table =
>          EN_OVSDB_GET(engine_get_input("NB_bfd", node));
> @@ -67,6 +70,7 @@ lflow_get_input_data(struct engine_node *node,
>      lflow_input->lr_ports = &northd_data->lr_ports;
>      lflow_input->ls_port_groups = &pg_data->ls_port_groups;
>      lflow_input->lr_lbnats = &lr_lb_nat_data->lr_lbnats;
> +    lflow_input->ls_lbacls = &ls_lbacls_data->ls_lbacls;
>      lflow_input->meter_groups = &sync_meters_data->meter_groups;
>      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
>      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> diff --git a/northd/en-lr-lb-nat-data.c b/northd/en-lr-lb-nat-data.c
> index 19b638ce0b..d816d2321d 100644
> --- a/northd/en-lr-lb-nat-data.c
> +++ b/northd/en-lr-lb-nat-data.c
> @@ -299,9 +299,11 @@ lr_lb_nat_data_lb_data_handler(struct engine_node
*node, void *data_)
>      if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
>          struct hmapx_node *hmapx_node;
>          /* For all the modified lr_lb_nat_data records (re)build the
> -         * vip nats. */
> +         * vip nats and re-evaluate 'has_lb_vip'. */
>          HMAPX_FOR_EACH (hmapx_node, &data->tracked_data.crupdated) {
> -            lr_lb_nat_data_build_vip_nats(hmapx_node->data);
> +            lr_lbnat_rec = hmapx_node->data;
> +            lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> +            lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);

Changes in this module should belong to an earlier patch instead of this
one?

>          }
>
>          data->tracked = true;
> @@ -523,6 +525,8 @@ lr_lb_nat_data_record_init(struct
lr_lb_nat_data_record *lr_lbnat_rec,
>      if (!nbr->n_nat) {
>          lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
>      }
> +
> +    lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
>  }
>
>  static struct lr_lb_nat_data_input
> diff --git a/northd/en-lr-lb-nat-data.h b/northd/en-lr-lb-nat-data.h
> index ffe41cad73..ac21d28a57 100644
> --- a/northd/en-lr-lb-nat-data.h
> +++ b/northd/en-lr-lb-nat-data.h
> @@ -39,6 +39,8 @@ struct lr_lb_nat_data_record {
>      const struct ovn_datapath *od;
>      const struct lr_nat_record *lrnat_rec;
>
> +    bool has_lb_vip;
> +
>      /* Load Balancer vIPs relevant for this datapath. */
>      struct ovn_lb_ip_set *lb_ips;
>
> diff --git a/northd/en-ls-lb-acls.c b/northd/en-ls-lb-acls.c
> new file mode 100644
> index 0000000000..1ba7ecb3e2
> --- /dev/null
> +++ b/northd/en-ls-lb-acls.c
> @@ -0,0 +1,527 @@
> +/*
> + * Copyright (c) 2023, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +
> +/* OVS includes */
> +#include "include/openvswitch/hmap.h"
> +#include "lib/bitmap.h"
> +#include "lib/socket-util.h"
> +#include "lib/uuidset.h"
> +#include "openvswitch/util.h"
> +#include "openvswitch/vlog.h"
> +#include "stopwatch.h"
> +
> +/* OVN includes */
> +#include "en-lb-data.h"
> +#include "en-ls-lb-acls.h"
> +#include "en-port-group.h"
> +#include "lib/inc-proc-eng.h"
> +#include "lib/lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "lib/ovn-util.h"
> +#include "lib/stopwatch-names.h"
> +#include "northd.h"
> +
> +VLOG_DEFINE_THIS_MODULE(en_ls_lbacls);
> +
> +/* Static function declarations. */
> +static void ls_lbacls_table_init(struct ls_lbacls_table *);
> +static void ls_lbacls_table_clear(struct ls_lbacls_table *);
> +static void ls_lbacls_table_destroy(struct ls_lbacls_table *);
> +static struct ls_lbacls_record *ls_lbacls_table_find_(
> +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> +static void ls_lbacls_table_build(struct ls_lbacls_table *,
> +                                  const struct ovn_datapaths
*ls_datapaths,
> +                                  const struct ls_port_group_table *);
> +
> +static struct ls_lbacls_input ls_lbacls_get_input_data(
> +    struct engine_node *);
> +
> +static struct ls_lbacls_record *ls_lbacls_record_create(
> +    struct ls_lbacls_table *,
> +    const struct ovn_datapath *,
> +    const struct ls_port_group_table *);
> +static void ls_lbacls_record_destroy(struct ls_lbacls_record *);
> +static void ls_lbacls_record_init(
> +    struct ls_lbacls_record *,
> +    const struct ovn_datapath *,
> +    const struct ls_port_group *,
> +    const struct ls_port_group_table *);
> +static void ls_lbacls_record_reinit(
> +    struct ls_lbacls_record *,
> +    const struct ls_port_group *,
> +    const struct ls_port_group_table *);
> +static bool ls_has_lb_vip(const struct ovn_datapath *);
> +static void ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *,
> +                                           const struct ovn_datapath *,
> +                                           const struct ls_port_group *,
> +                                           const struct
ls_port_group_table *);
> +static bool ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *,
> +                                            struct nbrec_acl **,
> +                                            size_t n_acls);
> +static struct ls_lbacls_input ls_lbacls_get_input_data(struct
engine_node *);
> +static bool is_ls_acls_changed(const struct nbrec_logical_switch *);
> +static bool is_acls_seqno_changed(struct nbrec_acl **, size_t n_nb_acls);
> +
> +
> +/* public functions. */
> +const struct ls_lbacls_record *
> +ls_lbacls_table_find(
> +    const struct ls_lbacls_table *table,
> +    const struct nbrec_logical_switch *nbs)
> +{
> +    return ls_lbacls_table_find_(table, nbs);
> +}
> +
> +void *
> +en_ls_lbacls_init(struct engine_node *node OVS_UNUSED,
> +                  struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_ls_lbacls *data = xzalloc(sizeof *data);
> +    ls_lbacls_table_init(&data->ls_lbacls);
> +    hmapx_init(&data->tracked_data.crupdated);
> +    hmapx_init(&data->tracked_data.deleted);
> +    return data;
> +}
> +
> +void
> +en_ls_lbacls_cleanup(void *data_)
> +{
> +    struct ed_type_ls_lbacls *data =
> +        (struct ed_type_ls_lbacls *) data_;
> +    ls_lbacls_table_destroy(&data->ls_lbacls);
> +    hmapx_destroy(&data->tracked_data.crupdated);
> +    hmapx_destroy(&data->tracked_data.deleted);
> +}
> +
> +void
> +en_ls_lbacls_clear_tracked_data(void *data_)
> +{
> +    struct ed_type_ls_lbacls *data =
> +        (struct ed_type_ls_lbacls *) data_;
> +
> +    struct hmapx_node *hmapx_node;
> +    HMAPX_FOR_EACH_SAFE (hmapx_node, &data->tracked_data.deleted) {
> +        ls_lbacls_record_destroy(hmapx_node->data);
> +        hmapx_delete(&data->tracked_data.deleted, hmapx_node);
> +    }
> +
> +    hmapx_clear(&data->tracked_data.crupdated);
> +    data->tracked = false;
> +}
> +
> +void
> +en_ls_lbacls_run(struct engine_node *node, void *data_)
> +{
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ed_type_ls_lbacls *data = data_;
> +
> +    stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> +
> +    ls_lbacls_table_clear(&data->ls_lbacls);
> +    ls_lbacls_table_build(&data->ls_lbacls, input_data.ls_datapaths,
> +                          input_data.ls_port_groups);
> +
> +    data->tracked = false;
> +    stopwatch_stop(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +/* Handler functions. */
> +bool
> +ls_lbacls_northd_handler(struct engine_node *node, void *data_)
> +{
> +    struct northd_data *northd_data = engine_get_input_data("northd",
node);
> +    if (!northd_data->change_tracked) {
> +        return false;
> +    }
> +
> +    struct northd_tracked_data *nd_changes =
&northd_data->trk_northd_changes;
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ls_lbacls_record *ls_lbacls_rec;
> +    struct ed_type_ls_lbacls *data = data_;
> +    const struct ovn_datapath *od;
> +    struct hmapx_node *hmapx_node;
> +
> +    HMAPX_FOR_EACH (hmapx_node,
&nd_changes->ls_with_changed_lbs.crupdated) {
> +        od = hmapx_node->data;
> +
> +        ls_lbacls_rec = ls_lbacls_table_find_(&data->ls_lbacls, od->nbs);
> +        if (!ls_lbacls_rec) {
> +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> +
 input_data.ls_port_groups);
> +        } else {
> +            ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> +                                    input_data.ls_port_groups);
> +        }
> +
> +        /* Add the ls_lbacls_rec to the tracking data. */
> +        hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +bool
> +ls_lbacls_port_group_handler(struct engine_node *node, void *data_)
> +{
> +    struct port_group_data *pg_data =
> +        engine_get_input_data("port_group", node);
> +
> +    if (pg_data->ls_port_groups_sets_changed) {
> +        return false;
> +    }
> +
> +    /* port_group engine node doesn't provide the tracking data yet.
> +     * Loop through all the ls port groups and update the ls_lbacls_rec.
> +     * This is still better than returning false. */
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ed_type_ls_lbacls *data = data_;
> +    const struct ls_port_group *ls_pg;
> +
> +    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
> +        struct ls_lbacls_record *ls_lbacls_rec =
> +            ls_lbacls_table_find_(&data->ls_lbacls, ls_pg->nbs);
> +
> +        bool modified = false;
> +        if (!ls_lbacls_rec) {
> +            const struct ovn_datapath *od;
> +            od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> +                                    &ls_pg->nbs->header_.uuid);
> +            ovs_assert(od);
> +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> +
 input_data.ls_port_groups);
> +            modified = true;
> +        } else {
> +            bool had_stateful_acl = ls_lbacls_rec->has_stateful_acl;
> +            uint64_t max_acl_tier = ls_lbacls_rec->max_acl_tier;
> +            bool had_acls = ls_lbacls_rec->has_acls;
> +
> +            ls_lbacls_record_reinit(ls_lbacls_rec, ls_pg,
> +                                    input_data.ls_port_groups);
> +
> +            if ((had_stateful_acl != ls_lbacls_rec->has_stateful_acl)
> +                || (had_acls != ls_lbacls_rec->has_acls)
> +                || max_acl_tier != ls_lbacls_rec->max_acl_tier) {
> +                modified = true;
> +            }
> +        }
> +
> +        if (modified) {
> +            /* Add the ls_lbacls_rec to the tracking data. */
> +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +        }
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +bool
> +ls_lbacls_logical_switch_handler(struct engine_node *node, void *data_)
> +{
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    const struct nbrec_logical_switch *nbs;
> +    struct ed_type_ls_lbacls *data = data_;
> +
> +    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs,
> +
 input_data.nbrec_logical_switch_table) {
> +        if (!is_ls_acls_changed(nbs)) {
> +            continue;
> +        }
> +
> +        struct ls_lbacls_record *ls_lbacls_rec =
> +            ls_lbacls_table_find_(&data->ls_lbacls, nbs);
> +
> +        if (nbrec_logical_switch_is_deleted(nbs)) {
> +            if (ls_lbacls_rec) {
> +                /* Remove the record from the entries. */
> +                hmap_remove(&data->ls_lbacls.entries,
> +                            &ls_lbacls_rec->key_node);
> +
> +                /* Add the ls_lbacls_rec to the tracking data. */
> +                hmapx_add(&data->tracked_data.deleted, ls_lbacls_rec);
> +            }
> +        } else {
> +            if (!ls_lbacls_rec) {
> +                const struct ovn_datapath *od;
> +                od =
ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> +                                       &nbs->header_.uuid);
> +                ovs_assert(od);
> +                ls_lbacls_rec =
ls_lbacls_record_create(&data->ls_lbacls, od,
> +
 input_data.ls_port_groups);
> +            } else {
> +                ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> +                                        input_data.ls_port_groups);
> +            }
> +
> +            /* Add the ls_lbacls_rec to the tracking data. */
> +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +        }
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)
> +        || !hmapx_is_empty(&data->tracked_data.deleted)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +/* static functions. */
> +static void
> +ls_lbacls_table_init(struct ls_lbacls_table *table)
> +{
> +    *table = (struct ls_lbacls_table) {
> +        .entries = HMAP_INITIALIZER(&table->entries),
> +    };
> +}
> +
> +static void
> +ls_lbacls_table_destroy(struct ls_lbacls_table *table)
> +{
> +    ls_lbacls_table_clear(table);
> +    hmap_destroy(&table->entries);
> +}
> +
> +static void
> +ls_lbacls_table_clear(struct ls_lbacls_table *table)
> +{
> +    struct ls_lbacls_record *ls_lbacls_rec;
> +    HMAP_FOR_EACH_POP (ls_lbacls_rec, key_node, &table->entries) {
> +        ls_lbacls_record_destroy(ls_lbacls_rec);
> +    }
> +}
> +
> +static void
> +ls_lbacls_table_build(struct ls_lbacls_table *table,
> +                      const struct ovn_datapaths *ls_datapaths,
> +                      const struct ls_port_group_table *ls_pgs)
> +{
> +    const struct ovn_datapath *od;
> +    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> +        ls_lbacls_record_create(table, od, ls_pgs);
> +    }
> +}
> +
> +struct ls_lbacls_record *
> +ls_lbacls_table_find_(const struct ls_lbacls_table *table,
> +                      const struct nbrec_logical_switch *nbs)
> +{
> +    struct ls_lbacls_record *rec;
> +
> +    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
> +                             uuid_hash(&nbs->header_.uuid),
&table->entries) {
> +        if (nbs == rec->od->nbs) {
> +            return rec;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static struct ls_lbacls_record *
> +ls_lbacls_record_create(struct ls_lbacls_table *table,
> +                        const struct ovn_datapath *od,
> +                        const struct ls_port_group_table *ls_pgs)
> +{
> +    struct ls_lbacls_record *ls_lbacls_rec = xzalloc(sizeof
*ls_lbacls_rec);
> +    ls_lbacls_rec->od = od;
> +    ls_lbacls_record_init(ls_lbacls_rec, od, NULL, ls_pgs);
> +
> +    hmap_insert(&table->entries, &ls_lbacls_rec->key_node,
> +                uuid_hash(&ls_lbacls_rec->od->nbs->header_.uuid));
> +
> +    return ls_lbacls_rec;
> +}
> +
> +static void
> +ls_lbacls_record_destroy(struct ls_lbacls_record *ls_lbacls_rec)
> +{
> +    free(ls_lbacls_rec);
> +}
> +
> +static void
> +ls_lbacls_record_init(struct ls_lbacls_record *ls_lbacls_rec,
> +                      const struct ovn_datapath *od,
> +                      const struct ls_port_group *ls_pg,
> +                      const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_rec->has_lb_vip = ls_has_lb_vip(od);
> +    ls_lbacls_record_set_acl_flags(ls_lbacls_rec, od, ls_pg, ls_pgs);
> +}
> +
> +static void
> +ls_lbacls_record_reinit(struct ls_lbacls_record *ls_lbacls_rec,
> +                        const struct ls_port_group *ls_pg,
> +                        const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_record_init(ls_lbacls_rec, ls_lbacls_rec->od, ls_pg,
ls_pgs);
> +}
> +
> +static bool
> +lb_has_vip(const struct nbrec_load_balancer *lb)
> +{
> +    return !smap_is_empty(&lb->vips);
> +}
> +
> +static bool
> +lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> +{
> +    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
> +        if (lb_has_vip(lb_group->load_balancer[i])) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static bool
> +ls_has_lb_vip(const struct ovn_datapath *od)
> +{
> +    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> +        if (lb_has_vip(od->nbs->load_balancer[i])) {
> +            return true;
> +        }
> +    }
> +
> +    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
> +        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static void
> +ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *ls_lbacls_rec,
> +                               const struct ovn_datapath *od,
> +                               const struct ls_port_group *ls_pg,
> +                               const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_rec->has_stateful_acl = false;
> +    ls_lbacls_rec->max_acl_tier = 0;
> +    ls_lbacls_rec->has_acls = false;
> +
> +    if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec, od->nbs->acls,
> +                                        od->nbs->n_acls)) {
> +        return;
> +    }
> +
> +    if (!ls_pg) {
> +        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
> +    }
> +
> +    if (!ls_pg) {
> +        return;
> +    }
> +
> +    const struct ls_port_group_record *ls_pg_rec;
> +    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> +        if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec,
> +                                            ls_pg_rec->nb_pg->acls,
> +                                            ls_pg_rec->nb_pg->n_acls)) {
> +            return;
> +        }
> +    }
> +}
> +
> +static bool
> +ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *ls_lbacls_rec,
> +                                struct nbrec_acl **acls,
> +                                size_t n_acls)
> +{
> +    /* A true return indicates that there are no possible ACL flags
> +     * left to set on ls_lbacls record. A false return indicates that
> +     * further ACLs should be explored in case more flags need to be
> +     * set on ls_lbacls record.
> +     */
> +    if (!n_acls) {
> +        return false;
> +    }
> +
> +    ls_lbacls_rec->has_acls = true;
> +    for (size_t i = 0; i < n_acls; i++) {
> +        const struct nbrec_acl *acl = acls[i];
> +        if (acl->tier > ls_lbacls_rec->max_acl_tier) {
> +            ls_lbacls_rec->max_acl_tier = acl->tier;
> +        }
> +        if (!ls_lbacls_rec->has_stateful_acl
> +                && !strcmp(acl->action, "allow-related")) {
> +            ls_lbacls_rec->has_stateful_acl = true;
> +        }
> +        if (ls_lbacls_rec->has_stateful_acl &&
> +            ls_lbacls_rec->max_acl_tier ==
> +                nbrec_acl_col_tier.type.value.integer.max) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
> +static struct ls_lbacls_input
> +ls_lbacls_get_input_data(struct engine_node *node)
> +{
> +    const struct northd_data *northd_data =
> +        engine_get_input_data("northd", node);
> +    const struct port_group_data *pg_data =
> +        engine_get_input_data("port_group", node);
> +
> +    return (struct ls_lbacls_input) {
> +        .nbrec_logical_switch_table =
> +            EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)),
> +        .ls_port_groups = &pg_data->ls_port_groups,
> +        .ls_datapaths = &northd_data->ls_datapaths,
> +    };
> +}
> +
> +static bool
> +is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
> +{
> +    for (size_t i = 0; i < n_nb_acls; i++) {
> +        if (nbrec_acl_row_get_seqno(nb_acls[i],
> +                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
> +static bool
> +is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
> +    return (nbrec_logical_switch_is_new(nbs)
> +            || nbrec_logical_switch_is_deleted(nbs)
> +            || nbrec_logical_switch_is_updated(nbs,
> +
NBREC_LOGICAL_SWITCH_COL_ACLS)
> +            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
> +}
> diff --git a/northd/en-ls-lb-acls.h b/northd/en-ls-lb-acls.h
> new file mode 100644
> index 0000000000..ccb75e40e8
> --- /dev/null
> +++ b/northd/en-ls-lb-acls.h
> @@ -0,0 +1,88 @@
> +/*
> + * Copyright (c) 2023, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +#ifndef EN_LS_LB_ACL_H
> +#define EN_LS_LB_ACL_H 1
> +
> +#include <stdint.h>
> +
> +/* OVS includes. */
> +#include "lib/hmapx.h"
> +#include "openvswitch/hmap.h"
> +#include "sset.h"
> +
> +/* OVN includes. */
> +#include "lib/inc-proc-eng.h"
> +#include "lib/lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "lib/ovn-util.h"
> +#include "lib/stopwatch-names.h"
> +
> +struct ls_lbacls_record {
> +    struct hmap_node key_node;
> +
> +    const struct ovn_datapath *od;

Such reference is error-prone. In the en-flow node it directly uses this
reference to access other data of the ovn_datapath, but the dependency is
not tracked and propagated through this node. For example,
ls_lbacls_rec->od->nbs->acls is used in build_acls but ACL changes are not
propagated through this node.

> +    bool has_stateful_acl;
> +    bool has_lb_vip;
> +    bool has_acls;
> +    uint64_t max_acl_tier;
> +};
> +
> +struct ls_lbacls_table {
> +    struct hmap entries;
> +};
> +
> +#define LS_LBACLS_TABLE_FOR_EACH(LS_LBACLS_REC, TABLE) \
> +    HMAP_FOR_EACH (LS_LBACLS_REC, key_node, &(TABLE)->entries)
> +
> +#define LS_LBACLS_TABLE_FOR_EACH_IN_P(LS_LBACLS_REC, JOBID, TABLE) \
> +    HMAP_FOR_EACH_IN_PARALLEL (LS_LBACLS_REC, key_node, JOBID, \
> +                               &(TABLE)->entries)
> +
> +struct ls_lbacls_tracked_data {
> +    /* Created or updated logical switch with LB and ACL data. */
> +    struct hmapx crupdated; /* Stores 'struct ls_lbacls_record'. */
> +
> +    /* Deleted logical switch with LB and ACL data. */
> +    struct hmapx deleted; /* Stores 'struct ls_lbacls_record'. */
> +};
> +
> +struct ed_type_ls_lbacls {
> +    struct ls_lbacls_table ls_lbacls;
> +
> +    bool tracked;
> +    struct ls_lbacls_tracked_data tracked_data;
> +};
> +
> +struct ls_lbacls_input {
> +    const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
> +    const struct ls_port_group_table *ls_port_groups;
> +    const struct ovn_datapaths *ls_datapaths;
> +};

nit: this seems should be module internal and should move to the .c

Thanks,
Han

> +
> +void *en_ls_lbacls_init(struct engine_node *, struct engine_arg *);
> +void en_ls_lbacls_cleanup(void *data);
> +void en_ls_lbacls_clear_tracked_data(void *data);
> +void en_ls_lbacls_run(struct engine_node *, void *data);
> +
> +bool ls_lbacls_northd_handler(struct engine_node *, void *data);
> +bool ls_lbacls_port_group_handler(struct engine_node *, void *data);
> +bool ls_lbacls_logical_switch_handler(struct engine_node *, void *data);
> +
> +const struct ls_lbacls_record *ls_lbacls_table_find(
> +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> +
> +#endif /* EN_LS_LB_ACL_H */
> diff --git a/northd/en-port-group.h b/northd/en-port-group.h
> index 3b28a23694..54014062ce 100644
> --- a/northd/en-port-group.h
> +++ b/northd/en-port-group.h
> @@ -48,6 +48,9 @@ struct ls_port_group_record {
>      struct sset ports;          /* Subset of 'nb_pg' ports in this
record. */
>  };
>
> +#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
> +    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
> +
>  void ls_port_group_table_init(struct ls_port_group_table *);
>  void ls_port_group_table_clear(struct ls_port_group_table *);
>  void ls_port_group_table_destroy(struct ls_port_group_table *);
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 84627070a8..ab4af92aeb 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -33,6 +33,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-lb-nat-data.h"
>  #include "en-lr-nat.h"
> +#include "en-ls-lb-acls.h"
>  #include "en-northd.h"
>  #include "en-lflow.h"
>  #include "en-northd-output.h"
> @@ -150,6 +151,7 @@ static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_lb_nat_data,
"lr_lb_nat_data");
> +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_lbacls, "ls_lbacls");
>
>  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>                            struct ovsdb_idl_loop *sb)
> @@ -205,6 +207,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lr_lb_nat_data, &en_lb_data,
>                       lr_lb_nat_data_lb_data_handler);
>
> +    engine_add_input(&en_ls_lbacls, &en_northd,
ls_lbacls_northd_handler);
> +    engine_add_input(&en_ls_lbacls, &en_port_group,
> +                     ls_lbacls_port_group_handler);
> +    engine_add_input(&en_ls_lbacls, &en_nb_logical_switch,
> +                     ls_lbacls_logical_switch_handler);
> +
>      engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> @@ -229,6 +237,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
>      engine_add_input(&en_lflow, &en_port_group,
lflow_port_group_handler);
>      engine_add_input(&en_lflow, &en_lr_lb_nat_data, NULL);
> +    engine_add_input(&en_lflow, &en_ls_lbacls, NULL);
>
>      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
>                       sync_to_sb_addr_set_nb_address_set_handler);
> diff --git a/northd/northd.c b/northd/northd.c
> index c8a224d3cd..924f5cd7e0 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -45,6 +45,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-lb-nat-data.h"
> +#include "en-ls-lb-acls.h"
>  #include "lib/ovn-parallel-hmap.h"
>  #include "ovn/actions.h"
>  #include "ovn/features.h"
> @@ -575,7 +576,7 @@ lb_group_has_vip(const struct
nbrec_load_balancer_group *lb_group)
>  }
>
>  static bool
> -ls_has_lb_vip(struct ovn_datapath *od)
> +ls_has_lb_vip(const struct ovn_datapath *od)
>  {
>      for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
>          if (lb_has_vip(od->nbs->load_balancer[i])) {
> @@ -592,7 +593,7 @@ ls_has_lb_vip(struct ovn_datapath *od)
>  }
>
>  static bool
> -lr_has_lb_vip(struct ovn_datapath *od)
> +lr_has_lb_vip(const struct ovn_datapath *od)
>  {
>      for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
>          if (lb_has_vip(od->nbr->load_balancer[i])) {
> @@ -608,13 +609,13 @@ lr_has_lb_vip(struct ovn_datapath *od)
>      return false;
>  }
>
> -static void
> -init_lb_for_datapath(struct ovn_datapath *od)
> +bool
> +od_has_lb_vip(const struct ovn_datapath *od)
>  {
>      if (od->nbs) {
> -        od->has_lb_vip = ls_has_lb_vip(od);
> +        return ls_has_lb_vip(od);
>      } else {
> -        od->has_lb_vip = lr_has_lb_vip(od);
> +        return lr_has_lb_vip(od);
>      }
>  }
>
> @@ -1058,7 +1059,6 @@ join_datapaths(const struct
nbrec_logical_switch_table *nbrec_ls_table,
>
>          init_ipam_info_for_datapath(od);
>          init_mcast_info_for_datapath(od);
> -        init_lb_for_datapath(od);
>      }
>
>      const struct nbrec_logical_router *nbr;
> @@ -1089,7 +1089,6 @@ join_datapaths(const struct
nbrec_logical_switch_table *nbrec_ls_table,
>              ovs_list_push_back(nb_only, &od->list);
>          }
>          init_mcast_info_for_datapath(od);
> -        init_lb_for_datapath(od);
>          if (smap_get(&od->nbr->options, "chassis")) {
>              od->is_gw_router = true;
>          }
> @@ -2570,7 +2569,8 @@ get_nat_addresses(const struct ovn_port *op, size_t
*n, bool routable_only,
>      size_t n_nats = 0;
>      struct eth_addr mac;
>      if (!op || !op->nbrp || !op->od || !op->od->nbr
> -        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> +        || (!op->od->nbr->n_nat && (!lr_lbnat_rec
> +                                    || !lr_lbnat_rec->has_lb_vip))
>          || !eth_addr_from_string(op->nbrp->mac, &mac)) {
>          *n = n_nats;
>          return NULL;
> @@ -3817,7 +3817,7 @@ build_lrouter_lbs_check(const struct ovn_datapaths
*lr_datapaths)
>      HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
>          ovs_assert(od->nbr);
>
> -        if (od->has_lb_vip && od->n_l3dgw_ports > 1
> +        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
>                  && !smap_get(&od->nbr->options, "chassis")) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1,
1);
>              VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
> @@ -5441,7 +5441,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
>                             lb_dps->nb_ls_map) {
>              od = ls_datapaths->array[index];
> -            init_lb_for_datapath(od);
>
>              /* Add the ls datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> @@ -5524,9 +5523,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>              }
>          }
>
> -        /* Re-evaluate 'od->has_lb_vip' */
> -        init_lb_for_datapath(od);
> -
>          /* Add the ls datapath to the northd tracked data. */
>          hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
>      }
> @@ -5564,9 +5560,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>              }
>          }
>
> -        /* Re-evaluate 'od->has_lb_vip' */
> -        init_lb_for_datapath(od);
> -
>          /* Add the lr datapath to the northd tracked data. */
>          hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
>      }
> @@ -5581,8 +5574,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
>                             lb_dps->nb_ls_map) {
>              od = ls_datapaths->array[index];
> -            /* Re-evaluate 'od->has_lb_vip' */
> -            init_lb_for_datapath(od);
>
>              /* Add the ls datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> @@ -5591,8 +5582,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
>                             lb_dps->nb_lr_map) {
>              od = lr_datapaths->array[index];
> -            /* Re-evaluate 'od->has_lb_vip' */
> -            init_lb_for_datapath(od);
>
>              /* Add the lr datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> @@ -5618,9 +5607,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>                  od = lbgrp_dps->lr[i];
>                  ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
>
> -                /* Re-evaluate 'od->has_lb_vip' */
> -                init_lb_for_datapath(od);
> -
>                  /* Add the lr datapath to the northd tracked data. */
>                  hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated,
od);
>              }
> @@ -5629,9 +5615,6 @@ northd_handle_lb_data_changes(struct
tracked_lb_data *trk_lb_data,
>                 od = lbgrp_dps->ls[i];
>                  ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
>
> -                /* Re-evaluate 'od->has_lb_vip' */
> -                init_lb_for_datapath(od);
> -
>                  /* Add the ls datapath to the northd tracked data. */
>                  hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated,
od);
>              }
> @@ -6573,63 +6556,6 @@ build_dhcpv6_action(struct ovn_port *op, struct
in6_addr *offer_ip,
>      return true;
>  }
>
> -static bool
> -od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
> -                 size_t n_acls)
> -{
> -    /* A true return indicates that there are no possible ACL flags
> -     * left to set on od. A false return indicates that further ACLs
> -     * should be explored in case more flags need to be set on od
> -     */
> -    if (!n_acls) {
> -        return false;
> -    }
> -
> -    od->has_acls = true;
> -    for (size_t i = 0; i < n_acls; i++) {
> -        const struct nbrec_acl *acl = acls[i];
> -        if (acl->tier > od->max_acl_tier) {
> -            od->max_acl_tier = acl->tier;
> -        }
> -        if (!od->has_stateful_acl && !strcmp(acl->action,
"allow-related")) {
> -            od->has_stateful_acl = true;
> -        }
> -        if (od->has_stateful_acl &&
> -            od->max_acl_tier ==
nbrec_acl_col_tier.type.value.integer.max) {
> -            return true;
> -        }
> -    }
> -
> -    return false;
> -}
> -
> -static void
> -ls_get_acl_flags(struct ovn_datapath *od,
> -                 const struct ls_port_group_table *ls_port_groups)
> -{
> -    od->has_acls = false;
> -    od->has_stateful_acl = false;
> -    od->max_acl_tier = 0;
> -
> -    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
> -        return;
> -    }
> -
> -    const struct ls_port_group *ls_pg =
> -        ls_port_group_table_find(ls_port_groups, od->nbs);
> -    if (!ls_pg) {
> -        return;
> -    }
> -
> -    const struct ls_port_group_record *ls_pg_rec;
> -    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> -        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
> -                             ls_pg_rec->nb_pg->n_acls)) {
> -            return;
> -        }
> -    }
> -}
> -
>  /* Adds the logical flows in the (in/out) check port sec stage only if
>   *   - the lport is disabled or
>   *   - lport is of type vtep - to skip the ingress pipeline.
> @@ -6774,9 +6700,10 @@ build_lswitch_output_port_sec_od(struct
ovn_datapath *od,
>  }
>
>  static void
> -skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> -                         enum ovn_stage in_stage, enum ovn_stage
out_stage,
> -                         uint16_t priority, struct hmap *lflows)
> +skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port
*op,
> +                         bool has_stateful_acl, enum ovn_stage in_stage,
> +                         enum ovn_stage out_stage, uint16_t priority,
> +                         struct hmap *lflows)
>  {
>      /* Can't use ct() for router ports. Consider the following
configuration:
>       * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB,
For a
> @@ -6789,7 +6716,7 @@ skip_port_from_conntrack(struct ovn_datapath *od,
struct ovn_port *op,
>       * conntrack state across all chassis. */
>
>      const char *ingress_action = "next;";
> -    const char *egress_action = od->has_stateful_acl
> +    const char *egress_action = has_stateful_acl
>                                  ? "next;"
>                                  : "ct_clear; next;";
>
> @@ -6808,7 +6735,7 @@ skip_port_from_conntrack(struct ovn_datapath *od,
struct ovn_port *op,
>  }
>
>  static void
> -build_stateless_filter(struct ovn_datapath *od,
> +build_stateless_filter(const struct ovn_datapath *od,
>                         const struct nbrec_acl *acl,
>                         struct hmap *lflows)
>  {
> @@ -6829,7 +6756,7 @@ build_stateless_filter(struct ovn_datapath *od,
>  }
>
>  static void
> -build_stateless_filters(struct ovn_datapath *od,
> +build_stateless_filters(const struct ovn_datapath *od,
>                          const struct ls_port_group_table *ls_port_groups,
>                          struct hmap *lflows)
>  {
> @@ -6859,9 +6786,7 @@ build_stateless_filters(struct ovn_datapath *od,
>  }
>
>  static void
> -build_pre_acls(struct ovn_datapath *od,
> -               const struct ls_port_group_table *ls_port_groups,
> -               struct hmap *lflows)
> +build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
>  {
>      /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
>       * allowed by default. */
> @@ -6873,18 +6798,26 @@ build_pre_acls(struct ovn_datapath *od,
>
>      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
>                    "eth.src == $svc_monitor_mac", "next;");
> +}
> +
> +static void
> +build_ls_lbacls_rec_pre_acls(const struct ls_lbacls_record
*ls_lbacls_rec,
> +                             const struct ls_port_group_table
*ls_port_groups,
> +                             struct hmap *lflows)
> +{
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
>
>      /* If there are any stateful ACL rules in this datapath, we may
>       * send IP packets for some (allow) filters through the conntrack
action,
>       * which handles defragmentation, in order to match L4 headers. */
> -    if (od->has_stateful_acl) {
> +    if (ls_lbacls_rec->has_stateful_acl) {
>          for (size_t i = 0; i < od->n_router_ports; i++) {
> -            skip_port_from_conntrack(od, od->router_ports[i],
> +            skip_port_from_conntrack(od, od->router_ports[i], true,
>                                       S_SWITCH_IN_PRE_ACL,
S_SWITCH_OUT_PRE_ACL,
>                                       110, lflows);
>          }
>          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> -            skip_port_from_conntrack(od, od->localnet_ports[i],
> +            skip_port_from_conntrack(od, od->localnet_ports[i], true,
>                                       S_SWITCH_IN_PRE_ACL,
>                                       S_SWITCH_OUT_PRE_ACL,
>                                       110, lflows);
> @@ -6922,7 +6855,7 @@ build_pre_acls(struct ovn_datapath *od,
>                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
>                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> -    } else if (od->has_lb_vip) {
> +    } else if (ls_lbacls_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);
> @@ -7050,30 +6983,40 @@ build_pre_lb(struct ovn_datapath *od, const
struct shash *meter_groups,
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
>
> +    /* Do not send statless flows via conntrack */
> +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> +                  REGBIT_ACL_STATELESS" == 1", "next;");
> +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> +                  REGBIT_ACL_STATELESS" == 1", "next;");
> +}
> +
> +static void
> +build_ls_lbacls_rec_pre_lb(const struct ls_lbacls_record *ls_lbacls_rec,
> +                           struct hmap *lflows)
> +{
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      for (size_t i = 0; i < od->n_router_ports; i++) {
>          skip_port_from_conntrack(od, od->router_ports[i],
> +                                 ls_lbacls_rec->has_stateful_acl,
>                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
>                                   110, lflows);
>      }
> +
>      /* Localnet ports have no need for going through conntrack, unless
>       * the logical switch has a load balancer. Then, conntrack is
necessary
>       * so that traffic arriving via the localnet port can be load
>       * balanced.
>       */
> -    if (!od->has_lb_vip) {
> +    if (!ls_lbacls_rec->has_lb_vip) {
>          for (size_t i = 0; i < od->n_localnet_ports; i++) {
>              skip_port_from_conntrack(od, od->localnet_ports[i],
> +                                     ls_lbacls_rec->has_stateful_acl,
>                                       S_SWITCH_IN_PRE_LB,
S_SWITCH_OUT_PRE_LB,
>                                       110, lflows);
>          }
>      }
>
> -    /* Do not sent statless flows via conntrack */
> -    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> -                  REGBIT_ACL_STATELESS" == 1", "next;");
> -    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> -                  REGBIT_ACL_STATELESS" == 1", "next;");
> -
>      /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
>       * packet to conntrack for defragmentation and possibly for
unNATting.
>       *
> @@ -7104,7 +7047,7 @@ build_pre_lb(struct ovn_datapath *od, const struct
shash *meter_groups,
>       * ingress pipeline if a load balancer is configured. We can now
>       * add a lflow to drop ct.inv packets.
>       */
> -    if (od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_lb_vip) {
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
>                        100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
> @@ -7145,10 +7088,12 @@ build_pre_stateful(struct ovn_datapath *od,
>  }
>
>  static void
> -build_acl_hints(struct ovn_datapath *od,
> +build_acl_hints(const struct ls_lbacls_record *ls_lbacls_rec,
>                  const struct chassis_features *features,
>                  struct hmap *lflows)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      /* This stage builds hints for the IN/OUT_ACL stage. Based on various
>       * combinations of ct flags packets may hit only a subset of the
logical
>       * flows in the IN/OUT_ACL stage.
> @@ -7172,13 +7117,13 @@ build_acl_hints(struct ovn_datapath *od,
>          const char *match;
>
>          /* In any case, advance to the next stage. */
> -        if (!od->has_acls && !od->has_lb_vip) {
> +        if (!ls_lbacls_rec->has_acls && !ls_lbacls_rec->has_lb_vip) {
>              ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
>          } else {
>              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
>          }
>
> -        if (!od->has_stateful_acl && !od->has_lb_vip) {
> +        if (!ls_lbacls_rec->has_stateful_acl &&
!ls_lbacls_rec->has_lb_vip) {
>              continue;
>          }
>
> @@ -7314,10 +7259,10 @@ build_acl_log(struct ds *actions, const struct
nbrec_acl *acl,
>  }
>
>  static void
> -consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> +consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
>               const struct nbrec_acl *acl, bool has_stateful,
>               bool ct_masked_mark, const struct shash *meter_groups,
> -             struct ds *match, struct ds *actions)
> +             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
>  {
>      const char *ct_blocked_match = ct_masked_mark
>                                     ? "ct_mark.blocked"
> @@ -7354,7 +7299,7 @@ consider_acl(struct hmap *lflows, struct
ovn_datapath *od,
>      /* All ACLS will start by matching on their respective tier. */
>      size_t match_tier_len = 0;
>      ds_clear(match);
> -    if (od->max_acl_tier) {
> +    if (max_acl_tier) {
>          ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ",
acl->tier);
>          match_tier_len = match->length;
>      }
> @@ -7543,12 +7488,15 @@ ovn_update_ipv6_options(struct hmap *lr_ports)
>  #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
>
>  static void
> -build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> +build_acl_action_lflows(const struct ls_lbacls_record *ls_lbacls_rec,
> +                        struct hmap *lflows,
>                          const char *default_acl_action,
>                          const struct shash *meter_groups,
>                          struct ds *match,
>                          struct ds *actions)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      enum ovn_stage stages [] = {
>          S_SWITCH_IN_ACL_ACTION,
>          S_SWITCH_IN_ACL_AFTER_LB_ACTION,
> @@ -7559,7 +7507,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
struct hmap *lflows,
>      ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
>                          REGBIT_ACL_VERDICT_DROP " = 0; "
>                          REGBIT_ACL_VERDICT_REJECT " = 0; ");
> -    if (od->max_acl_tier) {
> +    if (ls_lbacls_rec->max_acl_tier) {
>          ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
>      }
>
> @@ -7567,7 +7515,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
struct hmap *lflows,
>
>      for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
>          enum ovn_stage stage = stages[i];
> -        if (!od->has_acls) {
> +        if (!ls_lbacls_rec->has_acls) {
>              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
>              continue;
>          }
> @@ -7602,7 +7550,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
struct hmap *lflows,
>          ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
>
>          struct ds tier_actions = DS_EMPTY_INITIALIZER;
> -        for (size_t j = 0; j < od->max_acl_tier; j++) {
> +        for (size_t j = 0; j < ls_lbacls_rec->max_acl_tier; j++) {
>              ds_clear(match);
>              ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
>              ds_clear(&tier_actions);
> @@ -7618,7 +7566,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
struct hmap *lflows,
>  }
>
>  static void
> -build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> +build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap
*lflows,
>                              const struct nbrec_acl *acl, bool
has_stateful,
>                              bool ct_masked_mark,
>                              const struct shash *meter_groups,
> @@ -7691,15 +7639,19 @@ build_acl_log_related_flows(struct ovn_datapath
*od, struct hmap *lflows,
>  }
>
>  static void
> -build_acls(struct ovn_datapath *od, const struct chassis_features
*features,
> +build_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> +           const struct chassis_features *features,
>             struct hmap *lflows,
>             const struct ls_port_group_table *ls_port_groups,
>             const struct shash *meter_groups)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      const char *default_acl_action = default_acl_drop
>                                       ? debug_implicit_drop_action()
>                                       : "next;";
> -    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
> +    bool has_stateful = (ls_lbacls_rec->has_stateful_acl
> +                         || ls_lbacls_rec->has_lb_vip);
>      const char *ct_blocked_match = features->ct_no_masked_label
>                                     ? "ct_mark.blocked"
>                                     : "ct_label.blocked";
> @@ -7713,8 +7665,8 @@ build_acls(struct ovn_datapath *od, const struct
chassis_features *features,
>       *
>       * A related rule at priority 1 is added below if there
>       * are any stateful ACLs in this datapath. */
> -    if (!od->has_acls) {
> -        if (!od->has_lb_vip) {
> +    if (!ls_lbacls_rec->has_acls) {
> +        if (!ls_lbacls_rec->has_lb_vip) {
>              ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX,
"1",
>                            "next;");
>              ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX,
"1",
> @@ -7877,7 +7829,8 @@ build_acls(struct ovn_datapath *od, const struct
chassis_features *features,
>                                      meter_groups, &match, &actions);
>          consider_acl(lflows, od, acl, has_stateful,
>                       features->ct_no_masked_label,
> -                     meter_groups, &match, &actions);
> +                     meter_groups, ls_lbacls_rec->max_acl_tier,
> +                     &match, &actions);
>      }
>
>      const struct ls_port_group *ls_pg =
> @@ -7893,7 +7846,8 @@ build_acls(struct ovn_datapath *od, const struct
chassis_features *features,
>                                              meter_groups, &match,
&actions);
>                  consider_acl(lflows, od, acl, has_stateful,
>                               features->ct_no_masked_label,
> -                             meter_groups, &match, &actions);
> +                             meter_groups, ls_lbacls_rec->max_acl_tier,
> +                             &match, &actions);
>              }
>          }
>      }
> @@ -7911,7 +7865,7 @@ build_acls(struct ovn_datapath *od, const struct
chassis_features *features,
>              dns_actions);
>      }
>
> -    if (od->has_acls || od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_acls || ls_lbacls_rec->has_lb_vip) {
>          /* Add a 34000 priority flow to advance the service monitor reply
>          * packets to skip applying ingress ACLs. */
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
> @@ -7925,8 +7879,8 @@ build_acls(struct ovn_datapath *od, const struct
chassis_features *features,
>                      REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
>      }
>
> -    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
> -                            &match, &actions);
> +    build_acl_action_lflows(ls_lbacls_rec, lflows, default_acl_action,
> +                            meter_groups, &match, &actions);
>
>      ds_destroy(&match);
>      ds_destroy(&actions);
> @@ -8571,8 +8525,11 @@ build_stateful(struct ovn_datapath *od,
>  }
>
>  static void
> -build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> +build_lb_hairpin(const struct ls_lbacls_record *ls_lbacls_rec,
> +                 struct hmap *lflows)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
>       * Packets that don't need hairpinning should continue processing.
>       */
> @@ -8580,7 +8537,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct
hmap *lflows)
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
>
> -    if (od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_lb_vip) {
>          /* Check if the packet needs to be hairpinned.
>           * Set REGBIT_HAIRPIN in the original direction and
>           * REGBIT_HAIRPIN_REPLY in the reply direction.
> @@ -9413,22 +9370,16 @@ build_lswitch_lflows_l2_unknown(struct
ovn_datapath *od,
>  static void
>  build_lswitch_lflows_pre_acl_and_acl(
>      struct ovn_datapath *od,
> -    const struct ls_port_group_table *ls_port_groups,
>      const struct chassis_features *features,
>      struct hmap *lflows,
>      const struct shash *meter_groups)
>  {
>      ovs_assert(od->nbs);
> -    ls_get_acl_flags(od, ls_port_groups);
> -
> -    build_pre_acls(od, ls_port_groups, lflows);
> +    build_pre_acls(od, lflows);
>      build_pre_lb(od, meter_groups, lflows);
>      build_pre_stateful(od, features, lflows);
> -    build_acl_hints(od, features, lflows);
> -    build_acls(od, features, lflows, ls_port_groups, meter_groups);
>      build_qos(od, lflows);
>      build_stateful(od, features, lflows);
> -    build_lb_hairpin(od, lflows);
>      build_vtep_hairpin(od, lflows);
>  }
>
> @@ -15487,7 +15438,7 @@ build_lrouter_nat_defrag_and_lb(
>       * a dynamically negotiated FTP data channel), but will allow
>       * related traffic such as an ICMP Port Unreachable through
>       * that's generated from a non-listening UDP port.  */
> -    if (od->has_lb_vip && features->ct_lb_related) {
> +    if (lr_lbnat_rec->has_lb_vip && features->ct_lb_related) {
>          ds_clear(match);
>
>          ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
> @@ -15512,7 +15463,7 @@ build_lrouter_nat_defrag_and_lb(
>       * Pass the traffic that is already established to the next table
with
>       * proper flags set.
>       */
> -    if (od->has_lb_vip) {
> +    if (lr_lbnat_rec->has_lb_vip) {
>          ds_clear(match);
>
>          ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
> @@ -15542,7 +15493,7 @@ build_lrouter_nat_defrag_and_lb(
>       * not committed, it would produce ongoing datapath flows with the
ct.new
>       * flag set. Some NICs are unable to offload these flows.
>       */
> -    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
> +    if (od->is_gw_router && (od->nbr->n_nat ||
lr_lbnat_rec->has_lb_vip)) {
>          /* Do not send ND or ICMP packets to connection tracking. */
>          ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>                        "nd || nd_rs || nd_ra", "next;");
> @@ -15967,6 +15918,22 @@ build_lr_lbnat_data_flows(const struct
lr_lb_nat_data_record *lr_lbnat_rec,
>                                        meter_groups);
>  }
>
> +static void
> +build_ls_lbacls_flows(const struct ls_lbacls_record *ls_lbacls_rec,
> +                      const struct ls_port_group_table *ls_pgs,
> +                      const struct chassis_features *features,
> +                      const struct shash *meter_groups,
> +                      struct hmap *lflows)
> +{
> +    ovs_assert(ls_lbacls_rec->od);
> +
> +    build_ls_lbacls_rec_pre_acls(ls_lbacls_rec, ls_pgs, lflows);
> +    build_ls_lbacls_rec_pre_lb(ls_lbacls_rec, lflows);
> +    build_acl_hints(ls_lbacls_rec, features, lflows);
> +    build_acls(ls_lbacls_rec, features, lflows, ls_pgs, meter_groups);
> +    build_lb_hairpin(ls_lbacls_rec, lflows);
> +}
> +
>  struct lswitch_flow_build_info {
>      const struct ovn_datapaths *ls_datapaths;
>      const struct ovn_datapaths *lr_datapaths;
> @@ -15974,6 +15941,7 @@ struct lswitch_flow_build_info {
>      const struct hmap *lr_ports;
>      const struct ls_port_group_table *ls_port_groups;
>      const struct lr_lb_nat_data_table *lr_lbnats;
> +    const struct ls_lbacls_table *ls_lbacls;
>      struct hmap *lflows;
>      struct hmap *igmp_groups;
>      const struct shash *meter_groups;
> @@ -15998,9 +15966,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct
ovn_datapath *od,
>                                          struct lswitch_flow_build_info
*lsi)
>  {
>      ovs_assert(od->nbs);
> -    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
> -                                         lsi->features,
> -                                         lsi->lflows,
> +    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
>                                           lsi->meter_groups);
>
>      build_fwd_group_lflows(od, lsi->lflows);
> @@ -16115,6 +16081,7 @@ build_lflows_thread(void *arg)
>  {
>      struct worker_control *control = (struct worker_control *) arg;
>      const struct lr_lb_nat_data_record *lr_lbnat_rec;
> +    const struct ls_lbacls_record *ls_lbacls_rec;
>      struct lswitch_flow_build_info *lsi;
>      struct ovn_igmp_group *igmp_group;
>      struct ovn_lb_datapaths *lb_dps;
> @@ -16243,6 +16210,19 @@ build_lflows_thread(void *arg)
>                                                lsi->features);
>                  }
>              }
> +
> +            for (bnum = control->id;
> +                    bnum <= lsi->ls_lbacls->entries.mask;
> +                    bnum += control->pool->size)
> +            {
> +                LS_LBACLS_TABLE_FOR_EACH_IN_P (ls_lbacls_rec, bnum,
> +                                               lsi->ls_lbacls) {
> +                    build_ls_lbacls_flows(ls_lbacls_rec,
lsi->ls_port_groups,
> +                                          lsi->features,
lsi->meter_groups,
> +                                          lsi->lflows);
> +                }
> +            }
> +
>              for (bnum = control->id;
>                      bnum <= lsi->igmp_groups->mask;
>                      bnum += control->pool->size)
> @@ -16303,6 +16283,7 @@ build_lswitch_and_lrouter_flows(const struct
ovn_datapaths *ls_datapaths,
>                                  const struct hmap *lr_ports,
>                                  const struct ls_port_group_table *ls_pgs,
>                                  const struct lr_lb_nat_data_table
*lr_lbnats,
> +                                const struct ls_lbacls_table *ls_lbacls,
>                                  struct hmap *lflows,
>                                  struct hmap *igmp_groups,
>                                  const struct shash *meter_groups,
> @@ -16333,6 +16314,7 @@ build_lswitch_and_lrouter_flows(const struct
ovn_datapaths *ls_datapaths,
>              lsiv[index].lr_ports = lr_ports;
>              lsiv[index].ls_port_groups = ls_pgs;
>              lsiv[index].lr_lbnats = lr_lbnats;
> +            lsiv[index].ls_lbacls = ls_lbacls;
>              lsiv[index].igmp_groups = igmp_groups;
>              lsiv[index].meter_groups = meter_groups;
>              lsiv[index].lb_dps_map = lb_dps_map;
> @@ -16358,6 +16340,7 @@ build_lswitch_and_lrouter_flows(const struct
ovn_datapaths *ls_datapaths,
>          free(lsiv);
>      } else {
>          const struct lr_lb_nat_data_record *lr_lbnat_rec;
> +        const struct ls_lbacls_record *ls_lbacls_rec;
>          struct ovn_igmp_group *igmp_group;
>          struct ovn_lb_datapaths *lb_dps;
>          struct ovn_datapath *od;
> @@ -16370,6 +16353,7 @@ build_lswitch_and_lrouter_flows(const struct
ovn_datapaths *ls_datapaths,
>              .lr_ports = lr_ports,
>              .ls_port_groups = ls_pgs,
>              .lr_lbnats = lr_lbnats,
> +            .ls_lbacls = ls_lbacls,
>              .lflows = lflows,
>              .igmp_groups = igmp_groups,
>              .meter_groups = meter_groups,
> @@ -16439,6 +16423,12 @@ build_lswitch_and_lrouter_flows(const struct
ovn_datapaths *ls_datapaths,
>                                        lsi.features);
>          }
>
> +        LS_LBACLS_TABLE_FOR_EACH (ls_lbacls_rec, ls_lbacls) {
> +            build_ls_lbacls_flows(ls_lbacls_rec, lsi.ls_port_groups,
> +                                  lsi.features, lsi.meter_groups,
> +                                  lsi.lflows);
> +        }
> +
>          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
>          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
>              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> @@ -16535,6 +16525,7 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
>                                      input_data->lr_ports,
>                                      input_data->ls_port_groups,
>                                      input_data->lr_lbnats,
> +                                    input_data->ls_lbacls,
>                                      lflows,
>                                      &igmp_groups,
>                                      input_data->meter_groups,
> diff --git a/northd/northd.h b/northd/northd.h
> index 08a81b2c10..23b4754db4 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -89,6 +89,8 @@ ods_size(const struct ovn_datapaths *datapaths)
>      return hmap_count(&datapaths->datapaths);
>  }
>
> +bool od_has_lb_vip(const struct ovn_datapath *od);
> +
>  struct tracked_ovn_ports {
>      /* tracked created ports.
>       * hmapx node data is 'struct ovn_port *' */
> @@ -179,6 +181,7 @@ struct lflow_input {
>      const struct hmap *lr_ports;
>      const struct ls_port_group_table *ls_port_groups;
>      const struct lr_lb_nat_data_table *lr_lbnats;
> +    const struct ls_lbacls_table *ls_lbacls;
>      const struct shash *meter_groups;
>      const struct hmap *lb_datapaths_map;
>      const struct hmap *bfd_connections;
> @@ -288,11 +291,7 @@ struct ovn_datapath {
>      struct hmap port_tnlids;
>      uint32_t port_key_hint;
>
> -    bool has_stateful_acl;
> -    bool has_lb_vip;
>      bool has_unknown;
> -    bool has_acls;
> -    uint64_t max_acl_tier;
>      bool has_vtep_lports;
>      bool has_arp_proxy_port;
>
> --
> 2.41.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Numan Siddique Nov. 17, 2023, 10:25 p.m. UTC | #2
On Wed, Nov 15, 2023 at 1:55 AM Han Zhou <hzhou@ovn.org> wrote:
>
> On Thu, Oct 26, 2023 at 11:17 AM <numans@ovn.org> wrote:
> >
> > From: Numan Siddique <numans@ovn.org>
> >
> > This new engine now maintains the load balancer and ACL data of a
> > logical switch which was earlier part of northd engine node data.
> > The main inputs to this engine are:
> >     - northd node
> >     - NB logical switch node
> >     - Port group node
> >
> > A record for each logical switch is maintained in the 'ls_lbacls'
> > hmap table and this record stores the below data which was earlier
> > part of 'struct ovn_datapath'.
> >
> >     - bool has_stateful_acl;
> >     - bool has_lb_vip;
> >     - bool has_acls;
> >     - uint64_t max_acl_tier;
>
> The node name indicates it contains LBs and ACLs of each LS, but it is
> actually about the stateful related flags. So I think it is better to be
> renamed to avoid confusion.

Thanks.  It makes sense to rename this node to 'ls_stateful'.  And
perhaps also rename 'lr_lbnat_data' to 'lr_stateful' too
as both these nodes handle "LB + ACL" and "LB + NAT" respectively and
these make use of conntrack internally.


>
> >
> > This engine node becomes an input to 'lflow' node.
> >
> > Signed-off-by: Numan Siddique <numans@ovn.org>
> > ---
> >  lib/stopwatch-names.h      |   1 +
> >  northd/automake.mk         |   2 +
> >  northd/en-lflow.c          |   4 +
> >  northd/en-lr-lb-nat-data.c |   8 +-
> >  northd/en-lr-lb-nat-data.h |   2 +
> >  northd/en-ls-lb-acls.c     | 527 +++++++++++++++++++++++++++++++++++++
> >  northd/en-ls-lb-acls.h     |  88 +++++++
> >  northd/en-port-group.h     |   3 +
> >  northd/inc-proc-northd.c   |   9 +
> >  northd/northd.c            | 271 +++++++++----------
> >  northd/northd.h            |   7 +-
> >  11 files changed, 776 insertions(+), 146 deletions(-)
> >  create mode 100644 northd/en-ls-lb-acls.c
> >  create mode 100644 northd/en-ls-lb-acls.h
> >
> > diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> > index 7d85acdaea..8b0018a593 100644
> > --- a/lib/stopwatch-names.h
> > +++ b/lib/stopwatch-names.h
> > @@ -34,5 +34,6 @@
> >  #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
> >  #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
> >  #define LR_LB_NAT_DATA_RUN_STOPWATCH_NAME "lr_lb_nat_data"
> > +#define LS_LBACLS_RUN_STOPWATCH_NAME "lr_lb_acls"
> >
> >  #endif
> > diff --git a/northd/automake.mk b/northd/automake.mk
> > index 4116c487df..4593654726 100644
> > --- a/northd/automake.mk
> > +++ b/northd/automake.mk
> > @@ -28,6 +28,8 @@ northd_ovn_northd_SOURCES = \
> >         northd/en-lr-nat.h \
> >         northd/en-lr-lb-nat-data.c \
> >         northd/en-lr-lb-nat-data.h \
> > +       northd/en-ls-lb-acls.c \
> > +       northd/en-ls-lb-acls.h \
> >         northd/inc-proc-northd.c \
> >         northd/inc-proc-northd.h \
> >         northd/ipam.c \
> > diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> > index 229f4be1d0..648a477916 100644
> > --- a/northd/en-lflow.c
> > +++ b/northd/en-lflow.c
> > @@ -21,6 +21,7 @@
> >  #include "en-lflow.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-lb-nat-data.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "en-northd.h"
> >  #include "en-meters.h"
> >
> > @@ -44,6 +45,8 @@ lflow_get_input_data(struct engine_node *node,
> >          engine_get_input_data("sync_meters", node);
> >      struct ed_type_lr_lb_nat_data *lr_lb_nat_data =
> >          engine_get_input_data("lr_lb_nat_data", node);
> > +    struct ed_type_ls_lbacls *ls_lbacls_data =
> > +        engine_get_input_data("ls_lbacls", node);
> >
> >      lflow_input->nbrec_bfd_table =
> >          EN_OVSDB_GET(engine_get_input("NB_bfd", node));
> > @@ -67,6 +70,7 @@ lflow_get_input_data(struct engine_node *node,
> >      lflow_input->lr_ports = &northd_data->lr_ports;
> >      lflow_input->ls_port_groups = &pg_data->ls_port_groups;
> >      lflow_input->lr_lbnats = &lr_lb_nat_data->lr_lbnats;
> > +    lflow_input->ls_lbacls = &ls_lbacls_data->ls_lbacls;
> >      lflow_input->meter_groups = &sync_meters_data->meter_groups;
> >      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
> >      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> > diff --git a/northd/en-lr-lb-nat-data.c b/northd/en-lr-lb-nat-data.c
> > index 19b638ce0b..d816d2321d 100644
> > --- a/northd/en-lr-lb-nat-data.c
> > +++ b/northd/en-lr-lb-nat-data.c
> > @@ -299,9 +299,11 @@ lr_lb_nat_data_lb_data_handler(struct engine_node
> *node, void *data_)
> >      if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> >          struct hmapx_node *hmapx_node;
> >          /* For all the modified lr_lb_nat_data records (re)build the
> > -         * vip nats. */
> > +         * vip nats and re-evaluate 'has_lb_vip'. */
> >          HMAPX_FOR_EACH (hmapx_node, &data->tracked_data.crupdated) {
> > -            lr_lb_nat_data_build_vip_nats(hmapx_node->data);
> > +            lr_lbnat_rec = hmapx_node->data;
> > +            lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> > +            lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
>
> Changes in this module should belong to an earlier patch instead of this
> one?
>
> >          }
> >
> >          data->tracked = true;
> > @@ -523,6 +525,8 @@ lr_lb_nat_data_record_init(struct
> lr_lb_nat_data_record *lr_lbnat_rec,
> >      if (!nbr->n_nat) {
> >          lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> >      }
> > +
> > +    lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
> >  }
> >
> >  static struct lr_lb_nat_data_input
> > diff --git a/northd/en-lr-lb-nat-data.h b/northd/en-lr-lb-nat-data.h
> > index ffe41cad73..ac21d28a57 100644
> > --- a/northd/en-lr-lb-nat-data.h
> > +++ b/northd/en-lr-lb-nat-data.h
> > @@ -39,6 +39,8 @@ struct lr_lb_nat_data_record {
> >      const struct ovn_datapath *od;
> >      const struct lr_nat_record *lrnat_rec;
> >
> > +    bool has_lb_vip;
> > +
> >      /* Load Balancer vIPs relevant for this datapath. */
> >      struct ovn_lb_ip_set *lb_ips;
> >
> > diff --git a/northd/en-ls-lb-acls.c b/northd/en-ls-lb-acls.c
> > new file mode 100644
> > index 0000000000..1ba7ecb3e2
> > --- /dev/null
> > +++ b/northd/en-ls-lb-acls.c
> > @@ -0,0 +1,527 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#include <config.h>
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +
> > +/* OVS includes */
> > +#include "include/openvswitch/hmap.h"
> > +#include "lib/bitmap.h"
> > +#include "lib/socket-util.h"
> > +#include "lib/uuidset.h"
> > +#include "openvswitch/util.h"
> > +#include "openvswitch/vlog.h"
> > +#include "stopwatch.h"
> > +
> > +/* OVN includes */
> > +#include "en-lb-data.h"
> > +#include "en-ls-lb-acls.h"
> > +#include "en-port-group.h"
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +#include "northd.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(en_ls_lbacls);
> > +
> > +/* Static function declarations. */
> > +static void ls_lbacls_table_init(struct ls_lbacls_table *);
> > +static void ls_lbacls_table_clear(struct ls_lbacls_table *);
> > +static void ls_lbacls_table_destroy(struct ls_lbacls_table *);
> > +static struct ls_lbacls_record *ls_lbacls_table_find_(
> > +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> > +static void ls_lbacls_table_build(struct ls_lbacls_table *,
> > +                                  const struct ovn_datapaths
> *ls_datapaths,
> > +                                  const struct ls_port_group_table *);
> > +
> > +static struct ls_lbacls_input ls_lbacls_get_input_data(
> > +    struct engine_node *);
> > +
> > +static struct ls_lbacls_record *ls_lbacls_record_create(
> > +    struct ls_lbacls_table *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group_table *);
> > +static void ls_lbacls_record_destroy(struct ls_lbacls_record *);
> > +static void ls_lbacls_record_init(
> > +    struct ls_lbacls_record *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static void ls_lbacls_record_reinit(
> > +    struct ls_lbacls_record *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static bool ls_has_lb_vip(const struct ovn_datapath *);
> > +static void ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *,
> > +                                           const struct ovn_datapath *,
> > +                                           const struct ls_port_group *,
> > +                                           const struct
> ls_port_group_table *);
> > +static bool ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *,
> > +                                            struct nbrec_acl **,
> > +                                            size_t n_acls);
> > +static struct ls_lbacls_input ls_lbacls_get_input_data(struct
> engine_node *);
> > +static bool is_ls_acls_changed(const struct nbrec_logical_switch *);
> > +static bool is_acls_seqno_changed(struct nbrec_acl **, size_t n_nb_acls);
> > +
> > +
> > +/* public functions. */
> > +const struct ls_lbacls_record *
> > +ls_lbacls_table_find(
> > +    const struct ls_lbacls_table *table,
> > +    const struct nbrec_logical_switch *nbs)
> > +{
> > +    return ls_lbacls_table_find_(table, nbs);
> > +}
> > +
> > +void *
> > +en_ls_lbacls_init(struct engine_node *node OVS_UNUSED,
> > +                  struct engine_arg *arg OVS_UNUSED)
> > +{
> > +    struct ed_type_ls_lbacls *data = xzalloc(sizeof *data);
> > +    ls_lbacls_table_init(&data->ls_lbacls);
> > +    hmapx_init(&data->tracked_data.crupdated);
> > +    hmapx_init(&data->tracked_data.deleted);
> > +    return data;
> > +}
> > +
> > +void
> > +en_ls_lbacls_cleanup(void *data_)
> > +{
> > +    struct ed_type_ls_lbacls *data =
> > +        (struct ed_type_ls_lbacls *) data_;
> > +    ls_lbacls_table_destroy(&data->ls_lbacls);
> > +    hmapx_destroy(&data->tracked_data.crupdated);
> > +    hmapx_destroy(&data->tracked_data.deleted);
> > +}
> > +
> > +void
> > +en_ls_lbacls_clear_tracked_data(void *data_)
> > +{
> > +    struct ed_type_ls_lbacls *data =
> > +        (struct ed_type_ls_lbacls *) data_;
> > +
> > +    struct hmapx_node *hmapx_node;
> > +    HMAPX_FOR_EACH_SAFE (hmapx_node, &data->tracked_data.deleted) {
> > +        ls_lbacls_record_destroy(hmapx_node->data);
> > +        hmapx_delete(&data->tracked_data.deleted, hmapx_node);
> > +    }
> > +
> > +    hmapx_clear(&data->tracked_data.crupdated);
> > +    data->tracked = false;
> > +}
> > +
> > +void
> > +en_ls_lbacls_run(struct engine_node *node, void *data_)
> > +{
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ed_type_ls_lbacls *data = data_;
> > +
> > +    stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> > +
> > +    ls_lbacls_table_clear(&data->ls_lbacls);
> > +    ls_lbacls_table_build(&data->ls_lbacls, input_data.ls_datapaths,
> > +                          input_data.ls_port_groups);
> > +
> > +    data->tracked = false;
> > +    stopwatch_stop(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> > +    engine_set_node_state(node, EN_UPDATED);
> > +}
> > +
> > +/* Handler functions. */
> > +bool
> > +ls_lbacls_northd_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct northd_data *northd_data = engine_get_input_data("northd",
> node);
> > +    if (!northd_data->change_tracked) {
> > +        return false;
> > +    }
> > +
> > +    struct northd_tracked_data *nd_changes =
> &northd_data->trk_northd_changes;
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ls_lbacls_record *ls_lbacls_rec;
> > +    struct ed_type_ls_lbacls *data = data_;
> > +    const struct ovn_datapath *od;
> > +    struct hmapx_node *hmapx_node;
> > +
> > +    HMAPX_FOR_EACH (hmapx_node,
> &nd_changes->ls_with_changed_lbs.crupdated) {
> > +        od = hmapx_node->data;
> > +
> > +        ls_lbacls_rec = ls_lbacls_table_find_(&data->ls_lbacls, od->nbs);
> > +        if (!ls_lbacls_rec) {
> > +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> > +
>  input_data.ls_port_groups);
> > +        } else {
> > +            ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> > +                                    input_data.ls_port_groups);
> > +        }
> > +
> > +        /* Add the ls_lbacls_rec to the tracking data. */
> > +        hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +bool
> > +ls_lbacls_port_group_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    if (pg_data->ls_port_groups_sets_changed) {
> > +        return false;
> > +    }
> > +
> > +    /* port_group engine node doesn't provide the tracking data yet.
> > +     * Loop through all the ls port groups and update the ls_lbacls_rec.
> > +     * This is still better than returning false. */
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ed_type_ls_lbacls *data = data_;
> > +    const struct ls_port_group *ls_pg;
> > +
> > +    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
> > +        struct ls_lbacls_record *ls_lbacls_rec =
> > +            ls_lbacls_table_find_(&data->ls_lbacls, ls_pg->nbs);
> > +
> > +        bool modified = false;
> > +        if (!ls_lbacls_rec) {
> > +            const struct ovn_datapath *od;
> > +            od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> > +                                    &ls_pg->nbs->header_.uuid);
> > +            ovs_assert(od);
> > +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> > +
>  input_data.ls_port_groups);
> > +            modified = true;
> > +        } else {
> > +            bool had_stateful_acl = ls_lbacls_rec->has_stateful_acl;
> > +            uint64_t max_acl_tier = ls_lbacls_rec->max_acl_tier;
> > +            bool had_acls = ls_lbacls_rec->has_acls;
> > +
> > +            ls_lbacls_record_reinit(ls_lbacls_rec, ls_pg,
> > +                                    input_data.ls_port_groups);
> > +
> > +            if ((had_stateful_acl != ls_lbacls_rec->has_stateful_acl)
> > +                || (had_acls != ls_lbacls_rec->has_acls)
> > +                || max_acl_tier != ls_lbacls_rec->max_acl_tier) {
> > +                modified = true;
> > +            }
> > +        }
> > +
> > +        if (modified) {
> > +            /* Add the ls_lbacls_rec to the tracking data. */
> > +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +        }
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +bool
> > +ls_lbacls_logical_switch_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    const struct nbrec_logical_switch *nbs;
> > +    struct ed_type_ls_lbacls *data = data_;
> > +
> > +    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs,
> > +
>  input_data.nbrec_logical_switch_table) {
> > +        if (!is_ls_acls_changed(nbs)) {
> > +            continue;
> > +        }
> > +
> > +        struct ls_lbacls_record *ls_lbacls_rec =
> > +            ls_lbacls_table_find_(&data->ls_lbacls, nbs);
> > +
> > +        if (nbrec_logical_switch_is_deleted(nbs)) {
> > +            if (ls_lbacls_rec) {
> > +                /* Remove the record from the entries. */
> > +                hmap_remove(&data->ls_lbacls.entries,
> > +                            &ls_lbacls_rec->key_node);
> > +
> > +                /* Add the ls_lbacls_rec to the tracking data. */
> > +                hmapx_add(&data->tracked_data.deleted, ls_lbacls_rec);
> > +            }
> > +        } else {
> > +            if (!ls_lbacls_rec) {
> > +                const struct ovn_datapath *od;
> > +                od =
> ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> > +                                       &nbs->header_.uuid);
> > +                ovs_assert(od);
> > +                ls_lbacls_rec =
> ls_lbacls_record_create(&data->ls_lbacls, od,
> > +
>  input_data.ls_port_groups);
> > +            } else {
> > +                ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> > +                                        input_data.ls_port_groups);
> > +            }
> > +
> > +            /* Add the ls_lbacls_rec to the tracking data. */
> > +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +        }
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)
> > +        || !hmapx_is_empty(&data->tracked_data.deleted)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +/* static functions. */
> > +static void
> > +ls_lbacls_table_init(struct ls_lbacls_table *table)
> > +{
> > +    *table = (struct ls_lbacls_table) {
> > +        .entries = HMAP_INITIALIZER(&table->entries),
> > +    };
> > +}
> > +
> > +static void
> > +ls_lbacls_table_destroy(struct ls_lbacls_table *table)
> > +{
> > +    ls_lbacls_table_clear(table);
> > +    hmap_destroy(&table->entries);
> > +}
> > +
> > +static void
> > +ls_lbacls_table_clear(struct ls_lbacls_table *table)
> > +{
> > +    struct ls_lbacls_record *ls_lbacls_rec;
> > +    HMAP_FOR_EACH_POP (ls_lbacls_rec, key_node, &table->entries) {
> > +        ls_lbacls_record_destroy(ls_lbacls_rec);
> > +    }
> > +}
> > +
> > +static void
> > +ls_lbacls_table_build(struct ls_lbacls_table *table,
> > +                      const struct ovn_datapaths *ls_datapaths,
> > +                      const struct ls_port_group_table *ls_pgs)
> > +{
> > +    const struct ovn_datapath *od;
> > +    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> > +        ls_lbacls_record_create(table, od, ls_pgs);
> > +    }
> > +}
> > +
> > +struct ls_lbacls_record *
> > +ls_lbacls_table_find_(const struct ls_lbacls_table *table,
> > +                      const struct nbrec_logical_switch *nbs)
> > +{
> > +    struct ls_lbacls_record *rec;
> > +
> > +    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
> > +                             uuid_hash(&nbs->header_.uuid),
> &table->entries) {
> > +        if (nbs == rec->od->nbs) {
> > +            return rec;
> > +        }
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static struct ls_lbacls_record *
> > +ls_lbacls_record_create(struct ls_lbacls_table *table,
> > +                        const struct ovn_datapath *od,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    struct ls_lbacls_record *ls_lbacls_rec = xzalloc(sizeof
> *ls_lbacls_rec);
> > +    ls_lbacls_rec->od = od;
> > +    ls_lbacls_record_init(ls_lbacls_rec, od, NULL, ls_pgs);
> > +
> > +    hmap_insert(&table->entries, &ls_lbacls_rec->key_node,
> > +                uuid_hash(&ls_lbacls_rec->od->nbs->header_.uuid));
> > +
> > +    return ls_lbacls_rec;
> > +}
> > +
> > +static void
> > +ls_lbacls_record_destroy(struct ls_lbacls_record *ls_lbacls_rec)
> > +{
> > +    free(ls_lbacls_rec);
> > +}
> > +
> > +static void
> > +ls_lbacls_record_init(struct ls_lbacls_record *ls_lbacls_rec,
> > +                      const struct ovn_datapath *od,
> > +                      const struct ls_port_group *ls_pg,
> > +                      const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_rec->has_lb_vip = ls_has_lb_vip(od);
> > +    ls_lbacls_record_set_acl_flags(ls_lbacls_rec, od, ls_pg, ls_pgs);
> > +}
> > +
> > +static void
> > +ls_lbacls_record_reinit(struct ls_lbacls_record *ls_lbacls_rec,
> > +                        const struct ls_port_group *ls_pg,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_record_init(ls_lbacls_rec, ls_lbacls_rec->od, ls_pg,
> ls_pgs);
> > +}
> > +
> > +static bool
> > +lb_has_vip(const struct nbrec_load_balancer *lb)
> > +{
> > +    return !smap_is_empty(&lb->vips);
> > +}
> > +
> > +static bool
> > +lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> > +{
> > +    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
> > +        if (lb_has_vip(lb_group->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static bool
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> > +{
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> > +        if (lb_has_vip(od->nbs->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
> > +        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static void
> > +ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *ls_lbacls_rec,
> > +                               const struct ovn_datapath *od,
> > +                               const struct ls_port_group *ls_pg,
> > +                               const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_rec->has_stateful_acl = false;
> > +    ls_lbacls_rec->max_acl_tier = 0;
> > +    ls_lbacls_rec->has_acls = false;
> > +
> > +    if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec, od->nbs->acls,
> > +                                        od->nbs->n_acls)) {
> > +        return;
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        return;
> > +    }
> > +
> > +    const struct ls_port_group_record *ls_pg_rec;
> > +    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > +        if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec,
> > +                                            ls_pg_rec->nb_pg->acls,
> > +                                            ls_pg_rec->nb_pg->n_acls)) {
> > +            return;
> > +        }
> > +    }
> > +}
> > +
> > +static bool
> > +ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *ls_lbacls_rec,
> > +                                struct nbrec_acl **acls,
> > +                                size_t n_acls)
> > +{
> > +    /* A true return indicates that there are no possible ACL flags
> > +     * left to set on ls_lbacls record. A false return indicates that
> > +     * further ACLs should be explored in case more flags need to be
> > +     * set on ls_lbacls record.
> > +     */
> > +    if (!n_acls) {
> > +        return false;
> > +    }
> > +
> > +    ls_lbacls_rec->has_acls = true;
> > +    for (size_t i = 0; i < n_acls; i++) {
> > +        const struct nbrec_acl *acl = acls[i];
> > +        if (acl->tier > ls_lbacls_rec->max_acl_tier) {
> > +            ls_lbacls_rec->max_acl_tier = acl->tier;
> > +        }
> > +        if (!ls_lbacls_rec->has_stateful_acl
> > +                && !strcmp(acl->action, "allow-related")) {
> > +            ls_lbacls_rec->has_stateful_acl = true;
> > +        }
> > +        if (ls_lbacls_rec->has_stateful_acl &&
> > +            ls_lbacls_rec->max_acl_tier ==
> > +                nbrec_acl_col_tier.type.value.integer.max) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static struct ls_lbacls_input
> > +ls_lbacls_get_input_data(struct engine_node *node)
> > +{
> > +    const struct northd_data *northd_data =
> > +        engine_get_input_data("northd", node);
> > +    const struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    return (struct ls_lbacls_input) {
> > +        .nbrec_logical_switch_table =
> > +            EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)),
> > +        .ls_port_groups = &pg_data->ls_port_groups,
> > +        .ls_datapaths = &northd_data->ls_datapaths,
> > +    };
> > +}
> > +
> > +static bool
> > +is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
> > +{
> > +    for (size_t i = 0; i < n_nb_acls; i++) {
> > +        if (nbrec_acl_row_get_seqno(nb_acls[i],
> > +                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static bool
> > +is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
> > +    return (nbrec_logical_switch_is_new(nbs)
> > +            || nbrec_logical_switch_is_deleted(nbs)
> > +            || nbrec_logical_switch_is_updated(nbs,
> > +
> NBREC_LOGICAL_SWITCH_COL_ACLS)
> > +            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
> > +}
> > diff --git a/northd/en-ls-lb-acls.h b/northd/en-ls-lb-acls.h
> > new file mode 100644
> > index 0000000000..ccb75e40e8
> > --- /dev/null
> > +++ b/northd/en-ls-lb-acls.h
> > @@ -0,0 +1,88 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +#ifndef EN_LS_LB_ACL_H
> > +#define EN_LS_LB_ACL_H 1
> > +
> > +#include <stdint.h>
> > +
> > +/* OVS includes. */
> > +#include "lib/hmapx.h"
> > +#include "openvswitch/hmap.h"
> > +#include "sset.h"
> > +
> > +/* OVN includes. */
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +
> > +struct ls_lbacls_record {
> > +    struct hmap_node key_node;
> > +
> > +    const struct ovn_datapath *od;
>
> Such reference is error-prone. In the en-flow node it directly uses this
> reference to access other data of the ovn_datapath, but the dependency is
> not tracked and propagated through this node. For example,
> ls_lbacls_rec->od->nbs->acls is used in build_acls but ACL changes are not
> propagated through this node.

I don't think that's the case.  ls_lbacls engine node has the following inputs
     -  northd engine node
     -  NB_logical_switch
     - port_group

If  a logical switch's ACLs change (or a port group's ACLs change),
those changes are handled  and are tracked in this node so that
en_lflow engine can handle it.

We could also store 'struct nbrec_logical_switch *' in the 'struct
ls_lbacls_record.   But I don't see any benefit in it.  We would
just waste some pointer space.

Thanks
Numan

>
> > +    bool has_stateful_acl;
> > +    bool has_lb_vip;
> > +    bool has_acls;
> > +    uint64_t max_acl_tier;
> > +};
> > +
> > +struct ls_lbacls_table {
> > +    struct hmap entries;
> > +};
> > +
> > +#define LS_LBACLS_TABLE_FOR_EACH(LS_LBACLS_REC, TABLE) \
> > +    HMAP_FOR_EACH (LS_LBACLS_REC, key_node, &(TABLE)->entries)
> > +
> > +#define LS_LBACLS_TABLE_FOR_EACH_IN_P(LS_LBACLS_REC, JOBID, TABLE) \
> > +    HMAP_FOR_EACH_IN_PARALLEL (LS_LBACLS_REC, key_node, JOBID, \
> > +                               &(TABLE)->entries)
> > +
> > +struct ls_lbacls_tracked_data {
> > +    /* Created or updated logical switch with LB and ACL data. */
> > +    struct hmapx crupdated; /* Stores 'struct ls_lbacls_record'. */
> > +
> > +    /* Deleted logical switch with LB and ACL data. */
> > +    struct hmapx deleted; /* Stores 'struct ls_lbacls_record'. */
> > +};
> > +
> > +struct ed_type_ls_lbacls {
> > +    struct ls_lbacls_table ls_lbacls;
> > +
> > +    bool tracked;
> > +    struct ls_lbacls_tracked_data tracked_data;
> > +};
> > +
> > +struct ls_lbacls_input {
> > +    const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
> > +    const struct ls_port_group_table *ls_port_groups;
> > +    const struct ovn_datapaths *ls_datapaths;
> > +};
>
> nit: this seems should be module internal and should move to the .c
>
> Thanks,
> Han
>
> > +
> > +void *en_ls_lbacls_init(struct engine_node *, struct engine_arg *);
> > +void en_ls_lbacls_cleanup(void *data);
> > +void en_ls_lbacls_clear_tracked_data(void *data);
> > +void en_ls_lbacls_run(struct engine_node *, void *data);
> > +
> > +bool ls_lbacls_northd_handler(struct engine_node *, void *data);
> > +bool ls_lbacls_port_group_handler(struct engine_node *, void *data);
> > +bool ls_lbacls_logical_switch_handler(struct engine_node *, void *data);
> > +
> > +const struct ls_lbacls_record *ls_lbacls_table_find(
> > +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> > +
> > +#endif /* EN_LS_LB_ACL_H */
> > diff --git a/northd/en-port-group.h b/northd/en-port-group.h
> > index 3b28a23694..54014062ce 100644
> > --- a/northd/en-port-group.h
> > +++ b/northd/en-port-group.h
> > @@ -48,6 +48,9 @@ struct ls_port_group_record {
> >      struct sset ports;          /* Subset of 'nb_pg' ports in this
> record. */
> >  };
> >
> > +#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
> > +    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
> > +
> >  void ls_port_group_table_init(struct ls_port_group_table *);
> >  void ls_port_group_table_clear(struct ls_port_group_table *);
> >  void ls_port_group_table_destroy(struct ls_port_group_table *);
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index 84627070a8..ab4af92aeb 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -33,6 +33,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-lb-nat-data.h"
> >  #include "en-lr-nat.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "en-northd.h"
> >  #include "en-lflow.h"
> >  #include "en-northd-output.h"
> > @@ -150,6 +151,7 @@ static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_lb_nat_data,
> "lr_lb_nat_data");
> > +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_lbacls, "ls_lbacls");
> >
> >  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >                            struct ovsdb_idl_loop *sb)
> > @@ -205,6 +207,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lr_lb_nat_data, &en_lb_data,
> >                       lr_lb_nat_data_lb_data_handler);
> >
> > +    engine_add_input(&en_ls_lbacls, &en_northd,
> ls_lbacls_northd_handler);
> > +    engine_add_input(&en_ls_lbacls, &en_port_group,
> > +                     ls_lbacls_port_group_handler);
> > +    engine_add_input(&en_ls_lbacls, &en_nb_logical_switch,
> > +                     ls_lbacls_logical_switch_handler);
> > +
> >      engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> > @@ -229,6 +237,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
> >      engine_add_input(&en_lflow, &en_port_group,
> lflow_port_group_handler);
> >      engine_add_input(&en_lflow, &en_lr_lb_nat_data, NULL);
> > +    engine_add_input(&en_lflow, &en_ls_lbacls, NULL);
> >
> >      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
> >                       sync_to_sb_addr_set_nb_address_set_handler);
> > diff --git a/northd/northd.c b/northd/northd.c
> > index c8a224d3cd..924f5cd7e0 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -45,6 +45,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-lb-nat-data.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "lib/ovn-parallel-hmap.h"
> >  #include "ovn/actions.h"
> >  #include "ovn/features.h"
> > @@ -575,7 +576,7 @@ lb_group_has_vip(const struct
> nbrec_load_balancer_group *lb_group)
> >  }
> >
> >  static bool
> > -ls_has_lb_vip(struct ovn_datapath *od)
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbs->load_balancer[i])) {
> > @@ -592,7 +593,7 @@ ls_has_lb_vip(struct ovn_datapath *od)
> >  }
> >
> >  static bool
> > -lr_has_lb_vip(struct ovn_datapath *od)
> > +lr_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbr->load_balancer[i])) {
> > @@ -608,13 +609,13 @@ lr_has_lb_vip(struct ovn_datapath *od)
> >      return false;
> >  }
> >
> > -static void
> > -init_lb_for_datapath(struct ovn_datapath *od)
> > +bool
> > +od_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      if (od->nbs) {
> > -        od->has_lb_vip = ls_has_lb_vip(od);
> > +        return ls_has_lb_vip(od);
> >      } else {
> > -        od->has_lb_vip = lr_has_lb_vip(od);
> > +        return lr_has_lb_vip(od);
> >      }
> >  }
> >
> > @@ -1058,7 +1059,6 @@ join_datapaths(const struct
> nbrec_logical_switch_table *nbrec_ls_table,
> >
> >          init_ipam_info_for_datapath(od);
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >      }
> >
> >      const struct nbrec_logical_router *nbr;
> > @@ -1089,7 +1089,6 @@ join_datapaths(const struct
> nbrec_logical_switch_table *nbrec_ls_table,
> >              ovs_list_push_back(nb_only, &od->list);
> >          }
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >          if (smap_get(&od->nbr->options, "chassis")) {
> >              od->is_gw_router = true;
> >          }
> > @@ -2570,7 +2569,8 @@ get_nat_addresses(const struct ovn_port *op, size_t
> *n, bool routable_only,
> >      size_t n_nats = 0;
> >      struct eth_addr mac;
> >      if (!op || !op->nbrp || !op->od || !op->od->nbr
> > -        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> > +        || (!op->od->nbr->n_nat && (!lr_lbnat_rec
> > +                                    || !lr_lbnat_rec->has_lb_vip))
> >          || !eth_addr_from_string(op->nbrp->mac, &mac)) {
> >          *n = n_nats;
> >          return NULL;
> > @@ -3817,7 +3817,7 @@ build_lrouter_lbs_check(const struct ovn_datapaths
> *lr_datapaths)
> >      HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
> >          ovs_assert(od->nbr);
> >
> > -        if (od->has_lb_vip && od->n_l3dgw_ports > 1
> > +        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
> >                  && !smap_get(&od->nbr->options, "chassis")) {
> >              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1,
> 1);
> >              VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
> > @@ -5441,7 +5441,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the ls datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> > @@ -5524,9 +5523,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >              }
> >          }
> >
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> > -
> >          /* Add the ls datapath to the northd tracked data. */
> >          hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> >      }
> > @@ -5564,9 +5560,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >              }
> >          }
> >
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> > -
> >          /* Add the lr datapath to the northd tracked data. */
> >          hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> >      }
> > @@ -5581,8 +5574,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the ls datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> > @@ -5591,8 +5582,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
> >                             lb_dps->nb_lr_map) {
> >              od = lr_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the lr datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> > @@ -5618,9 +5607,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >                  od = lbgrp_dps->lr[i];
> >                  ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
> >
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> > -
> >                  /* Add the lr datapath to the northd tracked data. */
> >                  hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated,
> od);
> >              }
> > @@ -5629,9 +5615,6 @@ northd_handle_lb_data_changes(struct
> tracked_lb_data *trk_lb_data,
> >                 od = lbgrp_dps->ls[i];
> >                  ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
> >
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> > -
> >                  /* Add the ls datapath to the northd tracked data. */
> >                  hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated,
> od);
> >              }
> > @@ -6573,63 +6556,6 @@ build_dhcpv6_action(struct ovn_port *op, struct
> in6_addr *offer_ip,
> >      return true;
> >  }
> >
> > -static bool
> > -od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
> > -                 size_t n_acls)
> > -{
> > -    /* A true return indicates that there are no possible ACL flags
> > -     * left to set on od. A false return indicates that further ACLs
> > -     * should be explored in case more flags need to be set on od
> > -     */
> > -    if (!n_acls) {
> > -        return false;
> > -    }
> > -
> > -    od->has_acls = true;
> > -    for (size_t i = 0; i < n_acls; i++) {
> > -        const struct nbrec_acl *acl = acls[i];
> > -        if (acl->tier > od->max_acl_tier) {
> > -            od->max_acl_tier = acl->tier;
> > -        }
> > -        if (!od->has_stateful_acl && !strcmp(acl->action,
> "allow-related")) {
> > -            od->has_stateful_acl = true;
> > -        }
> > -        if (od->has_stateful_acl &&
> > -            od->max_acl_tier ==
> nbrec_acl_col_tier.type.value.integer.max) {
> > -            return true;
> > -        }
> > -    }
> > -
> > -    return false;
> > -}
> > -
> > -static void
> > -ls_get_acl_flags(struct ovn_datapath *od,
> > -                 const struct ls_port_group_table *ls_port_groups)
> > -{
> > -    od->has_acls = false;
> > -    od->has_stateful_acl = false;
> > -    od->max_acl_tier = 0;
> > -
> > -    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group *ls_pg =
> > -        ls_port_group_table_find(ls_port_groups, od->nbs);
> > -    if (!ls_pg) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group_record *ls_pg_rec;
> > -    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > -        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
> > -                             ls_pg_rec->nb_pg->n_acls)) {
> > -            return;
> > -        }
> > -    }
> > -}
> > -
> >  /* Adds the logical flows in the (in/out) check port sec stage only if
> >   *   - the lport is disabled or
> >   *   - lport is of type vtep - to skip the ingress pipeline.
> > @@ -6774,9 +6700,10 @@ build_lswitch_output_port_sec_od(struct
> ovn_datapath *od,
> >  }
> >
> >  static void
> > -skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> > -                         enum ovn_stage in_stage, enum ovn_stage
> out_stage,
> > -                         uint16_t priority, struct hmap *lflows)
> > +skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port
> *op,
> > +                         bool has_stateful_acl, enum ovn_stage in_stage,
> > +                         enum ovn_stage out_stage, uint16_t priority,
> > +                         struct hmap *lflows)
> >  {
> >      /* Can't use ct() for router ports. Consider the following
> configuration:
> >       * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB,
> For a
> > @@ -6789,7 +6716,7 @@ skip_port_from_conntrack(struct ovn_datapath *od,
> struct ovn_port *op,
> >       * conntrack state across all chassis. */
> >
> >      const char *ingress_action = "next;";
> > -    const char *egress_action = od->has_stateful_acl
> > +    const char *egress_action = has_stateful_acl
> >                                  ? "next;"
> >                                  : "ct_clear; next;";
> >
> > @@ -6808,7 +6735,7 @@ skip_port_from_conntrack(struct ovn_datapath *od,
> struct ovn_port *op,
> >  }
> >
> >  static void
> > -build_stateless_filter(struct ovn_datapath *od,
> > +build_stateless_filter(const struct ovn_datapath *od,
> >                         const struct nbrec_acl *acl,
> >                         struct hmap *lflows)
> >  {
> > @@ -6829,7 +6756,7 @@ build_stateless_filter(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_stateless_filters(struct ovn_datapath *od,
> > +build_stateless_filters(const struct ovn_datapath *od,
> >                          const struct ls_port_group_table *ls_port_groups,
> >                          struct hmap *lflows)
> >  {
> > @@ -6859,9 +6786,7 @@ build_stateless_filters(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_pre_acls(struct ovn_datapath *od,
> > -               const struct ls_port_group_table *ls_port_groups,
> > -               struct hmap *lflows)
> > +build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
> >  {
> >      /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
> >       * allowed by default. */
> > @@ -6873,18 +6798,26 @@ build_pre_acls(struct ovn_datapath *od,
> >
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
> >                    "eth.src == $svc_monitor_mac", "next;");
> > +}
> > +
> > +static void
> > +build_ls_lbacls_rec_pre_acls(const struct ls_lbacls_record
> *ls_lbacls_rec,
> > +                             const struct ls_port_group_table
> *ls_port_groups,
> > +                             struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> >
> >      /* If there are any stateful ACL rules in this datapath, we may
> >       * send IP packets for some (allow) filters through the conntrack
> action,
> >       * which handles defragmentation, in order to match L4 headers. */
> > -    if (od->has_stateful_acl) {
> > +    if (ls_lbacls_rec->has_stateful_acl) {
> >          for (size_t i = 0; i < od->n_router_ports; i++) {
> > -            skip_port_from_conntrack(od, od->router_ports[i],
> > +            skip_port_from_conntrack(od, od->router_ports[i], true,
> >                                       S_SWITCH_IN_PRE_ACL,
> S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> >          }
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> > -            skip_port_from_conntrack(od, od->localnet_ports[i],
> > +            skip_port_from_conntrack(od, od->localnet_ports[i], true,
> >                                       S_SWITCH_IN_PRE_ACL,
> >                                       S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> > @@ -6922,7 +6855,7 @@ build_pre_acls(struct ovn_datapath *od,
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> > -    } else if (od->has_lb_vip) {
> > +    } else if (ls_lbacls_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);
> > @@ -7050,30 +6983,40 @@ build_pre_lb(struct ovn_datapath *od, const
> struct shash *meter_groups,
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
> >
> > +    /* Do not send statless flows via conntrack */
> > +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +}
> > +
> > +static void
> > +build_ls_lbacls_rec_pre_lb(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                           struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      for (size_t i = 0; i < od->n_router_ports; i++) {
> >          skip_port_from_conntrack(od, od->router_ports[i],
> > +                                 ls_lbacls_rec->has_stateful_acl,
> >                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
> >                                   110, lflows);
> >      }
> > +
> >      /* Localnet ports have no need for going through conntrack, unless
> >       * the logical switch has a load balancer. Then, conntrack is
> necessary
> >       * so that traffic arriving via the localnet port can be load
> >       * balanced.
> >       */
> > -    if (!od->has_lb_vip) {
> > +    if (!ls_lbacls_rec->has_lb_vip) {
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> >              skip_port_from_conntrack(od, od->localnet_ports[i],
> > +                                     ls_lbacls_rec->has_stateful_acl,
> >                                       S_SWITCH_IN_PRE_LB,
> S_SWITCH_OUT_PRE_LB,
> >                                       110, lflows);
> >          }
> >      }
> >
> > -    /* Do not sent statless flows via conntrack */
> > -    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -
> >      /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
> >       * packet to conntrack for defragmentation and possibly for
> unNATting.
> >       *
> > @@ -7104,7 +7047,7 @@ build_pre_lb(struct ovn_datapath *od, const struct
> shash *meter_groups,
> >       * ingress pipeline if a load balancer is configured. We can now
> >       * add a lflow to drop ct.inv packets.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_lb_vip) {
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
> >                        100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
> > @@ -7145,10 +7088,12 @@ build_pre_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_acl_hints(struct ovn_datapath *od,
> > +build_acl_hints(const struct ls_lbacls_record *ls_lbacls_rec,
> >                  const struct chassis_features *features,
> >                  struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      /* This stage builds hints for the IN/OUT_ACL stage. Based on various
> >       * combinations of ct flags packets may hit only a subset of the
> logical
> >       * flows in the IN/OUT_ACL stage.
> > @@ -7172,13 +7117,13 @@ build_acl_hints(struct ovn_datapath *od,
> >          const char *match;
> >
> >          /* In any case, advance to the next stage. */
> > -        if (!od->has_acls && !od->has_lb_vip) {
> > +        if (!ls_lbacls_rec->has_acls && !ls_lbacls_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
> >          } else {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >          }
> >
> > -        if (!od->has_stateful_acl && !od->has_lb_vip) {
> > +        if (!ls_lbacls_rec->has_stateful_acl &&
> !ls_lbacls_rec->has_lb_vip) {
> >              continue;
> >          }
> >
> > @@ -7314,10 +7259,10 @@ build_acl_log(struct ds *actions, const struct
> nbrec_acl *acl,
> >  }
> >
> >  static void
> > -consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> > +consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
> >               const struct nbrec_acl *acl, bool has_stateful,
> >               bool ct_masked_mark, const struct shash *meter_groups,
> > -             struct ds *match, struct ds *actions)
> > +             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
> >  {
> >      const char *ct_blocked_match = ct_masked_mark
> >                                     ? "ct_mark.blocked"
> > @@ -7354,7 +7299,7 @@ consider_acl(struct hmap *lflows, struct
> ovn_datapath *od,
> >      /* All ACLS will start by matching on their respective tier. */
> >      size_t match_tier_len = 0;
> >      ds_clear(match);
> > -    if (od->max_acl_tier) {
> > +    if (max_acl_tier) {
> >          ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ",
> acl->tier);
> >          match_tier_len = match->length;
> >      }
> > @@ -7543,12 +7488,15 @@ ovn_update_ipv6_options(struct hmap *lr_ports)
> >  #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
> >
> >  static void
> > -build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_action_lflows(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                        struct hmap *lflows,
> >                          const char *default_acl_action,
> >                          const struct shash *meter_groups,
> >                          struct ds *match,
> >                          struct ds *actions)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      enum ovn_stage stages [] = {
> >          S_SWITCH_IN_ACL_ACTION,
> >          S_SWITCH_IN_ACL_AFTER_LB_ACTION,
> > @@ -7559,7 +7507,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
> struct hmap *lflows,
> >      ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
> >                          REGBIT_ACL_VERDICT_DROP " = 0; "
> >                          REGBIT_ACL_VERDICT_REJECT " = 0; ");
> > -    if (od->max_acl_tier) {
> > +    if (ls_lbacls_rec->max_acl_tier) {
> >          ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
> >      }
> >
> > @@ -7567,7 +7515,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
> struct hmap *lflows,
> >
> >      for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
> >          enum ovn_stage stage = stages[i];
> > -        if (!od->has_acls) {
> > +        if (!ls_lbacls_rec->has_acls) {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >              continue;
> >          }
> > @@ -7602,7 +7550,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
> struct hmap *lflows,
> >          ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
> >
> >          struct ds tier_actions = DS_EMPTY_INITIALIZER;
> > -        for (size_t j = 0; j < od->max_acl_tier; j++) {
> > +        for (size_t j = 0; j < ls_lbacls_rec->max_acl_tier; j++) {
> >              ds_clear(match);
> >              ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
> >              ds_clear(&tier_actions);
> > @@ -7618,7 +7566,7 @@ build_acl_action_lflows(struct ovn_datapath *od,
> struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap
> *lflows,
> >                              const struct nbrec_acl *acl, bool
> has_stateful,
> >                              bool ct_masked_mark,
> >                              const struct shash *meter_groups,
> > @@ -7691,15 +7639,19 @@ build_acl_log_related_flows(struct ovn_datapath
> *od, struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acls(struct ovn_datapath *od, const struct chassis_features
> *features,
> > +build_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> > +           const struct chassis_features *features,
> >             struct hmap *lflows,
> >             const struct ls_port_group_table *ls_port_groups,
> >             const struct shash *meter_groups)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      const char *default_acl_action = default_acl_drop
> >                                       ? debug_implicit_drop_action()
> >                                       : "next;";
> > -    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
> > +    bool has_stateful = (ls_lbacls_rec->has_stateful_acl
> > +                         || ls_lbacls_rec->has_lb_vip);
> >      const char *ct_blocked_match = features->ct_no_masked_label
> >                                     ? "ct_mark.blocked"
> >                                     : "ct_label.blocked";
> > @@ -7713,8 +7665,8 @@ build_acls(struct ovn_datapath *od, const struct
> chassis_features *features,
> >       *
> >       * A related rule at priority 1 is added below if there
> >       * are any stateful ACLs in this datapath. */
> > -    if (!od->has_acls) {
> > -        if (!od->has_lb_vip) {
> > +    if (!ls_lbacls_rec->has_acls) {
> > +        if (!ls_lbacls_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX,
> "1",
> >                            "next;");
> >              ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX,
> "1",
> > @@ -7877,7 +7829,8 @@ build_acls(struct ovn_datapath *od, const struct
> chassis_features *features,
> >                                      meter_groups, &match, &actions);
> >          consider_acl(lflows, od, acl, has_stateful,
> >                       features->ct_no_masked_label,
> > -                     meter_groups, &match, &actions);
> > +                     meter_groups, ls_lbacls_rec->max_acl_tier,
> > +                     &match, &actions);
> >      }
> >
> >      const struct ls_port_group *ls_pg =
> > @@ -7893,7 +7846,8 @@ build_acls(struct ovn_datapath *od, const struct
> chassis_features *features,
> >                                              meter_groups, &match,
> &actions);
> >                  consider_acl(lflows, od, acl, has_stateful,
> >                               features->ct_no_masked_label,
> > -                             meter_groups, &match, &actions);
> > +                             meter_groups, ls_lbacls_rec->max_acl_tier,
> > +                             &match, &actions);
> >              }
> >          }
> >      }
> > @@ -7911,7 +7865,7 @@ build_acls(struct ovn_datapath *od, const struct
> chassis_features *features,
> >              dns_actions);
> >      }
> >
> > -    if (od->has_acls || od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_acls || ls_lbacls_rec->has_lb_vip) {
> >          /* Add a 34000 priority flow to advance the service monitor reply
> >          * packets to skip applying ingress ACLs. */
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
> > @@ -7925,8 +7879,8 @@ build_acls(struct ovn_datapath *od, const struct
> chassis_features *features,
> >                      REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
> >      }
> >
> > -    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
> > -                            &match, &actions);
> > +    build_acl_action_lflows(ls_lbacls_rec, lflows, default_acl_action,
> > +                            meter_groups, &match, &actions);
> >
> >      ds_destroy(&match);
> >      ds_destroy(&actions);
> > @@ -8571,8 +8525,11 @@ build_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> > +build_lb_hairpin(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                 struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
> >       * Packets that don't need hairpinning should continue processing.
> >       */
> > @@ -8580,7 +8537,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct
> hmap *lflows)
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
> >
> > -    if (od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_lb_vip) {
> >          /* Check if the packet needs to be hairpinned.
> >           * Set REGBIT_HAIRPIN in the original direction and
> >           * REGBIT_HAIRPIN_REPLY in the reply direction.
> > @@ -9413,22 +9370,16 @@ build_lswitch_lflows_l2_unknown(struct
> ovn_datapath *od,
> >  static void
> >  build_lswitch_lflows_pre_acl_and_acl(
> >      struct ovn_datapath *od,
> > -    const struct ls_port_group_table *ls_port_groups,
> >      const struct chassis_features *features,
> >      struct hmap *lflows,
> >      const struct shash *meter_groups)
> >  {
> >      ovs_assert(od->nbs);
> > -    ls_get_acl_flags(od, ls_port_groups);
> > -
> > -    build_pre_acls(od, ls_port_groups, lflows);
> > +    build_pre_acls(od, lflows);
> >      build_pre_lb(od, meter_groups, lflows);
> >      build_pre_stateful(od, features, lflows);
> > -    build_acl_hints(od, features, lflows);
> > -    build_acls(od, features, lflows, ls_port_groups, meter_groups);
> >      build_qos(od, lflows);
> >      build_stateful(od, features, lflows);
> > -    build_lb_hairpin(od, lflows);
> >      build_vtep_hairpin(od, lflows);
> >  }
> >
> > @@ -15487,7 +15438,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * a dynamically negotiated FTP data channel), but will allow
> >       * related traffic such as an ICMP Port Unreachable through
> >       * that's generated from a non-listening UDP port.  */
> > -    if (od->has_lb_vip && features->ct_lb_related) {
> > +    if (lr_lbnat_rec->has_lb_vip && features->ct_lb_related) {
> >          ds_clear(match);
> >
> >          ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
> > @@ -15512,7 +15463,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * Pass the traffic that is already established to the next table
> with
> >       * proper flags set.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (lr_lbnat_rec->has_lb_vip) {
> >          ds_clear(match);
> >
> >          ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
> > @@ -15542,7 +15493,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * not committed, it would produce ongoing datapath flows with the
> ct.new
> >       * flag set. Some NICs are unable to offload these flows.
> >       */
> > -    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
> > +    if (od->is_gw_router && (od->nbr->n_nat ||
> lr_lbnat_rec->has_lb_vip)) {
> >          /* Do not send ND or ICMP packets to connection tracking. */
> >          ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
> >                        "nd || nd_rs || nd_ra", "next;");
> > @@ -15967,6 +15918,22 @@ build_lr_lbnat_data_flows(const struct
> lr_lb_nat_data_record *lr_lbnat_rec,
> >                                        meter_groups);
> >  }
> >
> > +static void
> > +build_ls_lbacls_flows(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                      const struct ls_port_group_table *ls_pgs,
> > +                      const struct chassis_features *features,
> > +                      const struct shash *meter_groups,
> > +                      struct hmap *lflows)
> > +{
> > +    ovs_assert(ls_lbacls_rec->od);
> > +
> > +    build_ls_lbacls_rec_pre_acls(ls_lbacls_rec, ls_pgs, lflows);
> > +    build_ls_lbacls_rec_pre_lb(ls_lbacls_rec, lflows);
> > +    build_acl_hints(ls_lbacls_rec, features, lflows);
> > +    build_acls(ls_lbacls_rec, features, lflows, ls_pgs, meter_groups);
> > +    build_lb_hairpin(ls_lbacls_rec, lflows);
> > +}
> > +
> >  struct lswitch_flow_build_info {
> >      const struct ovn_datapaths *ls_datapaths;
> >      const struct ovn_datapaths *lr_datapaths;
> > @@ -15974,6 +15941,7 @@ struct lswitch_flow_build_info {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_lb_nat_data_table *lr_lbnats;
> > +    const struct ls_lbacls_table *ls_lbacls;
> >      struct hmap *lflows;
> >      struct hmap *igmp_groups;
> >      const struct shash *meter_groups;
> > @@ -15998,9 +15966,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct
> ovn_datapath *od,
> >                                          struct lswitch_flow_build_info
> *lsi)
> >  {
> >      ovs_assert(od->nbs);
> > -    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
> > -                                         lsi->features,
> > -                                         lsi->lflows,
> > +    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
> >                                           lsi->meter_groups);
> >
> >      build_fwd_group_lflows(od, lsi->lflows);
> > @@ -16115,6 +16081,7 @@ build_lflows_thread(void *arg)
> >  {
> >      struct worker_control *control = (struct worker_control *) arg;
> >      const struct lr_lb_nat_data_record *lr_lbnat_rec;
> > +    const struct ls_lbacls_record *ls_lbacls_rec;
> >      struct lswitch_flow_build_info *lsi;
> >      struct ovn_igmp_group *igmp_group;
> >      struct ovn_lb_datapaths *lb_dps;
> > @@ -16243,6 +16210,19 @@ build_lflows_thread(void *arg)
> >                                                lsi->features);
> >                  }
> >              }
> > +
> > +            for (bnum = control->id;
> > +                    bnum <= lsi->ls_lbacls->entries.mask;
> > +                    bnum += control->pool->size)
> > +            {
> > +                LS_LBACLS_TABLE_FOR_EACH_IN_P (ls_lbacls_rec, bnum,
> > +                                               lsi->ls_lbacls) {
> > +                    build_ls_lbacls_flows(ls_lbacls_rec,
> lsi->ls_port_groups,
> > +                                          lsi->features,
> lsi->meter_groups,
> > +                                          lsi->lflows);
> > +                }
> > +            }
> > +
> >              for (bnum = control->id;
> >                      bnum <= lsi->igmp_groups->mask;
> >                      bnum += control->pool->size)
> > @@ -16303,6 +16283,7 @@ build_lswitch_and_lrouter_flows(const struct
> ovn_datapaths *ls_datapaths,
> >                                  const struct hmap *lr_ports,
> >                                  const struct ls_port_group_table *ls_pgs,
> >                                  const struct lr_lb_nat_data_table
> *lr_lbnats,
> > +                                const struct ls_lbacls_table *ls_lbacls,
> >                                  struct hmap *lflows,
> >                                  struct hmap *igmp_groups,
> >                                  const struct shash *meter_groups,
> > @@ -16333,6 +16314,7 @@ build_lswitch_and_lrouter_flows(const struct
> ovn_datapaths *ls_datapaths,
> >              lsiv[index].lr_ports = lr_ports;
> >              lsiv[index].ls_port_groups = ls_pgs;
> >              lsiv[index].lr_lbnats = lr_lbnats;
> > +            lsiv[index].ls_lbacls = ls_lbacls;
> >              lsiv[index].igmp_groups = igmp_groups;
> >              lsiv[index].meter_groups = meter_groups;
> >              lsiv[index].lb_dps_map = lb_dps_map;
> > @@ -16358,6 +16340,7 @@ build_lswitch_and_lrouter_flows(const struct
> ovn_datapaths *ls_datapaths,
> >          free(lsiv);
> >      } else {
> >          const struct lr_lb_nat_data_record *lr_lbnat_rec;
> > +        const struct ls_lbacls_record *ls_lbacls_rec;
> >          struct ovn_igmp_group *igmp_group;
> >          struct ovn_lb_datapaths *lb_dps;
> >          struct ovn_datapath *od;
> > @@ -16370,6 +16353,7 @@ build_lswitch_and_lrouter_flows(const struct
> ovn_datapaths *ls_datapaths,
> >              .lr_ports = lr_ports,
> >              .ls_port_groups = ls_pgs,
> >              .lr_lbnats = lr_lbnats,
> > +            .ls_lbacls = ls_lbacls,
> >              .lflows = lflows,
> >              .igmp_groups = igmp_groups,
> >              .meter_groups = meter_groups,
> > @@ -16439,6 +16423,12 @@ build_lswitch_and_lrouter_flows(const struct
> ovn_datapaths *ls_datapaths,
> >                                        lsi.features);
> >          }
> >
> > +        LS_LBACLS_TABLE_FOR_EACH (ls_lbacls_rec, ls_lbacls) {
> > +            build_ls_lbacls_flows(ls_lbacls_rec, lsi.ls_port_groups,
> > +                                  lsi.features, lsi.meter_groups,
> > +                                  lsi.lflows);
> > +        }
> > +
> >          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
> >          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
> >              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> > @@ -16535,6 +16525,7 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
> >                                      input_data->lr_ports,
> >                                      input_data->ls_port_groups,
> >                                      input_data->lr_lbnats,
> > +                                    input_data->ls_lbacls,
> >                                      lflows,
> >                                      &igmp_groups,
> >                                      input_data->meter_groups,
> > diff --git a/northd/northd.h b/northd/northd.h
> > index 08a81b2c10..23b4754db4 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -89,6 +89,8 @@ ods_size(const struct ovn_datapaths *datapaths)
> >      return hmap_count(&datapaths->datapaths);
> >  }
> >
> > +bool od_has_lb_vip(const struct ovn_datapath *od);
> > +
> >  struct tracked_ovn_ports {
> >      /* tracked created ports.
> >       * hmapx node data is 'struct ovn_port *' */
> > @@ -179,6 +181,7 @@ struct lflow_input {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_lb_nat_data_table *lr_lbnats;
> > +    const struct ls_lbacls_table *ls_lbacls;
> >      const struct shash *meter_groups;
> >      const struct hmap *lb_datapaths_map;
> >      const struct hmap *bfd_connections;
> > @@ -288,11 +291,7 @@ struct ovn_datapath {
> >      struct hmap port_tnlids;
> >      uint32_t port_key_hint;
> >
> > -    bool has_stateful_acl;
> > -    bool has_lb_vip;
> >      bool has_unknown;
> > -    bool has_acls;
> > -    uint64_t max_acl_tier;
> >      bool has_vtep_lports;
> >      bool has_arp_proxy_port;
> >
> > --
> > 2.41.0
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Dumitru Ceara Nov. 24, 2023, 10:53 a.m. UTC | #3
On 10/26/23 20:15, numans@ovn.org wrote:
> From: Numan Siddique <numans@ovn.org>
> 
> This new engine now maintains the load balancer and ACL data of a
> logical switch which was earlier part of northd engine node data.
> The main inputs to this engine are:
>     - northd node
>     - NB logical switch node
>     - Port group node
> 
> A record for each logical switch is maintained in the 'ls_lbacls'
> hmap table and this record stores the below data which was earlier
> part of 'struct ovn_datapath'.
> 
>     - bool has_stateful_acl;
>     - bool has_lb_vip;
>     - bool has_acls;
>     - uint64_t max_acl_tier;
> 
> This engine node becomes an input to 'lflow' node.
> 
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---
>  lib/stopwatch-names.h      |   1 +
>  northd/automake.mk         |   2 +
>  northd/en-lflow.c          |   4 +
>  northd/en-lr-lb-nat-data.c |   8 +-
>  northd/en-lr-lb-nat-data.h |   2 +
>  northd/en-ls-lb-acls.c     | 527 +++++++++++++++++++++++++++++++++++++
>  northd/en-ls-lb-acls.h     |  88 +++++++
>  northd/en-port-group.h     |   3 +
>  northd/inc-proc-northd.c   |   9 +
>  northd/northd.c            | 271 +++++++++----------
>  northd/northd.h            |   7 +-
>  11 files changed, 776 insertions(+), 146 deletions(-)
>  create mode 100644 northd/en-ls-lb-acls.c
>  create mode 100644 northd/en-ls-lb-acls.h
> 
> diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> index 7d85acdaea..8b0018a593 100644
> --- a/lib/stopwatch-names.h
> +++ b/lib/stopwatch-names.h
> @@ -34,5 +34,6 @@
>  #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
>  #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
>  #define LR_LB_NAT_DATA_RUN_STOPWATCH_NAME "lr_lb_nat_data"
> +#define LS_LBACLS_RUN_STOPWATCH_NAME "lr_lb_acls"
>  
>  #endif
> diff --git a/northd/automake.mk b/northd/automake.mk
> index 4116c487df..4593654726 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -28,6 +28,8 @@ northd_ovn_northd_SOURCES = \
>  	northd/en-lr-nat.h \
>  	northd/en-lr-lb-nat-data.c \
>  	northd/en-lr-lb-nat-data.h \
> +	northd/en-ls-lb-acls.c \
> +	northd/en-ls-lb-acls.h \
>  	northd/inc-proc-northd.c \
>  	northd/inc-proc-northd.h \
>  	northd/ipam.c \
> diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> index 229f4be1d0..648a477916 100644
> --- a/northd/en-lflow.c
> +++ b/northd/en-lflow.c
> @@ -21,6 +21,7 @@
>  #include "en-lflow.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-lb-nat-data.h"
> +#include "en-ls-lb-acls.h"
>  #include "en-northd.h"
>  #include "en-meters.h"
>  
> @@ -44,6 +45,8 @@ lflow_get_input_data(struct engine_node *node,
>          engine_get_input_data("sync_meters", node);
>      struct ed_type_lr_lb_nat_data *lr_lb_nat_data =
>          engine_get_input_data("lr_lb_nat_data", node);
> +    struct ed_type_ls_lbacls *ls_lbacls_data =
> +        engine_get_input_data("ls_lbacls", node);
>  
>      lflow_input->nbrec_bfd_table =
>          EN_OVSDB_GET(engine_get_input("NB_bfd", node));
> @@ -67,6 +70,7 @@ lflow_get_input_data(struct engine_node *node,
>      lflow_input->lr_ports = &northd_data->lr_ports;
>      lflow_input->ls_port_groups = &pg_data->ls_port_groups;
>      lflow_input->lr_lbnats = &lr_lb_nat_data->lr_lbnats;
> +    lflow_input->ls_lbacls = &ls_lbacls_data->ls_lbacls;
>      lflow_input->meter_groups = &sync_meters_data->meter_groups;
>      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
>      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> diff --git a/northd/en-lr-lb-nat-data.c b/northd/en-lr-lb-nat-data.c
> index 19b638ce0b..d816d2321d 100644
> --- a/northd/en-lr-lb-nat-data.c
> +++ b/northd/en-lr-lb-nat-data.c
> @@ -299,9 +299,11 @@ lr_lb_nat_data_lb_data_handler(struct engine_node *node, void *data_)
>      if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
>          struct hmapx_node *hmapx_node;
>          /* For all the modified lr_lb_nat_data records (re)build the
> -         * vip nats. */
> +         * vip nats and re-evaluate 'has_lb_vip'. */
>          HMAPX_FOR_EACH (hmapx_node, &data->tracked_data.crupdated) {
> -            lr_lb_nat_data_build_vip_nats(hmapx_node->data);
> +            lr_lbnat_rec = hmapx_node->data;
> +            lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> +            lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
>          }
>  
>          data->tracked = true;
> @@ -523,6 +525,8 @@ lr_lb_nat_data_record_init(struct lr_lb_nat_data_record *lr_lbnat_rec,
>      if (!nbr->n_nat) {
>          lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
>      }
> +
> +    lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
>  }
>  
>  static struct lr_lb_nat_data_input
> diff --git a/northd/en-lr-lb-nat-data.h b/northd/en-lr-lb-nat-data.h
> index ffe41cad73..ac21d28a57 100644
> --- a/northd/en-lr-lb-nat-data.h
> +++ b/northd/en-lr-lb-nat-data.h
> @@ -39,6 +39,8 @@ struct lr_lb_nat_data_record {
>      const struct ovn_datapath *od;
>      const struct lr_nat_record *lrnat_rec;
>  
> +    bool has_lb_vip;
> +
>      /* Load Balancer vIPs relevant for this datapath. */
>      struct ovn_lb_ip_set *lb_ips;
>  
> diff --git a/northd/en-ls-lb-acls.c b/northd/en-ls-lb-acls.c
> new file mode 100644
> index 0000000000..1ba7ecb3e2
> --- /dev/null
> +++ b/northd/en-ls-lb-acls.c
> @@ -0,0 +1,527 @@
> +/*
> + * Copyright (c) 2023, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <getopt.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +
> +/* OVS includes */
> +#include "include/openvswitch/hmap.h"
> +#include "lib/bitmap.h"
> +#include "lib/socket-util.h"
> +#include "lib/uuidset.h"
> +#include "openvswitch/util.h"
> +#include "openvswitch/vlog.h"
> +#include "stopwatch.h"
> +
> +/* OVN includes */
> +#include "en-lb-data.h"
> +#include "en-ls-lb-acls.h"
> +#include "en-port-group.h"
> +#include "lib/inc-proc-eng.h"
> +#include "lib/lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "lib/ovn-util.h"
> +#include "lib/stopwatch-names.h"
> +#include "northd.h"
> +
> +VLOG_DEFINE_THIS_MODULE(en_ls_lbacls);
> +
> +/* Static function declarations. */
> +static void ls_lbacls_table_init(struct ls_lbacls_table *);
> +static void ls_lbacls_table_clear(struct ls_lbacls_table *);
> +static void ls_lbacls_table_destroy(struct ls_lbacls_table *);
> +static struct ls_lbacls_record *ls_lbacls_table_find_(
> +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> +static void ls_lbacls_table_build(struct ls_lbacls_table *,
> +                                  const struct ovn_datapaths *ls_datapaths,
> +                                  const struct ls_port_group_table *);
> +
> +static struct ls_lbacls_input ls_lbacls_get_input_data(
> +    struct engine_node *);
> +
> +static struct ls_lbacls_record *ls_lbacls_record_create(
> +    struct ls_lbacls_table *,
> +    const struct ovn_datapath *,
> +    const struct ls_port_group_table *);
> +static void ls_lbacls_record_destroy(struct ls_lbacls_record *);
> +static void ls_lbacls_record_init(
> +    struct ls_lbacls_record *,
> +    const struct ovn_datapath *,
> +    const struct ls_port_group *,
> +    const struct ls_port_group_table *);
> +static void ls_lbacls_record_reinit(
> +    struct ls_lbacls_record *,
> +    const struct ls_port_group *,
> +    const struct ls_port_group_table *);
> +static bool ls_has_lb_vip(const struct ovn_datapath *);
> +static void ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *,
> +                                           const struct ovn_datapath *,
> +                                           const struct ls_port_group *,
> +                                           const struct ls_port_group_table *);
> +static bool ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *,
> +                                            struct nbrec_acl **,
> +                                            size_t n_acls);
> +static struct ls_lbacls_input ls_lbacls_get_input_data(struct engine_node *);
> +static bool is_ls_acls_changed(const struct nbrec_logical_switch *);
> +static bool is_acls_seqno_changed(struct nbrec_acl **, size_t n_nb_acls);
> +
> +
> +/* public functions. */
> +const struct ls_lbacls_record *
> +ls_lbacls_table_find(
> +    const struct ls_lbacls_table *table,
> +    const struct nbrec_logical_switch *nbs)
> +{
> +    return ls_lbacls_table_find_(table, nbs);
> +}
> +
> +void *
> +en_ls_lbacls_init(struct engine_node *node OVS_UNUSED,
> +                  struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_ls_lbacls *data = xzalloc(sizeof *data);
> +    ls_lbacls_table_init(&data->ls_lbacls);
> +    hmapx_init(&data->tracked_data.crupdated);
> +    hmapx_init(&data->tracked_data.deleted);
> +    return data;
> +}
> +
> +void
> +en_ls_lbacls_cleanup(void *data_)
> +{
> +    struct ed_type_ls_lbacls *data =
> +        (struct ed_type_ls_lbacls *) data_;
> +    ls_lbacls_table_destroy(&data->ls_lbacls);
> +    hmapx_destroy(&data->tracked_data.crupdated);
> +    hmapx_destroy(&data->tracked_data.deleted);
> +}
> +
> +void
> +en_ls_lbacls_clear_tracked_data(void *data_)
> +{
> +    struct ed_type_ls_lbacls *data =
> +        (struct ed_type_ls_lbacls *) data_;
> +
> +    struct hmapx_node *hmapx_node;
> +    HMAPX_FOR_EACH_SAFE (hmapx_node, &data->tracked_data.deleted) {
> +        ls_lbacls_record_destroy(hmapx_node->data);
> +        hmapx_delete(&data->tracked_data.deleted, hmapx_node);
> +    }
> +
> +    hmapx_clear(&data->tracked_data.crupdated);
> +    data->tracked = false;
> +}
> +
> +void
> +en_ls_lbacls_run(struct engine_node *node, void *data_)
> +{
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ed_type_ls_lbacls *data = data_;
> +
> +    stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> +
> +    ls_lbacls_table_clear(&data->ls_lbacls);
> +    ls_lbacls_table_build(&data->ls_lbacls, input_data.ls_datapaths,
> +                          input_data.ls_port_groups);
> +
> +    data->tracked = false;
> +    stopwatch_stop(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());

Same comment about missing stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME,
...) as in the previous patch in this series.

Thanks,
Dumitru

> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +/* Handler functions. */
> +bool
> +ls_lbacls_northd_handler(struct engine_node *node, void *data_)
> +{
> +    struct northd_data *northd_data = engine_get_input_data("northd", node);
> +    if (!northd_data->change_tracked) {
> +        return false;
> +    }
> +
> +    struct northd_tracked_data *nd_changes = &northd_data->trk_northd_changes;
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ls_lbacls_record *ls_lbacls_rec;
> +    struct ed_type_ls_lbacls *data = data_;
> +    const struct ovn_datapath *od;
> +    struct hmapx_node *hmapx_node;
> +
> +    HMAPX_FOR_EACH (hmapx_node, &nd_changes->ls_with_changed_lbs.crupdated) {
> +        od = hmapx_node->data;
> +
> +        ls_lbacls_rec = ls_lbacls_table_find_(&data->ls_lbacls, od->nbs);
> +        if (!ls_lbacls_rec) {
> +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> +                                                    input_data.ls_port_groups);
> +        } else {
> +            ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> +                                    input_data.ls_port_groups);
> +        }
> +
> +        /* Add the ls_lbacls_rec to the tracking data. */
> +        hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +bool
> +ls_lbacls_port_group_handler(struct engine_node *node, void *data_)
> +{
> +    struct port_group_data *pg_data =
> +        engine_get_input_data("port_group", node);
> +
> +    if (pg_data->ls_port_groups_sets_changed) {
> +        return false;
> +    }
> +
> +    /* port_group engine node doesn't provide the tracking data yet.
> +     * Loop through all the ls port groups and update the ls_lbacls_rec.
> +     * This is still better than returning false. */
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    struct ed_type_ls_lbacls *data = data_;
> +    const struct ls_port_group *ls_pg;
> +
> +    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
> +        struct ls_lbacls_record *ls_lbacls_rec =
> +            ls_lbacls_table_find_(&data->ls_lbacls, ls_pg->nbs);
> +
> +        bool modified = false;
> +        if (!ls_lbacls_rec) {
> +            const struct ovn_datapath *od;
> +            od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> +                                    &ls_pg->nbs->header_.uuid);
> +            ovs_assert(od);
> +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> +                                                input_data.ls_port_groups);
> +            modified = true;
> +        } else {
> +            bool had_stateful_acl = ls_lbacls_rec->has_stateful_acl;
> +            uint64_t max_acl_tier = ls_lbacls_rec->max_acl_tier;
> +            bool had_acls = ls_lbacls_rec->has_acls;
> +
> +            ls_lbacls_record_reinit(ls_lbacls_rec, ls_pg,
> +                                    input_data.ls_port_groups);
> +
> +            if ((had_stateful_acl != ls_lbacls_rec->has_stateful_acl)
> +                || (had_acls != ls_lbacls_rec->has_acls)
> +                || max_acl_tier != ls_lbacls_rec->max_acl_tier) {
> +                modified = true;
> +            }
> +        }
> +
> +        if (modified) {
> +            /* Add the ls_lbacls_rec to the tracking data. */
> +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +        }
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +bool
> +ls_lbacls_logical_switch_handler(struct engine_node *node, void *data_)
> +{
> +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> +    const struct nbrec_logical_switch *nbs;
> +    struct ed_type_ls_lbacls *data = data_;
> +
> +    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs,
> +                                    input_data.nbrec_logical_switch_table) {
> +        if (!is_ls_acls_changed(nbs)) {
> +            continue;
> +        }
> +
> +        struct ls_lbacls_record *ls_lbacls_rec =
> +            ls_lbacls_table_find_(&data->ls_lbacls, nbs);
> +
> +        if (nbrec_logical_switch_is_deleted(nbs)) {
> +            if (ls_lbacls_rec) {
> +                /* Remove the record from the entries. */
> +                hmap_remove(&data->ls_lbacls.entries,
> +                            &ls_lbacls_rec->key_node);
> +
> +                /* Add the ls_lbacls_rec to the tracking data. */
> +                hmapx_add(&data->tracked_data.deleted, ls_lbacls_rec);
> +            }
> +        } else {
> +            if (!ls_lbacls_rec) {
> +                const struct ovn_datapath *od;
> +                od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> +                                       &nbs->header_.uuid);
> +                ovs_assert(od);
> +                ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> +                                                    input_data.ls_port_groups);
> +            } else {
> +                ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> +                                        input_data.ls_port_groups);
> +            }
> +
> +            /* Add the ls_lbacls_rec to the tracking data. */
> +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> +        }
> +    }
> +
> +    if (!hmapx_is_empty(&data->tracked_data.crupdated)
> +        || !hmapx_is_empty(&data->tracked_data.deleted)) {
> +        data->tracked = true;
> +        engine_set_node_state(node, EN_UPDATED);
> +    }
> +
> +    return true;
> +}
> +
> +/* static functions. */
> +static void
> +ls_lbacls_table_init(struct ls_lbacls_table *table)
> +{
> +    *table = (struct ls_lbacls_table) {
> +        .entries = HMAP_INITIALIZER(&table->entries),
> +    };
> +}
> +
> +static void
> +ls_lbacls_table_destroy(struct ls_lbacls_table *table)
> +{
> +    ls_lbacls_table_clear(table);
> +    hmap_destroy(&table->entries);
> +}
> +
> +static void
> +ls_lbacls_table_clear(struct ls_lbacls_table *table)
> +{
> +    struct ls_lbacls_record *ls_lbacls_rec;
> +    HMAP_FOR_EACH_POP (ls_lbacls_rec, key_node, &table->entries) {
> +        ls_lbacls_record_destroy(ls_lbacls_rec);
> +    }
> +}
> +
> +static void
> +ls_lbacls_table_build(struct ls_lbacls_table *table,
> +                      const struct ovn_datapaths *ls_datapaths,
> +                      const struct ls_port_group_table *ls_pgs)
> +{
> +    const struct ovn_datapath *od;
> +    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> +        ls_lbacls_record_create(table, od, ls_pgs);
> +    }
> +}
> +
> +struct ls_lbacls_record *
> +ls_lbacls_table_find_(const struct ls_lbacls_table *table,
> +                      const struct nbrec_logical_switch *nbs)
> +{
> +    struct ls_lbacls_record *rec;
> +
> +    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
> +                             uuid_hash(&nbs->header_.uuid), &table->entries) {
> +        if (nbs == rec->od->nbs) {
> +            return rec;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static struct ls_lbacls_record *
> +ls_lbacls_record_create(struct ls_lbacls_table *table,
> +                        const struct ovn_datapath *od,
> +                        const struct ls_port_group_table *ls_pgs)
> +{
> +    struct ls_lbacls_record *ls_lbacls_rec = xzalloc(sizeof *ls_lbacls_rec);
> +    ls_lbacls_rec->od = od;
> +    ls_lbacls_record_init(ls_lbacls_rec, od, NULL, ls_pgs);
> +
> +    hmap_insert(&table->entries, &ls_lbacls_rec->key_node,
> +                uuid_hash(&ls_lbacls_rec->od->nbs->header_.uuid));
> +
> +    return ls_lbacls_rec;
> +}
> +
> +static void
> +ls_lbacls_record_destroy(struct ls_lbacls_record *ls_lbacls_rec)
> +{
> +    free(ls_lbacls_rec);
> +}
> +
> +static void
> +ls_lbacls_record_init(struct ls_lbacls_record *ls_lbacls_rec,
> +                      const struct ovn_datapath *od,
> +                      const struct ls_port_group *ls_pg,
> +                      const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_rec->has_lb_vip = ls_has_lb_vip(od);
> +    ls_lbacls_record_set_acl_flags(ls_lbacls_rec, od, ls_pg, ls_pgs);
> +}
> +
> +static void
> +ls_lbacls_record_reinit(struct ls_lbacls_record *ls_lbacls_rec,
> +                        const struct ls_port_group *ls_pg,
> +                        const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_record_init(ls_lbacls_rec, ls_lbacls_rec->od, ls_pg, ls_pgs);
> +}
> +
> +static bool
> +lb_has_vip(const struct nbrec_load_balancer *lb)
> +{
> +    return !smap_is_empty(&lb->vips);
> +}
> +
> +static bool
> +lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> +{
> +    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
> +        if (lb_has_vip(lb_group->load_balancer[i])) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static bool
> +ls_has_lb_vip(const struct ovn_datapath *od)
> +{
> +    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> +        if (lb_has_vip(od->nbs->load_balancer[i])) {
> +            return true;
> +        }
> +    }
> +
> +    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
> +        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
> +            return true;
> +        }
> +    }
> +    return false;
> +}
> +
> +static void
> +ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *ls_lbacls_rec,
> +                               const struct ovn_datapath *od,
> +                               const struct ls_port_group *ls_pg,
> +                               const struct ls_port_group_table *ls_pgs)
> +{
> +    ls_lbacls_rec->has_stateful_acl = false;
> +    ls_lbacls_rec->max_acl_tier = 0;
> +    ls_lbacls_rec->has_acls = false;
> +
> +    if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec, od->nbs->acls,
> +                                        od->nbs->n_acls)) {
> +        return;
> +    }
> +
> +    if (!ls_pg) {
> +        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
> +    }
> +
> +    if (!ls_pg) {
> +        return;
> +    }
> +
> +    const struct ls_port_group_record *ls_pg_rec;
> +    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> +        if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec,
> +                                            ls_pg_rec->nb_pg->acls,
> +                                            ls_pg_rec->nb_pg->n_acls)) {
> +            return;
> +        }
> +    }
> +}
> +
> +static bool
> +ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *ls_lbacls_rec,
> +                                struct nbrec_acl **acls,
> +                                size_t n_acls)
> +{
> +    /* A true return indicates that there are no possible ACL flags
> +     * left to set on ls_lbacls record. A false return indicates that
> +     * further ACLs should be explored in case more flags need to be
> +     * set on ls_lbacls record.
> +     */
> +    if (!n_acls) {
> +        return false;
> +    }
> +
> +    ls_lbacls_rec->has_acls = true;
> +    for (size_t i = 0; i < n_acls; i++) {
> +        const struct nbrec_acl *acl = acls[i];
> +        if (acl->tier > ls_lbacls_rec->max_acl_tier) {
> +            ls_lbacls_rec->max_acl_tier = acl->tier;
> +        }
> +        if (!ls_lbacls_rec->has_stateful_acl
> +                && !strcmp(acl->action, "allow-related")) {
> +            ls_lbacls_rec->has_stateful_acl = true;
> +        }
> +        if (ls_lbacls_rec->has_stateful_acl &&
> +            ls_lbacls_rec->max_acl_tier ==
> +                nbrec_acl_col_tier.type.value.integer.max) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
> +static struct ls_lbacls_input
> +ls_lbacls_get_input_data(struct engine_node *node)
> +{
> +    const struct northd_data *northd_data =
> +        engine_get_input_data("northd", node);
> +    const struct port_group_data *pg_data =
> +        engine_get_input_data("port_group", node);
> +
> +    return (struct ls_lbacls_input) {
> +        .nbrec_logical_switch_table =
> +            EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)),
> +        .ls_port_groups = &pg_data->ls_port_groups,
> +        .ls_datapaths = &northd_data->ls_datapaths,
> +    };
> +}
> +
> +static bool
> +is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
> +{
> +    for (size_t i = 0; i < n_nb_acls; i++) {
> +        if (nbrec_acl_row_get_seqno(nb_acls[i],
> +                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
> +            return true;
> +        }
> +    }
> +
> +    return false;
> +}
> +
> +static bool
> +is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
> +    return (nbrec_logical_switch_is_new(nbs)
> +            || nbrec_logical_switch_is_deleted(nbs)
> +            || nbrec_logical_switch_is_updated(nbs,
> +                                               NBREC_LOGICAL_SWITCH_COL_ACLS)
> +            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
> +}
> diff --git a/northd/en-ls-lb-acls.h b/northd/en-ls-lb-acls.h
> new file mode 100644
> index 0000000000..ccb75e40e8
> --- /dev/null
> +++ b/northd/en-ls-lb-acls.h
> @@ -0,0 +1,88 @@
> +/*
> + * Copyright (c) 2023, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +#ifndef EN_LS_LB_ACL_H
> +#define EN_LS_LB_ACL_H 1
> +
> +#include <stdint.h>
> +
> +/* OVS includes. */
> +#include "lib/hmapx.h"
> +#include "openvswitch/hmap.h"
> +#include "sset.h"
> +
> +/* OVN includes. */
> +#include "lib/inc-proc-eng.h"
> +#include "lib/lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "lib/ovn-util.h"
> +#include "lib/stopwatch-names.h"
> +
> +struct ls_lbacls_record {
> +    struct hmap_node key_node;
> +
> +    const struct ovn_datapath *od;
> +    bool has_stateful_acl;
> +    bool has_lb_vip;
> +    bool has_acls;
> +    uint64_t max_acl_tier;
> +};
> +
> +struct ls_lbacls_table {
> +    struct hmap entries;
> +};
> +
> +#define LS_LBACLS_TABLE_FOR_EACH(LS_LBACLS_REC, TABLE) \
> +    HMAP_FOR_EACH (LS_LBACLS_REC, key_node, &(TABLE)->entries)
> +
> +#define LS_LBACLS_TABLE_FOR_EACH_IN_P(LS_LBACLS_REC, JOBID, TABLE) \
> +    HMAP_FOR_EACH_IN_PARALLEL (LS_LBACLS_REC, key_node, JOBID, \
> +                               &(TABLE)->entries)
> +
> +struct ls_lbacls_tracked_data {
> +    /* Created or updated logical switch with LB and ACL data. */
> +    struct hmapx crupdated; /* Stores 'struct ls_lbacls_record'. */
> +
> +    /* Deleted logical switch with LB and ACL data. */
> +    struct hmapx deleted; /* Stores 'struct ls_lbacls_record'. */
> +};
> +
> +struct ed_type_ls_lbacls {
> +    struct ls_lbacls_table ls_lbacls;
> +
> +    bool tracked;
> +    struct ls_lbacls_tracked_data tracked_data;
> +};
> +
> +struct ls_lbacls_input {
> +    const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
> +    const struct ls_port_group_table *ls_port_groups;
> +    const struct ovn_datapaths *ls_datapaths;
> +};
> +
> +void *en_ls_lbacls_init(struct engine_node *, struct engine_arg *);
> +void en_ls_lbacls_cleanup(void *data);
> +void en_ls_lbacls_clear_tracked_data(void *data);
> +void en_ls_lbacls_run(struct engine_node *, void *data);
> +
> +bool ls_lbacls_northd_handler(struct engine_node *, void *data);
> +bool ls_lbacls_port_group_handler(struct engine_node *, void *data);
> +bool ls_lbacls_logical_switch_handler(struct engine_node *, void *data);
> +
> +const struct ls_lbacls_record *ls_lbacls_table_find(
> +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> +
> +#endif /* EN_LS_LB_ACL_H */
> diff --git a/northd/en-port-group.h b/northd/en-port-group.h
> index 3b28a23694..54014062ce 100644
> --- a/northd/en-port-group.h
> +++ b/northd/en-port-group.h
> @@ -48,6 +48,9 @@ struct ls_port_group_record {
>      struct sset ports;          /* Subset of 'nb_pg' ports in this record. */
>  };
>  
> +#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
> +    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
> +
>  void ls_port_group_table_init(struct ls_port_group_table *);
>  void ls_port_group_table_clear(struct ls_port_group_table *);
>  void ls_port_group_table_destroy(struct ls_port_group_table *);
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 84627070a8..ab4af92aeb 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -33,6 +33,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-lb-nat-data.h"
>  #include "en-lr-nat.h"
> +#include "en-ls-lb-acls.h"
>  #include "en-northd.h"
>  #include "en-lflow.h"
>  #include "en-northd-output.h"
> @@ -150,6 +151,7 @@ static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
>  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_lb_nat_data, "lr_lb_nat_data");
> +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_lbacls, "ls_lbacls");
>  
>  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>                            struct ovsdb_idl_loop *sb)
> @@ -205,6 +207,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lr_lb_nat_data, &en_lb_data,
>                       lr_lb_nat_data_lb_data_handler);
>  
> +    engine_add_input(&en_ls_lbacls, &en_northd, ls_lbacls_northd_handler);
> +    engine_add_input(&en_ls_lbacls, &en_port_group,
> +                     ls_lbacls_port_group_handler);
> +    engine_add_input(&en_ls_lbacls, &en_nb_logical_switch,
> +                     ls_lbacls_logical_switch_handler);
> +
>      engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
>      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> @@ -229,6 +237,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
>      engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
>      engine_add_input(&en_lflow, &en_lr_lb_nat_data, NULL);
> +    engine_add_input(&en_lflow, &en_ls_lbacls, NULL);
>  
>      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
>                       sync_to_sb_addr_set_nb_address_set_handler);
> diff --git a/northd/northd.c b/northd/northd.c
> index c8a224d3cd..924f5cd7e0 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -45,6 +45,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-lb-nat-data.h"
> +#include "en-ls-lb-acls.h"
>  #include "lib/ovn-parallel-hmap.h"
>  #include "ovn/actions.h"
>  #include "ovn/features.h"
> @@ -575,7 +576,7 @@ lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
>  }
>  
>  static bool
> -ls_has_lb_vip(struct ovn_datapath *od)
> +ls_has_lb_vip(const struct ovn_datapath *od)
>  {
>      for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
>          if (lb_has_vip(od->nbs->load_balancer[i])) {
> @@ -592,7 +593,7 @@ ls_has_lb_vip(struct ovn_datapath *od)
>  }
>  
>  static bool
> -lr_has_lb_vip(struct ovn_datapath *od)
> +lr_has_lb_vip(const struct ovn_datapath *od)
>  {
>      for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
>          if (lb_has_vip(od->nbr->load_balancer[i])) {
> @@ -608,13 +609,13 @@ lr_has_lb_vip(struct ovn_datapath *od)
>      return false;
>  }
>  
> -static void
> -init_lb_for_datapath(struct ovn_datapath *od)
> +bool
> +od_has_lb_vip(const struct ovn_datapath *od)
>  {
>      if (od->nbs) {
> -        od->has_lb_vip = ls_has_lb_vip(od);
> +        return ls_has_lb_vip(od);
>      } else {
> -        od->has_lb_vip = lr_has_lb_vip(od);
> +        return lr_has_lb_vip(od);
>      }
>  }
>  
> @@ -1058,7 +1059,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
>  
>          init_ipam_info_for_datapath(od);
>          init_mcast_info_for_datapath(od);
> -        init_lb_for_datapath(od);
>      }
>  
>      const struct nbrec_logical_router *nbr;
> @@ -1089,7 +1089,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
>              ovs_list_push_back(nb_only, &od->list);
>          }
>          init_mcast_info_for_datapath(od);
> -        init_lb_for_datapath(od);
>          if (smap_get(&od->nbr->options, "chassis")) {
>              od->is_gw_router = true;
>          }
> @@ -2570,7 +2569,8 @@ get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
>      size_t n_nats = 0;
>      struct eth_addr mac;
>      if (!op || !op->nbrp || !op->od || !op->od->nbr
> -        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> +        || (!op->od->nbr->n_nat && (!lr_lbnat_rec
> +                                    || !lr_lbnat_rec->has_lb_vip))
>          || !eth_addr_from_string(op->nbrp->mac, &mac)) {
>          *n = n_nats;
>          return NULL;
> @@ -3817,7 +3817,7 @@ build_lrouter_lbs_check(const struct ovn_datapaths *lr_datapaths)
>      HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
>          ovs_assert(od->nbr);
>  
> -        if (od->has_lb_vip && od->n_l3dgw_ports > 1
> +        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
>                  && !smap_get(&od->nbr->options, "chassis")) {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
>              VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
> @@ -5441,7 +5441,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
>                             lb_dps->nb_ls_map) {
>              od = ls_datapaths->array[index];
> -            init_lb_for_datapath(od);
>  
>              /* Add the ls datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> @@ -5524,9 +5523,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>              }
>          }
>  
> -        /* Re-evaluate 'od->has_lb_vip' */
> -        init_lb_for_datapath(od);
> -
>          /* Add the ls datapath to the northd tracked data. */
>          hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
>      }
> @@ -5564,9 +5560,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>              }
>          }
>  
> -        /* Re-evaluate 'od->has_lb_vip' */
> -        init_lb_for_datapath(od);
> -
>          /* Add the lr datapath to the northd tracked data. */
>          hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
>      }
> @@ -5581,8 +5574,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
>                             lb_dps->nb_ls_map) {
>              od = ls_datapaths->array[index];
> -            /* Re-evaluate 'od->has_lb_vip' */
> -            init_lb_for_datapath(od);
>  
>              /* Add the ls datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> @@ -5591,8 +5582,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>          BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
>                             lb_dps->nb_lr_map) {
>              od = lr_datapaths->array[index];
> -            /* Re-evaluate 'od->has_lb_vip' */
> -            init_lb_for_datapath(od);
>  
>              /* Add the lr datapath to the northd tracked data. */
>              hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> @@ -5618,9 +5607,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>                  od = lbgrp_dps->lr[i];
>                  ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
>  
> -                /* Re-evaluate 'od->has_lb_vip' */
> -                init_lb_for_datapath(od);
> -
>                  /* Add the lr datapath to the northd tracked data. */
>                  hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
>              }
> @@ -5629,9 +5615,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
>                 od = lbgrp_dps->ls[i];
>                  ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
>  
> -                /* Re-evaluate 'od->has_lb_vip' */
> -                init_lb_for_datapath(od);
> -
>                  /* Add the ls datapath to the northd tracked data. */
>                  hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
>              }
> @@ -6573,63 +6556,6 @@ build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
>      return true;
>  }
>  
> -static bool
> -od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
> -                 size_t n_acls)
> -{
> -    /* A true return indicates that there are no possible ACL flags
> -     * left to set on od. A false return indicates that further ACLs
> -     * should be explored in case more flags need to be set on od
> -     */
> -    if (!n_acls) {
> -        return false;
> -    }
> -
> -    od->has_acls = true;
> -    for (size_t i = 0; i < n_acls; i++) {
> -        const struct nbrec_acl *acl = acls[i];
> -        if (acl->tier > od->max_acl_tier) {
> -            od->max_acl_tier = acl->tier;
> -        }
> -        if (!od->has_stateful_acl && !strcmp(acl->action, "allow-related")) {
> -            od->has_stateful_acl = true;
> -        }
> -        if (od->has_stateful_acl &&
> -            od->max_acl_tier == nbrec_acl_col_tier.type.value.integer.max) {
> -            return true;
> -        }
> -    }
> -
> -    return false;
> -}
> -
> -static void
> -ls_get_acl_flags(struct ovn_datapath *od,
> -                 const struct ls_port_group_table *ls_port_groups)
> -{
> -    od->has_acls = false;
> -    od->has_stateful_acl = false;
> -    od->max_acl_tier = 0;
> -
> -    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
> -        return;
> -    }
> -
> -    const struct ls_port_group *ls_pg =
> -        ls_port_group_table_find(ls_port_groups, od->nbs);
> -    if (!ls_pg) {
> -        return;
> -    }
> -
> -    const struct ls_port_group_record *ls_pg_rec;
> -    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> -        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
> -                             ls_pg_rec->nb_pg->n_acls)) {
> -            return;
> -        }
> -    }
> -}
> -
>  /* Adds the logical flows in the (in/out) check port sec stage only if
>   *   - the lport is disabled or
>   *   - lport is of type vtep - to skip the ingress pipeline.
> @@ -6774,9 +6700,10 @@ build_lswitch_output_port_sec_od(struct ovn_datapath *od,
>  }
>  
>  static void
> -skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> -                         enum ovn_stage in_stage, enum ovn_stage out_stage,
> -                         uint16_t priority, struct hmap *lflows)
> +skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port *op,
> +                         bool has_stateful_acl, enum ovn_stage in_stage,
> +                         enum ovn_stage out_stage, uint16_t priority,
> +                         struct hmap *lflows)
>  {
>      /* Can't use ct() for router ports. Consider the following configuration:
>       * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a
> @@ -6789,7 +6716,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
>       * conntrack state across all chassis. */
>  
>      const char *ingress_action = "next;";
> -    const char *egress_action = od->has_stateful_acl
> +    const char *egress_action = has_stateful_acl
>                                  ? "next;"
>                                  : "ct_clear; next;";
>  
> @@ -6808,7 +6735,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
>  }
>  
>  static void
> -build_stateless_filter(struct ovn_datapath *od,
> +build_stateless_filter(const struct ovn_datapath *od,
>                         const struct nbrec_acl *acl,
>                         struct hmap *lflows)
>  {
> @@ -6829,7 +6756,7 @@ build_stateless_filter(struct ovn_datapath *od,
>  }
>  
>  static void
> -build_stateless_filters(struct ovn_datapath *od,
> +build_stateless_filters(const struct ovn_datapath *od,
>                          const struct ls_port_group_table *ls_port_groups,
>                          struct hmap *lflows)
>  {
> @@ -6859,9 +6786,7 @@ build_stateless_filters(struct ovn_datapath *od,
>  }
>  
>  static void
> -build_pre_acls(struct ovn_datapath *od,
> -               const struct ls_port_group_table *ls_port_groups,
> -               struct hmap *lflows)
> +build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
>  {
>      /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
>       * allowed by default. */
> @@ -6873,18 +6798,26 @@ build_pre_acls(struct ovn_datapath *od,
>  
>      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
>                    "eth.src == $svc_monitor_mac", "next;");
> +}
> +
> +static void
> +build_ls_lbacls_rec_pre_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> +                             const struct ls_port_group_table *ls_port_groups,
> +                             struct hmap *lflows)
> +{
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
>  
>      /* If there are any stateful ACL rules in this datapath, we may
>       * send IP packets for some (allow) filters through the conntrack action,
>       * which handles defragmentation, in order to match L4 headers. */
> -    if (od->has_stateful_acl) {
> +    if (ls_lbacls_rec->has_stateful_acl) {
>          for (size_t i = 0; i < od->n_router_ports; i++) {
> -            skip_port_from_conntrack(od, od->router_ports[i],
> +            skip_port_from_conntrack(od, od->router_ports[i], true,
>                                       S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL,
>                                       110, lflows);
>          }
>          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> -            skip_port_from_conntrack(od, od->localnet_ports[i],
> +            skip_port_from_conntrack(od, od->localnet_ports[i], true,
>                                       S_SWITCH_IN_PRE_ACL,
>                                       S_SWITCH_OUT_PRE_ACL,
>                                       110, lflows);
> @@ -6922,7 +6855,7 @@ build_pre_acls(struct ovn_datapath *od,
>                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
>                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> -    } else if (od->has_lb_vip) {
> +    } else if (ls_lbacls_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);
> @@ -7050,30 +6983,40 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
>  
> +    /* Do not send statless flows via conntrack */
> +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> +                  REGBIT_ACL_STATELESS" == 1", "next;");
> +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> +                  REGBIT_ACL_STATELESS" == 1", "next;");
> +}
> +
> +static void
> +build_ls_lbacls_rec_pre_lb(const struct ls_lbacls_record *ls_lbacls_rec,
> +                           struct hmap *lflows)
> +{
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      for (size_t i = 0; i < od->n_router_ports; i++) {
>          skip_port_from_conntrack(od, od->router_ports[i],
> +                                 ls_lbacls_rec->has_stateful_acl,
>                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
>                                   110, lflows);
>      }
> +
>      /* Localnet ports have no need for going through conntrack, unless
>       * the logical switch has a load balancer. Then, conntrack is necessary
>       * so that traffic arriving via the localnet port can be load
>       * balanced.
>       */
> -    if (!od->has_lb_vip) {
> +    if (!ls_lbacls_rec->has_lb_vip) {
>          for (size_t i = 0; i < od->n_localnet_ports; i++) {
>              skip_port_from_conntrack(od, od->localnet_ports[i],
> +                                     ls_lbacls_rec->has_stateful_acl,
>                                       S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
>                                       110, lflows);
>          }
>      }
>  
> -    /* Do not sent statless flows via conntrack */
> -    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> -                  REGBIT_ACL_STATELESS" == 1", "next;");
> -    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> -                  REGBIT_ACL_STATELESS" == 1", "next;");
> -
>      /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
>       * packet to conntrack for defragmentation and possibly for unNATting.
>       *
> @@ -7104,7 +7047,7 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
>       * ingress pipeline if a load balancer is configured. We can now
>       * add a lflow to drop ct.inv packets.
>       */
> -    if (od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_lb_vip) {
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
>                        100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
>          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
> @@ -7145,10 +7088,12 @@ build_pre_stateful(struct ovn_datapath *od,
>  }
>  
>  static void
> -build_acl_hints(struct ovn_datapath *od,
> +build_acl_hints(const struct ls_lbacls_record *ls_lbacls_rec,
>                  const struct chassis_features *features,
>                  struct hmap *lflows)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      /* This stage builds hints for the IN/OUT_ACL stage. Based on various
>       * combinations of ct flags packets may hit only a subset of the logical
>       * flows in the IN/OUT_ACL stage.
> @@ -7172,13 +7117,13 @@ build_acl_hints(struct ovn_datapath *od,
>          const char *match;
>  
>          /* In any case, advance to the next stage. */
> -        if (!od->has_acls && !od->has_lb_vip) {
> +        if (!ls_lbacls_rec->has_acls && !ls_lbacls_rec->has_lb_vip) {
>              ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
>          } else {
>              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
>          }
>  
> -        if (!od->has_stateful_acl && !od->has_lb_vip) {
> +        if (!ls_lbacls_rec->has_stateful_acl && !ls_lbacls_rec->has_lb_vip) {
>              continue;
>          }
>  
> @@ -7314,10 +7259,10 @@ build_acl_log(struct ds *actions, const struct nbrec_acl *acl,
>  }
>  
>  static void
> -consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> +consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
>               const struct nbrec_acl *acl, bool has_stateful,
>               bool ct_masked_mark, const struct shash *meter_groups,
> -             struct ds *match, struct ds *actions)
> +             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
>  {
>      const char *ct_blocked_match = ct_masked_mark
>                                     ? "ct_mark.blocked"
> @@ -7354,7 +7299,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
>      /* All ACLS will start by matching on their respective tier. */
>      size_t match_tier_len = 0;
>      ds_clear(match);
> -    if (od->max_acl_tier) {
> +    if (max_acl_tier) {
>          ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ", acl->tier);
>          match_tier_len = match->length;
>      }
> @@ -7543,12 +7488,15 @@ ovn_update_ipv6_options(struct hmap *lr_ports)
>  #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
>  
>  static void
> -build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> +build_acl_action_lflows(const struct ls_lbacls_record *ls_lbacls_rec,
> +                        struct hmap *lflows,
>                          const char *default_acl_action,
>                          const struct shash *meter_groups,
>                          struct ds *match,
>                          struct ds *actions)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      enum ovn_stage stages [] = {
>          S_SWITCH_IN_ACL_ACTION,
>          S_SWITCH_IN_ACL_AFTER_LB_ACTION,
> @@ -7559,7 +7507,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
>      ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
>                          REGBIT_ACL_VERDICT_DROP " = 0; "
>                          REGBIT_ACL_VERDICT_REJECT " = 0; ");
> -    if (od->max_acl_tier) {
> +    if (ls_lbacls_rec->max_acl_tier) {
>          ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
>      }
>  
> @@ -7567,7 +7515,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
>  
>      for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
>          enum ovn_stage stage = stages[i];
> -        if (!od->has_acls) {
> +        if (!ls_lbacls_rec->has_acls) {
>              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
>              continue;
>          }
> @@ -7602,7 +7550,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
>          ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
>  
>          struct ds tier_actions = DS_EMPTY_INITIALIZER;
> -        for (size_t j = 0; j < od->max_acl_tier; j++) {
> +        for (size_t j = 0; j < ls_lbacls_rec->max_acl_tier; j++) {
>              ds_clear(match);
>              ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
>              ds_clear(&tier_actions);
> @@ -7618,7 +7566,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
>  }
>  
>  static void
> -build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> +build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap *lflows,
>                              const struct nbrec_acl *acl, bool has_stateful,
>                              bool ct_masked_mark,
>                              const struct shash *meter_groups,
> @@ -7691,15 +7639,19 @@ build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
>  }
>  
>  static void
> -build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> +build_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> +           const struct chassis_features *features,
>             struct hmap *lflows,
>             const struct ls_port_group_table *ls_port_groups,
>             const struct shash *meter_groups)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      const char *default_acl_action = default_acl_drop
>                                       ? debug_implicit_drop_action()
>                                       : "next;";
> -    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
> +    bool has_stateful = (ls_lbacls_rec->has_stateful_acl
> +                         || ls_lbacls_rec->has_lb_vip);
>      const char *ct_blocked_match = features->ct_no_masked_label
>                                     ? "ct_mark.blocked"
>                                     : "ct_label.blocked";
> @@ -7713,8 +7665,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
>       *
>       * A related rule at priority 1 is added below if there
>       * are any stateful ACLs in this datapath. */
> -    if (!od->has_acls) {
> -        if (!od->has_lb_vip) {
> +    if (!ls_lbacls_rec->has_acls) {
> +        if (!ls_lbacls_rec->has_lb_vip) {
>              ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX, "1",
>                            "next;");
>              ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX, "1",
> @@ -7877,7 +7829,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
>                                      meter_groups, &match, &actions);
>          consider_acl(lflows, od, acl, has_stateful,
>                       features->ct_no_masked_label,
> -                     meter_groups, &match, &actions);
> +                     meter_groups, ls_lbacls_rec->max_acl_tier,
> +                     &match, &actions);
>      }
>  
>      const struct ls_port_group *ls_pg =
> @@ -7893,7 +7846,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
>                                              meter_groups, &match, &actions);
>                  consider_acl(lflows, od, acl, has_stateful,
>                               features->ct_no_masked_label,
> -                             meter_groups, &match, &actions);
> +                             meter_groups, ls_lbacls_rec->max_acl_tier,
> +                             &match, &actions);
>              }
>          }
>      }
> @@ -7911,7 +7865,7 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
>              dns_actions);
>      }
>  
> -    if (od->has_acls || od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_acls || ls_lbacls_rec->has_lb_vip) {
>          /* Add a 34000 priority flow to advance the service monitor reply
>          * packets to skip applying ingress ACLs. */
>          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
> @@ -7925,8 +7879,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
>                      REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
>      }
>  
> -    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
> -                            &match, &actions);
> +    build_acl_action_lflows(ls_lbacls_rec, lflows, default_acl_action,
> +                            meter_groups, &match, &actions);
>  
>      ds_destroy(&match);
>      ds_destroy(&actions);
> @@ -8571,8 +8525,11 @@ build_stateful(struct ovn_datapath *od,
>  }
>  
>  static void
> -build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> +build_lb_hairpin(const struct ls_lbacls_record *ls_lbacls_rec,
> +                 struct hmap *lflows)
>  {
> +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> +
>      /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
>       * Packets that don't need hairpinning should continue processing.
>       */
> @@ -8580,7 +8537,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
>  
> -    if (od->has_lb_vip) {
> +    if (ls_lbacls_rec->has_lb_vip) {
>          /* Check if the packet needs to be hairpinned.
>           * Set REGBIT_HAIRPIN in the original direction and
>           * REGBIT_HAIRPIN_REPLY in the reply direction.
> @@ -9413,22 +9370,16 @@ build_lswitch_lflows_l2_unknown(struct ovn_datapath *od,
>  static void
>  build_lswitch_lflows_pre_acl_and_acl(
>      struct ovn_datapath *od,
> -    const struct ls_port_group_table *ls_port_groups,
>      const struct chassis_features *features,
>      struct hmap *lflows,
>      const struct shash *meter_groups)
>  {
>      ovs_assert(od->nbs);
> -    ls_get_acl_flags(od, ls_port_groups);
> -
> -    build_pre_acls(od, ls_port_groups, lflows);
> +    build_pre_acls(od, lflows);
>      build_pre_lb(od, meter_groups, lflows);
>      build_pre_stateful(od, features, lflows);
> -    build_acl_hints(od, features, lflows);
> -    build_acls(od, features, lflows, ls_port_groups, meter_groups);
>      build_qos(od, lflows);
>      build_stateful(od, features, lflows);
> -    build_lb_hairpin(od, lflows);
>      build_vtep_hairpin(od, lflows);
>  }
>  
> @@ -15487,7 +15438,7 @@ build_lrouter_nat_defrag_and_lb(
>       * a dynamically negotiated FTP data channel), but will allow
>       * related traffic such as an ICMP Port Unreachable through
>       * that's generated from a non-listening UDP port.  */
> -    if (od->has_lb_vip && features->ct_lb_related) {
> +    if (lr_lbnat_rec->has_lb_vip && features->ct_lb_related) {
>          ds_clear(match);
>  
>          ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
> @@ -15512,7 +15463,7 @@ build_lrouter_nat_defrag_and_lb(
>       * Pass the traffic that is already established to the next table with
>       * proper flags set.
>       */
> -    if (od->has_lb_vip) {
> +    if (lr_lbnat_rec->has_lb_vip) {
>          ds_clear(match);
>  
>          ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
> @@ -15542,7 +15493,7 @@ build_lrouter_nat_defrag_and_lb(
>       * not committed, it would produce ongoing datapath flows with the ct.new
>       * flag set. Some NICs are unable to offload these flows.
>       */
> -    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
> +    if (od->is_gw_router && (od->nbr->n_nat || lr_lbnat_rec->has_lb_vip)) {
>          /* Do not send ND or ICMP packets to connection tracking. */
>          ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
>                        "nd || nd_rs || nd_ra", "next;");
> @@ -15967,6 +15918,22 @@ build_lr_lbnat_data_flows(const struct lr_lb_nat_data_record *lr_lbnat_rec,
>                                        meter_groups);
>  }
>  
> +static void
> +build_ls_lbacls_flows(const struct ls_lbacls_record *ls_lbacls_rec,
> +                      const struct ls_port_group_table *ls_pgs,
> +                      const struct chassis_features *features,
> +                      const struct shash *meter_groups,
> +                      struct hmap *lflows)
> +{
> +    ovs_assert(ls_lbacls_rec->od);
> +
> +    build_ls_lbacls_rec_pre_acls(ls_lbacls_rec, ls_pgs, lflows);
> +    build_ls_lbacls_rec_pre_lb(ls_lbacls_rec, lflows);
> +    build_acl_hints(ls_lbacls_rec, features, lflows);
> +    build_acls(ls_lbacls_rec, features, lflows, ls_pgs, meter_groups);
> +    build_lb_hairpin(ls_lbacls_rec, lflows);
> +}
> +
>  struct lswitch_flow_build_info {
>      const struct ovn_datapaths *ls_datapaths;
>      const struct ovn_datapaths *lr_datapaths;
> @@ -15974,6 +15941,7 @@ struct lswitch_flow_build_info {
>      const struct hmap *lr_ports;
>      const struct ls_port_group_table *ls_port_groups;
>      const struct lr_lb_nat_data_table *lr_lbnats;
> +    const struct ls_lbacls_table *ls_lbacls;
>      struct hmap *lflows;
>      struct hmap *igmp_groups;
>      const struct shash *meter_groups;
> @@ -15998,9 +15966,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct ovn_datapath *od,
>                                          struct lswitch_flow_build_info *lsi)
>  {
>      ovs_assert(od->nbs);
> -    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
> -                                         lsi->features,
> -                                         lsi->lflows,
> +    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
>                                           lsi->meter_groups);
>  
>      build_fwd_group_lflows(od, lsi->lflows);
> @@ -16115,6 +16081,7 @@ build_lflows_thread(void *arg)
>  {
>      struct worker_control *control = (struct worker_control *) arg;
>      const struct lr_lb_nat_data_record *lr_lbnat_rec;
> +    const struct ls_lbacls_record *ls_lbacls_rec;
>      struct lswitch_flow_build_info *lsi;
>      struct ovn_igmp_group *igmp_group;
>      struct ovn_lb_datapaths *lb_dps;
> @@ -16243,6 +16210,19 @@ build_lflows_thread(void *arg)
>                                                lsi->features);
>                  }
>              }
> +
> +            for (bnum = control->id;
> +                    bnum <= lsi->ls_lbacls->entries.mask;
> +                    bnum += control->pool->size)
> +            {
> +                LS_LBACLS_TABLE_FOR_EACH_IN_P (ls_lbacls_rec, bnum,
> +                                               lsi->ls_lbacls) {
> +                    build_ls_lbacls_flows(ls_lbacls_rec, lsi->ls_port_groups,
> +                                          lsi->features, lsi->meter_groups,
> +                                          lsi->lflows);
> +                }
> +            }
> +
>              for (bnum = control->id;
>                      bnum <= lsi->igmp_groups->mask;
>                      bnum += control->pool->size)
> @@ -16303,6 +16283,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
>                                  const struct hmap *lr_ports,
>                                  const struct ls_port_group_table *ls_pgs,
>                                  const struct lr_lb_nat_data_table *lr_lbnats,
> +                                const struct ls_lbacls_table *ls_lbacls,
>                                  struct hmap *lflows,
>                                  struct hmap *igmp_groups,
>                                  const struct shash *meter_groups,
> @@ -16333,6 +16314,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
>              lsiv[index].lr_ports = lr_ports;
>              lsiv[index].ls_port_groups = ls_pgs;
>              lsiv[index].lr_lbnats = lr_lbnats;
> +            lsiv[index].ls_lbacls = ls_lbacls;
>              lsiv[index].igmp_groups = igmp_groups;
>              lsiv[index].meter_groups = meter_groups;
>              lsiv[index].lb_dps_map = lb_dps_map;
> @@ -16358,6 +16340,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
>          free(lsiv);
>      } else {
>          const struct lr_lb_nat_data_record *lr_lbnat_rec;
> +        const struct ls_lbacls_record *ls_lbacls_rec;
>          struct ovn_igmp_group *igmp_group;
>          struct ovn_lb_datapaths *lb_dps;
>          struct ovn_datapath *od;
> @@ -16370,6 +16353,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
>              .lr_ports = lr_ports,
>              .ls_port_groups = ls_pgs,
>              .lr_lbnats = lr_lbnats,
> +            .ls_lbacls = ls_lbacls,
>              .lflows = lflows,
>              .igmp_groups = igmp_groups,
>              .meter_groups = meter_groups,
> @@ -16439,6 +16423,12 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
>                                        lsi.features);
>          }
>  
> +        LS_LBACLS_TABLE_FOR_EACH (ls_lbacls_rec, ls_lbacls) {
> +            build_ls_lbacls_flows(ls_lbacls_rec, lsi.ls_port_groups,
> +                                  lsi.features, lsi.meter_groups,
> +                                  lsi.lflows);
> +        }
> +
>          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
>          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
>              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> @@ -16535,6 +16525,7 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
>                                      input_data->lr_ports,
>                                      input_data->ls_port_groups,
>                                      input_data->lr_lbnats,
> +                                    input_data->ls_lbacls,
>                                      lflows,
>                                      &igmp_groups,
>                                      input_data->meter_groups,
> diff --git a/northd/northd.h b/northd/northd.h
> index 08a81b2c10..23b4754db4 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -89,6 +89,8 @@ ods_size(const struct ovn_datapaths *datapaths)
>      return hmap_count(&datapaths->datapaths);
>  }
>  
> +bool od_has_lb_vip(const struct ovn_datapath *od);
> +
>  struct tracked_ovn_ports {
>      /* tracked created ports.
>       * hmapx node data is 'struct ovn_port *' */
> @@ -179,6 +181,7 @@ struct lflow_input {
>      const struct hmap *lr_ports;
>      const struct ls_port_group_table *ls_port_groups;
>      const struct lr_lb_nat_data_table *lr_lbnats;
> +    const struct ls_lbacls_table *ls_lbacls;
>      const struct shash *meter_groups;
>      const struct hmap *lb_datapaths_map;
>      const struct hmap *bfd_connections;
> @@ -288,11 +291,7 @@ struct ovn_datapath {
>      struct hmap port_tnlids;
>      uint32_t port_key_hint;
>  
> -    bool has_stateful_acl;
> -    bool has_lb_vip;
>      bool has_unknown;
> -    bool has_acls;
> -    uint64_t max_acl_tier;
>      bool has_vtep_lports;
>      bool has_arp_proxy_port;
>
Numan Siddique Dec. 6, 2023, 3:46 a.m. UTC | #4
On Fri, Nov 24, 2023 at 5:54 AM Dumitru Ceara <dceara@redhat.com> wrote:
>
> On 10/26/23 20:15, numans@ovn.org wrote:
> > From: Numan Siddique <numans@ovn.org>
> >
> > This new engine now maintains the load balancer and ACL data of a
> > logical switch which was earlier part of northd engine node data.
> > The main inputs to this engine are:
> >     - northd node
> >     - NB logical switch node
> >     - Port group node
> >
> > A record for each logical switch is maintained in the 'ls_lbacls'
> > hmap table and this record stores the below data which was earlier
> > part of 'struct ovn_datapath'.
> >
> >     - bool has_stateful_acl;
> >     - bool has_lb_vip;
> >     - bool has_acls;
> >     - uint64_t max_acl_tier;
> >
> > This engine node becomes an input to 'lflow' node.
> >
> > Signed-off-by: Numan Siddique <numans@ovn.org>
> > ---
> >  lib/stopwatch-names.h      |   1 +
> >  northd/automake.mk         |   2 +
> >  northd/en-lflow.c          |   4 +
> >  northd/en-lr-lb-nat-data.c |   8 +-
> >  northd/en-lr-lb-nat-data.h |   2 +
> >  northd/en-ls-lb-acls.c     | 527 +++++++++++++++++++++++++++++++++++++
> >  northd/en-ls-lb-acls.h     |  88 +++++++
> >  northd/en-port-group.h     |   3 +
> >  northd/inc-proc-northd.c   |   9 +
> >  northd/northd.c            | 271 +++++++++----------
> >  northd/northd.h            |   7 +-
> >  11 files changed, 776 insertions(+), 146 deletions(-)
> >  create mode 100644 northd/en-ls-lb-acls.c
> >  create mode 100644 northd/en-ls-lb-acls.h
> >
> > diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> > index 7d85acdaea..8b0018a593 100644
> > --- a/lib/stopwatch-names.h
> > +++ b/lib/stopwatch-names.h
> > @@ -34,5 +34,6 @@
> >  #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
> >  #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
> >  #define LR_LB_NAT_DATA_RUN_STOPWATCH_NAME "lr_lb_nat_data"
> > +#define LS_LBACLS_RUN_STOPWATCH_NAME "lr_lb_acls"
> >
> >  #endif
> > diff --git a/northd/automake.mk b/northd/automake.mk
> > index 4116c487df..4593654726 100644
> > --- a/northd/automake.mk
> > +++ b/northd/automake.mk
> > @@ -28,6 +28,8 @@ northd_ovn_northd_SOURCES = \
> >       northd/en-lr-nat.h \
> >       northd/en-lr-lb-nat-data.c \
> >       northd/en-lr-lb-nat-data.h \
> > +     northd/en-ls-lb-acls.c \
> > +     northd/en-ls-lb-acls.h \
> >       northd/inc-proc-northd.c \
> >       northd/inc-proc-northd.h \
> >       northd/ipam.c \
> > diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> > index 229f4be1d0..648a477916 100644
> > --- a/northd/en-lflow.c
> > +++ b/northd/en-lflow.c
> > @@ -21,6 +21,7 @@
> >  #include "en-lflow.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-lb-nat-data.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "en-northd.h"
> >  #include "en-meters.h"
> >
> > @@ -44,6 +45,8 @@ lflow_get_input_data(struct engine_node *node,
> >          engine_get_input_data("sync_meters", node);
> >      struct ed_type_lr_lb_nat_data *lr_lb_nat_data =
> >          engine_get_input_data("lr_lb_nat_data", node);
> > +    struct ed_type_ls_lbacls *ls_lbacls_data =
> > +        engine_get_input_data("ls_lbacls", node);
> >
> >      lflow_input->nbrec_bfd_table =
> >          EN_OVSDB_GET(engine_get_input("NB_bfd", node));
> > @@ -67,6 +70,7 @@ lflow_get_input_data(struct engine_node *node,
> >      lflow_input->lr_ports = &northd_data->lr_ports;
> >      lflow_input->ls_port_groups = &pg_data->ls_port_groups;
> >      lflow_input->lr_lbnats = &lr_lb_nat_data->lr_lbnats;
> > +    lflow_input->ls_lbacls = &ls_lbacls_data->ls_lbacls;
> >      lflow_input->meter_groups = &sync_meters_data->meter_groups;
> >      lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
> >      lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
> > diff --git a/northd/en-lr-lb-nat-data.c b/northd/en-lr-lb-nat-data.c
> > index 19b638ce0b..d816d2321d 100644
> > --- a/northd/en-lr-lb-nat-data.c
> > +++ b/northd/en-lr-lb-nat-data.c
> > @@ -299,9 +299,11 @@ lr_lb_nat_data_lb_data_handler(struct engine_node *node, void *data_)
> >      if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> >          struct hmapx_node *hmapx_node;
> >          /* For all the modified lr_lb_nat_data records (re)build the
> > -         * vip nats. */
> > +         * vip nats and re-evaluate 'has_lb_vip'. */
> >          HMAPX_FOR_EACH (hmapx_node, &data->tracked_data.crupdated) {
> > -            lr_lb_nat_data_build_vip_nats(hmapx_node->data);
> > +            lr_lbnat_rec = hmapx_node->data;
> > +            lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> > +            lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
> >          }
> >
> >          data->tracked = true;
> > @@ -523,6 +525,8 @@ lr_lb_nat_data_record_init(struct lr_lb_nat_data_record *lr_lbnat_rec,
> >      if (!nbr->n_nat) {
> >          lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
> >      }
> > +
> > +    lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
> >  }
> >
> >  static struct lr_lb_nat_data_input
> > diff --git a/northd/en-lr-lb-nat-data.h b/northd/en-lr-lb-nat-data.h
> > index ffe41cad73..ac21d28a57 100644
> > --- a/northd/en-lr-lb-nat-data.h
> > +++ b/northd/en-lr-lb-nat-data.h
> > @@ -39,6 +39,8 @@ struct lr_lb_nat_data_record {
> >      const struct ovn_datapath *od;
> >      const struct lr_nat_record *lrnat_rec;
> >
> > +    bool has_lb_vip;
> > +
> >      /* Load Balancer vIPs relevant for this datapath. */
> >      struct ovn_lb_ip_set *lb_ips;
> >
> > diff --git a/northd/en-ls-lb-acls.c b/northd/en-ls-lb-acls.c
> > new file mode 100644
> > index 0000000000..1ba7ecb3e2
> > --- /dev/null
> > +++ b/northd/en-ls-lb-acls.c
> > @@ -0,0 +1,527 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#include <config.h>
> > +
> > +#include <getopt.h>
> > +#include <stdlib.h>
> > +#include <stdio.h>
> > +
> > +/* OVS includes */
> > +#include "include/openvswitch/hmap.h"
> > +#include "lib/bitmap.h"
> > +#include "lib/socket-util.h"
> > +#include "lib/uuidset.h"
> > +#include "openvswitch/util.h"
> > +#include "openvswitch/vlog.h"
> > +#include "stopwatch.h"
> > +
> > +/* OVN includes */
> > +#include "en-lb-data.h"
> > +#include "en-ls-lb-acls.h"
> > +#include "en-port-group.h"
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +#include "northd.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(en_ls_lbacls);
> > +
> > +/* Static function declarations. */
> > +static void ls_lbacls_table_init(struct ls_lbacls_table *);
> > +static void ls_lbacls_table_clear(struct ls_lbacls_table *);
> > +static void ls_lbacls_table_destroy(struct ls_lbacls_table *);
> > +static struct ls_lbacls_record *ls_lbacls_table_find_(
> > +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> > +static void ls_lbacls_table_build(struct ls_lbacls_table *,
> > +                                  const struct ovn_datapaths *ls_datapaths,
> > +                                  const struct ls_port_group_table *);
> > +
> > +static struct ls_lbacls_input ls_lbacls_get_input_data(
> > +    struct engine_node *);
> > +
> > +static struct ls_lbacls_record *ls_lbacls_record_create(
> > +    struct ls_lbacls_table *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group_table *);
> > +static void ls_lbacls_record_destroy(struct ls_lbacls_record *);
> > +static void ls_lbacls_record_init(
> > +    struct ls_lbacls_record *,
> > +    const struct ovn_datapath *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static void ls_lbacls_record_reinit(
> > +    struct ls_lbacls_record *,
> > +    const struct ls_port_group *,
> > +    const struct ls_port_group_table *);
> > +static bool ls_has_lb_vip(const struct ovn_datapath *);
> > +static void ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *,
> > +                                           const struct ovn_datapath *,
> > +                                           const struct ls_port_group *,
> > +                                           const struct ls_port_group_table *);
> > +static bool ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *,
> > +                                            struct nbrec_acl **,
> > +                                            size_t n_acls);
> > +static struct ls_lbacls_input ls_lbacls_get_input_data(struct engine_node *);
> > +static bool is_ls_acls_changed(const struct nbrec_logical_switch *);
> > +static bool is_acls_seqno_changed(struct nbrec_acl **, size_t n_nb_acls);
> > +
> > +
> > +/* public functions. */
> > +const struct ls_lbacls_record *
> > +ls_lbacls_table_find(
> > +    const struct ls_lbacls_table *table,
> > +    const struct nbrec_logical_switch *nbs)
> > +{
> > +    return ls_lbacls_table_find_(table, nbs);
> > +}
> > +
> > +void *
> > +en_ls_lbacls_init(struct engine_node *node OVS_UNUSED,
> > +                  struct engine_arg *arg OVS_UNUSED)
> > +{
> > +    struct ed_type_ls_lbacls *data = xzalloc(sizeof *data);
> > +    ls_lbacls_table_init(&data->ls_lbacls);
> > +    hmapx_init(&data->tracked_data.crupdated);
> > +    hmapx_init(&data->tracked_data.deleted);
> > +    return data;
> > +}
> > +
> > +void
> > +en_ls_lbacls_cleanup(void *data_)
> > +{
> > +    struct ed_type_ls_lbacls *data =
> > +        (struct ed_type_ls_lbacls *) data_;
> > +    ls_lbacls_table_destroy(&data->ls_lbacls);
> > +    hmapx_destroy(&data->tracked_data.crupdated);
> > +    hmapx_destroy(&data->tracked_data.deleted);
> > +}
> > +
> > +void
> > +en_ls_lbacls_clear_tracked_data(void *data_)
> > +{
> > +    struct ed_type_ls_lbacls *data =
> > +        (struct ed_type_ls_lbacls *) data_;
> > +
> > +    struct hmapx_node *hmapx_node;
> > +    HMAPX_FOR_EACH_SAFE (hmapx_node, &data->tracked_data.deleted) {
> > +        ls_lbacls_record_destroy(hmapx_node->data);
> > +        hmapx_delete(&data->tracked_data.deleted, hmapx_node);
> > +    }
> > +
> > +    hmapx_clear(&data->tracked_data.crupdated);
> > +    data->tracked = false;
> > +}
> > +
> > +void
> > +en_ls_lbacls_run(struct engine_node *node, void *data_)
> > +{
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ed_type_ls_lbacls *data = data_;
> > +
> > +    stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
> > +
> > +    ls_lbacls_table_clear(&data->ls_lbacls);
> > +    ls_lbacls_table_build(&data->ls_lbacls, input_data.ls_datapaths,
> > +                          input_data.ls_port_groups);
> > +
> > +    data->tracked = false;
> > +    stopwatch_stop(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
>
> Same comment about missing stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME,
> ...) as in the previous patch in this series.

Ack. Addressed in v3.

Numan

>
> Thanks,
> Dumitru
>
> > +    engine_set_node_state(node, EN_UPDATED);
> > +}
> > +
> > +/* Handler functions. */
> > +bool
> > +ls_lbacls_northd_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct northd_data *northd_data = engine_get_input_data("northd", node);
> > +    if (!northd_data->change_tracked) {
> > +        return false;
> > +    }
> > +
> > +    struct northd_tracked_data *nd_changes = &northd_data->trk_northd_changes;
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ls_lbacls_record *ls_lbacls_rec;
> > +    struct ed_type_ls_lbacls *data = data_;
> > +    const struct ovn_datapath *od;
> > +    struct hmapx_node *hmapx_node;
> > +
> > +    HMAPX_FOR_EACH (hmapx_node, &nd_changes->ls_with_changed_lbs.crupdated) {
> > +        od = hmapx_node->data;
> > +
> > +        ls_lbacls_rec = ls_lbacls_table_find_(&data->ls_lbacls, od->nbs);
> > +        if (!ls_lbacls_rec) {
> > +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> > +                                                    input_data.ls_port_groups);
> > +        } else {
> > +            ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> > +                                    input_data.ls_port_groups);
> > +        }
> > +
> > +        /* Add the ls_lbacls_rec to the tracking data. */
> > +        hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +bool
> > +ls_lbacls_port_group_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    if (pg_data->ls_port_groups_sets_changed) {
> > +        return false;
> > +    }
> > +
> > +    /* port_group engine node doesn't provide the tracking data yet.
> > +     * Loop through all the ls port groups and update the ls_lbacls_rec.
> > +     * This is still better than returning false. */
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    struct ed_type_ls_lbacls *data = data_;
> > +    const struct ls_port_group *ls_pg;
> > +
> > +    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
> > +        struct ls_lbacls_record *ls_lbacls_rec =
> > +            ls_lbacls_table_find_(&data->ls_lbacls, ls_pg->nbs);
> > +
> > +        bool modified = false;
> > +        if (!ls_lbacls_rec) {
> > +            const struct ovn_datapath *od;
> > +            od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> > +                                    &ls_pg->nbs->header_.uuid);
> > +            ovs_assert(od);
> > +            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> > +                                                input_data.ls_port_groups);
> > +            modified = true;
> > +        } else {
> > +            bool had_stateful_acl = ls_lbacls_rec->has_stateful_acl;
> > +            uint64_t max_acl_tier = ls_lbacls_rec->max_acl_tier;
> > +            bool had_acls = ls_lbacls_rec->has_acls;
> > +
> > +            ls_lbacls_record_reinit(ls_lbacls_rec, ls_pg,
> > +                                    input_data.ls_port_groups);
> > +
> > +            if ((had_stateful_acl != ls_lbacls_rec->has_stateful_acl)
> > +                || (had_acls != ls_lbacls_rec->has_acls)
> > +                || max_acl_tier != ls_lbacls_rec->max_acl_tier) {
> > +                modified = true;
> > +            }
> > +        }
> > +
> > +        if (modified) {
> > +            /* Add the ls_lbacls_rec to the tracking data. */
> > +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +        }
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +bool
> > +ls_lbacls_logical_switch_handler(struct engine_node *node, void *data_)
> > +{
> > +    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
> > +    const struct nbrec_logical_switch *nbs;
> > +    struct ed_type_ls_lbacls *data = data_;
> > +
> > +    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs,
> > +                                    input_data.nbrec_logical_switch_table) {
> > +        if (!is_ls_acls_changed(nbs)) {
> > +            continue;
> > +        }
> > +
> > +        struct ls_lbacls_record *ls_lbacls_rec =
> > +            ls_lbacls_table_find_(&data->ls_lbacls, nbs);
> > +
> > +        if (nbrec_logical_switch_is_deleted(nbs)) {
> > +            if (ls_lbacls_rec) {
> > +                /* Remove the record from the entries. */
> > +                hmap_remove(&data->ls_lbacls.entries,
> > +                            &ls_lbacls_rec->key_node);
> > +
> > +                /* Add the ls_lbacls_rec to the tracking data. */
> > +                hmapx_add(&data->tracked_data.deleted, ls_lbacls_rec);
> > +            }
> > +        } else {
> > +            if (!ls_lbacls_rec) {
> > +                const struct ovn_datapath *od;
> > +                od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
> > +                                       &nbs->header_.uuid);
> > +                ovs_assert(od);
> > +                ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
> > +                                                    input_data.ls_port_groups);
> > +            } else {
> > +                ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
> > +                                        input_data.ls_port_groups);
> > +            }
> > +
> > +            /* Add the ls_lbacls_rec to the tracking data. */
> > +            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
> > +        }
> > +    }
> > +
> > +    if (!hmapx_is_empty(&data->tracked_data.crupdated)
> > +        || !hmapx_is_empty(&data->tracked_data.deleted)) {
> > +        data->tracked = true;
> > +        engine_set_node_state(node, EN_UPDATED);
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +/* static functions. */
> > +static void
> > +ls_lbacls_table_init(struct ls_lbacls_table *table)
> > +{
> > +    *table = (struct ls_lbacls_table) {
> > +        .entries = HMAP_INITIALIZER(&table->entries),
> > +    };
> > +}
> > +
> > +static void
> > +ls_lbacls_table_destroy(struct ls_lbacls_table *table)
> > +{
> > +    ls_lbacls_table_clear(table);
> > +    hmap_destroy(&table->entries);
> > +}
> > +
> > +static void
> > +ls_lbacls_table_clear(struct ls_lbacls_table *table)
> > +{
> > +    struct ls_lbacls_record *ls_lbacls_rec;
> > +    HMAP_FOR_EACH_POP (ls_lbacls_rec, key_node, &table->entries) {
> > +        ls_lbacls_record_destroy(ls_lbacls_rec);
> > +    }
> > +}
> > +
> > +static void
> > +ls_lbacls_table_build(struct ls_lbacls_table *table,
> > +                      const struct ovn_datapaths *ls_datapaths,
> > +                      const struct ls_port_group_table *ls_pgs)
> > +{
> > +    const struct ovn_datapath *od;
> > +    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> > +        ls_lbacls_record_create(table, od, ls_pgs);
> > +    }
> > +}
> > +
> > +struct ls_lbacls_record *
> > +ls_lbacls_table_find_(const struct ls_lbacls_table *table,
> > +                      const struct nbrec_logical_switch *nbs)
> > +{
> > +    struct ls_lbacls_record *rec;
> > +
> > +    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
> > +                             uuid_hash(&nbs->header_.uuid), &table->entries) {
> > +        if (nbs == rec->od->nbs) {
> > +            return rec;
> > +        }
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static struct ls_lbacls_record *
> > +ls_lbacls_record_create(struct ls_lbacls_table *table,
> > +                        const struct ovn_datapath *od,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    struct ls_lbacls_record *ls_lbacls_rec = xzalloc(sizeof *ls_lbacls_rec);
> > +    ls_lbacls_rec->od = od;
> > +    ls_lbacls_record_init(ls_lbacls_rec, od, NULL, ls_pgs);
> > +
> > +    hmap_insert(&table->entries, &ls_lbacls_rec->key_node,
> > +                uuid_hash(&ls_lbacls_rec->od->nbs->header_.uuid));
> > +
> > +    return ls_lbacls_rec;
> > +}
> > +
> > +static void
> > +ls_lbacls_record_destroy(struct ls_lbacls_record *ls_lbacls_rec)
> > +{
> > +    free(ls_lbacls_rec);
> > +}
> > +
> > +static void
> > +ls_lbacls_record_init(struct ls_lbacls_record *ls_lbacls_rec,
> > +                      const struct ovn_datapath *od,
> > +                      const struct ls_port_group *ls_pg,
> > +                      const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_rec->has_lb_vip = ls_has_lb_vip(od);
> > +    ls_lbacls_record_set_acl_flags(ls_lbacls_rec, od, ls_pg, ls_pgs);
> > +}
> > +
> > +static void
> > +ls_lbacls_record_reinit(struct ls_lbacls_record *ls_lbacls_rec,
> > +                        const struct ls_port_group *ls_pg,
> > +                        const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_record_init(ls_lbacls_rec, ls_lbacls_rec->od, ls_pg, ls_pgs);
> > +}
> > +
> > +static bool
> > +lb_has_vip(const struct nbrec_load_balancer *lb)
> > +{
> > +    return !smap_is_empty(&lb->vips);
> > +}
> > +
> > +static bool
> > +lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> > +{
> > +    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
> > +        if (lb_has_vip(lb_group->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static bool
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> > +{
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> > +        if (lb_has_vip(od->nbs->load_balancer[i])) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
> > +        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
> > +            return true;
> > +        }
> > +    }
> > +    return false;
> > +}
> > +
> > +static void
> > +ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *ls_lbacls_rec,
> > +                               const struct ovn_datapath *od,
> > +                               const struct ls_port_group *ls_pg,
> > +                               const struct ls_port_group_table *ls_pgs)
> > +{
> > +    ls_lbacls_rec->has_stateful_acl = false;
> > +    ls_lbacls_rec->max_acl_tier = 0;
> > +    ls_lbacls_rec->has_acls = false;
> > +
> > +    if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec, od->nbs->acls,
> > +                                        od->nbs->n_acls)) {
> > +        return;
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
> > +    }
> > +
> > +    if (!ls_pg) {
> > +        return;
> > +    }
> > +
> > +    const struct ls_port_group_record *ls_pg_rec;
> > +    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > +        if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec,
> > +                                            ls_pg_rec->nb_pg->acls,
> > +                                            ls_pg_rec->nb_pg->n_acls)) {
> > +            return;
> > +        }
> > +    }
> > +}
> > +
> > +static bool
> > +ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *ls_lbacls_rec,
> > +                                struct nbrec_acl **acls,
> > +                                size_t n_acls)
> > +{
> > +    /* A true return indicates that there are no possible ACL flags
> > +     * left to set on ls_lbacls record. A false return indicates that
> > +     * further ACLs should be explored in case more flags need to be
> > +     * set on ls_lbacls record.
> > +     */
> > +    if (!n_acls) {
> > +        return false;
> > +    }
> > +
> > +    ls_lbacls_rec->has_acls = true;
> > +    for (size_t i = 0; i < n_acls; i++) {
> > +        const struct nbrec_acl *acl = acls[i];
> > +        if (acl->tier > ls_lbacls_rec->max_acl_tier) {
> > +            ls_lbacls_rec->max_acl_tier = acl->tier;
> > +        }
> > +        if (!ls_lbacls_rec->has_stateful_acl
> > +                && !strcmp(acl->action, "allow-related")) {
> > +            ls_lbacls_rec->has_stateful_acl = true;
> > +        }
> > +        if (ls_lbacls_rec->has_stateful_acl &&
> > +            ls_lbacls_rec->max_acl_tier ==
> > +                nbrec_acl_col_tier.type.value.integer.max) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static struct ls_lbacls_input
> > +ls_lbacls_get_input_data(struct engine_node *node)
> > +{
> > +    const struct northd_data *northd_data =
> > +        engine_get_input_data("northd", node);
> > +    const struct port_group_data *pg_data =
> > +        engine_get_input_data("port_group", node);
> > +
> > +    return (struct ls_lbacls_input) {
> > +        .nbrec_logical_switch_table =
> > +            EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)),
> > +        .ls_port_groups = &pg_data->ls_port_groups,
> > +        .ls_datapaths = &northd_data->ls_datapaths,
> > +    };
> > +}
> > +
> > +static bool
> > +is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
> > +{
> > +    for (size_t i = 0; i < n_nb_acls; i++) {
> > +        if (nbrec_acl_row_get_seqno(nb_acls[i],
> > +                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
> > +            return true;
> > +        }
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static bool
> > +is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
> > +    return (nbrec_logical_switch_is_new(nbs)
> > +            || nbrec_logical_switch_is_deleted(nbs)
> > +            || nbrec_logical_switch_is_updated(nbs,
> > +                                               NBREC_LOGICAL_SWITCH_COL_ACLS)
> > +            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
> > +}
> > diff --git a/northd/en-ls-lb-acls.h b/northd/en-ls-lb-acls.h
> > new file mode 100644
> > index 0000000000..ccb75e40e8
> > --- /dev/null
> > +++ b/northd/en-ls-lb-acls.h
> > @@ -0,0 +1,88 @@
> > +/*
> > + * Copyright (c) 2023, Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +#ifndef EN_LS_LB_ACL_H
> > +#define EN_LS_LB_ACL_H 1
> > +
> > +#include <stdint.h>
> > +
> > +/* OVS includes. */
> > +#include "lib/hmapx.h"
> > +#include "openvswitch/hmap.h"
> > +#include "sset.h"
> > +
> > +/* OVN includes. */
> > +#include "lib/inc-proc-eng.h"
> > +#include "lib/lb.h"
> > +#include "lib/ovn-nb-idl.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "lib/ovn-util.h"
> > +#include "lib/stopwatch-names.h"
> > +
> > +struct ls_lbacls_record {
> > +    struct hmap_node key_node;
> > +
> > +    const struct ovn_datapath *od;
> > +    bool has_stateful_acl;
> > +    bool has_lb_vip;
> > +    bool has_acls;
> > +    uint64_t max_acl_tier;
> > +};
> > +
> > +struct ls_lbacls_table {
> > +    struct hmap entries;
> > +};
> > +
> > +#define LS_LBACLS_TABLE_FOR_EACH(LS_LBACLS_REC, TABLE) \
> > +    HMAP_FOR_EACH (LS_LBACLS_REC, key_node, &(TABLE)->entries)
> > +
> > +#define LS_LBACLS_TABLE_FOR_EACH_IN_P(LS_LBACLS_REC, JOBID, TABLE) \
> > +    HMAP_FOR_EACH_IN_PARALLEL (LS_LBACLS_REC, key_node, JOBID, \
> > +                               &(TABLE)->entries)
> > +
> > +struct ls_lbacls_tracked_data {
> > +    /* Created or updated logical switch with LB and ACL data. */
> > +    struct hmapx crupdated; /* Stores 'struct ls_lbacls_record'. */
> > +
> > +    /* Deleted logical switch with LB and ACL data. */
> > +    struct hmapx deleted; /* Stores 'struct ls_lbacls_record'. */
> > +};
> > +
> > +struct ed_type_ls_lbacls {
> > +    struct ls_lbacls_table ls_lbacls;
> > +
> > +    bool tracked;
> > +    struct ls_lbacls_tracked_data tracked_data;
> > +};
> > +
> > +struct ls_lbacls_input {
> > +    const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
> > +    const struct ls_port_group_table *ls_port_groups;
> > +    const struct ovn_datapaths *ls_datapaths;
> > +};
> > +
> > +void *en_ls_lbacls_init(struct engine_node *, struct engine_arg *);
> > +void en_ls_lbacls_cleanup(void *data);
> > +void en_ls_lbacls_clear_tracked_data(void *data);
> > +void en_ls_lbacls_run(struct engine_node *, void *data);
> > +
> > +bool ls_lbacls_northd_handler(struct engine_node *, void *data);
> > +bool ls_lbacls_port_group_handler(struct engine_node *, void *data);
> > +bool ls_lbacls_logical_switch_handler(struct engine_node *, void *data);
> > +
> > +const struct ls_lbacls_record *ls_lbacls_table_find(
> > +    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
> > +
> > +#endif /* EN_LS_LB_ACL_H */
> > diff --git a/northd/en-port-group.h b/northd/en-port-group.h
> > index 3b28a23694..54014062ce 100644
> > --- a/northd/en-port-group.h
> > +++ b/northd/en-port-group.h
> > @@ -48,6 +48,9 @@ struct ls_port_group_record {
> >      struct sset ports;          /* Subset of 'nb_pg' ports in this record. */
> >  };
> >
> > +#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
> > +    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
> > +
> >  void ls_port_group_table_init(struct ls_port_group_table *);
> >  void ls_port_group_table_clear(struct ls_port_group_table *);
> >  void ls_port_group_table_destroy(struct ls_port_group_table *);
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index 84627070a8..ab4af92aeb 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -33,6 +33,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-lb-nat-data.h"
> >  #include "en-lr-nat.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "en-northd.h"
> >  #include "en-lflow.h"
> >  #include "en-northd-output.h"
> > @@ -150,6 +151,7 @@ static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
> >  static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_lb_nat_data, "lr_lb_nat_data");
> > +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_lbacls, "ls_lbacls");
> >
> >  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >                            struct ovsdb_idl_loop *sb)
> > @@ -205,6 +207,12 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lr_lb_nat_data, &en_lb_data,
> >                       lr_lb_nat_data_lb_data_handler);
> >
> > +    engine_add_input(&en_ls_lbacls, &en_northd, ls_lbacls_northd_handler);
> > +    engine_add_input(&en_ls_lbacls, &en_port_group,
> > +                     ls_lbacls_port_group_handler);
> > +    engine_add_input(&en_ls_lbacls, &en_nb_logical_switch,
> > +                     ls_lbacls_logical_switch_handler);
> > +
> >      engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
> >      engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
> > @@ -229,6 +237,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
> >      engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
> >      engine_add_input(&en_lflow, &en_lr_lb_nat_data, NULL);
> > +    engine_add_input(&en_lflow, &en_ls_lbacls, NULL);
> >
> >      engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
> >                       sync_to_sb_addr_set_nb_address_set_handler);
> > diff --git a/northd/northd.c b/northd/northd.c
> > index c8a224d3cd..924f5cd7e0 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -45,6 +45,7 @@
> >  #include "en-lb-data.h"
> >  #include "en-lr-nat.h"
> >  #include "en-lr-lb-nat-data.h"
> > +#include "en-ls-lb-acls.h"
> >  #include "lib/ovn-parallel-hmap.h"
> >  #include "ovn/actions.h"
> >  #include "ovn/features.h"
> > @@ -575,7 +576,7 @@ lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
> >  }
> >
> >  static bool
> > -ls_has_lb_vip(struct ovn_datapath *od)
> > +ls_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbs->load_balancer[i])) {
> > @@ -592,7 +593,7 @@ ls_has_lb_vip(struct ovn_datapath *od)
> >  }
> >
> >  static bool
> > -lr_has_lb_vip(struct ovn_datapath *od)
> > +lr_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
> >          if (lb_has_vip(od->nbr->load_balancer[i])) {
> > @@ -608,13 +609,13 @@ lr_has_lb_vip(struct ovn_datapath *od)
> >      return false;
> >  }
> >
> > -static void
> > -init_lb_for_datapath(struct ovn_datapath *od)
> > +bool
> > +od_has_lb_vip(const struct ovn_datapath *od)
> >  {
> >      if (od->nbs) {
> > -        od->has_lb_vip = ls_has_lb_vip(od);
> > +        return ls_has_lb_vip(od);
> >      } else {
> > -        od->has_lb_vip = lr_has_lb_vip(od);
> > +        return lr_has_lb_vip(od);
> >      }
> >  }
> >
> > @@ -1058,7 +1059,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
> >
> >          init_ipam_info_for_datapath(od);
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >      }
> >
> >      const struct nbrec_logical_router *nbr;
> > @@ -1089,7 +1089,6 @@ join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
> >              ovs_list_push_back(nb_only, &od->list);
> >          }
> >          init_mcast_info_for_datapath(od);
> > -        init_lb_for_datapath(od);
> >          if (smap_get(&od->nbr->options, "chassis")) {
> >              od->is_gw_router = true;
> >          }
> > @@ -2570,7 +2569,8 @@ get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
> >      size_t n_nats = 0;
> >      struct eth_addr mac;
> >      if (!op || !op->nbrp || !op->od || !op->od->nbr
> > -        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
> > +        || (!op->od->nbr->n_nat && (!lr_lbnat_rec
> > +                                    || !lr_lbnat_rec->has_lb_vip))
> >          || !eth_addr_from_string(op->nbrp->mac, &mac)) {
> >          *n = n_nats;
> >          return NULL;
> > @@ -3817,7 +3817,7 @@ build_lrouter_lbs_check(const struct ovn_datapaths *lr_datapaths)
> >      HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
> >          ovs_assert(od->nbr);
> >
> > -        if (od->has_lb_vip && od->n_l3dgw_ports > 1
> > +        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
> >                  && !smap_get(&od->nbr->options, "chassis")) {
> >              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> >              VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
> > @@ -5441,7 +5441,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the ls datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> > @@ -5524,9 +5523,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >              }
> >          }
> >
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> > -
> >          /* Add the ls datapath to the northd tracked data. */
> >          hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> >      }
> > @@ -5564,9 +5560,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >              }
> >          }
> >
> > -        /* Re-evaluate 'od->has_lb_vip' */
> > -        init_lb_for_datapath(od);
> > -
> >          /* Add the lr datapath to the northd tracked data. */
> >          hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> >      }
> > @@ -5581,8 +5574,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
> >                             lb_dps->nb_ls_map) {
> >              od = ls_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the ls datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> > @@ -5591,8 +5582,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >          BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
> >                             lb_dps->nb_lr_map) {
> >              od = lr_datapaths->array[index];
> > -            /* Re-evaluate 'od->has_lb_vip' */
> > -            init_lb_for_datapath(od);
> >
> >              /* Add the lr datapath to the northd tracked data. */
> >              hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> > @@ -5618,9 +5607,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >                  od = lbgrp_dps->lr[i];
> >                  ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
> >
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> > -
> >                  /* Add the lr datapath to the northd tracked data. */
> >                  hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
> >              }
> > @@ -5629,9 +5615,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
> >                 od = lbgrp_dps->ls[i];
> >                  ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
> >
> > -                /* Re-evaluate 'od->has_lb_vip' */
> > -                init_lb_for_datapath(od);
> > -
> >                  /* Add the ls datapath to the northd tracked data. */
> >                  hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
> >              }
> > @@ -6573,63 +6556,6 @@ build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
> >      return true;
> >  }
> >
> > -static bool
> > -od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
> > -                 size_t n_acls)
> > -{
> > -    /* A true return indicates that there are no possible ACL flags
> > -     * left to set on od. A false return indicates that further ACLs
> > -     * should be explored in case more flags need to be set on od
> > -     */
> > -    if (!n_acls) {
> > -        return false;
> > -    }
> > -
> > -    od->has_acls = true;
> > -    for (size_t i = 0; i < n_acls; i++) {
> > -        const struct nbrec_acl *acl = acls[i];
> > -        if (acl->tier > od->max_acl_tier) {
> > -            od->max_acl_tier = acl->tier;
> > -        }
> > -        if (!od->has_stateful_acl && !strcmp(acl->action, "allow-related")) {
> > -            od->has_stateful_acl = true;
> > -        }
> > -        if (od->has_stateful_acl &&
> > -            od->max_acl_tier == nbrec_acl_col_tier.type.value.integer.max) {
> > -            return true;
> > -        }
> > -    }
> > -
> > -    return false;
> > -}
> > -
> > -static void
> > -ls_get_acl_flags(struct ovn_datapath *od,
> > -                 const struct ls_port_group_table *ls_port_groups)
> > -{
> > -    od->has_acls = false;
> > -    od->has_stateful_acl = false;
> > -    od->max_acl_tier = 0;
> > -
> > -    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group *ls_pg =
> > -        ls_port_group_table_find(ls_port_groups, od->nbs);
> > -    if (!ls_pg) {
> > -        return;
> > -    }
> > -
> > -    const struct ls_port_group_record *ls_pg_rec;
> > -    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
> > -        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
> > -                             ls_pg_rec->nb_pg->n_acls)) {
> > -            return;
> > -        }
> > -    }
> > -}
> > -
> >  /* Adds the logical flows in the (in/out) check port sec stage only if
> >   *   - the lport is disabled or
> >   *   - lport is of type vtep - to skip the ingress pipeline.
> > @@ -6774,9 +6700,10 @@ build_lswitch_output_port_sec_od(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> > -                         enum ovn_stage in_stage, enum ovn_stage out_stage,
> > -                         uint16_t priority, struct hmap *lflows)
> > +skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port *op,
> > +                         bool has_stateful_acl, enum ovn_stage in_stage,
> > +                         enum ovn_stage out_stage, uint16_t priority,
> > +                         struct hmap *lflows)
> >  {
> >      /* Can't use ct() for router ports. Consider the following configuration:
> >       * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a
> > @@ -6789,7 +6716,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> >       * conntrack state across all chassis. */
> >
> >      const char *ingress_action = "next;";
> > -    const char *egress_action = od->has_stateful_acl
> > +    const char *egress_action = has_stateful_acl
> >                                  ? "next;"
> >                                  : "ct_clear; next;";
> >
> > @@ -6808,7 +6735,7 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
> >  }
> >
> >  static void
> > -build_stateless_filter(struct ovn_datapath *od,
> > +build_stateless_filter(const struct ovn_datapath *od,
> >                         const struct nbrec_acl *acl,
> >                         struct hmap *lflows)
> >  {
> > @@ -6829,7 +6756,7 @@ build_stateless_filter(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_stateless_filters(struct ovn_datapath *od,
> > +build_stateless_filters(const struct ovn_datapath *od,
> >                          const struct ls_port_group_table *ls_port_groups,
> >                          struct hmap *lflows)
> >  {
> > @@ -6859,9 +6786,7 @@ build_stateless_filters(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_pre_acls(struct ovn_datapath *od,
> > -               const struct ls_port_group_table *ls_port_groups,
> > -               struct hmap *lflows)
> > +build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
> >  {
> >      /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
> >       * allowed by default. */
> > @@ -6873,18 +6798,26 @@ build_pre_acls(struct ovn_datapath *od,
> >
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
> >                    "eth.src == $svc_monitor_mac", "next;");
> > +}
> > +
> > +static void
> > +build_ls_lbacls_rec_pre_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                             const struct ls_port_group_table *ls_port_groups,
> > +                             struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> >
> >      /* If there are any stateful ACL rules in this datapath, we may
> >       * send IP packets for some (allow) filters through the conntrack action,
> >       * which handles defragmentation, in order to match L4 headers. */
> > -    if (od->has_stateful_acl) {
> > +    if (ls_lbacls_rec->has_stateful_acl) {
> >          for (size_t i = 0; i < od->n_router_ports; i++) {
> > -            skip_port_from_conntrack(od, od->router_ports[i],
> > +            skip_port_from_conntrack(od, od->router_ports[i], true,
> >                                       S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> >          }
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> > -            skip_port_from_conntrack(od, od->localnet_ports[i],
> > +            skip_port_from_conntrack(od, od->localnet_ports[i], true,
> >                                       S_SWITCH_IN_PRE_ACL,
> >                                       S_SWITCH_OUT_PRE_ACL,
> >                                       110, lflows);
> > @@ -6922,7 +6855,7 @@ build_pre_acls(struct ovn_datapath *od,
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
> >                        REGBIT_CONNTRACK_DEFRAG" = 1; next;");
> > -    } else if (od->has_lb_vip) {
> > +    } else if (ls_lbacls_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);
> > @@ -7050,30 +6983,40 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
> >
> > +    /* Do not send statless flows via conntrack */
> > +    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > +                  REGBIT_ACL_STATELESS" == 1", "next;");
> > +}
> > +
> > +static void
> > +build_ls_lbacls_rec_pre_lb(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                           struct hmap *lflows)
> > +{
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      for (size_t i = 0; i < od->n_router_ports; i++) {
> >          skip_port_from_conntrack(od, od->router_ports[i],
> > +                                 ls_lbacls_rec->has_stateful_acl,
> >                                   S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
> >                                   110, lflows);
> >      }
> > +
> >      /* Localnet ports have no need for going through conntrack, unless
> >       * the logical switch has a load balancer. Then, conntrack is necessary
> >       * so that traffic arriving via the localnet port can be load
> >       * balanced.
> >       */
> > -    if (!od->has_lb_vip) {
> > +    if (!ls_lbacls_rec->has_lb_vip) {
> >          for (size_t i = 0; i < od->n_localnet_ports; i++) {
> >              skip_port_from_conntrack(od, od->localnet_ports[i],
> > +                                     ls_lbacls_rec->has_stateful_acl,
> >                                       S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
> >                                       110, lflows);
> >          }
> >      }
> >
> > -    /* Do not sent statless flows via conntrack */
> > -    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
> > -                  REGBIT_ACL_STATELESS" == 1", "next;");
> > -
> >      /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
> >       * packet to conntrack for defragmentation and possibly for unNATting.
> >       *
> > @@ -7104,7 +7047,7 @@ build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
> >       * ingress pipeline if a load balancer is configured. We can now
> >       * add a lflow to drop ct.inv packets.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_lb_vip) {
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
> >                        100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
> >          ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
> > @@ -7145,10 +7088,12 @@ build_pre_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_acl_hints(struct ovn_datapath *od,
> > +build_acl_hints(const struct ls_lbacls_record *ls_lbacls_rec,
> >                  const struct chassis_features *features,
> >                  struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      /* This stage builds hints for the IN/OUT_ACL stage. Based on various
> >       * combinations of ct flags packets may hit only a subset of the logical
> >       * flows in the IN/OUT_ACL stage.
> > @@ -7172,13 +7117,13 @@ build_acl_hints(struct ovn_datapath *od,
> >          const char *match;
> >
> >          /* In any case, advance to the next stage. */
> > -        if (!od->has_acls && !od->has_lb_vip) {
> > +        if (!ls_lbacls_rec->has_acls && !ls_lbacls_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
> >          } else {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >          }
> >
> > -        if (!od->has_stateful_acl && !od->has_lb_vip) {
> > +        if (!ls_lbacls_rec->has_stateful_acl && !ls_lbacls_rec->has_lb_vip) {
> >              continue;
> >          }
> >
> > @@ -7314,10 +7259,10 @@ build_acl_log(struct ds *actions, const struct nbrec_acl *acl,
> >  }
> >
> >  static void
> > -consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> > +consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
> >               const struct nbrec_acl *acl, bool has_stateful,
> >               bool ct_masked_mark, const struct shash *meter_groups,
> > -             struct ds *match, struct ds *actions)
> > +             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
> >  {
> >      const char *ct_blocked_match = ct_masked_mark
> >                                     ? "ct_mark.blocked"
> > @@ -7354,7 +7299,7 @@ consider_acl(struct hmap *lflows, struct ovn_datapath *od,
> >      /* All ACLS will start by matching on their respective tier. */
> >      size_t match_tier_len = 0;
> >      ds_clear(match);
> > -    if (od->max_acl_tier) {
> > +    if (max_acl_tier) {
> >          ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ", acl->tier);
> >          match_tier_len = match->length;
> >      }
> > @@ -7543,12 +7488,15 @@ ovn_update_ipv6_options(struct hmap *lr_ports)
> >  #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
> >
> >  static void
> > -build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_action_lflows(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                        struct hmap *lflows,
> >                          const char *default_acl_action,
> >                          const struct shash *meter_groups,
> >                          struct ds *match,
> >                          struct ds *actions)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      enum ovn_stage stages [] = {
> >          S_SWITCH_IN_ACL_ACTION,
> >          S_SWITCH_IN_ACL_AFTER_LB_ACTION,
> > @@ -7559,7 +7507,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> >      ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
> >                          REGBIT_ACL_VERDICT_DROP " = 0; "
> >                          REGBIT_ACL_VERDICT_REJECT " = 0; ");
> > -    if (od->max_acl_tier) {
> > +    if (ls_lbacls_rec->max_acl_tier) {
> >          ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
> >      }
> >
> > @@ -7567,7 +7515,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> >
> >      for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
> >          enum ovn_stage stage = stages[i];
> > -        if (!od->has_acls) {
> > +        if (!ls_lbacls_rec->has_acls) {
> >              ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
> >              continue;
> >          }
> > @@ -7602,7 +7550,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> >          ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
> >
> >          struct ds tier_actions = DS_EMPTY_INITIALIZER;
> > -        for (size_t j = 0; j < od->max_acl_tier; j++) {
> > +        for (size_t j = 0; j < ls_lbacls_rec->max_acl_tier; j++) {
> >              ds_clear(match);
> >              ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
> >              ds_clear(&tier_actions);
> > @@ -7618,7 +7566,7 @@ build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> > +build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap *lflows,
> >                              const struct nbrec_acl *acl, bool has_stateful,
> >                              bool ct_masked_mark,
> >                              const struct shash *meter_groups,
> > @@ -7691,15 +7639,19 @@ build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
> >  }
> >
> >  static void
> > -build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> > +build_acls(const struct ls_lbacls_record *ls_lbacls_rec,
> > +           const struct chassis_features *features,
> >             struct hmap *lflows,
> >             const struct ls_port_group_table *ls_port_groups,
> >             const struct shash *meter_groups)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      const char *default_acl_action = default_acl_drop
> >                                       ? debug_implicit_drop_action()
> >                                       : "next;";
> > -    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
> > +    bool has_stateful = (ls_lbacls_rec->has_stateful_acl
> > +                         || ls_lbacls_rec->has_lb_vip);
> >      const char *ct_blocked_match = features->ct_no_masked_label
> >                                     ? "ct_mark.blocked"
> >                                     : "ct_label.blocked";
> > @@ -7713,8 +7665,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> >       *
> >       * A related rule at priority 1 is added below if there
> >       * are any stateful ACLs in this datapath. */
> > -    if (!od->has_acls) {
> > -        if (!od->has_lb_vip) {
> > +    if (!ls_lbacls_rec->has_acls) {
> > +        if (!ls_lbacls_rec->has_lb_vip) {
> >              ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX, "1",
> >                            "next;");
> >              ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX, "1",
> > @@ -7877,7 +7829,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> >                                      meter_groups, &match, &actions);
> >          consider_acl(lflows, od, acl, has_stateful,
> >                       features->ct_no_masked_label,
> > -                     meter_groups, &match, &actions);
> > +                     meter_groups, ls_lbacls_rec->max_acl_tier,
> > +                     &match, &actions);
> >      }
> >
> >      const struct ls_port_group *ls_pg =
> > @@ -7893,7 +7846,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> >                                              meter_groups, &match, &actions);
> >                  consider_acl(lflows, od, acl, has_stateful,
> >                               features->ct_no_masked_label,
> > -                             meter_groups, &match, &actions);
> > +                             meter_groups, ls_lbacls_rec->max_acl_tier,
> > +                             &match, &actions);
> >              }
> >          }
> >      }
> > @@ -7911,7 +7865,7 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> >              dns_actions);
> >      }
> >
> > -    if (od->has_acls || od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_acls || ls_lbacls_rec->has_lb_vip) {
> >          /* Add a 34000 priority flow to advance the service monitor reply
> >          * packets to skip applying ingress ACLs. */
> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
> > @@ -7925,8 +7879,8 @@ build_acls(struct ovn_datapath *od, const struct chassis_features *features,
> >                      REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
> >      }
> >
> > -    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
> > -                            &match, &actions);
> > +    build_acl_action_lflows(ls_lbacls_rec, lflows, default_acl_action,
> > +                            meter_groups, &match, &actions);
> >
> >      ds_destroy(&match);
> >      ds_destroy(&actions);
> > @@ -8571,8 +8525,11 @@ build_stateful(struct ovn_datapath *od,
> >  }
> >
> >  static void
> > -build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> > +build_lb_hairpin(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                 struct hmap *lflows)
> >  {
> > +    const struct ovn_datapath *od = ls_lbacls_rec->od;
> > +
> >      /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
> >       * Packets that don't need hairpinning should continue processing.
> >       */
> > @@ -8580,7 +8537,7 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
> >      ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
> >
> > -    if (od->has_lb_vip) {
> > +    if (ls_lbacls_rec->has_lb_vip) {
> >          /* Check if the packet needs to be hairpinned.
> >           * Set REGBIT_HAIRPIN in the original direction and
> >           * REGBIT_HAIRPIN_REPLY in the reply direction.
> > @@ -9413,22 +9370,16 @@ build_lswitch_lflows_l2_unknown(struct ovn_datapath *od,
> >  static void
> >  build_lswitch_lflows_pre_acl_and_acl(
> >      struct ovn_datapath *od,
> > -    const struct ls_port_group_table *ls_port_groups,
> >      const struct chassis_features *features,
> >      struct hmap *lflows,
> >      const struct shash *meter_groups)
> >  {
> >      ovs_assert(od->nbs);
> > -    ls_get_acl_flags(od, ls_port_groups);
> > -
> > -    build_pre_acls(od, ls_port_groups, lflows);
> > +    build_pre_acls(od, lflows);
> >      build_pre_lb(od, meter_groups, lflows);
> >      build_pre_stateful(od, features, lflows);
> > -    build_acl_hints(od, features, lflows);
> > -    build_acls(od, features, lflows, ls_port_groups, meter_groups);
> >      build_qos(od, lflows);
> >      build_stateful(od, features, lflows);
> > -    build_lb_hairpin(od, lflows);
> >      build_vtep_hairpin(od, lflows);
> >  }
> >
> > @@ -15487,7 +15438,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * a dynamically negotiated FTP data channel), but will allow
> >       * related traffic such as an ICMP Port Unreachable through
> >       * that's generated from a non-listening UDP port.  */
> > -    if (od->has_lb_vip && features->ct_lb_related) {
> > +    if (lr_lbnat_rec->has_lb_vip && features->ct_lb_related) {
> >          ds_clear(match);
> >
> >          ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
> > @@ -15512,7 +15463,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * Pass the traffic that is already established to the next table with
> >       * proper flags set.
> >       */
> > -    if (od->has_lb_vip) {
> > +    if (lr_lbnat_rec->has_lb_vip) {
> >          ds_clear(match);
> >
> >          ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
> > @@ -15542,7 +15493,7 @@ build_lrouter_nat_defrag_and_lb(
> >       * not committed, it would produce ongoing datapath flows with the ct.new
> >       * flag set. Some NICs are unable to offload these flows.
> >       */
> > -    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
> > +    if (od->is_gw_router && (od->nbr->n_nat || lr_lbnat_rec->has_lb_vip)) {
> >          /* Do not send ND or ICMP packets to connection tracking. */
> >          ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
> >                        "nd || nd_rs || nd_ra", "next;");
> > @@ -15967,6 +15918,22 @@ build_lr_lbnat_data_flows(const struct lr_lb_nat_data_record *lr_lbnat_rec,
> >                                        meter_groups);
> >  }
> >
> > +static void
> > +build_ls_lbacls_flows(const struct ls_lbacls_record *ls_lbacls_rec,
> > +                      const struct ls_port_group_table *ls_pgs,
> > +                      const struct chassis_features *features,
> > +                      const struct shash *meter_groups,
> > +                      struct hmap *lflows)
> > +{
> > +    ovs_assert(ls_lbacls_rec->od);
> > +
> > +    build_ls_lbacls_rec_pre_acls(ls_lbacls_rec, ls_pgs, lflows);
> > +    build_ls_lbacls_rec_pre_lb(ls_lbacls_rec, lflows);
> > +    build_acl_hints(ls_lbacls_rec, features, lflows);
> > +    build_acls(ls_lbacls_rec, features, lflows, ls_pgs, meter_groups);
> > +    build_lb_hairpin(ls_lbacls_rec, lflows);
> > +}
> > +
> >  struct lswitch_flow_build_info {
> >      const struct ovn_datapaths *ls_datapaths;
> >      const struct ovn_datapaths *lr_datapaths;
> > @@ -15974,6 +15941,7 @@ struct lswitch_flow_build_info {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_lb_nat_data_table *lr_lbnats;
> > +    const struct ls_lbacls_table *ls_lbacls;
> >      struct hmap *lflows;
> >      struct hmap *igmp_groups;
> >      const struct shash *meter_groups;
> > @@ -15998,9 +15966,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct ovn_datapath *od,
> >                                          struct lswitch_flow_build_info *lsi)
> >  {
> >      ovs_assert(od->nbs);
> > -    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
> > -                                         lsi->features,
> > -                                         lsi->lflows,
> > +    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
> >                                           lsi->meter_groups);
> >
> >      build_fwd_group_lflows(od, lsi->lflows);
> > @@ -16115,6 +16081,7 @@ build_lflows_thread(void *arg)
> >  {
> >      struct worker_control *control = (struct worker_control *) arg;
> >      const struct lr_lb_nat_data_record *lr_lbnat_rec;
> > +    const struct ls_lbacls_record *ls_lbacls_rec;
> >      struct lswitch_flow_build_info *lsi;
> >      struct ovn_igmp_group *igmp_group;
> >      struct ovn_lb_datapaths *lb_dps;
> > @@ -16243,6 +16210,19 @@ build_lflows_thread(void *arg)
> >                                                lsi->features);
> >                  }
> >              }
> > +
> > +            for (bnum = control->id;
> > +                    bnum <= lsi->ls_lbacls->entries.mask;
> > +                    bnum += control->pool->size)
> > +            {
> > +                LS_LBACLS_TABLE_FOR_EACH_IN_P (ls_lbacls_rec, bnum,
> > +                                               lsi->ls_lbacls) {
> > +                    build_ls_lbacls_flows(ls_lbacls_rec, lsi->ls_port_groups,
> > +                                          lsi->features, lsi->meter_groups,
> > +                                          lsi->lflows);
> > +                }
> > +            }
> > +
> >              for (bnum = control->id;
> >                      bnum <= lsi->igmp_groups->mask;
> >                      bnum += control->pool->size)
> > @@ -16303,6 +16283,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
> >                                  const struct hmap *lr_ports,
> >                                  const struct ls_port_group_table *ls_pgs,
> >                                  const struct lr_lb_nat_data_table *lr_lbnats,
> > +                                const struct ls_lbacls_table *ls_lbacls,
> >                                  struct hmap *lflows,
> >                                  struct hmap *igmp_groups,
> >                                  const struct shash *meter_groups,
> > @@ -16333,6 +16314,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
> >              lsiv[index].lr_ports = lr_ports;
> >              lsiv[index].ls_port_groups = ls_pgs;
> >              lsiv[index].lr_lbnats = lr_lbnats;
> > +            lsiv[index].ls_lbacls = ls_lbacls;
> >              lsiv[index].igmp_groups = igmp_groups;
> >              lsiv[index].meter_groups = meter_groups;
> >              lsiv[index].lb_dps_map = lb_dps_map;
> > @@ -16358,6 +16340,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
> >          free(lsiv);
> >      } else {
> >          const struct lr_lb_nat_data_record *lr_lbnat_rec;
> > +        const struct ls_lbacls_record *ls_lbacls_rec;
> >          struct ovn_igmp_group *igmp_group;
> >          struct ovn_lb_datapaths *lb_dps;
> >          struct ovn_datapath *od;
> > @@ -16370,6 +16353,7 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
> >              .lr_ports = lr_ports,
> >              .ls_port_groups = ls_pgs,
> >              .lr_lbnats = lr_lbnats,
> > +            .ls_lbacls = ls_lbacls,
> >              .lflows = lflows,
> >              .igmp_groups = igmp_groups,
> >              .meter_groups = meter_groups,
> > @@ -16439,6 +16423,12 @@ build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
> >                                        lsi.features);
> >          }
> >
> > +        LS_LBACLS_TABLE_FOR_EACH (ls_lbacls_rec, ls_lbacls) {
> > +            build_ls_lbacls_flows(ls_lbacls_rec, lsi.ls_port_groups,
> > +                                  lsi.features, lsi.meter_groups,
> > +                                  lsi.lflows);
> > +        }
> > +
> >          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
> >          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
> >              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> > @@ -16535,6 +16525,7 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
> >                                      input_data->lr_ports,
> >                                      input_data->ls_port_groups,
> >                                      input_data->lr_lbnats,
> > +                                    input_data->ls_lbacls,
> >                                      lflows,
> >                                      &igmp_groups,
> >                                      input_data->meter_groups,
> > diff --git a/northd/northd.h b/northd/northd.h
> > index 08a81b2c10..23b4754db4 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -89,6 +89,8 @@ ods_size(const struct ovn_datapaths *datapaths)
> >      return hmap_count(&datapaths->datapaths);
> >  }
> >
> > +bool od_has_lb_vip(const struct ovn_datapath *od);
> > +
> >  struct tracked_ovn_ports {
> >      /* tracked created ports.
> >       * hmapx node data is 'struct ovn_port *' */
> > @@ -179,6 +181,7 @@ struct lflow_input {
> >      const struct hmap *lr_ports;
> >      const struct ls_port_group_table *ls_port_groups;
> >      const struct lr_lb_nat_data_table *lr_lbnats;
> > +    const struct ls_lbacls_table *ls_lbacls;
> >      const struct shash *meter_groups;
> >      const struct hmap *lb_datapaths_map;
> >      const struct hmap *bfd_connections;
> > @@ -288,11 +291,7 @@ struct ovn_datapath {
> >      struct hmap port_tnlids;
> >      uint32_t port_key_hint;
> >
> > -    bool has_stateful_acl;
> > -    bool has_lb_vip;
> >      bool has_unknown;
> > -    bool has_acls;
> > -    uint64_t max_acl_tier;
> >      bool has_vtep_lports;
> >      bool has_arp_proxy_port;
> >
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
index 7d85acdaea..8b0018a593 100644
--- a/lib/stopwatch-names.h
+++ b/lib/stopwatch-names.h
@@ -34,5 +34,6 @@ 
 #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
 #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
 #define LR_LB_NAT_DATA_RUN_STOPWATCH_NAME "lr_lb_nat_data"
+#define LS_LBACLS_RUN_STOPWATCH_NAME "lr_lb_acls"
 
 #endif
diff --git a/northd/automake.mk b/northd/automake.mk
index 4116c487df..4593654726 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -28,6 +28,8 @@  northd_ovn_northd_SOURCES = \
 	northd/en-lr-nat.h \
 	northd/en-lr-lb-nat-data.c \
 	northd/en-lr-lb-nat-data.h \
+	northd/en-ls-lb-acls.c \
+	northd/en-ls-lb-acls.h \
 	northd/inc-proc-northd.c \
 	northd/inc-proc-northd.h \
 	northd/ipam.c \
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index 229f4be1d0..648a477916 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -21,6 +21,7 @@ 
 #include "en-lflow.h"
 #include "en-lr-nat.h"
 #include "en-lr-lb-nat-data.h"
+#include "en-ls-lb-acls.h"
 #include "en-northd.h"
 #include "en-meters.h"
 
@@ -44,6 +45,8 @@  lflow_get_input_data(struct engine_node *node,
         engine_get_input_data("sync_meters", node);
     struct ed_type_lr_lb_nat_data *lr_lb_nat_data =
         engine_get_input_data("lr_lb_nat_data", node);
+    struct ed_type_ls_lbacls *ls_lbacls_data =
+        engine_get_input_data("ls_lbacls", node);
 
     lflow_input->nbrec_bfd_table =
         EN_OVSDB_GET(engine_get_input("NB_bfd", node));
@@ -67,6 +70,7 @@  lflow_get_input_data(struct engine_node *node,
     lflow_input->lr_ports = &northd_data->lr_ports;
     lflow_input->ls_port_groups = &pg_data->ls_port_groups;
     lflow_input->lr_lbnats = &lr_lb_nat_data->lr_lbnats;
+    lflow_input->ls_lbacls = &ls_lbacls_data->ls_lbacls;
     lflow_input->meter_groups = &sync_meters_data->meter_groups;
     lflow_input->lb_datapaths_map = &northd_data->lb_datapaths_map;
     lflow_input->svc_monitor_map = &northd_data->svc_monitor_map;
diff --git a/northd/en-lr-lb-nat-data.c b/northd/en-lr-lb-nat-data.c
index 19b638ce0b..d816d2321d 100644
--- a/northd/en-lr-lb-nat-data.c
+++ b/northd/en-lr-lb-nat-data.c
@@ -299,9 +299,11 @@  lr_lb_nat_data_lb_data_handler(struct engine_node *node, void *data_)
     if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
         struct hmapx_node *hmapx_node;
         /* For all the modified lr_lb_nat_data records (re)build the
-         * vip nats. */
+         * vip nats and re-evaluate 'has_lb_vip'. */
         HMAPX_FOR_EACH (hmapx_node, &data->tracked_data.crupdated) {
-            lr_lb_nat_data_build_vip_nats(hmapx_node->data);
+            lr_lbnat_rec = hmapx_node->data;
+            lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
+            lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
         }
 
         data->tracked = true;
@@ -523,6 +525,8 @@  lr_lb_nat_data_record_init(struct lr_lb_nat_data_record *lr_lbnat_rec,
     if (!nbr->n_nat) {
         lr_lb_nat_data_build_vip_nats(lr_lbnat_rec);
     }
+
+    lr_lbnat_rec->has_lb_vip = od_has_lb_vip(lr_lbnat_rec->od);
 }
 
 static struct lr_lb_nat_data_input
diff --git a/northd/en-lr-lb-nat-data.h b/northd/en-lr-lb-nat-data.h
index ffe41cad73..ac21d28a57 100644
--- a/northd/en-lr-lb-nat-data.h
+++ b/northd/en-lr-lb-nat-data.h
@@ -39,6 +39,8 @@  struct lr_lb_nat_data_record {
     const struct ovn_datapath *od;
     const struct lr_nat_record *lrnat_rec;
 
+    bool has_lb_vip;
+
     /* Load Balancer vIPs relevant for this datapath. */
     struct ovn_lb_ip_set *lb_ips;
 
diff --git a/northd/en-ls-lb-acls.c b/northd/en-ls-lb-acls.c
new file mode 100644
index 0000000000..1ba7ecb3e2
--- /dev/null
+++ b/northd/en-ls-lb-acls.c
@@ -0,0 +1,527 @@ 
+/*
+ * Copyright (c) 2023, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* OVS includes */
+#include "include/openvswitch/hmap.h"
+#include "lib/bitmap.h"
+#include "lib/socket-util.h"
+#include "lib/uuidset.h"
+#include "openvswitch/util.h"
+#include "openvswitch/vlog.h"
+#include "stopwatch.h"
+
+/* OVN includes */
+#include "en-lb-data.h"
+#include "en-ls-lb-acls.h"
+#include "en-port-group.h"
+#include "lib/inc-proc-eng.h"
+#include "lib/lb.h"
+#include "lib/ovn-nb-idl.h"
+#include "lib/ovn-sb-idl.h"
+#include "lib/ovn-util.h"
+#include "lib/stopwatch-names.h"
+#include "northd.h"
+
+VLOG_DEFINE_THIS_MODULE(en_ls_lbacls);
+
+/* Static function declarations. */
+static void ls_lbacls_table_init(struct ls_lbacls_table *);
+static void ls_lbacls_table_clear(struct ls_lbacls_table *);
+static void ls_lbacls_table_destroy(struct ls_lbacls_table *);
+static struct ls_lbacls_record *ls_lbacls_table_find_(
+    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
+static void ls_lbacls_table_build(struct ls_lbacls_table *,
+                                  const struct ovn_datapaths *ls_datapaths,
+                                  const struct ls_port_group_table *);
+
+static struct ls_lbacls_input ls_lbacls_get_input_data(
+    struct engine_node *);
+
+static struct ls_lbacls_record *ls_lbacls_record_create(
+    struct ls_lbacls_table *,
+    const struct ovn_datapath *,
+    const struct ls_port_group_table *);
+static void ls_lbacls_record_destroy(struct ls_lbacls_record *);
+static void ls_lbacls_record_init(
+    struct ls_lbacls_record *,
+    const struct ovn_datapath *,
+    const struct ls_port_group *,
+    const struct ls_port_group_table *);
+static void ls_lbacls_record_reinit(
+    struct ls_lbacls_record *,
+    const struct ls_port_group *,
+    const struct ls_port_group_table *);
+static bool ls_has_lb_vip(const struct ovn_datapath *);
+static void ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *,
+                                           const struct ovn_datapath *,
+                                           const struct ls_port_group *,
+                                           const struct ls_port_group_table *);
+static bool ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *,
+                                            struct nbrec_acl **,
+                                            size_t n_acls);
+static struct ls_lbacls_input ls_lbacls_get_input_data(struct engine_node *);
+static bool is_ls_acls_changed(const struct nbrec_logical_switch *);
+static bool is_acls_seqno_changed(struct nbrec_acl **, size_t n_nb_acls);
+
+
+/* public functions. */
+const struct ls_lbacls_record *
+ls_lbacls_table_find(
+    const struct ls_lbacls_table *table,
+    const struct nbrec_logical_switch *nbs)
+{
+    return ls_lbacls_table_find_(table, nbs);
+}
+
+void *
+en_ls_lbacls_init(struct engine_node *node OVS_UNUSED,
+                  struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_ls_lbacls *data = xzalloc(sizeof *data);
+    ls_lbacls_table_init(&data->ls_lbacls);
+    hmapx_init(&data->tracked_data.crupdated);
+    hmapx_init(&data->tracked_data.deleted);
+    return data;
+}
+
+void
+en_ls_lbacls_cleanup(void *data_)
+{
+    struct ed_type_ls_lbacls *data =
+        (struct ed_type_ls_lbacls *) data_;
+    ls_lbacls_table_destroy(&data->ls_lbacls);
+    hmapx_destroy(&data->tracked_data.crupdated);
+    hmapx_destroy(&data->tracked_data.deleted);
+}
+
+void
+en_ls_lbacls_clear_tracked_data(void *data_)
+{
+    struct ed_type_ls_lbacls *data =
+        (struct ed_type_ls_lbacls *) data_;
+
+    struct hmapx_node *hmapx_node;
+    HMAPX_FOR_EACH_SAFE (hmapx_node, &data->tracked_data.deleted) {
+        ls_lbacls_record_destroy(hmapx_node->data);
+        hmapx_delete(&data->tracked_data.deleted, hmapx_node);
+    }
+
+    hmapx_clear(&data->tracked_data.crupdated);
+    data->tracked = false;
+}
+
+void
+en_ls_lbacls_run(struct engine_node *node, void *data_)
+{
+    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
+    struct ed_type_ls_lbacls *data = data_;
+
+    stopwatch_start(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
+
+    ls_lbacls_table_clear(&data->ls_lbacls);
+    ls_lbacls_table_build(&data->ls_lbacls, input_data.ls_datapaths,
+                          input_data.ls_port_groups);
+
+    data->tracked = false;
+    stopwatch_stop(LS_LBACLS_RUN_STOPWATCH_NAME, time_msec());
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+/* Handler functions. */
+bool
+ls_lbacls_northd_handler(struct engine_node *node, void *data_)
+{
+    struct northd_data *northd_data = engine_get_input_data("northd", node);
+    if (!northd_data->change_tracked) {
+        return false;
+    }
+
+    struct northd_tracked_data *nd_changes = &northd_data->trk_northd_changes;
+    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
+    struct ls_lbacls_record *ls_lbacls_rec;
+    struct ed_type_ls_lbacls *data = data_;
+    const struct ovn_datapath *od;
+    struct hmapx_node *hmapx_node;
+
+    HMAPX_FOR_EACH (hmapx_node, &nd_changes->ls_with_changed_lbs.crupdated) {
+        od = hmapx_node->data;
+
+        ls_lbacls_rec = ls_lbacls_table_find_(&data->ls_lbacls, od->nbs);
+        if (!ls_lbacls_rec) {
+            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
+                                                    input_data.ls_port_groups);
+        } else {
+            ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
+                                    input_data.ls_port_groups);
+        }
+
+        /* Add the ls_lbacls_rec to the tracking data. */
+        hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
+    }
+
+    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
+        data->tracked = true;
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+bool
+ls_lbacls_port_group_handler(struct engine_node *node, void *data_)
+{
+    struct port_group_data *pg_data =
+        engine_get_input_data("port_group", node);
+
+    if (pg_data->ls_port_groups_sets_changed) {
+        return false;
+    }
+
+    /* port_group engine node doesn't provide the tracking data yet.
+     * Loop through all the ls port groups and update the ls_lbacls_rec.
+     * This is still better than returning false. */
+    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
+    struct ed_type_ls_lbacls *data = data_;
+    const struct ls_port_group *ls_pg;
+
+    LS_PORT_GROUP_TABLE_FOR_EACH (ls_pg, input_data.ls_port_groups) {
+        struct ls_lbacls_record *ls_lbacls_rec =
+            ls_lbacls_table_find_(&data->ls_lbacls, ls_pg->nbs);
+
+        bool modified = false;
+        if (!ls_lbacls_rec) {
+            const struct ovn_datapath *od;
+            od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
+                                    &ls_pg->nbs->header_.uuid);
+            ovs_assert(od);
+            ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
+                                                input_data.ls_port_groups);
+            modified = true;
+        } else {
+            bool had_stateful_acl = ls_lbacls_rec->has_stateful_acl;
+            uint64_t max_acl_tier = ls_lbacls_rec->max_acl_tier;
+            bool had_acls = ls_lbacls_rec->has_acls;
+
+            ls_lbacls_record_reinit(ls_lbacls_rec, ls_pg,
+                                    input_data.ls_port_groups);
+
+            if ((had_stateful_acl != ls_lbacls_rec->has_stateful_acl)
+                || (had_acls != ls_lbacls_rec->has_acls)
+                || max_acl_tier != ls_lbacls_rec->max_acl_tier) {
+                modified = true;
+            }
+        }
+
+        if (modified) {
+            /* Add the ls_lbacls_rec to the tracking data. */
+            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
+        }
+    }
+
+    if (!hmapx_is_empty(&data->tracked_data.crupdated)) {
+        data->tracked = true;
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+bool
+ls_lbacls_logical_switch_handler(struct engine_node *node, void *data_)
+{
+    struct ls_lbacls_input input_data = ls_lbacls_get_input_data(node);
+    const struct nbrec_logical_switch *nbs;
+    struct ed_type_ls_lbacls *data = data_;
+
+    NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (nbs,
+                                    input_data.nbrec_logical_switch_table) {
+        if (!is_ls_acls_changed(nbs)) {
+            continue;
+        }
+
+        struct ls_lbacls_record *ls_lbacls_rec =
+            ls_lbacls_table_find_(&data->ls_lbacls, nbs);
+
+        if (nbrec_logical_switch_is_deleted(nbs)) {
+            if (ls_lbacls_rec) {
+                /* Remove the record from the entries. */
+                hmap_remove(&data->ls_lbacls.entries,
+                            &ls_lbacls_rec->key_node);
+
+                /* Add the ls_lbacls_rec to the tracking data. */
+                hmapx_add(&data->tracked_data.deleted, ls_lbacls_rec);
+            }
+        } else {
+            if (!ls_lbacls_rec) {
+                const struct ovn_datapath *od;
+                od = ovn_datapath_find(&input_data.ls_datapaths->datapaths,
+                                       &nbs->header_.uuid);
+                ovs_assert(od);
+                ls_lbacls_rec = ls_lbacls_record_create(&data->ls_lbacls, od,
+                                                    input_data.ls_port_groups);
+            } else {
+                ls_lbacls_record_reinit(ls_lbacls_rec, NULL,
+                                        input_data.ls_port_groups);
+            }
+
+            /* Add the ls_lbacls_rec to the tracking data. */
+            hmapx_add(&data->tracked_data.crupdated, ls_lbacls_rec);
+        }
+    }
+
+    if (!hmapx_is_empty(&data->tracked_data.crupdated)
+        || !hmapx_is_empty(&data->tracked_data.deleted)) {
+        data->tracked = true;
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+/* static functions. */
+static void
+ls_lbacls_table_init(struct ls_lbacls_table *table)
+{
+    *table = (struct ls_lbacls_table) {
+        .entries = HMAP_INITIALIZER(&table->entries),
+    };
+}
+
+static void
+ls_lbacls_table_destroy(struct ls_lbacls_table *table)
+{
+    ls_lbacls_table_clear(table);
+    hmap_destroy(&table->entries);
+}
+
+static void
+ls_lbacls_table_clear(struct ls_lbacls_table *table)
+{
+    struct ls_lbacls_record *ls_lbacls_rec;
+    HMAP_FOR_EACH_POP (ls_lbacls_rec, key_node, &table->entries) {
+        ls_lbacls_record_destroy(ls_lbacls_rec);
+    }
+}
+
+static void
+ls_lbacls_table_build(struct ls_lbacls_table *table,
+                      const struct ovn_datapaths *ls_datapaths,
+                      const struct ls_port_group_table *ls_pgs)
+{
+    const struct ovn_datapath *od;
+    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
+        ls_lbacls_record_create(table, od, ls_pgs);
+    }
+}
+
+struct ls_lbacls_record *
+ls_lbacls_table_find_(const struct ls_lbacls_table *table,
+                      const struct nbrec_logical_switch *nbs)
+{
+    struct ls_lbacls_record *rec;
+
+    HMAP_FOR_EACH_WITH_HASH (rec, key_node,
+                             uuid_hash(&nbs->header_.uuid), &table->entries) {
+        if (nbs == rec->od->nbs) {
+            return rec;
+        }
+    }
+    return NULL;
+}
+
+static struct ls_lbacls_record *
+ls_lbacls_record_create(struct ls_lbacls_table *table,
+                        const struct ovn_datapath *od,
+                        const struct ls_port_group_table *ls_pgs)
+{
+    struct ls_lbacls_record *ls_lbacls_rec = xzalloc(sizeof *ls_lbacls_rec);
+    ls_lbacls_rec->od = od;
+    ls_lbacls_record_init(ls_lbacls_rec, od, NULL, ls_pgs);
+
+    hmap_insert(&table->entries, &ls_lbacls_rec->key_node,
+                uuid_hash(&ls_lbacls_rec->od->nbs->header_.uuid));
+
+    return ls_lbacls_rec;
+}
+
+static void
+ls_lbacls_record_destroy(struct ls_lbacls_record *ls_lbacls_rec)
+{
+    free(ls_lbacls_rec);
+}
+
+static void
+ls_lbacls_record_init(struct ls_lbacls_record *ls_lbacls_rec,
+                      const struct ovn_datapath *od,
+                      const struct ls_port_group *ls_pg,
+                      const struct ls_port_group_table *ls_pgs)
+{
+    ls_lbacls_rec->has_lb_vip = ls_has_lb_vip(od);
+    ls_lbacls_record_set_acl_flags(ls_lbacls_rec, od, ls_pg, ls_pgs);
+}
+
+static void
+ls_lbacls_record_reinit(struct ls_lbacls_record *ls_lbacls_rec,
+                        const struct ls_port_group *ls_pg,
+                        const struct ls_port_group_table *ls_pgs)
+{
+    ls_lbacls_record_init(ls_lbacls_rec, ls_lbacls_rec->od, ls_pg, ls_pgs);
+}
+
+static bool
+lb_has_vip(const struct nbrec_load_balancer *lb)
+{
+    return !smap_is_empty(&lb->vips);
+}
+
+static bool
+lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
+{
+    for (size_t i = 0; i < lb_group->n_load_balancer; i++) {
+        if (lb_has_vip(lb_group->load_balancer[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+ls_has_lb_vip(const struct ovn_datapath *od)
+{
+    for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
+        if (lb_has_vip(od->nbs->load_balancer[i])) {
+            return true;
+        }
+    }
+
+    for (size_t i = 0; i < od->nbs->n_load_balancer_group; i++) {
+        if (lb_group_has_vip(od->nbs->load_balancer_group[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static void
+ls_lbacls_record_set_acl_flags(struct ls_lbacls_record *ls_lbacls_rec,
+                               const struct ovn_datapath *od,
+                               const struct ls_port_group *ls_pg,
+                               const struct ls_port_group_table *ls_pgs)
+{
+    ls_lbacls_rec->has_stateful_acl = false;
+    ls_lbacls_rec->max_acl_tier = 0;
+    ls_lbacls_rec->has_acls = false;
+
+    if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec, od->nbs->acls,
+                                        od->nbs->n_acls)) {
+        return;
+    }
+
+    if (!ls_pg) {
+        ls_pg = ls_port_group_table_find(ls_pgs, od->nbs);
+    }
+
+    if (!ls_pg) {
+        return;
+    }
+
+    const struct ls_port_group_record *ls_pg_rec;
+    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
+        if (ls_lbacls_record_set_acl_flags_(ls_lbacls_rec,
+                                            ls_pg_rec->nb_pg->acls,
+                                            ls_pg_rec->nb_pg->n_acls)) {
+            return;
+        }
+    }
+}
+
+static bool
+ls_lbacls_record_set_acl_flags_(struct ls_lbacls_record *ls_lbacls_rec,
+                                struct nbrec_acl **acls,
+                                size_t n_acls)
+{
+    /* A true return indicates that there are no possible ACL flags
+     * left to set on ls_lbacls record. A false return indicates that
+     * further ACLs should be explored in case more flags need to be
+     * set on ls_lbacls record.
+     */
+    if (!n_acls) {
+        return false;
+    }
+
+    ls_lbacls_rec->has_acls = true;
+    for (size_t i = 0; i < n_acls; i++) {
+        const struct nbrec_acl *acl = acls[i];
+        if (acl->tier > ls_lbacls_rec->max_acl_tier) {
+            ls_lbacls_rec->max_acl_tier = acl->tier;
+        }
+        if (!ls_lbacls_rec->has_stateful_acl
+                && !strcmp(acl->action, "allow-related")) {
+            ls_lbacls_rec->has_stateful_acl = true;
+        }
+        if (ls_lbacls_rec->has_stateful_acl &&
+            ls_lbacls_rec->max_acl_tier ==
+                nbrec_acl_col_tier.type.value.integer.max) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static struct ls_lbacls_input
+ls_lbacls_get_input_data(struct engine_node *node)
+{
+    const struct northd_data *northd_data =
+        engine_get_input_data("northd", node);
+    const struct port_group_data *pg_data =
+        engine_get_input_data("port_group", node);
+
+    return (struct ls_lbacls_input) {
+        .nbrec_logical_switch_table =
+            EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)),
+        .ls_port_groups = &pg_data->ls_port_groups,
+        .ls_datapaths = &northd_data->ls_datapaths,
+    };
+}
+
+static bool
+is_acls_seqno_changed(struct nbrec_acl **nb_acls, size_t n_nb_acls)
+{
+    for (size_t i = 0; i < n_nb_acls; i++) {
+        if (nbrec_acl_row_get_seqno(nb_acls[i],
+                                    OVSDB_IDL_CHANGE_MODIFY) > 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool
+is_ls_acls_changed(const struct nbrec_logical_switch *nbs) {
+    return (nbrec_logical_switch_is_new(nbs)
+            || nbrec_logical_switch_is_deleted(nbs)
+            || nbrec_logical_switch_is_updated(nbs,
+                                               NBREC_LOGICAL_SWITCH_COL_ACLS)
+            || is_acls_seqno_changed(nbs->acls, nbs->n_acls));
+}
diff --git a/northd/en-ls-lb-acls.h b/northd/en-ls-lb-acls.h
new file mode 100644
index 0000000000..ccb75e40e8
--- /dev/null
+++ b/northd/en-ls-lb-acls.h
@@ -0,0 +1,88 @@ 
+/*
+ * Copyright (c) 2023, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef EN_LS_LB_ACL_H
+#define EN_LS_LB_ACL_H 1
+
+#include <stdint.h>
+
+/* OVS includes. */
+#include "lib/hmapx.h"
+#include "openvswitch/hmap.h"
+#include "sset.h"
+
+/* OVN includes. */
+#include "lib/inc-proc-eng.h"
+#include "lib/lb.h"
+#include "lib/ovn-nb-idl.h"
+#include "lib/ovn-sb-idl.h"
+#include "lib/ovn-util.h"
+#include "lib/stopwatch-names.h"
+
+struct ls_lbacls_record {
+    struct hmap_node key_node;
+
+    const struct ovn_datapath *od;
+    bool has_stateful_acl;
+    bool has_lb_vip;
+    bool has_acls;
+    uint64_t max_acl_tier;
+};
+
+struct ls_lbacls_table {
+    struct hmap entries;
+};
+
+#define LS_LBACLS_TABLE_FOR_EACH(LS_LBACLS_REC, TABLE) \
+    HMAP_FOR_EACH (LS_LBACLS_REC, key_node, &(TABLE)->entries)
+
+#define LS_LBACLS_TABLE_FOR_EACH_IN_P(LS_LBACLS_REC, JOBID, TABLE) \
+    HMAP_FOR_EACH_IN_PARALLEL (LS_LBACLS_REC, key_node, JOBID, \
+                               &(TABLE)->entries)
+
+struct ls_lbacls_tracked_data {
+    /* Created or updated logical switch with LB and ACL data. */
+    struct hmapx crupdated; /* Stores 'struct ls_lbacls_record'. */
+
+    /* Deleted logical switch with LB and ACL data. */
+    struct hmapx deleted; /* Stores 'struct ls_lbacls_record'. */
+};
+
+struct ed_type_ls_lbacls {
+    struct ls_lbacls_table ls_lbacls;
+
+    bool tracked;
+    struct ls_lbacls_tracked_data tracked_data;
+};
+
+struct ls_lbacls_input {
+    const struct nbrec_logical_switch_table *nbrec_logical_switch_table;
+    const struct ls_port_group_table *ls_port_groups;
+    const struct ovn_datapaths *ls_datapaths;
+};
+
+void *en_ls_lbacls_init(struct engine_node *, struct engine_arg *);
+void en_ls_lbacls_cleanup(void *data);
+void en_ls_lbacls_clear_tracked_data(void *data);
+void en_ls_lbacls_run(struct engine_node *, void *data);
+
+bool ls_lbacls_northd_handler(struct engine_node *, void *data);
+bool ls_lbacls_port_group_handler(struct engine_node *, void *data);
+bool ls_lbacls_logical_switch_handler(struct engine_node *, void *data);
+
+const struct ls_lbacls_record *ls_lbacls_table_find(
+    const struct ls_lbacls_table *, const struct nbrec_logical_switch *);
+
+#endif /* EN_LS_LB_ACL_H */
diff --git a/northd/en-port-group.h b/northd/en-port-group.h
index 3b28a23694..54014062ce 100644
--- a/northd/en-port-group.h
+++ b/northd/en-port-group.h
@@ -48,6 +48,9 @@  struct ls_port_group_record {
     struct sset ports;          /* Subset of 'nb_pg' ports in this record. */
 };
 
+#define LS_PORT_GROUP_TABLE_FOR_EACH(LS_PG, TABLE) \
+    HMAP_FOR_EACH (LS_PG, key_node, &(TABLE)->entries)
+
 void ls_port_group_table_init(struct ls_port_group_table *);
 void ls_port_group_table_clear(struct ls_port_group_table *);
 void ls_port_group_table_destroy(struct ls_port_group_table *);
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 84627070a8..ab4af92aeb 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -33,6 +33,7 @@ 
 #include "en-lb-data.h"
 #include "en-lr-lb-nat-data.h"
 #include "en-lr-nat.h"
+#include "en-ls-lb-acls.h"
 #include "en-northd.h"
 #include "en-lflow.h"
 #include "en-northd-output.h"
@@ -150,6 +151,7 @@  static ENGINE_NODE(sync_to_sb_pb, "sync_to_sb_pb");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lb_data, "lb_data");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_nat, "lr_nat");
 static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(lr_lb_nat_data, "lr_lb_nat_data");
+static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(ls_lbacls, "ls_lbacls");
 
 void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                           struct ovsdb_idl_loop *sb)
@@ -205,6 +207,12 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_lr_lb_nat_data, &en_lb_data,
                      lr_lb_nat_data_lb_data_handler);
 
+    engine_add_input(&en_ls_lbacls, &en_northd, ls_lbacls_northd_handler);
+    engine_add_input(&en_ls_lbacls, &en_port_group,
+                     ls_lbacls_port_group_handler);
+    engine_add_input(&en_ls_lbacls, &en_nb_logical_switch,
+                     ls_lbacls_logical_switch_handler);
+
     engine_add_input(&en_mac_binding_aging, &en_nb_nb_global, NULL);
     engine_add_input(&en_mac_binding_aging, &en_sb_mac_binding, NULL);
     engine_add_input(&en_mac_binding_aging, &en_northd, NULL);
@@ -229,6 +237,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_lflow, &en_northd, lflow_northd_handler);
     engine_add_input(&en_lflow, &en_port_group, lflow_port_group_handler);
     engine_add_input(&en_lflow, &en_lr_lb_nat_data, NULL);
+    engine_add_input(&en_lflow, &en_ls_lbacls, NULL);
 
     engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
                      sync_to_sb_addr_set_nb_address_set_handler);
diff --git a/northd/northd.c b/northd/northd.c
index c8a224d3cd..924f5cd7e0 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -45,6 +45,7 @@ 
 #include "en-lb-data.h"
 #include "en-lr-nat.h"
 #include "en-lr-lb-nat-data.h"
+#include "en-ls-lb-acls.h"
 #include "lib/ovn-parallel-hmap.h"
 #include "ovn/actions.h"
 #include "ovn/features.h"
@@ -575,7 +576,7 @@  lb_group_has_vip(const struct nbrec_load_balancer_group *lb_group)
 }
 
 static bool
-ls_has_lb_vip(struct ovn_datapath *od)
+ls_has_lb_vip(const struct ovn_datapath *od)
 {
     for (size_t i = 0; i < od->nbs->n_load_balancer; i++) {
         if (lb_has_vip(od->nbs->load_balancer[i])) {
@@ -592,7 +593,7 @@  ls_has_lb_vip(struct ovn_datapath *od)
 }
 
 static bool
-lr_has_lb_vip(struct ovn_datapath *od)
+lr_has_lb_vip(const struct ovn_datapath *od)
 {
     for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
         if (lb_has_vip(od->nbr->load_balancer[i])) {
@@ -608,13 +609,13 @@  lr_has_lb_vip(struct ovn_datapath *od)
     return false;
 }
 
-static void
-init_lb_for_datapath(struct ovn_datapath *od)
+bool
+od_has_lb_vip(const struct ovn_datapath *od)
 {
     if (od->nbs) {
-        od->has_lb_vip = ls_has_lb_vip(od);
+        return ls_has_lb_vip(od);
     } else {
-        od->has_lb_vip = lr_has_lb_vip(od);
+        return lr_has_lb_vip(od);
     }
 }
 
@@ -1058,7 +1059,6 @@  join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
 
         init_ipam_info_for_datapath(od);
         init_mcast_info_for_datapath(od);
-        init_lb_for_datapath(od);
     }
 
     const struct nbrec_logical_router *nbr;
@@ -1089,7 +1089,6 @@  join_datapaths(const struct nbrec_logical_switch_table *nbrec_ls_table,
             ovs_list_push_back(nb_only, &od->list);
         }
         init_mcast_info_for_datapath(od);
-        init_lb_for_datapath(od);
         if (smap_get(&od->nbr->options, "chassis")) {
             od->is_gw_router = true;
         }
@@ -2570,7 +2569,8 @@  get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
     size_t n_nats = 0;
     struct eth_addr mac;
     if (!op || !op->nbrp || !op->od || !op->od->nbr
-        || (!op->od->nbr->n_nat && !op->od->has_lb_vip)
+        || (!op->od->nbr->n_nat && (!lr_lbnat_rec
+                                    || !lr_lbnat_rec->has_lb_vip))
         || !eth_addr_from_string(op->nbrp->mac, &mac)) {
         *n = n_nats;
         return NULL;
@@ -3817,7 +3817,7 @@  build_lrouter_lbs_check(const struct ovn_datapaths *lr_datapaths)
     HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
         ovs_assert(od->nbr);
 
-        if (od->has_lb_vip && od->n_l3dgw_ports > 1
+        if (od_has_lb_vip(od) && od->n_l3dgw_ports > 1
                 && !smap_get(&od->nbr->options, "chassis")) {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
             VLOG_WARN_RL(&rl, "Load-balancers are configured on logical "
@@ -5441,7 +5441,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
         BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
                            lb_dps->nb_ls_map) {
             od = ls_datapaths->array[index];
-            init_lb_for_datapath(od);
 
             /* Add the ls datapath to the northd tracked data. */
             hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
@@ -5524,9 +5523,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             }
         }
 
-        /* Re-evaluate 'od->has_lb_vip' */
-        init_lb_for_datapath(od);
-
         /* Add the ls datapath to the northd tracked data. */
         hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
     }
@@ -5564,9 +5560,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             }
         }
 
-        /* Re-evaluate 'od->has_lb_vip' */
-        init_lb_for_datapath(od);
-
         /* Add the lr datapath to the northd tracked data. */
         hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
     }
@@ -5581,8 +5574,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
         BITMAP_FOR_EACH_1 (index, ods_size(ls_datapaths),
                            lb_dps->nb_ls_map) {
             od = ls_datapaths->array[index];
-            /* Re-evaluate 'od->has_lb_vip' */
-            init_lb_for_datapath(od);
 
             /* Add the ls datapath to the northd tracked data. */
             hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
@@ -5591,8 +5582,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
         BITMAP_FOR_EACH_1 (index, ods_size(lr_datapaths),
                            lb_dps->nb_lr_map) {
             od = lr_datapaths->array[index];
-            /* Re-evaluate 'od->has_lb_vip' */
-            init_lb_for_datapath(od);
 
             /* Add the lr datapath to the northd tracked data. */
             hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
@@ -5618,9 +5607,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                 od = lbgrp_dps->lr[i];
                 ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
 
-                /* Re-evaluate 'od->has_lb_vip' */
-                init_lb_for_datapath(od);
-
                 /* Add the lr datapath to the northd tracked data. */
                 hmapx_add(&nd_changes->lr_with_changed_lbs.crupdated, od);
             }
@@ -5629,9 +5615,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                od = lbgrp_dps->ls[i];
                 ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
 
-                /* Re-evaluate 'od->has_lb_vip' */
-                init_lb_for_datapath(od);
-
                 /* Add the ls datapath to the northd tracked data. */
                 hmapx_add(&nd_changes->ls_with_changed_lbs.crupdated, od);
             }
@@ -6573,63 +6556,6 @@  build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip,
     return true;
 }
 
-static bool
-od_set_acl_flags(struct ovn_datapath *od, struct nbrec_acl **acls,
-                 size_t n_acls)
-{
-    /* A true return indicates that there are no possible ACL flags
-     * left to set on od. A false return indicates that further ACLs
-     * should be explored in case more flags need to be set on od
-     */
-    if (!n_acls) {
-        return false;
-    }
-
-    od->has_acls = true;
-    for (size_t i = 0; i < n_acls; i++) {
-        const struct nbrec_acl *acl = acls[i];
-        if (acl->tier > od->max_acl_tier) {
-            od->max_acl_tier = acl->tier;
-        }
-        if (!od->has_stateful_acl && !strcmp(acl->action, "allow-related")) {
-            od->has_stateful_acl = true;
-        }
-        if (od->has_stateful_acl &&
-            od->max_acl_tier == nbrec_acl_col_tier.type.value.integer.max) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-static void
-ls_get_acl_flags(struct ovn_datapath *od,
-                 const struct ls_port_group_table *ls_port_groups)
-{
-    od->has_acls = false;
-    od->has_stateful_acl = false;
-    od->max_acl_tier = 0;
-
-    if (od_set_acl_flags(od, od->nbs->acls, od->nbs->n_acls)) {
-        return;
-    }
-
-    const struct ls_port_group *ls_pg =
-        ls_port_group_table_find(ls_port_groups, od->nbs);
-    if (!ls_pg) {
-        return;
-    }
-
-    const struct ls_port_group_record *ls_pg_rec;
-    HMAP_FOR_EACH (ls_pg_rec, key_node, &ls_pg->nb_pgs) {
-        if (od_set_acl_flags(od, ls_pg_rec->nb_pg->acls,
-                             ls_pg_rec->nb_pg->n_acls)) {
-            return;
-        }
-    }
-}
-
 /* Adds the logical flows in the (in/out) check port sec stage only if
  *   - the lport is disabled or
  *   - lport is of type vtep - to skip the ingress pipeline.
@@ -6774,9 +6700,10 @@  build_lswitch_output_port_sec_od(struct ovn_datapath *od,
 }
 
 static void
-skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
-                         enum ovn_stage in_stage, enum ovn_stage out_stage,
-                         uint16_t priority, struct hmap *lflows)
+skip_port_from_conntrack(const struct ovn_datapath *od, struct ovn_port *op,
+                         bool has_stateful_acl, enum ovn_stage in_stage,
+                         enum ovn_stage out_stage, uint16_t priority,
+                         struct hmap *lflows)
 {
     /* Can't use ct() for router ports. Consider the following configuration:
      * lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB, For a
@@ -6789,7 +6716,7 @@  skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
      * conntrack state across all chassis. */
 
     const char *ingress_action = "next;";
-    const char *egress_action = od->has_stateful_acl
+    const char *egress_action = has_stateful_acl
                                 ? "next;"
                                 : "ct_clear; next;";
 
@@ -6808,7 +6735,7 @@  skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op,
 }
 
 static void
-build_stateless_filter(struct ovn_datapath *od,
+build_stateless_filter(const struct ovn_datapath *od,
                        const struct nbrec_acl *acl,
                        struct hmap *lflows)
 {
@@ -6829,7 +6756,7 @@  build_stateless_filter(struct ovn_datapath *od,
 }
 
 static void
-build_stateless_filters(struct ovn_datapath *od,
+build_stateless_filters(const struct ovn_datapath *od,
                         const struct ls_port_group_table *ls_port_groups,
                         struct hmap *lflows)
 {
@@ -6859,9 +6786,7 @@  build_stateless_filters(struct ovn_datapath *od,
 }
 
 static void
-build_pre_acls(struct ovn_datapath *od,
-               const struct ls_port_group_table *ls_port_groups,
-               struct hmap *lflows)
+build_pre_acls(struct ovn_datapath *od, struct hmap *lflows)
 {
     /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
      * allowed by default. */
@@ -6873,18 +6798,26 @@  build_pre_acls(struct ovn_datapath *od,
 
     ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
                   "eth.src == $svc_monitor_mac", "next;");
+}
+
+static void
+build_ls_lbacls_rec_pre_acls(const struct ls_lbacls_record *ls_lbacls_rec,
+                             const struct ls_port_group_table *ls_port_groups,
+                             struct hmap *lflows)
+{
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
 
     /* If there are any stateful ACL rules in this datapath, we may
      * send IP packets for some (allow) filters through the conntrack action,
      * which handles defragmentation, in order to match L4 headers. */
-    if (od->has_stateful_acl) {
+    if (ls_lbacls_rec->has_stateful_acl) {
         for (size_t i = 0; i < od->n_router_ports; i++) {
-            skip_port_from_conntrack(od, od->router_ports[i],
+            skip_port_from_conntrack(od, od->router_ports[i], true,
                                      S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL,
                                      110, lflows);
         }
         for (size_t i = 0; i < od->n_localnet_ports; i++) {
-            skip_port_from_conntrack(od, od->localnet_ports[i],
+            skip_port_from_conntrack(od, od->localnet_ports[i], true,
                                      S_SWITCH_IN_PRE_ACL,
                                      S_SWITCH_OUT_PRE_ACL,
                                      110, lflows);
@@ -6922,7 +6855,7 @@  build_pre_acls(struct ovn_datapath *od,
                       REGBIT_CONNTRACK_DEFRAG" = 1; next;");
         ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip",
                       REGBIT_CONNTRACK_DEFRAG" = 1; next;");
-    } else if (od->has_lb_vip) {
+    } else if (ls_lbacls_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);
@@ -7050,30 +6983,40 @@  build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
     ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 0, "1", "next;");
     ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 0, "1", "next;");
 
+    /* Do not send statless flows via conntrack */
+    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
+                  REGBIT_ACL_STATELESS" == 1", "next;");
+    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
+                  REGBIT_ACL_STATELESS" == 1", "next;");
+}
+
+static void
+build_ls_lbacls_rec_pre_lb(const struct ls_lbacls_record *ls_lbacls_rec,
+                           struct hmap *lflows)
+{
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
+
     for (size_t i = 0; i < od->n_router_ports; i++) {
         skip_port_from_conntrack(od, od->router_ports[i],
+                                 ls_lbacls_rec->has_stateful_acl,
                                  S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
                                  110, lflows);
     }
+
     /* Localnet ports have no need for going through conntrack, unless
      * the logical switch has a load balancer. Then, conntrack is necessary
      * so that traffic arriving via the localnet port can be load
      * balanced.
      */
-    if (!od->has_lb_vip) {
+    if (!ls_lbacls_rec->has_lb_vip) {
         for (size_t i = 0; i < od->n_localnet_ports; i++) {
             skip_port_from_conntrack(od, od->localnet_ports[i],
+                                     ls_lbacls_rec->has_stateful_acl,
                                      S_SWITCH_IN_PRE_LB, S_SWITCH_OUT_PRE_LB,
                                      110, lflows);
         }
     }
 
-    /* Do not sent statless flows via conntrack */
-    ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 110,
-                  REGBIT_ACL_STATELESS" == 1", "next;");
-    ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, 110,
-                  REGBIT_ACL_STATELESS" == 1", "next;");
-
     /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send
      * packet to conntrack for defragmentation and possibly for unNATting.
      *
@@ -7104,7 +7047,7 @@  build_pre_lb(struct ovn_datapath *od, const struct shash *meter_groups,
      * ingress pipeline if a load balancer is configured. We can now
      * add a lflow to drop ct.inv packets.
      */
-    if (od->has_lb_vip) {
+    if (ls_lbacls_rec->has_lb_vip) {
         ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB,
                       100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;");
         ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB,
@@ -7145,10 +7088,12 @@  build_pre_stateful(struct ovn_datapath *od,
 }
 
 static void
-build_acl_hints(struct ovn_datapath *od,
+build_acl_hints(const struct ls_lbacls_record *ls_lbacls_rec,
                 const struct chassis_features *features,
                 struct hmap *lflows)
 {
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
+
     /* This stage builds hints for the IN/OUT_ACL stage. Based on various
      * combinations of ct flags packets may hit only a subset of the logical
      * flows in the IN/OUT_ACL stage.
@@ -7172,13 +7117,13 @@  build_acl_hints(struct ovn_datapath *od,
         const char *match;
 
         /* In any case, advance to the next stage. */
-        if (!od->has_acls && !od->has_lb_vip) {
+        if (!ls_lbacls_rec->has_acls && !ls_lbacls_rec->has_lb_vip) {
             ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;");
         } else {
             ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
         }
 
-        if (!od->has_stateful_acl && !od->has_lb_vip) {
+        if (!ls_lbacls_rec->has_stateful_acl && !ls_lbacls_rec->has_lb_vip) {
             continue;
         }
 
@@ -7314,10 +7259,10 @@  build_acl_log(struct ds *actions, const struct nbrec_acl *acl,
 }
 
 static void
-consider_acl(struct hmap *lflows, struct ovn_datapath *od,
+consider_acl(struct hmap *lflows, const struct ovn_datapath *od,
              const struct nbrec_acl *acl, bool has_stateful,
              bool ct_masked_mark, const struct shash *meter_groups,
-             struct ds *match, struct ds *actions)
+             uint64_t max_acl_tier, struct ds *match, struct ds *actions)
 {
     const char *ct_blocked_match = ct_masked_mark
                                    ? "ct_mark.blocked"
@@ -7354,7 +7299,7 @@  consider_acl(struct hmap *lflows, struct ovn_datapath *od,
     /* All ACLS will start by matching on their respective tier. */
     size_t match_tier_len = 0;
     ds_clear(match);
-    if (od->max_acl_tier) {
+    if (max_acl_tier) {
         ds_put_format(match, REG_ACL_TIER " == %"PRId64" && ", acl->tier);
         match_tier_len = match->length;
     }
@@ -7543,12 +7488,15 @@  ovn_update_ipv6_options(struct hmap *lr_ports)
 #define IPV6_CT_OMIT_MATCH "nd || nd_ra || nd_rs || mldv1 || mldv2"
 
 static void
-build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
+build_acl_action_lflows(const struct ls_lbacls_record *ls_lbacls_rec,
+                        struct hmap *lflows,
                         const char *default_acl_action,
                         const struct shash *meter_groups,
                         struct ds *match,
                         struct ds *actions)
 {
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
+
     enum ovn_stage stages [] = {
         S_SWITCH_IN_ACL_ACTION,
         S_SWITCH_IN_ACL_AFTER_LB_ACTION,
@@ -7559,7 +7507,7 @@  build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
     ds_put_cstr(actions, REGBIT_ACL_VERDICT_ALLOW " = 0; "
                         REGBIT_ACL_VERDICT_DROP " = 0; "
                         REGBIT_ACL_VERDICT_REJECT " = 0; ");
-    if (od->max_acl_tier) {
+    if (ls_lbacls_rec->max_acl_tier) {
         ds_put_cstr(actions, REG_ACL_TIER " = 0; ");
     }
 
@@ -7567,7 +7515,7 @@  build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
 
     for (size_t i = 0; i < ARRAY_SIZE(stages); i++) {
         enum ovn_stage stage = stages[i];
-        if (!od->has_acls) {
+        if (!ls_lbacls_rec->has_acls) {
             ovn_lflow_add(lflows, od, stage, 0, "1", "next;");
             continue;
         }
@@ -7602,7 +7550,7 @@  build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
         ovn_lflow_add(lflows, od, stage, 0, "1", ds_cstr(actions));
 
         struct ds tier_actions = DS_EMPTY_INITIALIZER;
-        for (size_t j = 0; j < od->max_acl_tier; j++) {
+        for (size_t j = 0; j < ls_lbacls_rec->max_acl_tier; j++) {
             ds_clear(match);
             ds_put_format(match, REG_ACL_TIER " == %"PRIuSIZE, j);
             ds_clear(&tier_actions);
@@ -7618,7 +7566,7 @@  build_acl_action_lflows(struct ovn_datapath *od, struct hmap *lflows,
 }
 
 static void
-build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
+build_acl_log_related_flows(const struct ovn_datapath *od, struct hmap *lflows,
                             const struct nbrec_acl *acl, bool has_stateful,
                             bool ct_masked_mark,
                             const struct shash *meter_groups,
@@ -7691,15 +7639,19 @@  build_acl_log_related_flows(struct ovn_datapath *od, struct hmap *lflows,
 }
 
 static void
-build_acls(struct ovn_datapath *od, const struct chassis_features *features,
+build_acls(const struct ls_lbacls_record *ls_lbacls_rec,
+           const struct chassis_features *features,
            struct hmap *lflows,
            const struct ls_port_group_table *ls_port_groups,
            const struct shash *meter_groups)
 {
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
+
     const char *default_acl_action = default_acl_drop
                                      ? debug_implicit_drop_action()
                                      : "next;";
-    bool has_stateful = od->has_stateful_acl || od->has_lb_vip;
+    bool has_stateful = (ls_lbacls_rec->has_stateful_acl
+                         || ls_lbacls_rec->has_lb_vip);
     const char *ct_blocked_match = features->ct_no_masked_label
                                    ? "ct_mark.blocked"
                                    : "ct_label.blocked";
@@ -7713,8 +7665,8 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
      *
      * A related rule at priority 1 is added below if there
      * are any stateful ACLs in this datapath. */
-    if (!od->has_acls) {
-        if (!od->has_lb_vip) {
+    if (!ls_lbacls_rec->has_acls) {
+        if (!ls_lbacls_rec->has_lb_vip) {
             ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, UINT16_MAX, "1",
                           "next;");
             ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL_EVAL, UINT16_MAX, "1",
@@ -7877,7 +7829,8 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
                                     meter_groups, &match, &actions);
         consider_acl(lflows, od, acl, has_stateful,
                      features->ct_no_masked_label,
-                     meter_groups, &match, &actions);
+                     meter_groups, ls_lbacls_rec->max_acl_tier,
+                     &match, &actions);
     }
 
     const struct ls_port_group *ls_pg =
@@ -7893,7 +7846,8 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
                                             meter_groups, &match, &actions);
                 consider_acl(lflows, od, acl, has_stateful,
                              features->ct_no_masked_label,
-                             meter_groups, &match, &actions);
+                             meter_groups, ls_lbacls_rec->max_acl_tier,
+                             &match, &actions);
             }
         }
     }
@@ -7911,7 +7865,7 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
             dns_actions);
     }
 
-    if (od->has_acls || od->has_lb_vip) {
+    if (ls_lbacls_rec->has_acls || ls_lbacls_rec->has_lb_vip) {
         /* Add a 34000 priority flow to advance the service monitor reply
         * packets to skip applying ingress ACLs. */
         ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL_EVAL, 34000,
@@ -7925,8 +7879,8 @@  build_acls(struct ovn_datapath *od, const struct chassis_features *features,
                     REGBIT_ACL_VERDICT_ALLOW" = 1; next;");
     }
 
-    build_acl_action_lflows(od, lflows, default_acl_action, meter_groups,
-                            &match, &actions);
+    build_acl_action_lflows(ls_lbacls_rec, lflows, default_acl_action,
+                            meter_groups, &match, &actions);
 
     ds_destroy(&match);
     ds_destroy(&actions);
@@ -8571,8 +8525,11 @@  build_stateful(struct ovn_datapath *od,
 }
 
 static void
-build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
+build_lb_hairpin(const struct ls_lbacls_record *ls_lbacls_rec,
+                 struct hmap *lflows)
 {
+    const struct ovn_datapath *od = ls_lbacls_rec->od;
+
     /* Ingress Pre-Hairpin/Nat-Hairpin/Hairpin tabled (Priority 0).
      * Packets that don't need hairpinning should continue processing.
      */
@@ -8580,7 +8537,7 @@  build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
     ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;");
     ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;");
 
-    if (od->has_lb_vip) {
+    if (ls_lbacls_rec->has_lb_vip) {
         /* Check if the packet needs to be hairpinned.
          * Set REGBIT_HAIRPIN in the original direction and
          * REGBIT_HAIRPIN_REPLY in the reply direction.
@@ -9413,22 +9370,16 @@  build_lswitch_lflows_l2_unknown(struct ovn_datapath *od,
 static void
 build_lswitch_lflows_pre_acl_and_acl(
     struct ovn_datapath *od,
-    const struct ls_port_group_table *ls_port_groups,
     const struct chassis_features *features,
     struct hmap *lflows,
     const struct shash *meter_groups)
 {
     ovs_assert(od->nbs);
-    ls_get_acl_flags(od, ls_port_groups);
-
-    build_pre_acls(od, ls_port_groups, lflows);
+    build_pre_acls(od, lflows);
     build_pre_lb(od, meter_groups, lflows);
     build_pre_stateful(od, features, lflows);
-    build_acl_hints(od, features, lflows);
-    build_acls(od, features, lflows, ls_port_groups, meter_groups);
     build_qos(od, lflows);
     build_stateful(od, features, lflows);
-    build_lb_hairpin(od, lflows);
     build_vtep_hairpin(od, lflows);
 }
 
@@ -15487,7 +15438,7 @@  build_lrouter_nat_defrag_and_lb(
      * a dynamically negotiated FTP data channel), but will allow
      * related traffic such as an ICMP Port Unreachable through
      * that's generated from a non-listening UDP port.  */
-    if (od->has_lb_vip && features->ct_lb_related) {
+    if (lr_lbnat_rec->has_lb_vip && features->ct_lb_related) {
         ds_clear(match);
 
         ds_put_cstr(match, "ct.rel && !ct.est && !ct.new");
@@ -15512,7 +15463,7 @@  build_lrouter_nat_defrag_and_lb(
      * Pass the traffic that is already established to the next table with
      * proper flags set.
      */
-    if (od->has_lb_vip) {
+    if (lr_lbnat_rec->has_lb_vip) {
         ds_clear(match);
 
         ds_put_format(match, "ct.est && !ct.rel && !ct.new && %s.natted",
@@ -15542,7 +15493,7 @@  build_lrouter_nat_defrag_and_lb(
      * not committed, it would produce ongoing datapath flows with the ct.new
      * flag set. Some NICs are unable to offload these flows.
      */
-    if (od->is_gw_router && (od->nbr->n_nat || od->has_lb_vip)) {
+    if (od->is_gw_router && (od->nbr->n_nat || lr_lbnat_rec->has_lb_vip)) {
         /* Do not send ND or ICMP packets to connection tracking. */
         ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100,
                       "nd || nd_rs || nd_ra", "next;");
@@ -15967,6 +15918,22 @@  build_lr_lbnat_data_flows(const struct lr_lb_nat_data_record *lr_lbnat_rec,
                                       meter_groups);
 }
 
+static void
+build_ls_lbacls_flows(const struct ls_lbacls_record *ls_lbacls_rec,
+                      const struct ls_port_group_table *ls_pgs,
+                      const struct chassis_features *features,
+                      const struct shash *meter_groups,
+                      struct hmap *lflows)
+{
+    ovs_assert(ls_lbacls_rec->od);
+
+    build_ls_lbacls_rec_pre_acls(ls_lbacls_rec, ls_pgs, lflows);
+    build_ls_lbacls_rec_pre_lb(ls_lbacls_rec, lflows);
+    build_acl_hints(ls_lbacls_rec, features, lflows);
+    build_acls(ls_lbacls_rec, features, lflows, ls_pgs, meter_groups);
+    build_lb_hairpin(ls_lbacls_rec, lflows);
+}
+
 struct lswitch_flow_build_info {
     const struct ovn_datapaths *ls_datapaths;
     const struct ovn_datapaths *lr_datapaths;
@@ -15974,6 +15941,7 @@  struct lswitch_flow_build_info {
     const struct hmap *lr_ports;
     const struct ls_port_group_table *ls_port_groups;
     const struct lr_lb_nat_data_table *lr_lbnats;
+    const struct ls_lbacls_table *ls_lbacls;
     struct hmap *lflows;
     struct hmap *igmp_groups;
     const struct shash *meter_groups;
@@ -15998,9 +15966,7 @@  build_lswitch_and_lrouter_iterate_by_ls(struct ovn_datapath *od,
                                         struct lswitch_flow_build_info *lsi)
 {
     ovs_assert(od->nbs);
-    build_lswitch_lflows_pre_acl_and_acl(od, lsi->ls_port_groups,
-                                         lsi->features,
-                                         lsi->lflows,
+    build_lswitch_lflows_pre_acl_and_acl(od, lsi->features, lsi->lflows,
                                          lsi->meter_groups);
 
     build_fwd_group_lflows(od, lsi->lflows);
@@ -16115,6 +16081,7 @@  build_lflows_thread(void *arg)
 {
     struct worker_control *control = (struct worker_control *) arg;
     const struct lr_lb_nat_data_record *lr_lbnat_rec;
+    const struct ls_lbacls_record *ls_lbacls_rec;
     struct lswitch_flow_build_info *lsi;
     struct ovn_igmp_group *igmp_group;
     struct ovn_lb_datapaths *lb_dps;
@@ -16243,6 +16210,19 @@  build_lflows_thread(void *arg)
                                               lsi->features);
                 }
             }
+
+            for (bnum = control->id;
+                    bnum <= lsi->ls_lbacls->entries.mask;
+                    bnum += control->pool->size)
+            {
+                LS_LBACLS_TABLE_FOR_EACH_IN_P (ls_lbacls_rec, bnum,
+                                               lsi->ls_lbacls) {
+                    build_ls_lbacls_flows(ls_lbacls_rec, lsi->ls_port_groups,
+                                          lsi->features, lsi->meter_groups,
+                                          lsi->lflows);
+                }
+            }
+
             for (bnum = control->id;
                     bnum <= lsi->igmp_groups->mask;
                     bnum += control->pool->size)
@@ -16303,6 +16283,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
                                 const struct hmap *lr_ports,
                                 const struct ls_port_group_table *ls_pgs,
                                 const struct lr_lb_nat_data_table *lr_lbnats,
+                                const struct ls_lbacls_table *ls_lbacls,
                                 struct hmap *lflows,
                                 struct hmap *igmp_groups,
                                 const struct shash *meter_groups,
@@ -16333,6 +16314,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
             lsiv[index].lr_ports = lr_ports;
             lsiv[index].ls_port_groups = ls_pgs;
             lsiv[index].lr_lbnats = lr_lbnats;
+            lsiv[index].ls_lbacls = ls_lbacls;
             lsiv[index].igmp_groups = igmp_groups;
             lsiv[index].meter_groups = meter_groups;
             lsiv[index].lb_dps_map = lb_dps_map;
@@ -16358,6 +16340,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
         free(lsiv);
     } else {
         const struct lr_lb_nat_data_record *lr_lbnat_rec;
+        const struct ls_lbacls_record *ls_lbacls_rec;
         struct ovn_igmp_group *igmp_group;
         struct ovn_lb_datapaths *lb_dps;
         struct ovn_datapath *od;
@@ -16370,6 +16353,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
             .lr_ports = lr_ports,
             .ls_port_groups = ls_pgs,
             .lr_lbnats = lr_lbnats,
+            .ls_lbacls = ls_lbacls,
             .lflows = lflows,
             .igmp_groups = igmp_groups,
             .meter_groups = meter_groups,
@@ -16439,6 +16423,12 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
                                       lsi.features);
         }
 
+        LS_LBACLS_TABLE_FOR_EACH (ls_lbacls_rec, ls_lbacls) {
+            build_ls_lbacls_flows(ls_lbacls_rec, lsi.ls_port_groups,
+                                  lsi.features, lsi.meter_groups,
+                                  lsi.lflows);
+        }
+
         stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
         HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
             build_lswitch_ip_mcast_igmp_mld(igmp_group,
@@ -16535,6 +16525,7 @@  void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                                     input_data->lr_ports,
                                     input_data->ls_port_groups,
                                     input_data->lr_lbnats,
+                                    input_data->ls_lbacls,
                                     lflows,
                                     &igmp_groups,
                                     input_data->meter_groups,
diff --git a/northd/northd.h b/northd/northd.h
index 08a81b2c10..23b4754db4 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -89,6 +89,8 @@  ods_size(const struct ovn_datapaths *datapaths)
     return hmap_count(&datapaths->datapaths);
 }
 
+bool od_has_lb_vip(const struct ovn_datapath *od);
+
 struct tracked_ovn_ports {
     /* tracked created ports.
      * hmapx node data is 'struct ovn_port *' */
@@ -179,6 +181,7 @@  struct lflow_input {
     const struct hmap *lr_ports;
     const struct ls_port_group_table *ls_port_groups;
     const struct lr_lb_nat_data_table *lr_lbnats;
+    const struct ls_lbacls_table *ls_lbacls;
     const struct shash *meter_groups;
     const struct hmap *lb_datapaths_map;
     const struct hmap *bfd_connections;
@@ -288,11 +291,7 @@  struct ovn_datapath {
     struct hmap port_tnlids;
     uint32_t port_key_hint;
 
-    bool has_stateful_acl;
-    bool has_lb_vip;
     bool has_unknown;
-    bool has_acls;
-    uint64_t max_acl_tier;
     bool has_vtep_lports;
     bool has_arp_proxy_port;