diff mbox series

[ovs-dev,v6,02/13] northd: Add a new engine 'lr_stateful' to manage lr's stateful data.

Message ID 20240130212100.1482233-1-numans@ovn.org
State Accepted
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
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Numan Siddique Jan. 30, 2024, 9:21 p.m. UTC
From: Numan Siddique <numans@ovn.org>

This new engine now maintains the load balancer and NAT data of a
logical router which was earlier part of northd engine node data.
The main inputs to this engine are:
   - northd node
   - lr_nat node
   - lb_data node

A record for each logical router is maintained in the 'lr_stateful_table'
hmap table and this record
   - stores the lb related data
   - embeds the 'lr_nat' record.

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-nat.h       |   3 +
 northd/en-lr-stateful.c  | 659 ++++++++++++++++++++++++++++++++++++
 northd/en-lr-stateful.h  | 112 ++++++
 northd/en-sync-sb.c      |  54 +--
 northd/inc-proc-northd.c |  13 +-
 northd/northd.c          | 710 ++++++++++++---------------------------
 northd/northd.h          | 146 +++++++-
 northd/ovn-northd.c      |   1 +
 tests/ovn-northd.at      |  62 ++++
 12 files changed, 1250 insertions(+), 517 deletions(-)
 create mode 100644 northd/en-lr-stateful.c
 create mode 100644 northd/en-lr-stateful.h

Comments

Dumitru Ceara Feb. 2, 2024, 11:52 a.m. UTC | #1
On 1/30/24 22:21, numans@ovn.org wrote:
> From: Numan Siddique <numans@ovn.org>
> 
> This new engine now maintains the load balancer and NAT data of a
> logical router which was earlier part of northd engine node data.
> The main inputs to this engine are:
>    - northd node
>    - lr_nat node
>    - lb_data node
> 
> A record for each logical router is maintained in the 'lr_stateful_table'
> hmap table and this record
>    - stores the lb related data
>    - embeds the 'lr_nat' record.
> 
> This engine node becomes an input to 'lflow' node.
> 
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---

I have two small comments, style related, below.  With those addressed:

Acked-by: Dumitru Ceara <dceara@redhat.com>

[...]

> +static void lr_stateful_table_build(struct lr_stateful_table *,
> +                                   const struct lr_nat_table *,
> +                                   const struct ovn_datapaths *lr_datapaths,
> +                                   const struct hmap *lb_datapaths_map,
> +                                   const struct hmap *lbgrp_datapaths_map);

Nit: indentation.

[...]

> +
> +static inline bool
> +lr_stateful_has_tracked_data(struct lr_stateful_tracked_data *trk_data) {

Nit: curly brace on next line.

Thanks,
Dumitru
diff mbox series

Patch

diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
index 782d64320a..e5e41fbfd8 100644
--- a/lib/stopwatch-names.h
+++ b/lib/stopwatch-names.h
@@ -30,5 +30,6 @@ 
 #define PORT_GROUP_RUN_STOPWATCH_NAME "port_group_run"
 #define SYNC_METERS_RUN_STOPWATCH_NAME "sync_meters_run"
 #define LR_NAT_RUN_STOPWATCH_NAME "lr_nat_run"
+#define LR_STATEFUL_RUN_STOPWATCH_NAME "lr_stateful"
 
 #endif
diff --git a/northd/automake.mk b/northd/automake.mk
index a477105470..b886356c9c 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -26,6 +26,8 @@  northd_ovn_northd_SOURCES = \
 	northd/en-lb-data.h \
 	northd/en-lr-nat.c \
 	northd/en-lr-nat.h \
+	northd/en-lr-stateful.c \
+	northd/en-lr-stateful.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 f1fa92cdfb..97c7f383cd 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -20,6 +20,7 @@ 
 
 #include "en-lflow.h"
 #include "en-lr-nat.h"
+#include "en-lr-stateful.h"
 #include "en-northd.h"
 #include "en-meters.h"
 
@@ -43,6 +44,8 @@  lflow_get_input_data(struct engine_node *node,
         engine_get_input_data("sync_meters", node);
     struct ed_type_lr_nat_data *lr_nat_data =
         engine_get_input_data("lr_nat", node);
+    struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
 
     lflow_input->nbrec_bfd_table =
         EN_OVSDB_GET(engine_get_input("NB_bfd", node));
@@ -66,6 +69,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_nats = &lr_nat_data->lr_nats;
+    lflow_input->lr_stateful_table = &lr_stateful_data->table;
     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-nat.h b/northd/en-lr-nat.h
index 79390f8db8..16b166ee05 100644
--- a/northd/en-lr-nat.h
+++ b/northd/en-lr-nat.h
@@ -91,6 +91,9 @@  struct lr_nat_table {
 const struct lr_nat_record * lr_nat_table_find_by_index(
     const struct lr_nat_table *, size_t od_index);
 
+#define LR_NAT_TABLE_FOR_EACH(LR_NAT_REC, TABLE) \
+    HMAP_FOR_EACH (LR_NAT_REC, key_node, &(TABLE)->entries)
+
 struct ed_type_lr_nat_data {
     struct lr_nat_table lr_nats;
 
diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
new file mode 100644
index 0000000000..4404caaf93
--- /dev/null
+++ b/northd/en-lr-stateful.c
@@ -0,0 +1,659 @@ 
+/*
+ * Copyright (c) 2024, 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-lr-nat.h"
+#include "en-lr-stateful.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_lr_stateful);
+
+/* Static function declarations. */
+static void lr_stateful_table_init(struct lr_stateful_table *);
+static void lr_stateful_table_clear(struct lr_stateful_table *);
+static void lr_stateful_table_destroy(struct lr_stateful_table *);
+static struct lr_stateful_record *lr_stateful_table_find_(
+    const struct lr_stateful_table *, const struct nbrec_logical_router *);
+static struct lr_stateful_record *lr_stateful_table_find_by_index_(
+    const struct lr_stateful_table *table, size_t od_index);
+
+static void lr_stateful_table_build(struct lr_stateful_table *,
+                                   const struct lr_nat_table *,
+                                   const struct ovn_datapaths *lr_datapaths,
+                                   const struct hmap *lb_datapaths_map,
+                                   const struct hmap *lbgrp_datapaths_map);
+
+static struct lr_stateful_input lr_stateful_get_input_data(
+    struct engine_node *);
+
+static struct lr_stateful_record *lr_stateful_record_create(
+    struct lr_stateful_table *, const struct lr_nat_record *,
+    const struct ovn_datapath *od,
+    const struct hmap *lb_datapaths_map,
+    const struct hmap *lbgrp_datapaths_map);
+static void lr_stateful_record_destroy(struct lr_stateful_record *);
+
+static void build_lrouter_lb_reachable_ips(struct lr_stateful_record *,
+                                           const struct ovn_datapath *,
+                                           const struct ovn_northd_lb *);
+static void add_neigh_ips_to_lrouter(struct lr_stateful_record *,
+                                     const struct ovn_datapath *,
+                                     enum lb_neighbor_responder_mode,
+                                     const struct sset *lb_ips_v4,
+                                     const struct sset *lb_ips_v6);
+static void remove_lrouter_lb_reachable_ips(struct lr_stateful_record *,
+                                            enum lb_neighbor_responder_mode,
+                                            const struct sset *lb_ips_v4,
+                                            const struct sset *lb_ips_v6);
+static void lr_stateful_rebuild_vip_nats(struct lr_stateful_record *);
+
+/* 'lr_stateful' engine node manages the NB logical router LB data.
+ */
+void *
+en_lr_stateful_init(struct engine_node *node OVS_UNUSED,
+                    struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_lr_stateful *data = xzalloc(sizeof *data);
+    lr_stateful_table_init(&data->table);
+    hmapx_init(&data->trk_data.crupdated);
+    return data;
+}
+
+void
+en_lr_stateful_cleanup(void *data_)
+{
+    struct ed_type_lr_stateful *data = data_;
+    lr_stateful_table_destroy(&data->table);
+    hmapx_destroy(&data->trk_data.crupdated);
+}
+
+void
+en_lr_stateful_clear_tracked_data(void *data_)
+{
+    struct ed_type_lr_stateful *data = data_;
+
+    hmapx_clear(&data->trk_data.crupdated);
+}
+
+void
+en_lr_stateful_run(struct engine_node *node, void *data_)
+{
+    struct lr_stateful_input input_data = lr_stateful_get_input_data(node);
+    struct ed_type_lr_stateful *data = data_;
+
+    stopwatch_start(LR_STATEFUL_RUN_STOPWATCH_NAME, time_msec());
+
+    lr_stateful_table_clear(&data->table);
+    lr_stateful_table_build(&data->table, input_data.lr_nats,
+                            input_data.lr_datapaths,
+                            input_data.lb_datapaths_map,
+                            input_data.lbgrp_datapaths_map);
+
+    stopwatch_stop(LR_STATEFUL_RUN_STOPWATCH_NAME, time_msec());
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+bool
+lr_stateful_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    struct northd_data *northd_data = engine_get_input_data("northd", node);
+    if (!northd_has_tracked_data(&northd_data->trk_data)) {
+        return false;
+    }
+
+    /* This node uses the below data from the en_northd engine node.
+     * See (lr_stateful_get_input_data())
+     *   1. northd_data->lr_datapaths
+     *      This data gets updated when a logical router is created or deleted.
+     *      northd engine node presently falls back to full recompute when
+     *      this happens and so does this node.
+     *      Note: When we add I-P to the created/deleted logical routers, we
+     *      need to revisit this handler.
+     *
+     *      This node also accesses the router ports of the logical router
+     *      (od->ports).  When these logical router ports gets updated,
+     *      en_northd engine recomputes and so does this node.
+     *      Note: When we add I-P to handle router port changes, we need
+     *      to revisit this handler.
+     *
+     *   2. northd_data->lb_datapaths_map
+     *   3. northd_data->lb_group_datapaths_map
+     *
+     * (2) and (3) northd data gets updated when en_lb_data engine node gets
+     * updated and en_lb_data is also an input to this node and it provides
+     * the changes in its tracking data. So we can ignore changes in (2)
+     * and (3) if any.
+     *
+     * */
+    return true;
+}
+
+bool
+lr_stateful_lb_data_handler(struct engine_node *node, void *data_)
+{
+    struct ed_type_lb_data *lb_data = engine_get_input_data("lb_data", node);
+    if (!lb_data->tracked) {
+        return false;
+    }
+
+    struct lr_stateful_input input_data = lr_stateful_get_input_data(node);
+    const struct tracked_lb_data *trk_lb_data = &lb_data->tracked_lb_data;
+    const struct crupdated_od_lb_data *codlb;
+    struct ed_type_lr_stateful *data = data_;
+
+    LIST_FOR_EACH (codlb, list_node, &trk_lb_data->crupdated_lr_lbs) {
+        const struct ovn_datapath *od = ovn_datapath_find(
+            &input_data.lr_datapaths->datapaths, &codlb->od_uuid);
+        ovs_assert(od);
+
+        struct lr_stateful_record *lr_stateful_rec =
+            lr_stateful_table_find_(&data->table, od->nbr);
+        if (!lr_stateful_rec) {
+            const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
+                input_data.lr_nats, od->index);
+            ovs_assert(lrnat_rec);
+
+            lr_stateful_rec =
+                lr_stateful_record_create(&data->table, lrnat_rec, od,
+                                          input_data.lb_datapaths_map,
+                                          input_data.lbgrp_datapaths_map);
+
+            /* Add the lr_stateful_rec rec to the tracking data. */
+            hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
+            continue;
+        }
+
+        /* Update. */
+        struct uuidset_node *uuidnode;
+        UUIDSET_FOR_EACH (uuidnode, &codlb->assoc_lbs) {
+            const struct ovn_lb_datapaths *lb_dps = ovn_lb_datapaths_find(
+                input_data.lb_datapaths_map, &uuidnode->uuid);
+            ovs_assert(lb_dps);
+
+            /* Add the lb_ips of lb_dps to the od. */
+            build_lrouter_lb_ips(lr_stateful_rec->lb_ips, lb_dps->lb);
+            build_lrouter_lb_reachable_ips(lr_stateful_rec, od, lb_dps->lb);
+        }
+
+        UUIDSET_FOR_EACH (uuidnode, &codlb->assoc_lbgrps) {
+            const struct ovn_lb_group_datapaths *lbgrp_dps =
+                ovn_lb_group_datapaths_find(input_data.lbgrp_datapaths_map,
+                                            &uuidnode->uuid);
+            ovs_assert(lbgrp_dps);
+
+            for (size_t j = 0; j < lbgrp_dps->lb_group->n_lbs; j++) {
+                const struct uuid *lb_uuid
+                    = &lbgrp_dps->lb_group->lbs[j]->nlb->header_.uuid;
+                const struct ovn_lb_datapaths *lb_dps = ovn_lb_datapaths_find(
+                    input_data.lb_datapaths_map, lb_uuid);
+                ovs_assert(lb_dps);
+
+                /* Add the lb_ips of lb_dps to the od. */
+                build_lrouter_lb_ips(lr_stateful_rec->lb_ips, lb_dps->lb);
+                build_lrouter_lb_reachable_ips(lr_stateful_rec, od,
+                                               lb_dps->lb);
+            }
+        }
+
+        /* Add the lr_stateful_rec rec to the tracking data. */
+        hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
+    }
+
+    const struct crupdated_lb *clb;
+    HMAP_FOR_EACH (clb, hmap_node, &trk_lb_data->crupdated_lbs) {
+        const struct uuid *lb_uuid = &clb->lb->nlb->header_.uuid;
+        const struct ovn_northd_lb *lb = clb->lb;
+
+        const struct ovn_lb_datapaths *lb_dps = ovn_lb_datapaths_find(
+            input_data.lb_datapaths_map, lb_uuid);
+        ovs_assert(lb_dps);
+
+        size_t index;
+        BITMAP_FOR_EACH_1 (index, ods_size(input_data.lr_datapaths),
+                           lb_dps->nb_lr_map) {
+            const struct ovn_datapath *od =
+                ovn_datapaths_find_by_index(input_data.lr_datapaths, index);
+
+            struct lr_stateful_record *lr_stateful_rec =
+                lr_stateful_table_find_(&data->table, od->nbr);
+            ovs_assert(lr_stateful_rec);
+
+            /* Update the od->lb_ips with the deleted and inserted
+             * vips (if any). */
+            remove_ips_from_lb_ip_set(lr_stateful_rec->lb_ips, lb->routable,
+                                      &clb->deleted_vips_v4,
+                                      &clb->deleted_vips_v6);
+            add_ips_to_lb_ip_set(lr_stateful_rec->lb_ips, lb->routable,
+                                 &clb->inserted_vips_v4,
+                                 &clb->inserted_vips_v6);
+
+            remove_lrouter_lb_reachable_ips(lr_stateful_rec, lb->neigh_mode,
+                                            &clb->deleted_vips_v4,
+                                            &clb->deleted_vips_v6);
+            add_neigh_ips_to_lrouter(lr_stateful_rec, od, lb->neigh_mode,
+                                     &clb->inserted_vips_v4,
+                                     &clb->inserted_vips_v6);
+
+            /* Add the lr_stateful_rec rec to the tracking data. */
+            hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
+        }
+    }
+
+    const struct crupdated_lbgrp *crupdated_lbgrp;
+    HMAP_FOR_EACH (crupdated_lbgrp, hmap_node,
+                   &trk_lb_data->crupdated_lbgrps) {
+        const struct uuid *lb_uuid = &crupdated_lbgrp->lbgrp->uuid;
+        const struct ovn_lb_group_datapaths *lbgrp_dps =
+            ovn_lb_group_datapaths_find(input_data.lbgrp_datapaths_map,
+                                        lb_uuid);
+        ovs_assert(lbgrp_dps);
+
+        struct hmapx_node *hnode;
+        HMAPX_FOR_EACH (hnode, &crupdated_lbgrp->assoc_lbs) {
+            const struct ovn_northd_lb *lb = hnode->data;
+            lb_uuid = &lb->nlb->header_.uuid;
+            const struct ovn_lb_datapaths *lb_dps = ovn_lb_datapaths_find(
+                input_data.lb_datapaths_map, lb_uuid);
+            ovs_assert(lb_dps);
+            for (size_t i = 0; i < lbgrp_dps->n_lr; i++) {
+                const struct ovn_datapath *od = lbgrp_dps->lr[i];
+                struct lr_stateful_record *lr_stateful_rec =
+                    lr_stateful_table_find_(&data->table, od->nbr);
+                ovs_assert(lr_stateful_rec);
+                /* Add the lb_ips of lb_dps to the lr lb data. */
+                build_lrouter_lb_ips(lr_stateful_rec->lb_ips, lb_dps->lb);
+                build_lrouter_lb_reachable_ips(lr_stateful_rec, od,
+                                               lb_dps->lb);
+
+                /* Add the lr_stateful_rec rec to the tracking data. */
+                hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
+            }
+        }
+    }
+
+    if (lr_stateful_has_tracked_data(&data->trk_data)) {
+        /* For all the modified lr_stateful records (re)build the vip nats. */
+        struct hmapx_node *hmapx_node;
+
+        HMAPX_FOR_EACH (hmapx_node, &data->trk_data.crupdated) {
+            lr_stateful_rebuild_vip_nats(hmapx_node->data);
+        }
+
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+bool
+lr_stateful_lr_nat_handler(struct engine_node *node, void *data_)
+{
+    struct ed_type_lr_nat_data *lr_nat_data =
+        engine_get_input_data("lr_nat", node);
+
+    if (!lr_nat_has_tracked_data(&lr_nat_data->trk_data)) {
+        return false;
+    }
+
+    struct lr_stateful_input input_data = lr_stateful_get_input_data(node);
+    struct ed_type_lr_stateful *data = data_;
+    struct hmapx_node *hmapx_node;
+
+    HMAPX_FOR_EACH (hmapx_node, &lr_nat_data->trk_data.crupdated) {
+        const struct lr_nat_record *lrnat_rec = hmapx_node->data;
+        const struct ovn_datapath *od =
+                ovn_datapaths_find_by_index(input_data.lr_datapaths,
+                                            lrnat_rec->lr_index);
+        struct lr_stateful_record *lr_stateful_rec =
+            lr_stateful_table_find_(&data->table, od->nbr);
+        if (!lr_stateful_rec) {
+            lr_stateful_rec =
+                lr_stateful_record_create(&data->table, lrnat_rec, od,
+                                          input_data.lb_datapaths_map,
+                                          input_data.lbgrp_datapaths_map);
+        } else {
+            lr_stateful_rebuild_vip_nats(lr_stateful_rec);
+        }
+
+        /* Add the lr_stateful_rec rec to the tracking data. */
+        hmapx_add(&data->trk_data.crupdated, lr_stateful_rec);
+    }
+
+    if (lr_stateful_has_tracked_data(&data->trk_data)) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
+    return true;
+}
+
+const struct lr_stateful_record *
+lr_stateful_table_find_by_index(const struct lr_stateful_table *table,
+                                   size_t od_index)
+{
+    return lr_stateful_table_find_by_index_(table, od_index);
+}
+
+/* static functions. */
+static void
+lr_stateful_table_init(struct lr_stateful_table *table)
+{
+    *table = (struct lr_stateful_table) {
+        .entries = HMAP_INITIALIZER(&table->entries),
+    };
+}
+
+static void
+lr_stateful_table_destroy(struct lr_stateful_table *table)
+{
+    lr_stateful_table_clear(table);
+    hmap_destroy(&table->entries);
+}
+
+static void
+lr_stateful_table_clear(struct lr_stateful_table *table)
+{
+    struct lr_stateful_record *lr_stateful_rec;
+    HMAP_FOR_EACH_POP (lr_stateful_rec, key_node, &table->entries) {
+        lr_stateful_record_destroy(lr_stateful_rec);
+    }
+
+    free(table->array);
+    table->array = NULL;
+}
+
+static void
+lr_stateful_table_build(struct lr_stateful_table *table,
+                       const struct lr_nat_table *lr_nats,
+                       const struct ovn_datapaths *lr_datapaths,
+                       const struct hmap *lb_datapaths_map,
+                       const struct hmap *lbgrp_datapaths_map)
+{
+    table->array = xrealloc(table->array,
+                            ods_size(lr_datapaths) * sizeof *table->array);
+    const struct lr_nat_record *lrnat_rec;
+    LR_NAT_TABLE_FOR_EACH (lrnat_rec, lr_nats) {
+        const struct ovn_datapath *od =
+            ovn_datapaths_find_by_index(lr_datapaths, lrnat_rec->lr_index);
+        lr_stateful_record_create(table, lrnat_rec, od, lb_datapaths_map,
+                                  lbgrp_datapaths_map);
+    }
+}
+
+static struct lr_stateful_record *
+lr_stateful_table_find_(const struct lr_stateful_table *table,
+                        const struct nbrec_logical_router *nbr)
+{
+    struct lr_stateful_record *lr_stateful_rec;
+
+    HMAP_FOR_EACH_WITH_HASH (lr_stateful_rec, key_node,
+                             uuid_hash(&nbr->header_.uuid), &table->entries) {
+        if (uuid_equals(&lr_stateful_rec->nbr_uuid, &nbr->header_.uuid)) {
+            return lr_stateful_rec;
+        }
+    }
+    return NULL;
+}
+
+static struct lr_stateful_record *
+lr_stateful_table_find_by_index_(const struct lr_stateful_table *table,
+                                 size_t od_index)
+{
+    ovs_assert(od_index <= hmap_count(&table->entries));
+    return table->array[od_index];
+}
+
+static struct lr_stateful_record *
+lr_stateful_record_create(struct lr_stateful_table *table,
+                         const struct lr_nat_record *lrnat_rec,
+                         const struct ovn_datapath *od,
+                         const struct hmap *lb_datapaths_map,
+                         const struct hmap *lbgrp_datapaths_map)
+{
+    struct lr_stateful_record *lr_stateful_rec =
+        xzalloc(sizeof *lr_stateful_rec);
+    lr_stateful_rec->nbr_uuid = od->nbr->header_.uuid;
+    lr_stateful_rec->lrnat_rec = lrnat_rec;
+    lr_stateful_rec->lr_index = od->index;
+
+
+    /* Initialize the record.
+     * Checking load balancer groups first, starting from the largest one,
+     * to more efficiently copy IP sets. */
+    size_t largest_group = 0;
+
+    const struct nbrec_logical_router *nbr = od->nbr;
+    for (size_t i = 1; i < nbr->n_load_balancer_group; i++) {
+        if (nbr->load_balancer_group[i]->n_load_balancer >
+                nbr->load_balancer_group[largest_group]->n_load_balancer) {
+            largest_group = i;
+        }
+    }
+
+    for (size_t i = 0; i < nbr->n_load_balancer_group; i++) {
+        size_t idx = (i + largest_group) % nbr->n_load_balancer_group;
+
+        const struct nbrec_load_balancer_group *nbrec_lb_group =
+            nbr->load_balancer_group[idx];
+        const struct uuid *lbgrp_uuid = &nbrec_lb_group->header_.uuid;
+
+         const struct ovn_lb_group_datapaths *lbgrp_dps =
+            ovn_lb_group_datapaths_find(lbgrp_datapaths_map,
+                                        lbgrp_uuid);
+        ovs_assert(lbgrp_dps);
+
+        if (!lr_stateful_rec->lb_ips) {
+            lr_stateful_rec->lb_ips =
+                ovn_lb_ip_set_clone(lbgrp_dps->lb_group->lb_ips);
+        } else {
+            for (size_t j = 0; j < lbgrp_dps->lb_group->n_lbs; j++) {
+                build_lrouter_lb_ips(lr_stateful_rec->lb_ips,
+                                     lbgrp_dps->lb_group->lbs[j]);
+            }
+        }
+
+        for (size_t j = 0; j < lbgrp_dps->lb_group->n_lbs; j++) {
+            build_lrouter_lb_reachable_ips(lr_stateful_rec, od,
+                                           lbgrp_dps->lb_group->lbs[j]);
+        }
+    }
+
+    if (!lr_stateful_rec->lb_ips) {
+        lr_stateful_rec->lb_ips = ovn_lb_ip_set_create();
+    }
+
+    for (size_t i = 0; i < nbr->n_load_balancer; i++) {
+        const struct uuid *lb_uuid = &nbr->load_balancer[i]->header_.uuid;
+        const struct ovn_lb_datapaths *lb_dps =
+            ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
+        ovs_assert(lb_dps);
+        build_lrouter_lb_ips(lr_stateful_rec->lb_ips, lb_dps->lb);
+        build_lrouter_lb_reachable_ips(lr_stateful_rec, od, lb_dps->lb);
+    }
+
+    sset_init(&lr_stateful_rec->vip_nats);
+
+    if (nbr->n_nat) {
+        lr_stateful_rebuild_vip_nats(lr_stateful_rec);
+    }
+
+    hmap_insert(&table->entries, &lr_stateful_rec->key_node,
+                uuid_hash(&lr_stateful_rec->nbr_uuid));
+
+    table->array[od->index] = lr_stateful_rec;
+    return lr_stateful_rec;
+}
+
+static void
+lr_stateful_record_destroy(struct lr_stateful_record *lr_stateful_rec)
+{
+    ovn_lb_ip_set_destroy(lr_stateful_rec->lb_ips);
+    sset_destroy(&lr_stateful_rec->vip_nats);
+    free(lr_stateful_rec);
+}
+
+static struct lr_stateful_input
+lr_stateful_get_input_data(struct engine_node *node)
+{
+    struct northd_data *northd_data = engine_get_input_data("northd", node);
+    struct ed_type_lr_nat_data *lr_nat_data =
+        engine_get_input_data("lr_nat", node);
+
+    return (struct lr_stateful_input) {
+        .lr_datapaths = &northd_data->lr_datapaths,
+        .lb_datapaths_map = &northd_data->lb_datapaths_map,
+        .lbgrp_datapaths_map = &northd_data->lb_group_datapaths_map,
+        .lr_nats = &lr_nat_data->lr_nats,
+    };
+}
+
+static void
+build_lrouter_lb_reachable_ips(struct lr_stateful_record *lr_stateful_rec,
+                               const struct ovn_datapath *od,
+                               const struct ovn_northd_lb *lb)
+{
+    add_neigh_ips_to_lrouter(lr_stateful_rec, od, lb->neigh_mode, &lb->ips_v4,
+                             &lb->ips_v6);
+}
+
+static void
+add_neigh_ips_to_lrouter(struct lr_stateful_record *lr_stateful_rec,
+                         const struct ovn_datapath *od,
+                         enum lb_neighbor_responder_mode neigh_mode,
+                         const struct sset *lb_ips_v4,
+                         const struct sset *lb_ips_v6)
+{
+    ovs_assert(od->nbr);
+
+    /* If configured to not reply to any neighbor requests for all VIPs
+     * return early.
+     */
+    if (neigh_mode == LB_NEIGH_RESPOND_NONE) {
+        return;
+    }
+
+    const char *ip_address;
+
+    /* If configured to reply to neighbor requests for all VIPs force them
+     * all to be considered "reachable".
+     */
+    if (neigh_mode == LB_NEIGH_RESPOND_ALL) {
+        SSET_FOR_EACH (ip_address, lb_ips_v4) {
+            sset_add(&lr_stateful_rec->lb_ips->ips_v4_reachable, ip_address);
+        }
+        SSET_FOR_EACH (ip_address, lb_ips_v6) {
+            sset_add(&lr_stateful_rec->lb_ips->ips_v6_reachable, ip_address);
+        }
+
+        return;
+    }
+
+    /* Otherwise, a VIP is reachable if there's at least one router
+     * subnet that includes it.
+     */
+    ovs_assert(neigh_mode == LB_NEIGH_RESPOND_REACHABLE);
+
+    SSET_FOR_EACH (ip_address, lb_ips_v4) {
+        struct ovn_port *op;
+        ovs_be32 vip_ip4;
+        if (ip_parse(ip_address, &vip_ip4)) {
+            HMAP_FOR_EACH (op, dp_node, &od->ports) {
+                if (lrouter_port_ipv4_reachable(op, vip_ip4)) {
+                    sset_add(&lr_stateful_rec->lb_ips->ips_v4_reachable,
+                             ip_address);
+                    break;
+                }
+            }
+        }
+    }
+
+    SSET_FOR_EACH (ip_address, lb_ips_v6) {
+        struct ovn_port *op;
+        struct in6_addr vip;
+        if (ipv6_parse(ip_address, &vip)) {
+            HMAP_FOR_EACH (op, dp_node, &od->ports) {
+                if (lrouter_port_ipv6_reachable(op, &vip)) {
+                    sset_add(&lr_stateful_rec->lb_ips->ips_v6_reachable,
+                             ip_address);
+                    break;
+                }
+            }
+        }
+    }
+}
+
+static void
+remove_lrouter_lb_reachable_ips(struct lr_stateful_record *lr_stateful_rec,
+                                enum lb_neighbor_responder_mode neigh_mode,
+                                const struct sset *lb_ips_v4,
+                                const struct sset *lb_ips_v6)
+{
+    if (neigh_mode == LB_NEIGH_RESPOND_NONE) {
+        return;
+    }
+
+    const char *ip_address;
+    SSET_FOR_EACH (ip_address, lb_ips_v4) {
+        sset_find_and_delete(&lr_stateful_rec->lb_ips->ips_v4_reachable,
+                             ip_address);
+    }
+    SSET_FOR_EACH (ip_address, lb_ips_v6) {
+        sset_find_and_delete(&lr_stateful_rec->lb_ips->ips_v6_reachable,
+                             ip_address);
+    }
+}
+
+static void
+lr_stateful_rebuild_vip_nats(struct lr_stateful_record *lr_stateful_rec)
+{
+    sset_clear(&lr_stateful_rec->vip_nats);
+    const char *external_ip;
+    SSET_FOR_EACH (external_ip, &lr_stateful_rec->lrnat_rec->external_ips) {
+        bool is_vip_nat = false;
+        if (addr_is_ipv6(external_ip)) {
+            is_vip_nat = sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
+                                       external_ip);
+        } else {
+            is_vip_nat = sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
+                                       external_ip);
+        }
+
+        if (is_vip_nat) {
+            sset_add(&lr_stateful_rec->vip_nats, external_ip);
+        }
+    }
+}
diff --git a/northd/en-lr-stateful.h b/northd/en-lr-stateful.h
new file mode 100644
index 0000000000..bc3265adac
--- /dev/null
+++ b/northd/en-lr-stateful.h
@@ -0,0 +1,112 @@ 
+/*
+ * Copyright (c) 2024, 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_LR_STATEFUL_H
+#define EN_LR_STATEFUL_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"
+
+struct ovn_datapath;
+struct lr_nat_record;
+
+/* lr_stateful_table:  This represents a table of logical routers with
+ *                     stateful related data.
+ * stateful related data has two main components
+ *     - NAT and
+ *     - Load balancers.
+ *
+ * lr_stateful_record: It is a record in the lr_stateful_table for each
+ *                     logical router.
+ */
+
+struct lr_stateful_record {
+    struct hmap_node key_node; /* Index on 'nbr->header_.uuid'. */
+
+    /* UUID of the NB Logical Router. */
+    struct uuid nbr_uuid;
+
+    /* Unique id of the logical router.  Note : This id is assigned
+     * by the northd engine node for each logical router. */
+    size_t lr_index;
+
+    /* This lrnat_rec comes from the en_lrnat engine node data. */
+    const struct lr_nat_record *lrnat_rec;
+
+    /* Load Balancer vIPs relevant for this datapath. */
+    struct ovn_lb_ip_set *lb_ips;
+
+    /* sset of vips which are also part of lr nats. */
+    struct sset vip_nats;
+};
+
+struct lr_stateful_table {
+    struct hmap entries;
+
+    /* The array index of each element in 'entries'. */
+    struct lr_stateful_record **array;
+};
+
+#define LR_STATEFUL_TABLE_FOR_EACH(LR_LB_NAT_REC, TABLE) \
+    HMAP_FOR_EACH (LR_LB_NAT_REC, key_node, &(TABLE)->entries)
+
+struct lr_stateful_tracked_data {
+    /* Created or updated logical router with LB and/or NAT data. */
+    struct hmapx crupdated; /* Stores 'struct lr_stateful_record'. */
+};
+
+struct ed_type_lr_stateful {
+    struct lr_stateful_table table;
+
+    /* Node's tracked data. */
+    struct lr_stateful_tracked_data trk_data;
+};
+
+struct lr_stateful_input {
+    const struct ovn_datapaths *lr_datapaths;
+    const struct hmap *lb_datapaths_map;
+    const struct hmap *lbgrp_datapaths_map;
+    const struct lr_nat_table *lr_nats;
+};
+
+void *en_lr_stateful_init(struct engine_node *, struct engine_arg *);
+void en_lr_stateful_cleanup(void *data);
+void en_lr_stateful_clear_tracked_data(void *data);
+void en_lr_stateful_run(struct engine_node *, void *data);
+
+bool lr_stateful_northd_handler(struct engine_node *, void *data);
+bool lr_stateful_lr_nat_handler(struct engine_node *, void *data);
+bool lr_stateful_lb_data_handler(struct engine_node *, void *data);
+
+const struct lr_stateful_record *lr_stateful_table_find_by_index(
+    const struct lr_stateful_table *, size_t od_index);
+
+static inline bool
+lr_stateful_has_tracked_data(struct lr_stateful_tracked_data *trk_data) {
+    return !hmapx_is_empty(&trk_data->crupdated);
+}
+
+#endif /* EN_lr_stateful_H */
diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index 11e12428f7..53f687f220 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -22,6 +22,7 @@ 
 #include "openvswitch/util.h"
 
 #include "en-lr-nat.h"
+#include "en-lr-stateful.h"
 #include "en-sync-sb.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/lb.h"
@@ -41,7 +42,8 @@  static void sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
                            const struct nbrec_address_set_table *,
                            const struct nbrec_port_group_table *,
                            const struct sbrec_address_set_table *,
-                           const struct ovn_datapaths *lr_datapaths);
+                           const struct lr_stateful_table *,
+                           const struct ovn_datapaths *);
 static const struct sbrec_address_set *sb_address_set_lookup_by_name(
     struct ovsdb_idl_index *, const char *name);
 static void update_sb_addr_set(struct sorted_array *,
@@ -87,10 +89,12 @@  en_sync_to_sb_addr_set_run(struct engine_node *node, void *data OVS_UNUSED)
         EN_OVSDB_GET(engine_get_input("SB_address_set", node));
 
     const struct engine_context *eng_ctx = engine_get_context();
+    const struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
     struct northd_data *northd_data = engine_get_input_data("northd", node);
-
     sync_addr_sets(eng_ctx->ovnsb_idl_txn, nb_address_set_table,
                    nb_port_group_table, sb_address_set_table,
+                   &lr_stateful_data->table,
                    &northd_data->lr_datapaths);
 
     engine_set_node_state(node, EN_UPDATED);
@@ -288,10 +292,12 @@  en_sync_to_sb_pb_run(struct engine_node *node, void *data OVS_UNUSED)
 {
     const struct engine_context *eng_ctx = engine_get_context();
     struct northd_data *northd_data = engine_get_input_data("northd", node);
-    struct ed_type_lr_nat_data *lr_nat_data =
-        engine_get_input_data("lr_nat", node);
+    struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
+
     sync_pbs(eng_ctx->ovnsb_idl_txn, &northd_data->ls_ports,
-             &northd_data->lr_ports, &lr_nat_data->lr_nats);
+             &northd_data->lr_ports,
+             &lr_stateful_data->table);
     engine_set_node_state(node, EN_UPDATED);
 }
 
@@ -316,7 +322,11 @@  sync_to_sb_pb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
         return false;
     }
 
-    if (!sync_pbs_for_northd_changed_ovn_ports(&nd->trk_data.trk_lsps)) {
+    struct ed_type_lr_stateful *lr_stateful_data =
+        engine_get_input_data("lr_stateful", node);
+
+    if (!sync_pbs_for_northd_changed_ovn_ports(&nd->trk_data.trk_lsps,
+                                               &lr_stateful_data->table)) {
         return false;
     }
 
@@ -362,6 +372,7 @@  sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
                const struct nbrec_address_set_table *nb_address_set_table,
                const struct nbrec_port_group_table *nb_port_group_table,
                const struct sbrec_address_set_table *sb_address_set_table,
+               const struct lr_stateful_table *lr_statefuls,
                const struct ovn_datapaths *lr_datapaths)
 {
     struct shash sb_address_sets = SHASH_INITIALIZER(&sb_address_sets);
@@ -406,16 +417,17 @@  sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
     }
 
     /* Sync router load balancer VIP generated address sets. */
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
-        ovs_assert(od->nbr);
-
-        if (sset_count(&od->lb_ips->ips_v4_reachable)) {
-            char *ipv4_addrs_name = lr_lb_address_set_name(od->tunnel_key,
-                                                           AF_INET);
-
-            struct sorted_array ipv4_addrs_sorted =
-                    sorted_array_from_sset(&od->lb_ips->ips_v4_reachable);
+    const struct lr_stateful_record *lr_stateful_rec;
+    LR_STATEFUL_TABLE_FOR_EACH (lr_stateful_rec, lr_statefuls) {
+        const struct ovn_datapath *od =
+            ovn_datapaths_find_by_index(lr_datapaths,
+                                        lr_stateful_rec->lr_index);
+        if (sset_count(&lr_stateful_rec->lb_ips->ips_v4_reachable)) {
+            char *ipv4_addrs_name =
+                lr_lb_address_set_name(od->tunnel_key, AF_INET);
+
+            struct sorted_array ipv4_addrs_sorted = sorted_array_from_sset(
+                &lr_stateful_rec->lb_ips->ips_v4_reachable);
 
             sync_addr_set(ovnsb_txn, ipv4_addrs_name,
                           &ipv4_addrs_sorted, &sb_address_sets);
@@ -423,11 +435,11 @@  sync_addr_sets(struct ovsdb_idl_txn *ovnsb_txn,
             free(ipv4_addrs_name);
         }
 
-        if (sset_count(&od->lb_ips->ips_v6_reachable)) {
-            char *ipv6_addrs_name = lr_lb_address_set_name(od->tunnel_key,
-                                                           AF_INET6);
-            struct sorted_array ipv6_addrs_sorted =
-                    sorted_array_from_sset(&od->lb_ips->ips_v6_reachable);
+        if (sset_count(&lr_stateful_rec->lb_ips->ips_v6_reachable)) {
+            char *ipv6_addrs_name =
+                lr_lb_address_set_name(od->tunnel_key, AF_INET6);
+            struct sorted_array ipv6_addrs_sorted = sorted_array_from_sset(
+                &lr_stateful_rec->lb_ips->ips_v6_reachable);
 
             sync_addr_set(ovnsb_txn, ipv6_addrs_name,
                           &ipv6_addrs_sorted, &sb_address_sets);
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 1f211b278e..97bcce9655 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -31,6 +31,7 @@ 
 #include "openvswitch/vlog.h"
 #include "inc-proc-northd.h"
 #include "en-lb-data.h"
+#include "en-lr-stateful.h"
 #include "en-lr-nat.h"
 #include "en-northd.h"
 #include "en-lflow.h"
@@ -148,6 +149,7 @@  static ENGINE_NODE(sync_to_sb_lb, "sync_to_sb_lb");
 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_stateful, "lr_stateful");
 
 void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                           struct ovsdb_idl_loop *sb)
@@ -193,6 +195,11 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_add_input(&en_lr_nat, &en_northd, lr_nat_northd_handler);
 
+    engine_add_input(&en_lr_stateful, &en_northd, lr_stateful_northd_handler);
+    engine_add_input(&en_lr_stateful, &en_lr_nat, lr_stateful_lr_nat_handler);
+    engine_add_input(&en_lr_stateful, &en_lb_data,
+                     lr_stateful_lb_data_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);
@@ -214,15 +221,17 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_lflow, &en_sb_logical_flow, NULL);
     engine_add_input(&en_lflow, &en_sb_multicast_group, NULL);
     engine_add_input(&en_lflow, &en_sb_igmp_group, NULL);
+    engine_add_input(&en_lflow, &en_lr_nat, NULL);
+    engine_add_input(&en_lflow, &en_lr_stateful, NULL);
     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_nat, NULL);
 
     engine_add_input(&en_sync_to_sb_addr_set, &en_nb_address_set,
                      sync_to_sb_addr_set_nb_address_set_handler);
     engine_add_input(&en_sync_to_sb_addr_set, &en_nb_port_group,
                      sync_to_sb_addr_set_nb_port_group_handler);
     engine_add_input(&en_sync_to_sb_addr_set, &en_northd, NULL);
+    engine_add_input(&en_sync_to_sb_addr_set, &en_lr_stateful, NULL);
     engine_add_input(&en_sync_to_sb_addr_set, &en_sb_address_set, NULL);
 
     engine_add_input(&en_port_group, &en_nb_port_group,
@@ -240,7 +249,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
 
     engine_add_input(&en_sync_to_sb_pb, &en_northd,
                      sync_to_sb_pb_northd_handler);
-    engine_add_input(&en_sync_to_sb_pb, &en_lr_nat, NULL);
+    engine_add_input(&en_sync_to_sb_pb, &en_lr_stateful, NULL);
 
     /* en_sync_to_sb engine node syncs the SB database tables from
      * the NB database tables.
diff --git a/northd/northd.c b/northd/northd.c
index 070214fc68..a425a8d7ab 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -45,6 +45,7 @@ 
 #include "northd.h"
 #include "en-lb-data.h"
 #include "en-lr-nat.h"
+#include "en-lr-stateful.h"
 #include "lib/ovn-parallel-hmap.h"
 #include "ovn/actions.h"
 #include "ovn/features.h"
@@ -618,13 +619,6 @@  init_lb_for_datapath(struct ovn_datapath *od)
     }
 }
 
-static void
-destroy_lb_for_datapath(struct ovn_datapath *od)
-{
-    ovn_lb_ip_set_destroy(od->lb_ips);
-    od->lb_ips = NULL;
-}
-
 /* A group of logical router datapaths which are connected - either
  * directly or indirectly.
  * Each logical router can belong to only one group. */
@@ -677,7 +671,6 @@  ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od)
         destroy_ipam_info(&od->ipam_info);
         free(od->router_ports);
         free(od->ls_peers);
-        destroy_lb_for_datapath(od);
         free(od->localnet_ports);
         free(od->l3dgw_ports);
         destroy_mcast_info_for_datapath(od);
@@ -1318,124 +1311,6 @@  struct lflow_ref_node {
     struct ovn_lflow *lflow;
 };
 
-/* A logical switch port or logical router port.
- *
- * In steady state, an ovn_port points to a northbound Logical_Switch_Port
- * record (via 'nbsp') *or* a Logical_Router_Port record (via 'nbrp'), and to a
- * southbound Port_Binding record (via 'sb').  As the state of the system
- * changes, join_logical_ports() may determine that there is a new LSP or LRP
- * that has no corresponding Port_Binding record (in which case build_ports())
- * will create the missing Port_Binding) or that a Port_Binding record exists
- * that has no coresponding LSP (in which case build_ports() will delete the
- * spurious Port_Binding).  Thus, after build_ports() runs, any given ovn_port
- * will have 'sb' nonnull, and 'nbsp' xor 'nbrp' nonnull.
- *
- * Ordinarily there is only one ovn_port that points to a given LSP or LRP (but
- * distributed gateway ports point a "derived" ovn_port to a duplicate LRP).
- */
-struct ovn_port {
-    /* Port name aka key.
-     *
-     * This is ordinarily the same as nbsp->name or nbrp->name and
-     * sb->logical_port.  (A distributed gateway port creates a "derived"
-     * ovn_port with key "cr-%s" % nbrp->name.) */
-    struct hmap_node key_node;  /* Index on 'key'. */
-    char *key;                  /* nbsp->name, nbrp->name, sb->logical_port. */
-    char *json_key;             /* 'key', quoted for use in JSON. */
-
-    const struct sbrec_port_binding *sb;         /* May be NULL. */
-
-    uint32_t tunnel_key;
-
-    /* Logical switch port data. */
-    const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */
-
-    struct lport_addresses *lsp_addrs;  /* Logical switch port addresses. */
-    unsigned int n_lsp_addrs;  /* Total length of lsp_addrs. */
-    unsigned int n_lsp_non_router_addrs; /* Number of elements from the
-                                          * beginning of 'lsp_addrs' extracted
-                                          * directly from LSP 'addresses'. */
-
-    struct lport_addresses *ps_addrs;   /* Port security addresses. */
-    unsigned int n_ps_addrs;
-
-    bool lsp_can_be_inc_processed; /* If it can be incrementally processed when
-                                      the port changes. */
-
-    /* Logical router port data. */
-    const struct nbrec_logical_router_port *nbrp; /* May be NULL. */
-
-    struct lport_addresses lrp_networks;
-
-    struct ovn_port_routable_addresses routables;
-
-    /* Logical port multicast data. */
-    struct mcast_port_info mcast_info;
-
-    /* At most one of l3dgw_port and cr_port can be not NULL. */
-
-    /* This is set to a distributed gateway port if and only if this ovn_port
-     * is "derived" from it. Otherwise this is set to NULL. The derived
-     * ovn_port represents the instance of distributed gateway port on the
-     * gateway chassis.*/
-    struct ovn_port *l3dgw_port;
-
-    /* This is set to the "derived" chassis-redirect port of this port if and
-     * only if this port is a distributed gateway port. Otherwise this is set
-     * to NULL. */
-    struct ovn_port *cr_port;
-
-    bool has_unknown; /* If the addresses have 'unknown' defined. */
-
-    bool has_bfd;
-
-    /* The port's peer:
-     *
-     *     - A switch port S of type "router" has a router port R as a peer,
-     *       and R in turn has S has its peer.
-     *
-     *     - Two connected logical router ports have each other as peer.
-     *
-     *     - Other kinds of ports have no peer. */
-    struct ovn_port *peer;
-
-    struct ovn_datapath *od;
-
-    struct ovs_list list;       /* In list of similar records. */
-
-    struct hmap_node dp_node;   /* Node in od->ports. */
-
-    struct lport_addresses proxy_arp_addrs;
-
-    /* Temporarily used for traversing a list (or hmap) of ports. */
-    bool visited;
-
-    /* List of struct lflow_ref_node that points to the lflows generated by
-     * this ovn_port.
-     *
-     * This data is initialized and destroyed by the en_northd node, but
-     * populated and used only by the en_lflow node. Ideally this data should
-     * be maintained as part of en_lflow's data (struct lflow_data): a hash
-     * index from ovn_port key to lflows.  However, it would be less efficient
-     * and more complex:
-     *
-     * 1. It would require an extra search (using the index) to find the
-     * lflows.
-     *
-     * 2. Building the index needs to be thread-safe, using either a global
-     * lock which is obviously less efficient, or hash-based lock array which
-     * is more complex.
-     *
-     * Adding the list here is more straightforward. The drawback is that we
-     * need to keep in mind that this data belongs to en_lflow node, so never
-     * access it from any other nodes.
-     */
-    struct ovs_list lflows;
-
-    /* Only used for the router type LSP whose peer is l3dgw_port */
-    bool enable_router_port_acl;
-};
-
 static bool lsp_can_be_inc_processed(const struct nbrec_logical_switch_port *);
 
 static bool
@@ -1460,16 +1335,21 @@  destroy_routable_addresses(struct ovn_port_routable_addresses *ra)
 }
 
 static char **get_nat_addresses(const struct ovn_port *op, size_t *n,
-                                bool routable_only, bool include_lb_ips);
+                                bool routable_only, bool include_lb_ips,
+                                const struct lr_stateful_record *);
 
-static void
-assign_routable_addresses(struct ovn_port *op)
+static struct ovn_port_routable_addresses
+get_op_routable_addresses(struct ovn_port *op,
+                          const struct lr_stateful_record *lr_stateful_rec)
 {
     size_t n;
-    char **nats = get_nat_addresses(op, &n, true, true);
+    char **nats = get_nat_addresses(op, &n, true, true, lr_stateful_rec);
 
     if (!nats) {
-        return;
+        return (struct ovn_port_routable_addresses) {
+            .laddrs = NULL,
+            .n_addrs = 0,
+        };
     }
 
     struct lport_addresses *laddrs = xcalloc(n, sizeof(*laddrs));
@@ -1485,9 +1365,15 @@  assign_routable_addresses(struct ovn_port *op)
     }
     free(nats);
 
-    /* Everything seems to have worked out */
-    op->routables.laddrs = laddrs;
-    op->routables.n_addrs = n_addrs;
+    if (!n_addrs) {
+        free(laddrs);
+        laddrs = NULL;
+    }
+
+    return (struct ovn_port_routable_addresses) {
+        .laddrs = laddrs,
+        .n_addrs = n_addrs,
+    };
 }
 
 
@@ -1547,8 +1433,6 @@  ovn_port_destroy_orphan(struct ovn_port *port)
     }
     free(port->ps_addrs);
 
-    destroy_routable_addresses(&port->routables);
-
     destroy_lport_addresses(&port->lrp_networks);
     destroy_lport_addresses(&port->proxy_arp_addrs);
     free(port->json_key);
@@ -2590,9 +2474,7 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
                                                  sizeof *od->l3dgw_ports);
                 }
                 od->l3dgw_ports[od->n_l3dgw_ports++] = op;
-
-                assign_routable_addresses(op);
-            }
+           }
         }
     }
 
@@ -2695,7 +2577,8 @@  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
  * and must free the returned array when it is no longer needed. */
 static char **
 get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
-                  bool include_lb_ips)
+                  bool include_lb_ips,
+                  const struct lr_stateful_record *lr_stateful_rec)
 {
     size_t n_nats = 0;
     struct eth_addr mac;
@@ -2780,23 +2663,25 @@  get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only,
         }
     }
 
-    if (include_lb_ips) {
+    if (include_lb_ips && lr_stateful_rec) {
         const char *ip_address;
         if (routable_only) {
-            SSET_FOR_EACH (ip_address, &op->od->lb_ips->ips_v4_routable) {
+            SSET_FOR_EACH (ip_address,
+                           &lr_stateful_rec->lb_ips->ips_v4_routable) {
                 ds_put_format(&c_addresses, " %s", ip_address);
                 central_ip_address = true;
             }
-            SSET_FOR_EACH (ip_address, &op->od->lb_ips->ips_v6_routable) {
+            SSET_FOR_EACH (ip_address,
+                           &lr_stateful_rec->lb_ips->ips_v6_routable) {
                 ds_put_format(&c_addresses, " %s", ip_address);
                 central_ip_address = true;
             }
         } else {
-            SSET_FOR_EACH (ip_address, &op->od->lb_ips->ips_v4) {
+            SSET_FOR_EACH (ip_address, &lr_stateful_rec->lb_ips->ips_v4) {
                 ds_put_format(&c_addresses, " %s", ip_address);
                 central_ip_address = true;
             }
-            SSET_FOR_EACH (ip_address, &op->od->lb_ips->ips_v6) {
+            SSET_FOR_EACH (ip_address, &lr_stateful_rec->lb_ips->ips_v6) {
                 ds_put_format(&c_addresses, " %s", ip_address);
                 central_ip_address = true;
             }
@@ -3867,21 +3752,8 @@  build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
     HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
         ovs_assert(od->nbr);
 
-        /* Checking load balancer groups first, starting from the largest one,
-         * to more efficiently copy IP sets. */
-        size_t largest_group = 0;
-
-        for (size_t i = 1; i < od->nbr->n_load_balancer_group; i++) {
-            if (od->nbr->load_balancer_group[i]->n_load_balancer >
-                od->nbr->load_balancer_group[largest_group]->n_load_balancer) {
-                largest_group = i;
-            }
-        }
-
         for (size_t i = 0; i < od->nbr->n_load_balancer_group; i++) {
-            size_t idx = (i + largest_group) % od->nbr->n_load_balancer_group;
-
-            nbrec_lb_group = od->nbr->load_balancer_group[idx];
+            nbrec_lb_group = od->nbr->load_balancer_group[i];
             const struct uuid *lb_group_uuid = &nbrec_lb_group->header_.uuid;
 
             lb_group_dps =
@@ -3889,20 +3761,6 @@  build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
                                             lb_group_uuid);
             ovs_assert(lb_group_dps);
             ovn_lb_group_datapaths_add_lr(lb_group_dps, od);
-
-            if (!od->lb_ips) {
-                od->lb_ips =
-                    ovn_lb_ip_set_clone(lb_group_dps->lb_group->lb_ips);
-            } else {
-                for (size_t j = 0; j < lb_group_dps->lb_group->n_lbs; j++) {
-                    build_lrouter_lb_ips(od->lb_ips,
-                                         lb_group_dps->lb_group->lbs[j]);
-                }
-            }
-        }
-
-        if (!od->lb_ips) {
-            od->lb_ips = ovn_lb_ip_set_create();
         }
 
         for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
@@ -3911,7 +3769,6 @@  build_lb_datapaths(const struct hmap *lbs, const struct hmap *lb_groups,
             lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
             ovs_assert(lb_dps);
             ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
-            build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
         }
     }
 
@@ -3965,102 +3822,6 @@  build_lb_svcs(
     }
 }
 
-static bool lrouter_port_ipv4_reachable(const struct ovn_port *op,
-                                        ovs_be32 addr);
-static bool lrouter_port_ipv6_reachable(const struct ovn_port *op,
-                                        const struct in6_addr *addr);
-
-static void
-add_neigh_ips_to_lrouter(struct ovn_datapath *od,
-                         enum lb_neighbor_responder_mode neigh_mode,
-                         const struct sset *lb_ips_v4,
-                         const struct sset *lb_ips_v6)
-{
-    /* If configured to not reply to any neighbor requests for all VIPs
-     * return early.
-     */
-    if (neigh_mode == LB_NEIGH_RESPOND_NONE) {
-        return;
-    }
-
-    const char *ip_address;
-
-    /* If configured to reply to neighbor requests for all VIPs force them
-     * all to be considered "reachable".
-     */
-    if (neigh_mode == LB_NEIGH_RESPOND_ALL) {
-        SSET_FOR_EACH (ip_address, lb_ips_v4) {
-            sset_add(&od->lb_ips->ips_v4_reachable, ip_address);
-        }
-        SSET_FOR_EACH (ip_address, lb_ips_v6) {
-            sset_add(&od->lb_ips->ips_v6_reachable, ip_address);
-        }
-
-        return;
-    }
-
-    /* Otherwise, a VIP is reachable if there's at least one router
-     * subnet that includes it.
-     */
-    ovs_assert(neigh_mode == LB_NEIGH_RESPOND_REACHABLE);
-
-    SSET_FOR_EACH (ip_address, lb_ips_v4) {
-        struct ovn_port *op;
-        ovs_be32 vip_ip4;
-        if (ip_parse(ip_address, &vip_ip4)) {
-            HMAP_FOR_EACH (op, dp_node, &od->ports) {
-                if (lrouter_port_ipv4_reachable(op, vip_ip4)) {
-                    sset_add(&od->lb_ips->ips_v4_reachable,
-                             ip_address);
-                    break;
-                }
-            }
-        }
-    }
-
-    SSET_FOR_EACH (ip_address, lb_ips_v6) {
-        struct ovn_port *op;
-        struct in6_addr vip;
-        if (ipv6_parse(ip_address, &vip)) {
-            HMAP_FOR_EACH (op, dp_node, &od->ports) {
-                if (lrouter_port_ipv6_reachable(op, &vip)) {
-                    sset_add(&od->lb_ips->ips_v6_reachable,
-                             ip_address);
-                    break;
-                }
-            }
-        }
-    }
-}
-
-static void
-remove_lrouter_lb_reachable_ips(struct ovn_datapath *od,
-                                enum lb_neighbor_responder_mode neigh_mode,
-                                const struct sset *lb_ips_v4,
-                                const struct sset *lb_ips_v6)
-{
-    if (neigh_mode == LB_NEIGH_RESPOND_NONE) {
-        return;
-    }
-
-    const char *ip_address;
-    SSET_FOR_EACH (ip_address, lb_ips_v4) {
-        sset_find_and_delete(&od->lb_ips->ips_v4_reachable, ip_address);
-    }
-    SSET_FOR_EACH (ip_address, lb_ips_v6) {
-        sset_find_and_delete(&od->lb_ips->ips_v6_reachable, ip_address);
-    }
-}
-
-static void
-build_lrouter_lb_reachable_ips(struct ovn_datapath *od,
-                               const struct ovn_northd_lb *lb)
-{
-    add_neigh_ips_to_lrouter(od, lb->neigh_mode, &lb->ips_v4,
-                             &lb->ips_v6);
-}
-
-
 static void
 build_lrouter_lbs_check(const struct ovn_datapaths *lr_datapaths)
 {
@@ -4082,43 +3843,6 @@  build_lrouter_lbs_check(const struct ovn_datapaths *lr_datapaths)
     }
 }
 
-static void
-build_lrouter_lbs_reachable_ips(struct ovn_datapaths *lr_datapaths,
-                                struct hmap *lb_dps_map,
-                                struct hmap *lb_group_dps_map)
-{
-    struct ovn_datapath *od;
-
-    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
-        if (!od->nbr) {
-            continue;
-        }
-
-        for (size_t i = 0; i < od->nbr->n_load_balancer; i++) {
-            struct ovn_lb_datapaths *lb_dps =
-                ovn_lb_datapaths_find(lb_dps_map,
-                                &od->nbr->load_balancer[i]->header_.uuid);
-            ovs_assert(lb_dps);
-            build_lrouter_lb_reachable_ips(od, lb_dps->lb);
-        }
-
-        for (size_t i = 0; i < od->nbr->n_load_balancer_group; i++) {
-            const struct nbrec_load_balancer_group *nbrec_lb_group =
-                od->nbr->load_balancer_group[i];
-            struct ovn_lb_group_datapaths *lb_group_dps;
-
-            lb_group_dps =
-                ovn_lb_group_datapaths_find(lb_group_dps_map,
-                                            &nbrec_lb_group->header_.uuid);
-             ovs_assert(lb_group_dps);
-            for (size_t j = 0; j < lb_group_dps->lb_group->n_lbs; j++) {
-                build_lrouter_lb_reachable_ips(od,
-                                               lb_group_dps->lb_group->lbs[j]);
-            }
-        }
-    }
-}
-
 static void
 build_lswitch_lbs_from_lrouter(struct ovn_datapaths *lr_datapaths,
                                struct hmap *lb_dps_map,
@@ -4182,8 +3906,6 @@  build_lb_port_related_data(
     struct hmap *svc_monitor_map)
 {
     build_lrouter_lbs_check(lr_datapaths);
-    build_lrouter_lbs_reachable_ips(lr_datapaths, lb_dps_map,
-                                    lb_group_dps_map);
     build_lb_svcs(ovnsb_txn, sbrec_service_monitor_table, ls_ports, lb_dps_map,
                   svc_monitor_lsps, svc_monitor_map);
     build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
@@ -4549,7 +4271,8 @@  check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
  * Caller should make sure that the OVN SB IDL txn is not NULL.  Presently it
  * only syncs the nat column of port binding corresponding to the 'op->nbsp' */
 static void
-sync_pb_for_lsp(struct ovn_port *op)
+sync_pb_for_lsp(struct ovn_port *op,
+                const struct lr_stateful_table *lr_stateful_table)
 {
     ovs_assert(op->nbsp);
 
@@ -4568,10 +4291,17 @@  sync_pb_for_lsp(struct ovn_port *op)
         if (nat_addresses && !strcmp(nat_addresses, "router")) {
             if (op->peer && op->peer->od
                 && (chassis || op->peer->od->n_l3dgw_ports)) {
-                bool exclude_lb_vips = smap_get_bool(&op->nbsp->options,
+                bool include_lb_vips = !smap_get_bool(&op->nbsp->options,
                         "exclude-lb-vips-from-garp", false);
+
+                const struct lr_stateful_record *lr_stateful_rec = NULL;
+
+                if (include_lb_vips) {
+                    lr_stateful_rec = lr_stateful_table_find_by_index(
+                        lr_stateful_table, op->peer->od->index);
+                }
                 nats = get_nat_addresses(op->peer, &n_nats, false,
-                                            !exclude_lb_vips);
+                                         include_lb_vips, lr_stateful_rec);
             }
         } else if (nat_addresses && (chassis || l3dgw_ports)) {
             struct lport_addresses laddrs;
@@ -4678,7 +4408,8 @@  sync_pb_for_lsp(struct ovn_port *op)
  * Caller should make sure that the OVN SB IDL txn is not NULL.  Presently it
  * only sets the port binding options column for the router ports */
 static void
-sync_pb_for_lrp(struct ovn_port *op, const struct lr_nat_table *lr_nats)
+sync_pb_for_lrp(struct ovn_port *op,
+                const struct lr_stateful_table *lr_stateful_table)
 {
     ovs_assert(op->nbrp);
 
@@ -4687,14 +4418,14 @@  sync_pb_for_lrp(struct ovn_port *op, const struct lr_nat_table *lr_nats)
 
     const char *chassis_name = smap_get(&op->od->nbr->options, "chassis");
     if (is_cr_port(op)) {
-        const struct lr_nat_record *lrnat_rec =
-            lr_nat_table_find_by_index(lr_nats, op->od->index);
-        ovs_assert(lrnat_rec);
+        const struct lr_stateful_record *lr_stateful_rec =
+            lr_stateful_table_find_by_index(lr_stateful_table, op->od->index);
+        ovs_assert(lr_stateful_rec);
 
         smap_add(&new, "distributed-port", op->nbrp->name);
 
         bool always_redirect =
-            !lrnat_rec->has_distributed_nat &&
+            !lr_stateful_rec->lrnat_rec->has_distributed_nat &&
             !l3dgw_port_has_associated_vtep_lports(op->l3dgw_port);
 
         const char *redirect_type = smap_get(&op->nbrp->options,
@@ -4744,17 +4475,18 @@  static void ovn_update_ipv6_opt_for_op(struct ovn_port *op);
  * the logical switch ports. */
 void
 sync_pbs(struct ovsdb_idl_txn *ovnsb_idl_txn, struct hmap *ls_ports,
-         struct hmap *lr_ports, const struct lr_nat_table *lr_nats)
+         struct hmap *lr_ports,
+         const struct lr_stateful_table *lr_stateful_table)
 {
     ovs_assert(ovnsb_idl_txn);
 
     struct ovn_port *op;
     HMAP_FOR_EACH (op, key_node, ls_ports) {
-        sync_pb_for_lsp(op);
+        sync_pb_for_lsp(op, lr_stateful_table);
     }
 
     HMAP_FOR_EACH (op, key_node, lr_ports) {
-        sync_pb_for_lrp(op, lr_nats);
+        sync_pb_for_lrp(op, lr_stateful_table);
     }
 
     ovn_update_ipv6_options(lr_ports);
@@ -4763,16 +4495,18 @@  sync_pbs(struct ovsdb_idl_txn *ovnsb_idl_txn, struct hmap *ls_ports,
 /* Sync the SB Port bindings for the added and updated logical switch ports
  * of the tracked northd engine data. */
 bool
-sync_pbs_for_northd_changed_ovn_ports(struct tracked_ovn_ports *trk_ovn_ports)
+sync_pbs_for_northd_changed_ovn_ports(
+    struct tracked_ovn_ports *trk_ovn_ports,
+    const struct lr_stateful_table *lr_stateful_table)
 {
     struct hmapx_node *hmapx_node;
 
     HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->created) {
-        sync_pb_for_lsp(hmapx_node->data);
+        sync_pb_for_lsp(hmapx_node->data, lr_stateful_table);
     }
 
     HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->updated) {
-        sync_pb_for_lsp(hmapx_node->data);
+        sync_pb_for_lsp(hmapx_node->data, lr_stateful_table);
     }
 
     return true;
@@ -5441,8 +5175,7 @@  fail:
  *    - NAT changes
  */
 static bool
-lr_changes_can_be_handled(
-    const struct nbrec_logical_router *lr)
+lr_changes_can_be_handled(const struct nbrec_logical_router *lr)
 {
     /* Check if the columns are changed in this row. */
     enum nbrec_logical_router_column_id col;
@@ -5506,17 +5239,6 @@  is_lr_nats_changed(const struct nbrec_logical_router *nbr) {
             || is_lr_nats_seqno_changed(nbr));
 }
 
-static bool
-lr_has_routable_nats(const struct nbrec_logical_router *nbr) {
-    for (size_t i = 0; i < nbr->n_nat; i++) {
-        if (smap_get_bool(&nbr->nat[i]->options, "add_route", false)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
 /* Return true if changes are handled incrementally, false otherwise.
  *
  * Note: Changes to load balancer and load balancer groups associated with
@@ -5543,12 +5265,6 @@  northd_handle_lr_changes(const struct northd_input *ni,
         }
 
         if (is_lr_nats_changed(changed_lr)) {
-            if (lr_has_routable_nats(changed_lr)) {
-                /* router has routable NATs.  We can't handle these changes
-                 * incrementally yet.  Fall back to recompute. */
-                goto fail;
-            }
-
             struct ovn_datapath *od = ovn_datapath_find_(
                                     &nd->lr_datapaths.datapaths,
                                     &changed_lr->header_.uuid);
@@ -5830,10 +5546,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             ovs_assert(lb_dps);
             ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
 
-            /* Add the lb_ips of lb_dps to the od. */
-            build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
-            build_lrouter_lb_reachable_ips(od, lb_dps->lb);
-
             /* Add the lb to the northd tracked data. */
             hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
         }
@@ -5852,10 +5564,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                 ovs_assert(lb_dps);
                 ovn_lb_datapaths_add_lr(lb_dps, 1, &od);
 
-                /* Add the lb_ips of lb_dps to the od. */
-                build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
-                build_lrouter_lb_reachable_ips(od, lb_dps->lb);
-
                 /* Add the lb to the northd tracked data. */
                 hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
             }
@@ -5884,22 +5592,6 @@  northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             od = lr_datapaths->array[index];
             /* Re-evaluate 'od->has_lb_vip' */
             init_lb_for_datapath(od);
-
-            /* Update the od->lb_ips with the deleted and inserted
-             * vips (if any). */
-            remove_ips_from_lb_ip_set(od->lb_ips, lb->routable,
-                                      &clb->deleted_vips_v4,
-                                      &clb->deleted_vips_v6);
-            add_ips_to_lb_ip_set(od->lb_ips, lb->routable,
-                                 &clb->inserted_vips_v4,
-                                 &clb->inserted_vips_v6);
-
-            remove_lrouter_lb_reachable_ips(od, lb->neigh_mode,
-                                            &clb->deleted_vips_v4,
-                                            &clb->deleted_vips_v6);
-            add_neigh_ips_to_lrouter(od, lb->neigh_mode,
-                                     &clb->inserted_vips_v4,
-                                     &clb->inserted_vips_v6);
         }
     }
 
@@ -5924,9 +5616,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 lb_ips of lb_dps to the od. */
-                build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
             }
 
             for (size_t i = 0; i < lbgrp_dps->n_ls; i++) {
@@ -9226,7 +8915,7 @@  arp_nd_ns_match(const char *ips, int addr_family, struct ds *match)
 /* Returns 'true' if the IPv4 'addr' is on the same subnet with one of the
  * IPs configured on the router port.
  */
-static bool
+bool
 lrouter_port_ipv4_reachable(const struct ovn_port *op, ovs_be32 addr)
 {
     for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
@@ -9242,7 +8931,7 @@  lrouter_port_ipv4_reachable(const struct ovn_port *op, ovs_be32 addr)
 /* Returns 'true' if the IPv6 'addr' is on the same subnet with one of the
  * IPs configured on the router port.
  */
-static bool
+bool
 lrouter_port_ipv6_reachable(const struct ovn_port *op,
                             const struct in6_addr *addr)
 {
@@ -9304,12 +8993,11 @@  build_lswitch_rport_arp_req_flow(const char *ips,
  * - 75: ARP requests to router owned IPs (interface IP/LB/NAT).
  */
 static void
-build_lswitch_rport_arp_req_flows(struct ovn_port *op,
-                                  struct ovn_datapath *sw_od,
-                                  struct ovn_port *sw_op,
-                                  const struct lr_nat_table *lr_nats,
-                                  struct hmap *lflows,
-                                  const struct ovsdb_idl_row *stage_hint)
+build_lswitch_rport_arp_req_flows(
+    struct ovn_port *op, struct ovn_datapath *sw_od,
+    struct ovn_port *sw_op, const struct lr_nat_table *lr_nats,
+    const struct lr_stateful_table *lr_stateful_table,
+    struct hmap *lflows, const struct ovsdb_idl_row *stage_hint)
 {
     if (!op || !op->nbrp) {
         return;
@@ -9323,32 +9011,38 @@  build_lswitch_rport_arp_req_flows(struct ovn_port *op,
      * router port.
      * Priority: 80.
      */
-
-    const char *ip_addr;
-    SSET_FOR_EACH (ip_addr, &op->od->lb_ips->ips_v4_reachable) {
-        ovs_be32 ipv4_addr;
-
-        /* Check if the ovn port has a network configured on which we could
-         * expect ARP requests for the LB VIP.
-         */
-        if (ip_parse(ip_addr, &ipv4_addr) &&
-            lrouter_port_ipv4_reachable(op, ipv4_addr)) {
-            build_lswitch_rport_arp_req_flow(
-                ip_addr, AF_INET, sw_op, sw_od, 80, lflows,
-                stage_hint);
+    const struct lr_stateful_record *lr_stateful_rec = NULL;
+    if (op->od->nbr->n_load_balancer || op->od->nbr->n_load_balancer_group) {
+        lr_stateful_rec = lr_stateful_table_find_by_index(lr_stateful_table,
+                                                          op->od->index);
+        ovs_assert(lr_stateful_rec);
+
+        const char *ip_addr;
+        SSET_FOR_EACH (ip_addr, &lr_stateful_rec->lb_ips->ips_v4_reachable) {
+            ovs_be32 ipv4_addr;
+
+            /* Check if the ovn port has a network configured on which we could
+             * expect ARP requests for the LB VIP.
+             */
+            if (ip_parse(ip_addr, &ipv4_addr) &&
+                lrouter_port_ipv4_reachable(op, ipv4_addr)) {
+                build_lswitch_rport_arp_req_flow(
+                    ip_addr, AF_INET, sw_op, sw_od, 80, lflows,
+                    stage_hint);
+            }
         }
-    }
-    SSET_FOR_EACH (ip_addr, &op->od->lb_ips->ips_v6_reachable) {
-        struct in6_addr ipv6_addr;
+        SSET_FOR_EACH (ip_addr, &lr_stateful_rec->lb_ips->ips_v6_reachable) {
+            struct in6_addr ipv6_addr;
 
-        /* Check if the ovn port has a network configured on which we could
-         * expect NS requests for the LB VIP.
-         */
-        if (ipv6_parse(ip_addr, &ipv6_addr) &&
-            lrouter_port_ipv6_reachable(op, &ipv6_addr)) {
-            build_lswitch_rport_arp_req_flow(
-                ip_addr, AF_INET6, sw_op, sw_od, 80, lflows,
-                stage_hint);
+            /* Check if the ovn port has a network configured on which we could
+             * expect NS requests for the LB VIP.
+             */
+            if (ipv6_parse(ip_addr, &ipv6_addr) &&
+                lrouter_port_ipv6_reachable(op, &ipv6_addr)) {
+                build_lswitch_rport_arp_req_flow(
+                    ip_addr, AF_INET6, sw_op, sw_od, 80, lflows,
+                    stage_hint);
+            }
         }
     }
 
@@ -9398,13 +9092,17 @@  build_lswitch_rport_arp_req_flows(struct ovn_port *op,
          * expect ARP requests/NS for the DNAT external_ip.
          */
         if (nat_entry_is_v6(nat_entry)) {
-            if (!sset_contains(&op->od->lb_ips->ips_v6, nat->external_ip)) {
+            if (!lr_stateful_rec ||
+                !sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
+                               nat->external_ip)) {
                 build_lswitch_rport_arp_req_flow(
                     nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
                     stage_hint);
             }
         } else {
-            if (!sset_contains(&op->od->lb_ips->ips_v4, nat->external_ip)) {
+            if (!lr_stateful_rec ||
+                !sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
+                               nat->external_ip)) {
                 build_lswitch_rport_arp_req_flow(
                     nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
                     stage_hint);
@@ -9429,13 +9127,17 @@  build_lswitch_rport_arp_req_flows(struct ovn_port *op,
          * expect ARP requests/NS for the SNAT external_ip.
          */
         if (nat_entry_is_v6(nat_entry)) {
-            if (!sset_contains(&op->od->lb_ips->ips_v6, nat->external_ip)) {
+            if (!lr_stateful_rec ||
+                !sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
+                               nat->external_ip)) {
                 build_lswitch_rport_arp_req_flow(
                     nat->external_ip, AF_INET6, sw_op, sw_od, 80, lflows,
                     stage_hint);
             }
         } else {
-            if (!sset_contains(&op->od->lb_ips->ips_v4, nat->external_ip)) {
+            if (!lr_stateful_rec ||
+                !sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
+                               nat->external_ip)) {
                 build_lswitch_rport_arp_req_flow(
                     nat->external_ip, AF_INET, sw_op, sw_od, 80, lflows,
                     stage_hint);
@@ -10481,11 +10183,11 @@  build_lswitch_ip_mcast_igmp_mld(struct ovn_igmp_group *igmp_group,
 
 /* Ingress table 25: Destination lookup, unicast handling (priority 50), */
 static void
-build_lswitch_ip_unicast_lookup(struct ovn_port *op,
-                                const struct lr_nat_table *lr_nats,
-                                struct hmap *lflows,
-                                struct ds *actions,
-                                struct ds *match)
+build_lswitch_ip_unicast_lookup(
+    struct ovn_port *op, const struct lr_nat_table *lr_nats,
+    const struct lr_stateful_table *lr_stateful_table,
+    struct hmap *lflows, struct ds *actions,
+    struct ds *match)
 {
     ovs_assert(op->nbsp);
     if (lsp_is_external(op->nbsp)) {
@@ -10498,7 +10200,8 @@  build_lswitch_ip_unicast_lookup(struct ovn_port *op,
      */
     if (lsp_is_router(op->nbsp)) {
         build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lr_nats,
-                                          lflows, &op->nbsp->header_);
+                                          lr_stateful_table, lflows,
+                                          &op->nbsp->header_);
     }
 
     for (size_t i = 0; i < op->nbsp->n_addresses; i++) {
@@ -12688,6 +12391,7 @@  build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
 static void
 build_lrouter_drop_own_dest(struct ovn_port *op,
                             const struct lr_nat_record *lrnat_rec,
+                            const struct lr_stateful_record *lr_stateful_rec,
                             enum ovn_stage stage,
                             uint16_t priority, bool drop_snat_ip,
                             struct hmap *lflows)
@@ -12700,8 +12404,8 @@  build_lrouter_drop_own_dest(struct ovn_port *op,
 
             bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
                                                       ip);
-            bool router_ip_in_lb_ips =
-                    !!sset_find(&op->od->lb_ips->ips_v4, ip);
+            bool router_ip_in_lb_ips = lr_stateful_rec &&
+                !!sset_find(&lr_stateful_rec->lb_ips->ips_v4, ip);
             bool drop_router_ip = (drop_snat_ip == (router_ip_in_snat_ips ||
                                                     router_ip_in_lb_ips));
 
@@ -12730,8 +12434,8 @@  build_lrouter_drop_own_dest(struct ovn_port *op,
 
             bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
                                                       ip);
-            bool router_ip_in_lb_ips =
-                    !!sset_find(&op->od->lb_ips->ips_v6, ip);
+            bool router_ip_in_lb_ips = lr_stateful_rec &&
+                !!sset_find(&lr_stateful_rec->lb_ips->ips_v6, ip);
             bool drop_router_ip = (drop_snat_ip == (router_ip_in_snat_ips ||
                                                     router_ip_in_lb_ips));
 
@@ -13516,7 +13220,8 @@  build_ip_routing_flows_for_lrp(
  */
 static void
 build_ip_routing_flows_for_router_type_lsp(
-        struct ovn_port *op, const struct hmap *lr_ports, struct hmap *lflows)
+        struct ovn_port *op, const struct lr_stateful_table *lr_stateful_table,
+        const struct hmap *lr_ports, struct hmap *lflows)
 {
     ovs_assert(op->nbsp);
     if (!lsp_is_router(op->nbsp)) {
@@ -13524,28 +13229,38 @@  build_ip_routing_flows_for_router_type_lsp(
     }
 
     struct ovn_port *peer = ovn_port_get_peer(lr_ports, op);
-    if (!peer || !peer->nbrp || !peer->lrp_networks.n_ipv4_addrs) {
+    if (!peer || !peer->nbrp || !peer->lrp_networks.n_ipv4_addrs
+        || !op->od->n_router_ports) {
         return;
     }
 
-    for (int i = 0; i < op->od->n_router_ports; i++) {
+    for (size_t i = 0; i < op->od->n_router_ports; i++) {
         struct ovn_port *router_port = ovn_port_get_peer(
                 lr_ports, op->od->router_ports[i]);
         if (!router_port || !router_port->nbrp || router_port == peer) {
             continue;
         }
 
-        struct ovn_port_routable_addresses *ra = &router_port->routables;
-        for (size_t j = 0; j < ra->n_addrs; j++) {
-            struct lport_addresses *laddrs = &ra->laddrs[j];
-            for (size_t k = 0; k < laddrs->n_ipv4_addrs; k++) {
-                add_route(lflows, peer->od, peer,
-                          peer->lrp_networks.ipv4_addrs[0].addr_s,
-                          laddrs->ipv4_addrs[k].network_s,
-                          laddrs->ipv4_addrs[k].plen, NULL, false, 0,
-                          &peer->nbrp->header_, false,
-                          ROUTE_PRIO_OFFSET_CONNECTED);
+        const struct lr_stateful_record *lr_stateful_rec =
+            lr_stateful_table_find_by_index(lr_stateful_table,
+                                            router_port->od->index);
+
+        if (router_port->nbrp->ha_chassis_group ||
+                router_port->nbrp->n_gateway_chassis) {
+            struct ovn_port_routable_addresses ra =
+                get_op_routable_addresses(router_port, lr_stateful_rec);
+            for (size_t j = 0; j < ra.n_addrs; j++) {
+                struct lport_addresses *laddrs = &ra.laddrs[j];
+                for (size_t k = 0; k < laddrs->n_ipv4_addrs; k++) {
+                    add_route(lflows, peer->od, peer,
+                            peer->lrp_networks.ipv4_addrs[0].addr_s,
+                            laddrs->ipv4_addrs[k].network_s,
+                            laddrs->ipv4_addrs[k].plen, NULL, false, 0,
+                            &peer->nbrp->header_, false,
+                            ROUTE_PRIO_OFFSET_CONNECTED);
+                }
             }
+            destroy_routable_addresses(&ra);
         }
     }
 }
@@ -13771,33 +13486,36 @@  build_arp_resolve_flows_for_lrouter(
 
 static void
 routable_addresses_to_lflows(struct hmap *lflows, struct ovn_port *router_port,
-                             struct ovn_port *peer, struct ds *match,
-                             struct ds *actions)
+                             struct ovn_port *peer,
+                             const struct lr_stateful_record *lr_stateful_rec,
+                             struct ds *match, struct ds *actions)
 {
-    struct ovn_port_routable_addresses *ra = &router_port->routables;
-    if (!ra->n_addrs) {
+    struct ovn_port_routable_addresses ra =
+        get_op_routable_addresses(router_port, lr_stateful_rec);
+    if (!ra.n_addrs) {
         return;
     }
 
-    for (size_t i = 0; i < ra->n_addrs; i++) {
+    for (size_t i = 0; i < ra.n_addrs; i++) {
         ds_clear(match);
         ds_put_format(match, "outport == %s && "REG_NEXT_HOP_IPV4" == {",
                       peer->json_key);
         bool first = true;
-        for (size_t j = 0; j < ra->laddrs[i].n_ipv4_addrs; j++) {
+        for (size_t j = 0; j < ra.laddrs[i].n_ipv4_addrs; j++) {
             if (!first) {
                 ds_put_cstr(match, ", ");
             }
-            ds_put_cstr(match, ra->laddrs[i].ipv4_addrs[j].addr_s);
+            ds_put_cstr(match, ra.laddrs[i].ipv4_addrs[j].addr_s);
             first = false;
         }
         ds_put_cstr(match, "}");
 
         ds_clear(actions);
-        ds_put_format(actions, "eth.dst = %s; next;", ra->laddrs[i].ea_s);
+        ds_put_format(actions, "eth.dst = %s; next;", ra.laddrs[i].ea_s);
         ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, 100,
                       ds_cstr(match), ds_cstr(actions));
     }
+    destroy_routable_addresses(&ra);
 }
 
 /* Local router ingress table ARP_RESOLVE: ARP Resolution.
@@ -13814,6 +13532,7 @@  routable_addresses_to_lflows(struct hmap *lflows, struct ovn_port *router_port,
 static void
 build_arp_resolve_flows_for_lrp(
         struct ovn_port *op, const struct lr_nat_record *lrnat_rec,
+        const struct lr_stateful_record *lr_stateful_rec,
         struct hmap *lflows, struct ds *match, struct ds *actions)
 {
     ovs_assert(op->nbrp);
@@ -13890,8 +13609,8 @@  build_arp_resolve_flows_for_lrp(
      *
      * Priority 2.
      */
-    build_lrouter_drop_own_dest(op, lrnat_rec, S_ROUTER_IN_ARP_RESOLVE, 2,
-                                true, lflows);
+    build_lrouter_drop_own_dest(op, lrnat_rec, lr_stateful_rec,
+                                S_ROUTER_IN_ARP_RESOLVE, 2, true, lflows);
 }
 
 /* This function adds ARP resolve flows related to a LSP. */
@@ -13899,6 +13618,7 @@  static void
 build_arp_resolve_flows_for_lsp(
         struct ovn_port *op, struct hmap *lflows,
         const struct hmap *lr_ports,
+        const struct lr_stateful_table *lr_stateful_table,
         struct ds *match, struct ds *actions)
 {
     ovs_assert(op->nbsp);
@@ -14042,8 +13762,11 @@  build_arp_resolve_flows_for_lsp(
 
             if (smap_get(&peer->od->nbr->options, "chassis")
                 || peer->cr_port) {
+                const struct lr_stateful_record *lr_stateful_rec =
+                    lr_stateful_table_find_by_index(lr_stateful_table,
+                                                    router_port->od->index);
                 routable_addresses_to_lflows(lflows, router_port, peer,
-                                             match, actions);
+                                             lr_stateful_rec, match, actions);
             }
         }
     }
@@ -14763,6 +14486,7 @@  static void
 build_lrouter_ipv4_ip_input(struct ovn_port *op,
                             struct hmap *lflows,
                             const struct lr_nat_record *lrnat_rec,
+                            const struct lr_stateful_record *lr_stateful_rec,
                             struct ds *match, struct ds *actions,
                             const struct shash *meter_groups)
 {
@@ -14887,7 +14611,8 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
                                &op->nbrp->header_, lflows);
     }
 
-    if (sset_count(&op->od->lb_ips->ips_v4_reachable)) {
+    if (lr_stateful_rec && sset_count(
+            &lr_stateful_rec->lb_ips->ips_v4_reachable)) {
         ds_clear(match);
         if (is_l3dgw_port(op)) {
             ds_put_format(match, "is_chassis_resident(%s)",
@@ -14903,7 +14628,8 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
         free(lb_ips_v4_as);
     }
 
-    if (sset_count(&op->od->lb_ips->ips_v6_reachable)) {
+    if (lr_stateful_rec && sset_count(
+            &lr_stateful_rec->lb_ips->ips_v6_reachable)) {
         ds_clear(match);
 
         if (is_l3dgw_port(op)) {
@@ -15005,8 +14731,8 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
      * Priority 60.
      */
     if (!lrnat_rec->lb_force_snat_router_ip) {
-        build_lrouter_drop_own_dest(op, lrnat_rec, S_ROUTER_IN_IP_INPUT, 60,
-                                    false, lflows);
+        build_lrouter_drop_own_dest(op, lrnat_rec, lr_stateful_rec,
+                                    S_ROUTER_IN_IP_INPUT, 60, false, lflows);
     }
     /* ARP / ND handling for external IP addresses.
      *
@@ -16145,6 +15871,7 @@  struct lswitch_flow_build_info {
     const struct hmap *lr_ports;
     const struct ls_port_group_table *ls_port_groups;
     const struct lr_nat_table *lr_nats;
+    const struct lr_stateful_table *lr_stateful_table;
     struct hmap *lflows;
     struct hmap *igmp_groups;
     const struct shash *meter_groups;
@@ -16228,14 +15955,15 @@  build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,
  * switch port.
  */
 static void
-build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
-                                         const struct hmap *ls_ports,
-                                         const struct hmap *lr_ports,
-                                         const struct lr_nat_table *lr_nats,
-                                         const struct shash *meter_groups,
-                                         struct ds *match,
-                                         struct ds *actions,
-                                         struct hmap *lflows)
+build_lswitch_and_lrouter_iterate_by_lsp(
+    struct ovn_port *op, const struct hmap *ls_ports,
+    const struct hmap *lr_ports,
+    const struct lr_nat_table *lr_nats,
+    const struct lr_stateful_table *lr_stateful_table,
+    const struct shash *meter_groups,
+    struct ds *match,
+    struct ds *actions,
+    struct hmap *lflows)
 {
     ovs_assert(op->nbsp);
     start_collecting_lflows();
@@ -16249,11 +15977,14 @@  build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
     build_lswitch_dhcp_options_and_response(op, lflows, meter_groups);
     build_lswitch_external_port(op, lflows);
     build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
-    build_lswitch_ip_unicast_lookup(op, lr_nats, lflows, actions, match);
+    build_lswitch_ip_unicast_lookup(op, lr_nats, lr_stateful_table, lflows,
+                                    actions, match);
 
     /* Build Logical Router Flows. */
-    build_ip_routing_flows_for_router_type_lsp(op, lr_ports, lflows);
-    build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
+    build_ip_routing_flows_for_router_type_lsp(op, lr_stateful_table, lr_ports,
+                                               lflows);
+    build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, lr_stateful_table,
+                                    match, actions);
 
     link_ovn_port_to_lflows(op, &collected_lflows);
     end_collecting_lflows();
@@ -16272,6 +16003,8 @@  build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
         lsi->lr_nats, op->od->index);
     ovs_assert(lrnet_rec);
 
+    const struct lr_stateful_record *lr_stateful_rec =
+        lr_stateful_table_find_by_index(lsi->lr_stateful_table, op->od->index);
     build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
                                           &lsi->actions);
     build_neigh_learning_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
@@ -16279,15 +16012,15 @@  build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
     build_ip_routing_flows_for_lrp(op, lsi->lflows);
     build_ND_RA_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
                                        &lsi->actions, lsi->meter_groups);
-    build_arp_resolve_flows_for_lrp(op, lrnet_rec, lsi->lflows, &lsi->match,
-                                    &lsi->actions);
+    build_arp_resolve_flows_for_lrp(op, lrnet_rec, lr_stateful_rec,
+                                    lsi->lflows, &lsi->match, &lsi->actions);
     build_egress_delivery_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
                                                  &lsi->actions);
     build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match);
     build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
                                             &lsi->match, &lsi->actions,
                                             lsi->meter_groups);
-    build_lrouter_ipv4_ip_input(op, lsi->lflows, lrnet_rec,
+    build_lrouter_ipv4_ip_input(op, lsi->lflows, lrnet_rec, lr_stateful_rec,
                                 &lsi->match, &lsi->actions, lsi->meter_groups);
     build_lrouter_force_snat_flows_op(op, lrnet_rec, lsi->lflows, &lsi->match,
                                       &lsi->actions);
@@ -16349,13 +16082,10 @@  build_lflows_thread(void *arg)
                     if (stop_parallel_processing()) {
                         return NULL;
                     }
-                    build_lswitch_and_lrouter_iterate_by_lsp(op, lsi->ls_ports,
-                                                             lsi->lr_ports,
-                                                             lsi->lr_nats,
-                                                             lsi->meter_groups,
-                                                             &lsi->match,
-                                                             &lsi->actions,
-                                                             lsi->lflows);
+                    build_lswitch_and_lrouter_iterate_by_lsp(
+                        op, lsi->ls_ports, lsi->lr_ports, lsi->lr_nats,
+                        lsi->lr_stateful_table, lsi->meter_groups,
+                        &lsi->match, &lsi->actions, lsi->lflows);
                 }
             }
             for (bnum = control->id;
@@ -16456,19 +16186,21 @@  fix_flow_map_size(struct hmap *lflow_map,
 }
 
 static void
-build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
-                                const struct ovn_datapaths *lr_datapaths,
-                                const struct hmap *ls_ports,
-                                const struct hmap *lr_ports,
-                                const struct ls_port_group_table *ls_pgs,
-                                const struct lr_nat_table *lr_nats,
-                                struct hmap *lflows,
-                                struct hmap *igmp_groups,
-                                const struct shash *meter_groups,
-                                const struct hmap *lb_dps_map,
-                                const struct hmap *svc_monitor_map,
-                                const struct hmap *bfd_connections,
-                                const struct chassis_features *features)
+build_lswitch_and_lrouter_flows(
+    const struct ovn_datapaths *ls_datapaths,
+    const struct ovn_datapaths *lr_datapaths,
+    const struct hmap *ls_ports,
+    const struct hmap *lr_ports,
+    const struct ls_port_group_table *ls_pgs,
+    const struct lr_nat_table *lr_nats,
+    const struct lr_stateful_table *lr_stateful_table,
+    struct hmap *lflows,
+    struct hmap *igmp_groups,
+    const struct shash *meter_groups,
+    const struct hmap *lb_dps_map,
+    const struct hmap *svc_monitor_map,
+    const struct hmap *bfd_connections,
+    const struct chassis_features *features)
 {
 
     char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac);
@@ -16492,6 +16224,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_nats = lr_nats;
+            lsiv[index].lr_stateful_table = lr_stateful_table;
             lsiv[index].igmp_groups = igmp_groups;
             lsiv[index].meter_groups = meter_groups;
             lsiv[index].lb_dps_map = lb_dps_map;
@@ -16527,6 +16260,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
             .lr_ports = lr_ports,
             .ls_port_groups = ls_pgs,
             .lr_nats = lr_nats,
+            .lr_stateful_table = lr_stateful_table,
             .lflows = lflows,
             .igmp_groups = igmp_groups,
             .meter_groups = meter_groups,
@@ -16555,6 +16289,7 @@  build_lswitch_and_lrouter_flows(const struct ovn_datapaths *ls_datapaths,
             build_lswitch_and_lrouter_iterate_by_lsp(op, lsi.ls_ports,
                                                      lsi.lr_ports,
                                                      lsi.lr_nats,
+                                                     lsi.lr_stateful_table,
                                                      lsi.meter_groups,
                                                      &lsi.match, &lsi.actions,
                                                      lsi.lflows);
@@ -16676,6 +16411,7 @@  void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                                     input_data->lr_ports,
                                     input_data->ls_port_groups,
                                     input_data->lr_nats,
+                                    input_data->lr_stateful_table,
                                     lflows,
                                     &igmp_groups,
                                     input_data->meter_groups,
@@ -17151,12 +16887,10 @@  lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
         /* Generate new lflows. */
         struct ds match = DS_EMPTY_INITIALIZER;
         struct ds actions = DS_EMPTY_INITIALIZER;
-        build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
-                                                 lflow_input->lr_ports,
-                                                 lflow_input->lr_nats,
-                                                 lflow_input->meter_groups,
-                                                 &match, &actions,
-                                                 lflows);
+        build_lswitch_and_lrouter_iterate_by_lsp(
+            op, lflow_input->ls_ports, lflow_input->lr_ports,
+            lflow_input->lr_nats, lflow_input->lr_stateful_table,
+            lflow_input->meter_groups, &match, &actions, lflows);
         ds_destroy(&match);
         ds_destroy(&actions);
 
@@ -17188,12 +16922,10 @@  lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
 
         struct ds match = DS_EMPTY_INITIALIZER;
         struct ds actions = DS_EMPTY_INITIALIZER;
-        build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
-                                                 lflow_input->lr_ports,
-                                                 lflow_input->lr_nats,
-                                                 lflow_input->meter_groups,
-                                                 &match, &actions,
-                                                 lflows);
+        build_lswitch_and_lrouter_iterate_by_lsp(
+            op, lflow_input->ls_ports, lflow_input->lr_ports,
+            lflow_input->lr_nats, lflow_input->lr_stateful_table,
+            lflow_input->meter_groups, &match, &actions, lflows);
         ds_destroy(&match);
         ds_destroy(&actions);
 
diff --git a/northd/northd.h b/northd/northd.h
index 88e290373e..a6733b0f8f 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -180,6 +180,7 @@  struct lflow_input {
     const struct hmap *lr_ports;
     const struct ls_port_group_table *ls_port_groups;
     const struct lr_nat_table *lr_nats;
+    const struct lr_stateful_table *lr_stateful_table;
     const struct shash *meter_groups;
     const struct hmap *lb_datapaths_map;
     const struct hmap *bfd_connections;
@@ -319,9 +320,6 @@  struct ovn_datapath {
     /* router datapath has a logical port with redirect-type set to bridged. */
     bool redirect_bridged;
 
-    /* Load Balancer vIPs relevant for this datapath. */
-    struct ovn_lb_ip_set *lb_ips;
-
     struct ovn_port **localnet_ports;
     size_t n_localnet_ports;
 
@@ -337,6 +335,129 @@  struct ovn_datapath {
 
 const struct ovn_datapath *ovn_datapath_find(const struct hmap *datapaths,
                                              const struct uuid *uuid);
+static inline struct ovn_datapath *
+ovn_datapaths_find_by_index(const struct ovn_datapaths *ovn_datapaths,
+                            size_t od_index)
+{
+    ovs_assert(od_index <= hmap_count(&ovn_datapaths->datapaths));
+    return ovn_datapaths->array[od_index];
+}
+
+/* A logical switch port or logical router port.
+ *
+ * In steady state, an ovn_port points to a northbound Logical_Switch_Port
+ * record (via 'nbsp') *or* a Logical_Router_Port record (via 'nbrp'), and to a
+ * southbound Port_Binding record (via 'sb').  As the state of the system
+ * changes, join_logical_ports() may determine that there is a new LSP or LRP
+ * that has no corresponding Port_Binding record (in which case build_ports())
+ * will create the missing Port_Binding) or that a Port_Binding record exists
+ * that has no coresponding LSP (in which case build_ports() will delete the
+ * spurious Port_Binding).  Thus, after build_ports() runs, any given ovn_port
+ * will have 'sb' nonnull, and 'nbsp' xor 'nbrp' nonnull.
+ *
+ * Ordinarily there is only one ovn_port that points to a given LSP or LRP (but
+ * distributed gateway ports point a "derived" ovn_port to a duplicate LRP).
+ */
+struct ovn_port {
+    /* Port name aka key.
+     *
+     * This is ordinarily the same as nbsp->name or nbrp->name and
+     * sb->logical_port.  (A distributed gateway port creates a "derived"
+     * ovn_port with key "cr-%s" % nbrp->name.) */
+    struct hmap_node key_node;  /* Index on 'key'. */
+    char *key;                  /* nbsp->name, nbrp->name, sb->logical_port. */
+    char *json_key;             /* 'key', quoted for use in JSON. */
+
+    const struct sbrec_port_binding *sb;         /* May be NULL. */
+
+    uint32_t tunnel_key;
+
+    /* Logical switch port data. */
+    const struct nbrec_logical_switch_port *nbsp; /* May be NULL. */
+
+    struct lport_addresses *lsp_addrs;  /* Logical switch port addresses. */
+    unsigned int n_lsp_addrs;  /* Total length of lsp_addrs. */
+    unsigned int n_lsp_non_router_addrs; /* Number of elements from the
+                                          * beginning of 'lsp_addrs' extracted
+                                          * directly from LSP 'addresses'. */
+
+    struct lport_addresses *ps_addrs;   /* Port security addresses. */
+    unsigned int n_ps_addrs;
+
+    bool lsp_can_be_inc_processed; /* If it can be incrementally processed when
+                                      the port changes. */
+
+    /* Logical router port data. */
+    const struct nbrec_logical_router_port *nbrp; /* May be NULL. */
+
+    struct lport_addresses lrp_networks;
+
+    /* Logical port multicast data. */
+    struct mcast_port_info mcast_info;
+
+    /* At most one of l3dgw_port and cr_port can be not NULL. */
+
+    /* This is set to a distributed gateway port if and only if this ovn_port
+     * is "derived" from it. Otherwise this is set to NULL. The derived
+     * ovn_port represents the instance of distributed gateway port on the
+     * gateway chassis.*/
+    struct ovn_port *l3dgw_port;
+
+    /* This is set to the "derived" chassis-redirect port of this port if and
+     * only if this port is a distributed gateway port. Otherwise this is set
+     * to NULL. */
+    struct ovn_port *cr_port;
+
+    bool has_unknown; /* If the addresses have 'unknown' defined. */
+
+    bool has_bfd;
+
+    /* The port's peer:
+     *
+     *     - A switch port S of type "router" has a router port R as a peer,
+     *       and R in turn has S has its peer.
+     *
+     *     - Two connected logical router ports have each other as peer.
+     *
+     *     - Other kinds of ports have no peer. */
+    struct ovn_port *peer;
+
+    struct ovn_datapath *od;
+
+    struct ovs_list list;       /* In list of similar records. */
+
+    struct hmap_node dp_node;   /* Node in od->ports. */
+
+    struct lport_addresses proxy_arp_addrs;
+
+    /* Temporarily used for traversing a list (or hmap) of ports. */
+    bool visited;
+
+    /* List of struct lflow_ref_node that points to the lflows generated by
+     * this ovn_port.
+     *
+     * This data is initialized and destroyed by the en_northd node, but
+     * populated and used only by the en_lflow node. Ideally this data should
+     * be maintained as part of en_lflow's data (struct lflow_data): a hash
+     * index from ovn_port key to lflows.  However, it would be less efficient
+     * and more complex:
+     *
+     * 1. It would require an extra search (using the index) to find the
+     * lflows.
+     *
+     * 2. Building the index needs to be thread-safe, using either a global
+     * lock which is obviously less efficient, or hash-based lock array which
+     * is more complex.
+     *
+     * Adding the list here is more straightforward. The drawback is that we
+     * need to keep in mind that this data belongs to en_lflow node, so never
+     * access it from any other nodes.
+     */
+    struct ovs_list lflows;
+
+    /* Only used for the router type LSP whose peer is l3dgw_port */
+    bool enable_router_port_acl;
+};
 
 void ovnnb_db_run(struct northd_input *input_data,
                   struct northd_data *data,
@@ -397,9 +518,13 @@  void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
               struct chassis_features *chassis_features);
 bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
 
+struct lr_stateful_table;
 void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports,
-              struct hmap *lr_ports, const struct lr_nat_table *);
-bool sync_pbs_for_northd_changed_ovn_ports(struct tracked_ovn_ports *);
+              struct hmap *lr_ports,
+              const struct lr_stateful_table *);
+bool sync_pbs_for_northd_changed_ovn_ports(
+    struct tracked_ovn_ports *,
+    const struct lr_stateful_table *);
 
 static inline bool
 northd_has_tracked_data(struct northd_tracked_data *trk_nd_changes) {
@@ -424,4 +549,15 @@  northd_has_lr_nats_in_tracked_data(struct northd_tracked_data *trk_nd_changes)
     return trk_nd_changes->type & NORTHD_TRACKED_LR_NATS;
 }
 
+/* Returns 'true' if the IPv4 'addr' is on the same subnet with one of the
+ * IPs configured on the router port.
+ */
+bool lrouter_port_ipv4_reachable(const struct ovn_port *, ovs_be32 addr);
+
+/* Returns 'true' if the IPv6 'addr' is on the same subnet with one of the
+ * IPs configured on the router port.
+ */
+bool lrouter_port_ipv6_reachable(const struct ovn_port *,
+                                 const struct in6_addr *);
+
 #endif /* NORTHD_H */
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 04731e95dc..5c68350442 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -879,6 +879,7 @@  main(int argc, char *argv[])
     stopwatch_create(PORT_GROUP_RUN_STOPWATCH_NAME, SW_MS);
     stopwatch_create(SYNC_METERS_RUN_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LR_NAT_RUN_STOPWATCH_NAME, SW_MS);
+    stopwatch_create(LR_STATEFUL_RUN_STOPWATCH_NAME, SW_MS);
 
     /* Initialize incremental processing engine for ovn-northd */
     inc_proc_northd_init(&ovnnb_idl_loop, &ovnsb_idl_loop);
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index f66e2369e0..f5cf4f25c9 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -10613,18 +10613,21 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
 check ovn-nbctl --wait=sb set load_balancer . options:foo=bar
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
 check ovn-nbctl --wait=sb -- lb-add lb2 20.0.0.10:80 20.0.0.20:80 -- lb-add lb3 30.0.0.10:80 30.0.0.20:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10634,6 +10637,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb -- lb-del lb2 -- lb-del lb3
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10647,6 +10651,7 @@  AT_CHECK([ovn-nbctl --wait=sb \
 ])
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10664,6 +10669,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear Load_Balancer . health_check
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10678,6 +10684,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute compute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10686,6 +10693,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 # A LB applied to a switch/router triggers:
 # - a recompute in the first iteration (handling northd change)
@@ -10698,6 +10706,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.100:80"'
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
@@ -10707,6 +10716,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
@@ -10716,6 +10726,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
@@ -10725,6 +10736,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
@@ -10734,6 +10746,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-del sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
@@ -10744,6 +10757,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb1 -- lsp-add sw0 sw0p1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 
@@ -10764,6 +10778,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-lb-add lr0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10773,6 +10788,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.100:80"'
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10782,6 +10798,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10791,6 +10808,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10800,6 +10818,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10829,6 +10848,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10836,6 +10856,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear load_balancer_group . load_Balancer
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -10852,6 +10873,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 
@@ -10868,6 +10890,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.100:80"'
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10877,6 +10900,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10886,6 +10910,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10895,6 +10920,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10910,6 +10936,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl add logical_router lr0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10919,6 +10946,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer lb1 vips:'"10.0.0.10:80"'='"10.0.0.100:80"'
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10928,6 +10956,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear load_Balancer lb1 vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10937,6 +10966,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10946,6 +10976,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-add lb1 10.0.0.20:80 10.0.0.30:8080
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -10954,6 +10985,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear logical_router lr0 load_balancer_group
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 
@@ -10962,6 +10994,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb add logical_switch sw0 load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 
@@ -10970,6 +11003,7 @@  check ovn-nbctl --wait=sb clear logical_switch sw0 load_balancer_group -- \
     destroy load_balancer_group $lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb compute compute
 
@@ -10993,6 +11027,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute nocompute
 check_engine_stats sync_to_sb_lb norecompute nocompute
 
@@ -11000,6 +11035,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 
@@ -11007,6 +11043,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set logical_switch sw0 load_balancer_group=$lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11015,6 +11052,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set logical_router lr1 load_balancer_group=$lbg1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11023,6 +11061,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11031,6 +11070,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-add sw0 lb3
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11040,6 +11080,7 @@  check ovn-nbctl --wait=sb lr-lb-add lr1 lb1
 check ovn-nbctl --wait=sb lr-lb-add lr1 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11048,6 +11089,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb ls-lb-del sw0 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11056,6 +11098,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-lb-del lr1 lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11066,6 +11109,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-del lb4
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11076,6 +11120,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lb-del lb2
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11084,6 +11129,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb remove load_balancer_group . load_balancer $lb3_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11326,6 +11372,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-add lr0
 check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11338,6 +11385,7 @@  check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
 # for the SB port binding change.
 check_engine_stats northd recompute compute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11349,6 +11397,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lsp-set-options sw0-lr0 router-port=lr0-sw0
 check_engine_stats northd recompute compute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11374,6 +11423,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl set logical_router_port lr0-sw0 options:foo=bar
 check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11403,6 +11453,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set NAT . external_ip=172.168.0.120
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11412,6 +11463,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set NAT . logical_ip=10.0.0.10
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11421,6 +11473,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set NAT . type=snat
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11430,6 +11483,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-nat-add lr0 dnat_and_snat 172.168.0.110 10.0.0.4 sw0p1 30:54:00:00:00:03
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11440,6 +11494,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb set NAT $nat2_uuid external_mac='"30:54:00:00:00:04"'
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11456,6 +11511,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-nat-add lr0 dnat_and_snat 172.168.0.140 10.0.0.20
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11464,6 +11520,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-nat-add lr0 dnat_and_snat 172.168.0.150 10.0.0.41
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11472,6 +11529,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-nat-del lr0 dnat_and_snat 172.168.0.150
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11480,6 +11538,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-nat-del lr0 dnat_and_snat 172.168.0.140
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11489,6 +11548,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb clear logical_router lr0 nat
 check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
+check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11498,6 +11558,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-policy-add lr0  10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102
 check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
@@ -11506,6 +11567,7 @@  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 check ovn-nbctl --wait=sb lr-policy-del lr0  10 "ip4.src == 10.0.0.3"
 check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
+check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE