diff mbox series

[ovs-dev,v6,03/13] northd: Generate router's stateful flows using lr_stateful data.

Message ID 20240130212111.1482259-1-numans@ovn.org
State Accepted
Headers show
Series northd lflow incremental processing | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_ovn-kubernetes success github build: passed
ovsrobot/github-robot-_Build_and_Test fail github build: failed
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>

Previous commits added new engine nodes to store logical router's
stateful (LB and NAT data).  Make use of the data stored by these
engine nodes to generate logical flows related to router's LBs and NATs.

Acked-by: Dumitru Ceara <dceara@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 lib/stopwatch-names.h    |   1 +
 northd/en-lflow.c        |   3 -
 northd/en-lr-stateful.h  |   4 +
 northd/inc-proc-northd.c |   1 -
 northd/northd.c          | 816 +++++++++++++++++++++++++--------------
 northd/northd.h          |   1 -
 northd/ovn-northd.c      |   1 +
 7 files changed, 531 insertions(+), 296 deletions(-)

Comments

Numan Siddique Feb. 1, 2024, 3:10 p.m. UTC | #1
Recheck-request: github-robot-_Build_and_Test

On Tue, Jan 30, 2024 at 4:22 PM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> Previous commits added new engine nodes to store logical router's
> stateful (LB and NAT data).  Make use of the data stored by these
> engine nodes to generate logical flows related to router's LBs and NATs.
>
> Acked-by: Dumitru Ceara <dceara@redhat.com>
> Signed-off-by: Numan Siddique <numans@ovn.org>

Recheck-request: github-robot-_Build_and_Test

> ---
>  lib/stopwatch-names.h    |   1 +
>  northd/en-lflow.c        |   3 -
>  northd/en-lr-stateful.h  |   4 +
>  northd/inc-proc-northd.c |   1 -
>  northd/northd.c          | 816 +++++++++++++++++++++++++--------------
>  northd/northd.h          |   1 -
>  northd/ovn-northd.c      |   1 +
>  7 files changed, 531 insertions(+), 296 deletions(-)
>
> diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
> index e5e41fbfd8..1e8a5b656f 100644
> --- a/lib/stopwatch-names.h
> +++ b/lib/stopwatch-names.h
> @@ -24,6 +24,7 @@
>  #define LFLOWS_DATAPATHS_STOPWATCH_NAME "lflows_datapaths"
>  #define LFLOWS_PORTS_STOPWATCH_NAME "lflows_ports"
>  #define LFLOWS_LBS_STOPWATCH_NAME "lflows_lbs"
> +#define LFLOWS_LR_STATEFUL_STOPWATCH_NAME "lflows_lr_stateful"
>  #define LFLOWS_IGMP_STOPWATCH_NAME "lflows_igmp"
>  #define LFLOWS_DP_GROUPS_STOPWATCH_NAME "lflows_dp_groups"
>  #define LFLOWS_TO_SB_STOPWATCH_NAME "lflows_to_sb"
> diff --git a/northd/en-lflow.c b/northd/en-lflow.c
> index 97c7f383cd..bd2296ac43 100644
> --- a/northd/en-lflow.c
> +++ b/northd/en-lflow.c
> @@ -42,8 +42,6 @@ lflow_get_input_data(struct engine_node *node,
>          engine_get_input_data("port_group", node);
>      struct sync_meters_data *sync_meters_data =
>          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);
>
> @@ -68,7 +66,6 @@ lflow_get_input_data(struct engine_node *node,
>      lflow_input->ls_ports = &northd_data->ls_ports;
>      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;
> diff --git a/northd/en-lr-stateful.h b/northd/en-lr-stateful.h
> index bc3265adac..c6d62cb835 100644
> --- a/northd/en-lr-stateful.h
> +++ b/northd/en-lr-stateful.h
> @@ -73,6 +73,10 @@ struct lr_stateful_table {
>  #define LR_STATEFUL_TABLE_FOR_EACH(LR_LB_NAT_REC, TABLE) \
>      HMAP_FOR_EACH (LR_LB_NAT_REC, key_node, &(TABLE)->entries)
>
> +#define LR_STATEFUL_TABLE_FOR_EACH_IN_P(LR_STATEFUL_REC, JOBID, TABLE) \
> +    HMAP_FOR_EACH_IN_PARALLEL (LR_STATEFUL_REC, key_node, JOBID, \
> +                               &(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'. */
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 97bcce9655..adb38dde78 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -221,7 +221,6 @@ 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);
> diff --git a/northd/northd.c b/northd/northd.c
> index a425a8d7ab..c4b4f7f886 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -8857,18 +8857,14 @@ build_lrouter_groups(struct hmap *lr_ports, struct ovs_list *lr_list)
>   */
>  static void
>  build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op,
> -                                           uint32_t priority,
> -                                           struct ovn_datapath *od,
> -                                           const struct lr_nat_table *lr_nats,
> -                                           struct hmap *lflows)
> +                                        uint32_t priority,
> +                                        const struct ovn_datapath *od,
> +                                        const struct lr_nat_record *lrnat_rec,
> +                                        struct hmap *lflows)
>  {
>      struct ds eth_src = DS_EMPTY_INITIALIZER;
>      struct ds match = DS_EMPTY_INITIALIZER;
>
> -    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
> -            lr_nats, op->od->index);
> -    ovs_assert(lrnat_rec);
> -
>      /* Self originated ARP requests/RARP/ND need to be flooded to the L2 domain
>       * (except on router ports).  Determine that packets are self originated
>       * by also matching on source MAC. Matching on ingress port is not
> @@ -8954,10 +8950,11 @@ lrouter_port_ipv6_reachable(const struct ovn_port *op,
>   * switching domain as regular broadcast.
>   */
>  static void
> -build_lswitch_rport_arp_req_flow(const char *ips,
> -    int addr_family, struct ovn_port *patch_op, struct ovn_datapath *od,
> -    uint32_t priority, struct hmap *lflows,
> -    const struct ovsdb_idl_row *stage_hint)
> +build_lswitch_rport_arp_req_flow(const char *ips, int addr_family,
> +                                 struct ovn_port *patch_op,
> +                                 const struct ovn_datapath *od,
> +                                 uint32_t priority, struct hmap *lflows,
> +                                 const struct ovsdb_idl_row *stage_hint)
>  {
>      struct ds match   = DS_EMPTY_INITIALIZER;
>      struct ds actions = DS_EMPTY_INITIALIZER;
> @@ -8993,10 +8990,47 @@ 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,
> -    const struct lr_stateful_table *lr_stateful_table,
> +build_lswitch_rport_arp_req_flows(struct ovn_port *op,
> +                                  struct ovn_datapath *sw_od,
> +                                  struct ovn_port *sw_op,
> +                                  struct hmap *lflows,
> +                                  const struct ovsdb_idl_row *stage_hint)
> +{
> +    if (!op || !op->nbrp) {
> +        return;
> +    }
> +
> +    if (!lrport_is_enabled(op->nbrp)) {
> +        return;
> +    }
> +
> +    /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this
> +     * router port.
> +     * Priority: 80.
> +     */
> +    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> +        build_lswitch_rport_arp_req_flow(
> +            op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
> +            lflows, stage_hint);
> +    }
> +    for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> +        build_lswitch_rport_arp_req_flow(
> +            op->lrp_networks.ipv6_addrs[i].addr_s, AF_INET6, sw_op, sw_od, 80,
> +            lflows, stage_hint);
> +    }
> +}
> +
> +/*
> + * Ingress table 25: Flows that forward ARP/ND requests only to the routers
> + * that own the addresses.
> + * Priorities:
> + * - 80: self originated GARPs that need to follow regular processing.
> + * - 75: ARP requests to router owned IPs (interface IP/LB/NAT).
> + */
> +static void
> +build_lswitch_rport_arp_req_flows_for_lbnats(
> +    struct ovn_port *op, const struct lr_stateful_record *lr_stateful_rec,
> +    const struct ovn_datapath *sw_od, struct ovn_port *sw_op,
>      struct hmap *lflows, const struct ovsdb_idl_row *stage_hint)
>  {
>      if (!op || !op->nbrp) {
> @@ -9007,16 +9041,14 @@ build_lswitch_rport_arp_req_flows(
>          return;
>      }
>
> +    ovs_assert(uuid_equals(&op->od->nbr->header_.uuid,
> +                           &lr_stateful_rec->nbr_uuid));
> +
>      /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this
>       * router port.
>       * Priority: 80.
>       */
> -    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;
> @@ -9046,17 +9078,6 @@ build_lswitch_rport_arp_req_flows(
>          }
>      }
>
> -    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> -        build_lswitch_rport_arp_req_flow(
> -            op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
> -            lflows, stage_hint);
> -    }
> -    for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> -        build_lswitch_rport_arp_req_flow(
> -            op->lrp_networks.ipv6_addrs[i].addr_s, AF_INET6, sw_op, sw_od, 80,
> -            lflows, stage_hint);
> -    }
> -
>      /* Self originated ARP requests/RARP/ND need to be flooded as usual.
>       *
>       * However, if the switch doesn't have any non-router ports we shouldn't
> @@ -9065,19 +9086,14 @@ build_lswitch_rport_arp_req_flows(
>       * Priority: 75.
>       */
>      if (sw_od->n_router_ports != sw_od->nbs->n_ports) {
> -        build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lr_nats,
> +        build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od,
> +                                                   lr_stateful_rec->lrnat_rec,
>                                                     lflows);
>      }
>
> -    const struct lr_nat_record *lrnat_rec =
> -        lr_nat_table_find_by_index(lr_nats, op->od->index);
> -
> -    if (!lrnat_rec) {
> -        return;
> -    }
> -
> -    for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
> -        struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
> +    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
> +        struct ovn_nat *nat_entry =
> +            &lr_stateful_rec->lrnat_rec->nat_entries[i];
>          const struct nbrec_nat *nat = nat_entry->nb;
>
>          if (!nat_entry_is_valid(nat_entry)) {
> @@ -9092,16 +9108,14 @@ build_lswitch_rport_arp_req_flows(
>           * expect ARP requests/NS for the DNAT external_ip.
>           */
>          if (nat_entry_is_v6(nat_entry)) {
> -            if (!lr_stateful_rec ||
> -                !sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
> +            if (!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 (!lr_stateful_rec ||
> -                !sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
> +            if (!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,
> @@ -9111,7 +9125,7 @@ build_lswitch_rport_arp_req_flows(
>      }
>
>      struct shash_node *snat_snode;
> -    SHASH_FOR_EACH (snat_snode, &lrnat_rec->snat_ips) {
> +    SHASH_FOR_EACH (snat_snode, &lr_stateful_rec->lrnat_rec->snat_ips) {
>          struct ovn_snat_ip *snat_ip = snat_snode->data;
>
>          if (ovs_list_is_empty(&snat_ip->snat_entries)) {
> @@ -10183,11 +10197,8 @@ 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,
> -    const struct lr_stateful_table *lr_stateful_table,
> -    struct hmap *lflows, struct ds *actions,
> -    struct ds *match)
> +build_lswitch_ip_unicast_lookup(struct ovn_port *op, struct hmap *lflows,
> +                                struct ds *actions, struct ds *match)
>  {
>      ovs_assert(op->nbsp);
>      if (lsp_is_external(op->nbsp)) {
> @@ -10199,8 +10210,7 @@ build_lswitch_ip_unicast_lookup(
>       * requests only to the router port that owns the IP address.
>       */
>      if (lsp_is_router(op->nbsp)) {
> -        build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lr_nats,
> -                                          lr_stateful_table, lflows,
> +        build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows,
>                                            &op->nbsp->header_);
>      }
>
> @@ -10297,33 +10307,6 @@ build_lswitch_ip_unicast_lookup(
>                                      S_SWITCH_IN_L2_LKUP, 50,
>                                      ds_cstr(match), ds_cstr(actions),
>                                      &op->nbsp->header_);
> -
> -            /* Add ethernet addresses specified in NAT rules on
> -             * distributed logical routers. */
> -            if (is_l3dgw_port(op->peer)) {
> -                for (int j = 0; j < op->peer->od->nbr->n_nat; j++) {
> -                    const struct nbrec_nat *nat
> -                                              = op->peer->od->nbr->nat[j];
> -                    if (!strcmp(nat->type, "dnat_and_snat")
> -                        && nat->logical_port && nat->external_mac
> -                        && eth_addr_from_string(nat->external_mac, &mac)) {
> -
> -                        ds_clear(match);
> -                        ds_put_format(match, "eth.dst == "ETH_ADDR_FMT
> -                                      " && is_chassis_resident(\"%s\")",
> -                                      ETH_ADDR_ARGS(mac),
> -                                      nat->logical_port);
> -
> -                        ds_clear(actions);
> -                        ds_put_format(actions, action, op->json_key);
> -                        ovn_lflow_add_with_hint(lflows, op->od,
> -                                                S_SWITCH_IN_L2_LKUP, 50,
> -                                                ds_cstr(match),
> -                                                ds_cstr(actions),
> -                                                &op->nbsp->header_);
> -                    }
> -                }
> -            }
>          } else {
>              static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
>
> @@ -10334,6 +10317,55 @@ build_lswitch_ip_unicast_lookup(
>      }
>  }
>
> +/* Ingress table 25: Destination lookup, unicast handling (priority 50), */
> +static void
> +build_lswitch_ip_unicast_lookup_for_nats(
> +    struct ovn_port *op, const struct lr_stateful_record *lr_stateful_rec,
> +    struct hmap *lflows, struct ds *match, struct ds *actions)
> +{
> +    ovs_assert(op->nbsp);
> +
> +    if (!op->peer || !is_l3dgw_port(op->peer)) {
> +        return;
> +    }
> +
> +    /* Make sure the lr_stateful_rec belongs to the peer port's
> +     * logical router. */
> +    ovs_assert(uuid_equals(&op->peer->od->nbr->header_.uuid,
> +                           &lr_stateful_rec->nbr_uuid));
> +
> +    const char *action = lsp_is_enabled(op->nbsp) ?
> +                         "outport = %s; output;" :
> +                         debug_drop_action();
> +    struct eth_addr mac;
> +
> +    /* Add ethernet addresses specified in NAT rules on
> +     * distributed logical routers. */
> +    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
> +        const struct ovn_nat *nat =
> +            &lr_stateful_rec->lrnat_rec->nat_entries[i];
> +
> +        if (!strcmp(nat->nb->type, "dnat_and_snat")
> +            && nat->nb->logical_port && nat->nb->external_mac
> +            && eth_addr_from_string(nat->nb->external_mac, &mac)) {
> +
> +            ds_clear(match);
> +            ds_put_format(match, "eth.dst == "ETH_ADDR_FMT
> +                                 " && is_chassis_resident(\"%s\")",
> +                          ETH_ADDR_ARGS(mac),
> +                          nat->nb->logical_port);
> +
> +            ds_clear(actions);
> +            ds_put_format(actions, action, op->json_key);
> +            ovn_lflow_add_with_hint(lflows, op->od,
> +                                    S_SWITCH_IN_L2_LKUP, 50,
> +                                    ds_cstr(match),
> +                                    ds_cstr(actions),
> +                                    &op->nbsp->header_);
> +        }
> +    }
> +}
> +
>  struct bfd_entry {
>      struct hmap_node hmap_node;
>
> @@ -11711,16 +11743,17 @@ build_gw_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
>  }
>
>  static void
> -build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
> -                               struct ovn_lb_datapaths *lb_dps,
> -                               struct ovn_northd_lb_vip *vips_nb,
> -                               const struct ovn_datapaths *lr_datapaths,
> -                               const struct lr_nat_table *lr_nats,
> -                               struct hmap *lflows,
> -                               struct ds *match, struct ds *action,
> -                               const struct shash *meter_groups,
> -                               const struct chassis_features *features,
> -                               const struct hmap *svc_monitor_map)
> +build_lrouter_nat_flows_for_lb(
> +    struct ovn_lb_vip *lb_vip,
> +    struct ovn_lb_datapaths *lb_dps,
> +    struct ovn_northd_lb_vip *vips_nb,
> +    const struct ovn_datapaths *lr_datapaths,
> +    const struct lr_stateful_table *lr_stateful_table,
> +    struct hmap *lflows,
> +    struct ds *match, struct ds *action,
> +    const struct shash *meter_groups,
> +    const struct chassis_features *features,
> +    const struct hmap *svc_monitor_map)
>  {
>      const struct ovn_northd_lb *lb = lb_dps->lb;
>      bool ipv4 = lb_vip->address_family == AF_INET;
> @@ -11821,9 +11854,11 @@ build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
>          struct ovn_datapath *od = lr_datapaths->array[index];
>          enum lrouter_nat_lb_flow_type type;
>
> -        const struct lr_nat_record *lrnat_rec =
> -            lr_nat_table_find_by_index(lr_nats, od->index);
> -        ovs_assert(lrnat_rec);
> +        const struct lr_stateful_record *lr_stateful_rec =
> +            lr_stateful_table_find_by_index(lr_stateful_table, od->index);
> +        ovs_assert(lr_stateful_rec);
> +
> +        const struct lr_nat_record *lrnat_rec = lr_stateful_rec->lrnat_rec;
>          if (lb->skip_snat) {
>              type = LROUTER_NAT_LB_FLOW_SKIP_SNAT;
>          } else if (!lport_addresses_is_empty(&lrnat_rec->lb_force_snat_addrs)
> @@ -11973,7 +12008,7 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
>                             struct hmap *lflows,
>                             const struct shash *meter_groups,
>                             const struct ovn_datapaths *lr_datapaths,
> -                           const struct lr_nat_table *lr_nats,
> +                           const struct lr_stateful_table *lr_stateful_table,
>                             const struct chassis_features *features,
>                             const struct hmap *svc_monitor_map,
>                             struct ds *match, struct ds *action)
> @@ -11989,8 +12024,8 @@ build_lrouter_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
>          struct ovn_lb_vip *lb_vip = &lb->vips[i];
>
>          build_lrouter_nat_flows_for_lb(lb_vip, lb_dps, &lb->vips_nb[i],
> -                                       lr_datapaths, lr_nats, lflows, match,
> -                                       action, meter_groups, features,
> +                                       lr_datapaths, lr_stateful_table, lflows,
> +                                       match, action, meter_groups, features,
>                                         svc_monitor_map);
>
>          if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
> @@ -12127,7 +12162,7 @@ lrouter_dnat_and_snat_is_stateless(const struct nbrec_nat *nat)
>   * and action says "next" instead of ct*.
>   */
>  static inline void
> -lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
> +lrouter_nat_add_ext_ip_match(const struct ovn_datapath *od,
>                               struct hmap *lflows, struct ds *match,
>                               const struct nbrec_nat *nat,
>                               bool is_v6, bool is_src, int cidr_bits)
> @@ -12191,7 +12226,7 @@ lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
>   * with the given priority.
>   */
>  static void
> -build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
> +build_lrouter_arp_flow(const struct ovn_datapath *od, struct ovn_port *op,
>                         const char *ip_address, const char *eth_addr,
>                         struct ds *extra_match, bool drop, uint16_t priority,
>                         const struct ovsdb_idl_row *hint,
> @@ -12240,7 +12275,7 @@ build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
>   * 'sn_ip_address'.
>   */
>  static void
> -build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
> +build_lrouter_nd_flow(const struct ovn_datapath *od, struct ovn_port *op,
>                        const char *action, const char *ip_address,
>                        const char *sn_ip_address, const char *eth_addr,
>                        struct ds *extra_match, bool drop, uint16_t priority,
> @@ -12294,7 +12329,7 @@ build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
>  }
>
>  static void
> -build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od,
> +build_lrouter_nat_arp_nd_flow(const struct ovn_datapath *od,
>                                struct ovn_nat *nat_entry,
>                                struct hmap *lflows,
>                                const struct shash *meter_groups)
> @@ -12390,7 +12425,6 @@ 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,
> @@ -12402,9 +12436,9 @@ build_lrouter_drop_own_dest(struct ovn_port *op,
>          for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
>              const char *ip = op->lrp_networks.ipv4_addrs[i].addr_s;
>
> -            bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
> -                                                      ip);
> -            bool router_ip_in_lb_ips = lr_stateful_rec &&
> +            bool router_ip_in_snat_ips =
> +                !!shash_find(&lr_stateful_rec->lrnat_rec->snat_ips, ip);
> +            bool router_ip_in_lb_ips =
>                  !!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));
> @@ -12432,9 +12466,9 @@ build_lrouter_drop_own_dest(struct ovn_port *op,
>          for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
>              const char *ip = op->lrp_networks.ipv6_addrs[i].addr_s;
>
> -            bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
> -                                                      ip);
> -            bool router_ip_in_lb_ips = lr_stateful_rec &&
> +            bool router_ip_in_snat_ips =
> +                !!shash_find(&lr_stateful_rec->lrnat_rec->snat_ips, ip);
> +            bool router_ip_in_lb_ips =
>                  !!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));
> @@ -12459,7 +12493,8 @@ build_lrouter_drop_own_dest(struct ovn_port *op,
>  }
>
>  static void
> -build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_force_snat_flows(struct hmap *lflows,
> +                               const struct ovn_datapath *od,
>                                 const char *ip_version, const char *ip_addr,
>                                 const char *context)
>  {
> @@ -13530,10 +13565,8 @@ routable_addresses_to_lflows(struct hmap *lflows, struct ovn_port *router_port,
>
>  /* This function adds ARP resolve flows related to a LRP. */
>  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)
> +build_arp_resolve_flows_for_lrp(struct ovn_port *op, struct hmap *lflows,
> +                                struct ds *match, struct ds *actions)
>  {
>      ovs_assert(op->nbrp);
>      /* This is a logical router port. If next-hop IP address in
> @@ -13602,15 +13635,6 @@ build_arp_resolve_flows_for_lrp(
>                                      &op->nbrp->header_);
>          }
>      }
> -
> -    /* Drop IP traffic destined to router owned IPs. Part of it is dropped
> -     * in stage "lr_in_ip_input" but traffic that could have been unSNATed
> -     * but didn't match any existing session might still end up here.
> -     *
> -     * Priority 2.
> -     */
> -    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. */
> @@ -13618,7 +13642,6 @@ 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);
> @@ -13759,15 +13782,50 @@ build_arp_resolve_flows_for_lsp(
>                                          ds_cstr(match), ds_cstr(actions),
>                                          &op->nbsp->header_);
>              }
> +        }
> +    }
> +}
>
> -            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,
> -                                             lr_stateful_rec, match, actions);
> -            }
> +static void
> +build_arp_resolve_flows_for_lsp_routable_addresses(
> +        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)
> +{
> +    if (!lsp_is_router(op->nbsp)) {
> +        return;
> +    }
> +
> +    struct ovn_port *peer = ovn_port_get_peer(lr_ports, op);
> +    if (!peer || !peer->nbrp) {
> +        return;
> +    }
> +
> +    if (peer->od->nbr &&
> +        smap_get_bool(&peer->od->nbr->options,
> +                      "dynamic_neigh_routers", false)) {
> +        return;
> +    }
> +
> +    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) {
> +            continue;
> +        }
> +
> +        /* Skip the router port under consideration. */
> +        if (router_port == peer) {
> +            continue;
> +        }
> +
> +        if (smap_get(&peer->od->nbr->options, "chassis") || peer->cr_port) {
> +            const struct lr_stateful_record *lr_stateful_rec;
> +            lr_stateful_rec = lr_stateful_table_find_by_index(
> +                lr_stateful_table, router_port->od->index);
> +            routable_addresses_to_lflows(lflows, router_port, peer,
> +                                         lr_stateful_rec, match, actions);
>          }
>      }
>  }
> @@ -13944,7 +14002,6 @@ build_check_pkt_len_flows_for_lrouter(
>  static void
>  build_gateway_redirect_flows_for_lrouter(
>          struct ovn_datapath *od, struct hmap *lflows,
> -        const struct lr_nat_table *lr_nats,
>          struct ds *match, struct ds *actions)
>  {
>      ovs_assert(od->nbr);
> @@ -13961,7 +14018,6 @@ build_gateway_redirect_flows_for_lrouter(
>          }
>
>          const struct ovsdb_idl_row *stage_hint = NULL;
> -        bool add_def_flow = true;
>
>          if (od->l3dgw_ports[i]->nbrp) {
>              stage_hint = &od->l3dgw_ports[i]->nbrp->header_;
> @@ -13980,14 +14036,33 @@ build_gateway_redirect_flows_for_lrouter(
>          ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
>                                  ds_cstr(match), ds_cstr(actions),
>                                  stage_hint);
> +    }
>
> -        const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
> -            lr_nats, od->index);
> +    /* Packets are allowed by default. */
> +    ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
> +}
>
> -        if (!lrnat_rec) {
> +/* Logical router ingress table GW_REDIRECT: Gateway redirect. */
> +static void
> +build_lr_gateway_redirect_flows_for_nats(
> +    const struct ovn_datapath *od, const struct lr_nat_record *lrnat_rec,
> +    struct hmap *lflows, struct ds *match, struct ds *actions)
> +{
> +    ovs_assert(od->nbr);
> +    for (size_t i = 0; i < od->n_l3dgw_ports; i++) {
> +        if (l3dgw_port_has_associated_vtep_lports(od->l3dgw_ports[i])) {
> +            /* Skip adding redirect lflow for vtep-enabled l3dgw ports.
> +             * Traffic from hypervisor to VTEP (ramp) switch should go in
> +             * distributed manner. Only returning routed traffic must go
> +             * through centralized gateway (or ha-chassis-group).
> +             * This assumes that attached logical switch with vtep lport(s) has
> +             * no localnet port(s) for NAT. Otherwise centralized NAT will not
> +             * work. */
>              continue;
>          }
>
> +        bool add_def_flow = true;
> +
>          for (int j = 0; j < lrnat_rec->n_nat_entries; j++) {
>              const struct ovn_nat *nat = &lrnat_rec->nat_entries[j];
>
> @@ -13996,6 +14071,12 @@ build_gateway_redirect_flows_for_lrouter(
>                  continue;
>              }
>
> +            const struct ovsdb_idl_row *stage_hint = NULL;
> +
> +            if (od->l3dgw_ports[i]->nbrp) {
> +                stage_hint = &od->l3dgw_ports[i]->nbrp->header_;
> +            }
> +
>              struct ds match_ext = DS_EMPTY_INITIALIZER;
>              struct nbrec_address_set  *as = nat->nb->allowed_ext_ips
>                  ? nat->nb->allowed_ext_ips : nat->nb->exempted_ext_ips;
> @@ -14025,9 +14106,6 @@ build_gateway_redirect_flows_for_lrouter(
>              ds_destroy(&match_ext);
>          }
>      }
> -
> -    /* Packets are allowed by default. */
> -    ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
>  }
>
>  /* Local router ingress table ARP_REQUEST: ARP request.
> @@ -14426,8 +14504,8 @@ build_ipv6_input_flows_for_lrouter_port(
>  }
>
>  static void
> -build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
> -                                  const struct lr_nat_table *lr_nats,
> +build_lrouter_arp_nd_for_datapath(const struct ovn_datapath *od,
> +                                  const struct lr_nat_record *lrnat_rec,
>                                    struct hmap *lflows,
>                                    const struct shash *meter_groups)
>  {
> @@ -14444,10 +14522,6 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
>       * port to handle the special cases. In case we get the packet
>       * on a regular port, just reply with the port's ETH address.
>       */
> -    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
> -        lr_nats, od->index);
> -    ovs_assert(lrnat_rec);
> -
>      for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
>          struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
>
> @@ -14485,8 +14559,6 @@ build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
>  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)
>  {
> @@ -14611,41 +14683,6 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>                                 &op->nbrp->header_, lflows);
>      }
>
> -    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)",
> -                          op->cr_port->json_key);
> -        }
> -
> -        /* Create a single ARP rule for all IPs that are used as VIPs. */
> -        char *lb_ips_v4_as = lr_lb_address_set_ref(op->od->tunnel_key,
> -                                                   AF_INET);
> -        build_lrouter_arp_flow(op->od, op, lb_ips_v4_as,
> -                               REG_INPORT_ETH_ADDR,
> -                               match, false, 90, NULL, lflows);
> -        free(lb_ips_v4_as);
> -    }
> -
> -    if (lr_stateful_rec && sset_count(
> -            &lr_stateful_rec->lb_ips->ips_v6_reachable)) {
> -        ds_clear(match);
> -
> -        if (is_l3dgw_port(op)) {
> -            ds_put_format(match, "is_chassis_resident(%s)",
> -                          op->cr_port->json_key);
> -        }
> -
> -        /* Create a single ND rule for all IPs that are used as VIPs. */
> -        char *lb_ips_v6_as = lr_lb_address_set_ref(op->od->tunnel_key,
> -                                                   AF_INET6);
> -        build_lrouter_nd_flow(op->od, op, "nd_na", lb_ips_v6_as, NULL,
> -                              REG_INPORT_ETH_ADDR, match, false, 90,
> -                              NULL, lflows, meter_groups);
> -        free(lb_ips_v6_as);
> -    }
> -
>      if (!op->od->is_gw_router && !op->od->n_l3dgw_ports) {
>          /* UDP/TCP/SCTP port unreachable. */
>          for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> @@ -14720,20 +14757,55 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>                                        &op->nbrp->header_);
>          }
>      }
> +}
>
> -    /* Drop IP traffic destined to router owned IPs except if the IP is
> -     * also a SNAT IP. Those are dropped later, in stage
> -     * "lr_in_arp_resolve", if unSNAT was unsuccessful.
> -     *
> -     * If lrnat_rec->lb_force_snat_router_ip is true, it means the IP of the
> -     * router port is also SNAT IP.
> -     *
> -     * Priority 60.
> -     */
> -    if (!lrnat_rec->lb_force_snat_router_ip) {
> -        build_lrouter_drop_own_dest(op, lrnat_rec, lr_stateful_rec,
> -                                    S_ROUTER_IN_IP_INPUT, 60, false, lflows);
> +/* Logical router ingress table 3: IP Input for IPv4. */
> +static void
> +build_lrouter_ipv4_ip_input_for_lbnats(
> +    struct ovn_port *op, struct hmap *lflows,
> +    const struct lr_stateful_record *lr_stateful_rec,
> +    struct ds *match, const struct shash *meter_groups)
> +{
> +    ovs_assert(op->nbrp);
> +    /* No ingress packets are accepted on a chassisredirect
> +     * port, so no need to program flows for that port. */
> +    if (is_cr_port(op)) {
> +        return;
> +    }
> +
> +    if (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)",
> +                          op->cr_port->json_key);
> +        }
> +
> +        /* Create a single ARP rule for all IPs that are used as VIPs. */
> +        char *lb_ips_v4_as = lr_lb_address_set_ref(op->od->tunnel_key,
> +                                                   AF_INET);
> +        build_lrouter_arp_flow(op->od, op, lb_ips_v4_as,
> +                               REG_INPORT_ETH_ADDR,
> +                               match, false, 90, NULL, lflows);
> +        free(lb_ips_v4_as);
> +    }
> +
> +    if (sset_count(&lr_stateful_rec->lb_ips->ips_v6_reachable)) {
> +        ds_clear(match);
> +
> +        if (is_l3dgw_port(op)) {
> +            ds_put_format(match, "is_chassis_resident(%s)",
> +                          op->cr_port->json_key);
> +        }
> +
> +        /* Create a single ND rule for all IPs that are used as VIPs. */
> +        char *lb_ips_v6_as = lr_lb_address_set_ref(op->od->tunnel_key,
> +                                                   AF_INET6);
> +        build_lrouter_nd_flow(op->od, op, "nd_na", lb_ips_v6_as, NULL,
> +                              REG_INPORT_ETH_ADDR, match, false, 90,
> +                              NULL, lflows, meter_groups);
> +        free(lb_ips_v6_as);
>      }
> +
>      /* ARP / ND handling for external IP addresses.
>       *
>       * DNAT and SNAT IP addresses are external IP addresses that need ARP
> @@ -14747,8 +14819,9 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>          return;
>      }
>
> -    for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
> -        struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
> +    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
> +        struct ovn_nat *nat_entry =
> +            &lr_stateful_rec->lrnat_rec->nat_entries[i];
>
>          /* Skip entries we failed to parse. */
>          if (!nat_entry_is_valid(nat_entry)) {
> @@ -14767,7 +14840,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>
>      /* Now handle SNAT entries too, one per unique SNAT IP. */
>      struct shash_node *snat_snode;
> -    SHASH_FOR_EACH (snat_snode, &lrnat_rec->snat_ips) {
> +    SHASH_FOR_EACH (snat_snode, &lr_stateful_rec->lrnat_rec->snat_ips) {
>          struct ovn_snat_ip *snat_ip = snat_snode->data;
>
>          if (ovs_list_is_empty(&snat_ip->snat_entries)) {
> @@ -14783,7 +14856,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
>  }
>
>  static void
> -build_lrouter_in_unsnat_match(struct ovn_datapath *od,
> +build_lrouter_in_unsnat_match(const struct ovn_datapath *od,
>                                const struct nbrec_nat *nat, struct ds *match,
>                                bool distributed_nat, bool is_v6,
>                                struct ovn_port *l3dgw_port)
> @@ -14810,7 +14883,7 @@ build_lrouter_in_unsnat_match(struct ovn_datapath *od,
>
>  static void
>  build_lrouter_in_unsnat_stateless_flow(struct hmap *lflows,
> -                                       struct ovn_datapath *od,
> +                                       const struct ovn_datapath *od,
>                                         const struct nbrec_nat *nat,
>                                         struct ds *match,
>                                         bool distributed_nat, bool is_v6,
> @@ -14832,7 +14905,7 @@ build_lrouter_in_unsnat_stateless_flow(struct hmap *lflows,
>
>  static void
>  build_lrouter_in_unsnat_in_czone_flow(struct hmap *lflows,
> -                                      struct ovn_datapath *od,
> +                                      const struct ovn_datapath *od,
>                                        const struct nbrec_nat *nat,
>                                        struct ds *match, bool distributed_nat,
>                                        bool is_v6, struct ovn_port *l3dgw_port)
> @@ -14865,7 +14938,8 @@ build_lrouter_in_unsnat_in_czone_flow(struct hmap *lflows,
>  }
>
>  static void
> -build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_in_unsnat_flow(struct hmap *lflows,
> +                             const struct ovn_datapath *od,
>                               const struct nbrec_nat *nat, struct ds *match,
>                               bool distributed_nat, bool is_v6,
>                               struct ovn_port *l3dgw_port)
> @@ -14886,7 +14960,8 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>  }
>
>  static void
> -build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_in_dnat_flow(struct hmap *lflows,
> +                           const struct ovn_datapath *od,
>                             const struct lr_nat_record *lrnat_rec,
>                             const struct nbrec_nat *nat, struct ds *match,
>                             struct ds *actions, bool distributed_nat,
> @@ -14957,7 +15032,8 @@ build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>  }
>
>  static void
> -build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_out_undnat_flow(struct hmap *lflows,
> +                              const struct ovn_datapath *od,
>                                const struct nbrec_nat *nat, struct ds *match,
>                                struct ds *actions, bool distributed_nat,
>                                struct eth_addr mac, bool is_v6,
> @@ -15007,7 +15083,8 @@ build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
>  }
>
>  static void
> -build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_out_is_dnat_local(struct hmap *lflows,
> +                                const struct ovn_datapath *od,
>                                  const struct nbrec_nat *nat, struct ds *match,
>                                  struct ds *actions, bool distributed_nat,
>                                  bool is_v6, struct ovn_port *l3dgw_port)
> @@ -15037,7 +15114,8 @@ build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
>  }
>
>  static void
> -build_lrouter_out_snat_match(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_out_snat_match(struct hmap *lflows,
> +                             const struct ovn_datapath *od,
>                               const struct nbrec_nat *nat, struct ds *match,
>                               bool distributed_nat, int cidr_bits, bool is_v6,
>                               struct ovn_port *l3dgw_port)
> @@ -15066,7 +15144,7 @@ build_lrouter_out_snat_match(struct hmap *lflows, struct ovn_datapath *od,
>
>  static void
>  build_lrouter_out_snat_stateless_flow(struct hmap *lflows,
> -                                      struct ovn_datapath *od,
> +                                      const struct ovn_datapath *od,
>                                        const struct nbrec_nat *nat,
>                                        struct ds *match, struct ds *actions,
>                                        bool distributed_nat,
> @@ -15109,7 +15187,7 @@ build_lrouter_out_snat_stateless_flow(struct hmap *lflows,
>
>  static void
>  build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
> -                                     struct ovn_datapath *od,
> +                                     const struct ovn_datapath *od,
>                                       const struct nbrec_nat *nat,
>                                       struct ds *match,
>                                       struct ds *actions, bool distributed_nat,
> @@ -15170,7 +15248,8 @@ build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
>  }
>
>  static void
> -build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_out_snat_flow(struct hmap *lflows,
> +                            const struct ovn_datapath *od,
>                              const struct nbrec_nat *nat, struct ds *match,
>                              struct ds *actions, bool distributed_nat,
>                              struct eth_addr mac, int cidr_bits, bool is_v6,
> @@ -15217,9 +15296,10 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
>  static void
>  build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
>                                          const struct nbrec_nat *nat,
> -                                        struct ovn_datapath *od, bool is_v6,
> -                                        struct ds *match, struct ds *actions,
> -                                        int mtu, struct ovn_port *l3dgw_port,
> +                                        const struct ovn_datapath *od,
> +                                        bool is_v6, struct ds *match,
> +                                        struct ds *actions, int mtu,
> +                                        struct ovn_port *l3dgw_port,
>                                          const struct shash *meter_groups)
>  {
>          ds_clear(match);
> @@ -15286,7 +15366,8 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
>  }
>
>  static void
> -build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
> +build_lrouter_ingress_flow(struct hmap *lflows,
> +                           const struct ovn_datapath *od,
>                             const struct nbrec_nat *nat, struct ds *match,
>                             struct ds *actions, struct eth_addr mac,
>                             bool distributed_nat, bool is_v6,
> @@ -15336,7 +15417,8 @@ build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
>  }
>
>  static int
> -lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
> +lrouter_check_nat_entry(const struct ovn_datapath *od,
> +                        const struct nbrec_nat *nat,
>                          const struct hmap *lr_ports, ovs_be32 *mask,
>                          bool *is_v6, int *cidr_bits, struct eth_addr *mac,
>                          bool *distributed, struct ovn_port **nat_l3dgw_port)
> @@ -15463,15 +15545,8 @@ lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
>  }
>
>  /* NAT, Defrag and load balancing. */
> -static void
> -build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
> -                                const struct hmap *ls_ports,
> -                                const struct hmap *lr_ports,
> -                                const struct lr_nat_table *lr_nats,
> -                                struct ds *match,
> -                                struct ds *actions,
> -                                const struct shash *meter_groups,
> -                                const struct chassis_features *features)
> +static void build_lr_nat_defrag_and_lb_default_flows(struct ovn_datapath *od,
> +                                                     struct hmap *lflows)
>  {
>      ovs_assert(od->nbr);
>
> @@ -15488,6 +15563,21 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>      ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
>      ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
>
> +    /* Send the IPv6 NS packets to next table. When ovn-controller
> +     * generates IPv6 NS (for the action - nd_ns{}), the injected
> +     * packet would go through conntrack - which is not required. */
> +    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
> +}
> +
> +static void
> +build_lrouter_nat_defrag_and_lb(
> +    const struct lr_stateful_record *lr_stateful_rec,
> +    const struct ovn_datapath *od, struct hmap *lflows,
> +    const struct hmap *ls_ports, const struct hmap *lr_ports,
> +    struct ds *match, struct ds *actions,
> +    const struct shash *meter_groups,
> +    const struct chassis_features *features)
> +{
>      const char *ct_flag_reg = features->ct_no_masked_label
>                                ? "ct_mark"
>                                : "ct_label";
> @@ -15565,11 +15655,6 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>                        "ip && ct.new", "ct_commit { } ; next; ");
>      }
>
> -    /* Send the IPv6 NS packets to next table. When ovn-controller
> -     * generates IPv6 NS (for the action - nd_ns{}), the injected
> -     * packet would go through conntrack - which is not required. */
> -    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
> -
>      /* NAT rules are only valid on Gateway routers and routers with
>       * l3dgw_ports (router has port(s) with gateway chassis
>       * specified). */
> @@ -15578,8 +15663,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>      }
>
>      struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
> -    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(lr_nats,
> -                                                                    od->index);
> +    const struct lr_nat_record *lrnat_rec = lr_stateful_rec->lrnat_rec;
>      ovs_assert(lrnat_rec);
>
>      bool dnat_force_snat_ip =
> @@ -15862,7 +15946,129 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
>      sset_destroy(&nat_entries);
>  }
>
> +static void
> +build_lsp_lflows_for_lbnats(struct ovn_port *lsp,
> +                            const struct lr_stateful_record *lr_stateful_rec,
> +                            const struct lr_stateful_table *lr_stateful_table,
> +                            const struct hmap *lr_ports,
> +                            struct hmap *lflows,
> +                            struct ds *match,
> +                            struct ds *actions)
> +{
> +    ovs_assert(lsp->nbsp);
> +    ovs_assert(lsp->peer);
> +    start_collecting_lflows();
> +    build_lswitch_rport_arp_req_flows_for_lbnats(
> +        lsp->peer, lr_stateful_rec, lsp->od, lsp,
> +        lflows, &lsp->nbsp->header_);
> +    build_ip_routing_flows_for_router_type_lsp(lsp, lr_stateful_table,
> +                                               lr_ports, lflows);
> +    build_arp_resolve_flows_for_lsp_routable_addresses(
> +        lsp, lflows, lr_ports, lr_stateful_table, match, actions);
> +    build_lswitch_ip_unicast_lookup_for_nats(lsp, lr_stateful_rec, lflows,
> +                                             match, actions);
> +    link_ovn_port_to_lflows(lsp, &collected_lflows);
> +    end_collecting_lflows();
> +}
> +
> +static void
> +build_lbnat_lflows_iterate_by_lsp(
> +    struct ovn_port *op, const struct lr_stateful_table *lr_stateful_table,
> +    const struct hmap *lr_ports, struct ds *match, struct ds *actions,
> +    struct hmap *lflows)
> +{
> +    ovs_assert(op->nbsp);
> +
> +    if (!lsp_is_router(op->nbsp) || !op->peer) {
> +        return;
> +    }
> +
> +    const struct lr_stateful_record *lr_stateful_rec;
> +    lr_stateful_rec = lr_stateful_table_find_by_index(lr_stateful_table,
> +                                                      op->peer->od->index);
> +    ovs_assert(lr_stateful_rec);
> +
> +    build_lsp_lflows_for_lbnats(op, lr_stateful_rec, lr_stateful_table,
> +                                lr_ports, lflows, match, actions);
> +}
> +
> +static void
> +build_lrp_lflows_for_lbnats(struct ovn_port *op,
> +                            const struct lr_stateful_record *lr_stateful_rec,
> +                            const struct shash *meter_groups,
> +                            struct ds *match, struct ds *actions,
> +                            struct hmap *lflows)
> +{
> +    /* Drop IP traffic destined to router owned IPs except if the IP is
> +     * also a SNAT IP. Those are dropped later, in stage
> +     * "lr_in_arp_resolve", if unSNAT was unsuccessful.
> +     *
> +     * If lrnat_rec->lb_force_snat_router_ip is true, it means the IP of the
> +     * router port is also SNAT IP.
> +     *
> +     * Priority 60.
> +     */
> +    if (!lr_stateful_rec->lrnat_rec->lb_force_snat_router_ip) {
> +        build_lrouter_drop_own_dest(op, lr_stateful_rec,
> +                                    S_ROUTER_IN_IP_INPUT, 60, false, lflows);
> +    }
> +
> +    /* Drop IP traffic destined to router owned IPs. Part of it is dropped
> +     * in stage "lr_in_ip_input" but traffic that could have been unSNATed
> +     * but didn't match any existing session might still end up here.
> +     *
> +     * Priority 2.
> +     */
> +    build_lrouter_drop_own_dest(op, lr_stateful_rec,
> +                                S_ROUTER_IN_ARP_RESOLVE, 2, true, lflows);
> +
> +    build_lrouter_ipv4_ip_input_for_lbnats(op, lflows, lr_stateful_rec,
> +                                           match, meter_groups);
> +    build_lrouter_force_snat_flows_op(op, lr_stateful_rec->lrnat_rec, lflows,
> +                                      match, actions);
> +}
> +
> +static void
> +build_lbnat_lflows_iterate_by_lrp(
> +    struct ovn_port *op, 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->nbrp);
> +
> +    const struct lr_stateful_record *lr_stateful_rec;
> +    lr_stateful_rec = lr_stateful_table_find_by_index(lr_stateful_table,
> +                                                      op->od->index);
> +    ovs_assert(lr_stateful_rec);
>
> +    build_lrp_lflows_for_lbnats(op, lr_stateful_rec, meter_groups, match,
> +                                actions, lflows);
> +}
> +
> +static void
> +build_lr_stateful_flows(const struct lr_stateful_record *lr_stateful_rec,
> +                        const struct ovn_datapaths *lr_datapaths,
> +                        struct hmap *lflows,
> +                        const struct hmap *ls_ports,
> +                        const struct hmap *lr_ports,
> +                        struct ds *match,
> +                        struct ds *actions,
> +                        const struct shash *meter_groups,
> +                        const struct chassis_features *features)
> +{
> +    const struct ovn_datapath *od =
> +        ovn_datapaths_find_by_index(lr_datapaths, lr_stateful_rec->lr_index);
> +    ovs_assert(od->nbr);
> +    ovs_assert(uuid_equals(&od->nbr->header_.uuid,
> +                           &lr_stateful_rec->nbr_uuid));
> +    build_lrouter_nat_defrag_and_lb(lr_stateful_rec, od, lflows, ls_ports,
> +                                    lr_ports, match, actions,
> +                                    meter_groups, features);
> +    build_lr_gateway_redirect_flows_for_nats(od, lr_stateful_rec->lrnat_rec,
> +                                             lflows, match, actions);
> +    build_lrouter_arp_nd_for_datapath(od, lr_stateful_rec->lrnat_rec, lflows,
> +                                      meter_groups);
> +}
>
>  struct lswitch_flow_build_info {
>      const struct ovn_datapaths *ls_datapaths;
> @@ -15870,7 +16076,6 @@ struct lswitch_flow_build_info {
>      const struct hmap *ls_ports;
>      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;
> @@ -15937,17 +16142,13 @@ build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,
>      build_check_pkt_len_flows_for_lrouter(od, lsi->lflows, lsi->lr_ports,
>                                            &lsi->match, &lsi->actions,
>                                            lsi->meter_groups);
> -    build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, lsi->lr_nats,
> -                                             &lsi->match, &lsi->actions);
> +    build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,
> +                                             &lsi->actions);
>      build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
>                                          &lsi->actions, lsi->meter_groups);
>      build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
> -    build_lrouter_arp_nd_for_datapath(od, lsi->lr_nats, lsi->lflows,
> -                                      lsi->meter_groups);
> -    build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ls_ports,
> -                                    lsi->lr_ports,lsi->lr_nats, &lsi->match,
> -                                    &lsi->actions, lsi->meter_groups,
> -                                    lsi->features);
> +
> +    build_lr_nat_defrag_and_lb_default_flows(od, lsi->lflows);
>      build_lrouter_lb_affinity_default_flows(od, lsi->lflows);
>  }
>
> @@ -15955,15 +16156,13 @@ 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 lr_stateful_table *lr_stateful_table,
> -    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 shash *meter_groups,
> +                                         struct ds *match,
> +                                         struct ds *actions,
> +                                         struct hmap *lflows)
>  {
>      ovs_assert(op->nbsp);
>      start_collecting_lflows();
> @@ -15977,14 +16176,11 @@ build_lswitch_and_lrouter_iterate_by_lsp(
>      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, lr_stateful_table, lflows,
> -                                    actions, match);
> +    build_lswitch_ip_unicast_lookup(op, lflows, actions,
> +                                    match);
>
>      /* Build Logical Router Flows. */
> -    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);
> +    build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
>
>      link_ovn_port_to_lflows(op, &collected_lflows);
>      end_collecting_lflows();
> @@ -15999,12 +16195,6 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
>  {
>      ovs_assert(op->nbrp);
>
> -    const struct lr_nat_record *lrnet_rec = lr_nat_table_find_by_index(
> -        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,
> @@ -16012,18 +16202,16 @@ 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, lr_stateful_rec,
> -                                    lsi->lflows, &lsi->match, &lsi->actions);
> +    build_arp_resolve_flows_for_lrp(op, 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, 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);
> +    build_lrouter_ipv4_ip_input(op, lsi->lflows, &lsi->match, &lsi->actions,
> +                                lsi->meter_groups);
>      build_lrouter_icmp_packet_toobig_admin_flows(op, lsi->lflows, &lsi->match,
>                                                   &lsi->actions);
>  }
> @@ -16032,12 +16220,12 @@ static void *
>  build_lflows_thread(void *arg)
>  {
>      struct worker_control *control = (struct worker_control *) arg;
> +    const struct lr_stateful_record *lr_stateful_rec;
>      struct lswitch_flow_build_info *lsi;
> -
> +    struct ovn_igmp_group *igmp_group;
> +    struct ovn_lb_datapaths *lb_dps;
>      struct ovn_datapath *od;
>      struct ovn_port *op;
> -    struct ovn_lb_datapaths *lb_dps;
> -    struct ovn_igmp_group *igmp_group;
>      int bnum;
>
>      while (!stop_parallel_processing()) {
> @@ -16082,10 +16270,15 @@ 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->lr_stateful_table, lsi->meter_groups,
> -                        &lsi->match, &lsi->actions, lsi->lflows);
> +                    build_lswitch_and_lrouter_iterate_by_lsp(op, lsi->ls_ports,
> +                                                             lsi->lr_ports,
> +                                                             lsi->meter_groups,
> +                                                             &lsi->match,
> +                                                             &lsi->actions,
> +                                                             lsi->lflows);
> +                    build_lbnat_lflows_iterate_by_lsp(
> +                        op, lsi->lr_stateful_table, lsi->lr_ports, &lsi->match,
> +                        &lsi->actions, lsi->lflows);
>                  }
>              }
>              for (bnum = control->id;
> @@ -16098,6 +16291,9 @@ build_lflows_thread(void *arg)
>                          return NULL;
>                      }
>                      build_lswitch_and_lrouter_iterate_by_lrp(op, lsi);
> +                    build_lbnat_lflows_iterate_by_lrp(
> +                        op, lsi->lr_stateful_table, lsi->meter_groups,
> +                        &lsi->match, &lsi->actions, lsi->lflows);
>                  }
>              }
>              for (bnum = control->id;
> @@ -16120,7 +16316,7 @@ build_lflows_thread(void *arg)
>                      build_lrouter_flows_for_lb(lb_dps, lsi->lflows,
>                                                 lsi->meter_groups,
>                                                 lsi->lr_datapaths,
> -                                               lsi->lr_nats,
> +                                               lsi->lr_stateful_table,
>                                                 lsi->features,
>                                                 lsi->svc_monitor_map,
>                                                 &lsi->match, &lsi->actions);
> @@ -16132,6 +16328,23 @@ build_lflows_thread(void *arg)
>                                                 &lsi->match, &lsi->actions);
>                  }
>              }
> +            for (bnum = control->id;
> +                    bnum <= lsi->lr_stateful_table->entries.mask;
> +                    bnum += control->pool->size)
> +            {
> +                LR_STATEFUL_TABLE_FOR_EACH_IN_P (lr_stateful_rec, bnum,
> +                                                 lsi->lr_stateful_table) {
> +                    if (stop_parallel_processing()) {
> +                        return NULL;
> +                    }
> +                    build_lr_stateful_flows(lr_stateful_rec, lsi->lr_datapaths,
> +                                            lsi->lflows, lsi->ls_ports,
> +                                            lsi->lr_ports, &lsi->match,
> +                                            &lsi->actions,
> +                                            lsi->meter_groups,
> +                                            lsi->features);
> +                }
> +            }
>              for (bnum = control->id;
>                      bnum <= lsi->igmp_groups->mask;
>                      bnum += control->pool->size)
> @@ -16192,7 +16405,6 @@ build_lswitch_and_lrouter_flows(
>      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,
> @@ -16223,7 +16435,6 @@ build_lswitch_and_lrouter_flows(
>              lsiv[index].ls_ports = ls_ports;
>              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;
> @@ -16249,17 +16460,18 @@ build_lswitch_and_lrouter_flows(
>          }
>          free(lsiv);
>      } else {
> +        const struct lr_stateful_record *lr_stateful_rec;
> +        struct ovn_igmp_group *igmp_group;
> +        struct ovn_lb_datapaths *lb_dps;
>          struct ovn_datapath *od;
>          struct ovn_port *op;
> -        struct ovn_lb_datapaths *lb_dps;
> -        struct ovn_igmp_group *igmp_group;
> +
>          struct lswitch_flow_build_info lsi = {
>              .ls_datapaths = ls_datapaths,
>              .lr_datapaths = lr_datapaths,
>              .ls_ports = ls_ports,
>              .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,
> @@ -16288,14 +16500,21 @@ build_lswitch_and_lrouter_flows(
>          HMAP_FOR_EACH (op, key_node, ls_ports) {
>              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.match,
> +                                                     &lsi.actions,
>                                                       lsi.lflows);
> +            build_lbnat_lflows_iterate_by_lsp(op, lsi.lr_stateful_table,
> +                                              lsi.lr_ports, &lsi.match,
> +                                              &lsi.actions, lsi.lflows);
>          }
>          HMAP_FOR_EACH (op, key_node, lr_ports) {
>              build_lswitch_and_lrouter_iterate_by_lrp(op, &lsi);
> +            build_lbnat_lflows_iterate_by_lrp(op, lsi.lr_stateful_table,
> +                                              lsi.meter_groups,
> +                                              &lsi.match,
> +                                              &lsi.actions,
> +                                              lsi.lflows);
>          }
>          stopwatch_stop(LFLOWS_PORTS_STOPWATCH_NAME, time_msec());
>          stopwatch_start(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
> @@ -16306,7 +16525,7 @@ build_lswitch_and_lrouter_flows(
>              build_lrouter_defrag_flows_for_lb(lb_dps, lsi.lflows,
>                                                lsi.lr_datapaths, &lsi.match);
>              build_lrouter_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
> -                                       lsi.lr_datapaths, lsi.lr_nats,
> +                                       lsi.lr_datapaths, lsi.lr_stateful_table,
>                                         lsi.features, lsi.svc_monitor_map,
>                                         &lsi.match, &lsi.actions);
>              build_lswitch_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
> @@ -16315,6 +16534,14 @@ build_lswitch_and_lrouter_flows(
>                                         &lsi.match, &lsi.actions);
>          }
>          stopwatch_stop(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
> +        stopwatch_start(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, time_msec());
> +        LR_STATEFUL_TABLE_FOR_EACH (lr_stateful_rec, lr_stateful_table) {
> +            build_lr_stateful_flows(lr_stateful_rec, lsi.lr_datapaths,
> +                                    lsi.lflows, lsi.ls_ports, lsi.lr_ports,
> +                                    &lsi.match, &lsi.actions,
> +                                    lsi.meter_groups, lsi.features);
> +        }
> +        stopwatch_stop(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, time_msec());
>          stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
>          HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
>              build_lswitch_ip_mcast_igmp_mld(igmp_group,
> @@ -16410,7 +16637,6 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
>                                      input_data->ls_ports,
>                                      input_data->lr_ports,
>                                      input_data->ls_port_groups,
> -                                    input_data->lr_nats,
>                                      input_data->lr_stateful_table,
>                                      lflows,
>                                      &igmp_groups,
> @@ -16887,10 +17113,14 @@ 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->lr_stateful_table,
> -            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->meter_groups,
> +                                                 &match, &actions,
> +                                                 lflows);
> +        build_lbnat_lflows_iterate_by_lsp(op, lflow_input->lr_stateful_table,
> +                                          lflow_input->lr_ports, &match,
> +                                          &actions, lflows);
>          ds_destroy(&match);
>          ds_destroy(&actions);
>
> @@ -16922,10 +17152,14 @@ 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->lr_stateful_table,
> -            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->meter_groups,
> +                                                 &match, &actions, lflows);
> +
> +        build_lbnat_lflows_iterate_by_lsp(op, lflow_input->lr_stateful_table,
> +                                          lflow_input->lr_ports, &match,
> +                                          &actions, lflows);
>          ds_destroy(&match);
>          ds_destroy(&actions);
>
> diff --git a/northd/northd.h b/northd/northd.h
> index a6733b0f8f..9291e97abd 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -179,7 +179,6 @@ struct lflow_input {
>      const struct hmap *ls_ports;
>      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;
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 5c68350442..0ad160b9ff 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -873,6 +873,7 @@ main(int argc, char *argv[])
>      stopwatch_create(LFLOWS_DATAPATHS_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(LFLOWS_PORTS_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(LFLOWS_LBS_STOPWATCH_NAME, SW_MS);
> +    stopwatch_create(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(LFLOWS_IGMP_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(LFLOWS_DP_GROUPS_STOPWATCH_NAME, SW_MS);
>      stopwatch_create(LFLOWS_TO_SB_STOPWATCH_NAME, SW_MS);
> --
> 2.43.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/lib/stopwatch-names.h b/lib/stopwatch-names.h
index e5e41fbfd8..1e8a5b656f 100644
--- a/lib/stopwatch-names.h
+++ b/lib/stopwatch-names.h
@@ -24,6 +24,7 @@ 
 #define LFLOWS_DATAPATHS_STOPWATCH_NAME "lflows_datapaths"
 #define LFLOWS_PORTS_STOPWATCH_NAME "lflows_ports"
 #define LFLOWS_LBS_STOPWATCH_NAME "lflows_lbs"
+#define LFLOWS_LR_STATEFUL_STOPWATCH_NAME "lflows_lr_stateful"
 #define LFLOWS_IGMP_STOPWATCH_NAME "lflows_igmp"
 #define LFLOWS_DP_GROUPS_STOPWATCH_NAME "lflows_dp_groups"
 #define LFLOWS_TO_SB_STOPWATCH_NAME "lflows_to_sb"
diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index 97c7f383cd..bd2296ac43 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -42,8 +42,6 @@  lflow_get_input_data(struct engine_node *node,
         engine_get_input_data("port_group", node);
     struct sync_meters_data *sync_meters_data =
         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);
 
@@ -68,7 +66,6 @@  lflow_get_input_data(struct engine_node *node,
     lflow_input->ls_ports = &northd_data->ls_ports;
     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;
diff --git a/northd/en-lr-stateful.h b/northd/en-lr-stateful.h
index bc3265adac..c6d62cb835 100644
--- a/northd/en-lr-stateful.h
+++ b/northd/en-lr-stateful.h
@@ -73,6 +73,10 @@  struct lr_stateful_table {
 #define LR_STATEFUL_TABLE_FOR_EACH(LR_LB_NAT_REC, TABLE) \
     HMAP_FOR_EACH (LR_LB_NAT_REC, key_node, &(TABLE)->entries)
 
+#define LR_STATEFUL_TABLE_FOR_EACH_IN_P(LR_STATEFUL_REC, JOBID, TABLE) \
+    HMAP_FOR_EACH_IN_PARALLEL (LR_STATEFUL_REC, key_node, JOBID, \
+                               &(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'. */
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 97bcce9655..adb38dde78 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -221,7 +221,6 @@  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);
diff --git a/northd/northd.c b/northd/northd.c
index a425a8d7ab..c4b4f7f886 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -8857,18 +8857,14 @@  build_lrouter_groups(struct hmap *lr_ports, struct ovs_list *lr_list)
  */
 static void
 build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op,
-                                           uint32_t priority,
-                                           struct ovn_datapath *od,
-                                           const struct lr_nat_table *lr_nats,
-                                           struct hmap *lflows)
+                                        uint32_t priority,
+                                        const struct ovn_datapath *od,
+                                        const struct lr_nat_record *lrnat_rec,
+                                        struct hmap *lflows)
 {
     struct ds eth_src = DS_EMPTY_INITIALIZER;
     struct ds match = DS_EMPTY_INITIALIZER;
 
-    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
-            lr_nats, op->od->index);
-    ovs_assert(lrnat_rec);
-
     /* Self originated ARP requests/RARP/ND need to be flooded to the L2 domain
      * (except on router ports).  Determine that packets are self originated
      * by also matching on source MAC. Matching on ingress port is not
@@ -8954,10 +8950,11 @@  lrouter_port_ipv6_reachable(const struct ovn_port *op,
  * switching domain as regular broadcast.
  */
 static void
-build_lswitch_rport_arp_req_flow(const char *ips,
-    int addr_family, struct ovn_port *patch_op, struct ovn_datapath *od,
-    uint32_t priority, struct hmap *lflows,
-    const struct ovsdb_idl_row *stage_hint)
+build_lswitch_rport_arp_req_flow(const char *ips, int addr_family,
+                                 struct ovn_port *patch_op,
+                                 const struct ovn_datapath *od,
+                                 uint32_t priority, struct hmap *lflows,
+                                 const struct ovsdb_idl_row *stage_hint)
 {
     struct ds match   = DS_EMPTY_INITIALIZER;
     struct ds actions = DS_EMPTY_INITIALIZER;
@@ -8993,10 +8990,47 @@  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,
-    const struct lr_stateful_table *lr_stateful_table,
+build_lswitch_rport_arp_req_flows(struct ovn_port *op,
+                                  struct ovn_datapath *sw_od,
+                                  struct ovn_port *sw_op,
+                                  struct hmap *lflows,
+                                  const struct ovsdb_idl_row *stage_hint)
+{
+    if (!op || !op->nbrp) {
+        return;
+    }
+
+    if (!lrport_is_enabled(op->nbrp)) {
+        return;
+    }
+
+    /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this
+     * router port.
+     * Priority: 80.
+     */
+    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+        build_lswitch_rport_arp_req_flow(
+            op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
+            lflows, stage_hint);
+    }
+    for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+        build_lswitch_rport_arp_req_flow(
+            op->lrp_networks.ipv6_addrs[i].addr_s, AF_INET6, sw_op, sw_od, 80,
+            lflows, stage_hint);
+    }
+}
+
+/*
+ * Ingress table 25: Flows that forward ARP/ND requests only to the routers
+ * that own the addresses.
+ * Priorities:
+ * - 80: self originated GARPs that need to follow regular processing.
+ * - 75: ARP requests to router owned IPs (interface IP/LB/NAT).
+ */
+static void
+build_lswitch_rport_arp_req_flows_for_lbnats(
+    struct ovn_port *op, const struct lr_stateful_record *lr_stateful_rec,
+    const struct ovn_datapath *sw_od, struct ovn_port *sw_op,
     struct hmap *lflows, const struct ovsdb_idl_row *stage_hint)
 {
     if (!op || !op->nbrp) {
@@ -9007,16 +9041,14 @@  build_lswitch_rport_arp_req_flows(
         return;
     }
 
+    ovs_assert(uuid_equals(&op->od->nbr->header_.uuid,
+                           &lr_stateful_rec->nbr_uuid));
+
     /* Forward ARP requests for owned IP addresses (L3, VIP, NAT) only to this
      * router port.
      * Priority: 80.
      */
-    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;
@@ -9046,17 +9078,6 @@  build_lswitch_rport_arp_req_flows(
         }
     }
 
-    for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
-        build_lswitch_rport_arp_req_flow(
-            op->lrp_networks.ipv4_addrs[i].addr_s, AF_INET, sw_op, sw_od, 80,
-            lflows, stage_hint);
-    }
-    for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
-        build_lswitch_rport_arp_req_flow(
-            op->lrp_networks.ipv6_addrs[i].addr_s, AF_INET6, sw_op, sw_od, 80,
-            lflows, stage_hint);
-    }
-
     /* Self originated ARP requests/RARP/ND need to be flooded as usual.
      *
      * However, if the switch doesn't have any non-router ports we shouldn't
@@ -9065,19 +9086,14 @@  build_lswitch_rport_arp_req_flows(
      * Priority: 75.
      */
     if (sw_od->n_router_ports != sw_od->nbs->n_ports) {
-        build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lr_nats,
+        build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od,
+                                                   lr_stateful_rec->lrnat_rec,
                                                    lflows);
     }
 
-    const struct lr_nat_record *lrnat_rec =
-        lr_nat_table_find_by_index(lr_nats, op->od->index);
-
-    if (!lrnat_rec) {
-        return;
-    }
-
-    for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
-        struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
+    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
+        struct ovn_nat *nat_entry =
+            &lr_stateful_rec->lrnat_rec->nat_entries[i];
         const struct nbrec_nat *nat = nat_entry->nb;
 
         if (!nat_entry_is_valid(nat_entry)) {
@@ -9092,16 +9108,14 @@  build_lswitch_rport_arp_req_flows(
          * expect ARP requests/NS for the DNAT external_ip.
          */
         if (nat_entry_is_v6(nat_entry)) {
-            if (!lr_stateful_rec ||
-                !sset_contains(&lr_stateful_rec->lb_ips->ips_v6,
+            if (!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 (!lr_stateful_rec ||
-                !sset_contains(&lr_stateful_rec->lb_ips->ips_v4,
+            if (!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,
@@ -9111,7 +9125,7 @@  build_lswitch_rport_arp_req_flows(
     }
 
     struct shash_node *snat_snode;
-    SHASH_FOR_EACH (snat_snode, &lrnat_rec->snat_ips) {
+    SHASH_FOR_EACH (snat_snode, &lr_stateful_rec->lrnat_rec->snat_ips) {
         struct ovn_snat_ip *snat_ip = snat_snode->data;
 
         if (ovs_list_is_empty(&snat_ip->snat_entries)) {
@@ -10183,11 +10197,8 @@  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,
-    const struct lr_stateful_table *lr_stateful_table,
-    struct hmap *lflows, struct ds *actions,
-    struct ds *match)
+build_lswitch_ip_unicast_lookup(struct ovn_port *op, struct hmap *lflows,
+                                struct ds *actions, struct ds *match)
 {
     ovs_assert(op->nbsp);
     if (lsp_is_external(op->nbsp)) {
@@ -10199,8 +10210,7 @@  build_lswitch_ip_unicast_lookup(
      * requests only to the router port that owns the IP address.
      */
     if (lsp_is_router(op->nbsp)) {
-        build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lr_nats,
-                                          lr_stateful_table, lflows,
+        build_lswitch_rport_arp_req_flows(op->peer, op->od, op, lflows,
                                           &op->nbsp->header_);
     }
 
@@ -10297,33 +10307,6 @@  build_lswitch_ip_unicast_lookup(
                                     S_SWITCH_IN_L2_LKUP, 50,
                                     ds_cstr(match), ds_cstr(actions),
                                     &op->nbsp->header_);
-
-            /* Add ethernet addresses specified in NAT rules on
-             * distributed logical routers. */
-            if (is_l3dgw_port(op->peer)) {
-                for (int j = 0; j < op->peer->od->nbr->n_nat; j++) {
-                    const struct nbrec_nat *nat
-                                              = op->peer->od->nbr->nat[j];
-                    if (!strcmp(nat->type, "dnat_and_snat")
-                        && nat->logical_port && nat->external_mac
-                        && eth_addr_from_string(nat->external_mac, &mac)) {
-
-                        ds_clear(match);
-                        ds_put_format(match, "eth.dst == "ETH_ADDR_FMT
-                                      " && is_chassis_resident(\"%s\")",
-                                      ETH_ADDR_ARGS(mac),
-                                      nat->logical_port);
-
-                        ds_clear(actions);
-                        ds_put_format(actions, action, op->json_key);
-                        ovn_lflow_add_with_hint(lflows, op->od,
-                                                S_SWITCH_IN_L2_LKUP, 50,
-                                                ds_cstr(match),
-                                                ds_cstr(actions),
-                                                &op->nbsp->header_);
-                    }
-                }
-            }
         } else {
             static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
 
@@ -10334,6 +10317,55 @@  build_lswitch_ip_unicast_lookup(
     }
 }
 
+/* Ingress table 25: Destination lookup, unicast handling (priority 50), */
+static void
+build_lswitch_ip_unicast_lookup_for_nats(
+    struct ovn_port *op, const struct lr_stateful_record *lr_stateful_rec,
+    struct hmap *lflows, struct ds *match, struct ds *actions)
+{
+    ovs_assert(op->nbsp);
+
+    if (!op->peer || !is_l3dgw_port(op->peer)) {
+        return;
+    }
+
+    /* Make sure the lr_stateful_rec belongs to the peer port's
+     * logical router. */
+    ovs_assert(uuid_equals(&op->peer->od->nbr->header_.uuid,
+                           &lr_stateful_rec->nbr_uuid));
+
+    const char *action = lsp_is_enabled(op->nbsp) ?
+                         "outport = %s; output;" :
+                         debug_drop_action();
+    struct eth_addr mac;
+
+    /* Add ethernet addresses specified in NAT rules on
+     * distributed logical routers. */
+    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
+        const struct ovn_nat *nat =
+            &lr_stateful_rec->lrnat_rec->nat_entries[i];
+
+        if (!strcmp(nat->nb->type, "dnat_and_snat")
+            && nat->nb->logical_port && nat->nb->external_mac
+            && eth_addr_from_string(nat->nb->external_mac, &mac)) {
+
+            ds_clear(match);
+            ds_put_format(match, "eth.dst == "ETH_ADDR_FMT
+                                 " && is_chassis_resident(\"%s\")",
+                          ETH_ADDR_ARGS(mac),
+                          nat->nb->logical_port);
+
+            ds_clear(actions);
+            ds_put_format(actions, action, op->json_key);
+            ovn_lflow_add_with_hint(lflows, op->od,
+                                    S_SWITCH_IN_L2_LKUP, 50,
+                                    ds_cstr(match),
+                                    ds_cstr(actions),
+                                    &op->nbsp->header_);
+        }
+    }
+}
+
 struct bfd_entry {
     struct hmap_node hmap_node;
 
@@ -11711,16 +11743,17 @@  build_gw_lrouter_nat_flows_for_lb(struct lrouter_nat_lb_flows_ctx *ctx,
 }
 
 static void
-build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
-                               struct ovn_lb_datapaths *lb_dps,
-                               struct ovn_northd_lb_vip *vips_nb,
-                               const struct ovn_datapaths *lr_datapaths,
-                               const struct lr_nat_table *lr_nats,
-                               struct hmap *lflows,
-                               struct ds *match, struct ds *action,
-                               const struct shash *meter_groups,
-                               const struct chassis_features *features,
-                               const struct hmap *svc_monitor_map)
+build_lrouter_nat_flows_for_lb(
+    struct ovn_lb_vip *lb_vip,
+    struct ovn_lb_datapaths *lb_dps,
+    struct ovn_northd_lb_vip *vips_nb,
+    const struct ovn_datapaths *lr_datapaths,
+    const struct lr_stateful_table *lr_stateful_table,
+    struct hmap *lflows,
+    struct ds *match, struct ds *action,
+    const struct shash *meter_groups,
+    const struct chassis_features *features,
+    const struct hmap *svc_monitor_map)
 {
     const struct ovn_northd_lb *lb = lb_dps->lb;
     bool ipv4 = lb_vip->address_family == AF_INET;
@@ -11821,9 +11854,11 @@  build_lrouter_nat_flows_for_lb(struct ovn_lb_vip *lb_vip,
         struct ovn_datapath *od = lr_datapaths->array[index];
         enum lrouter_nat_lb_flow_type type;
 
-        const struct lr_nat_record *lrnat_rec =
-            lr_nat_table_find_by_index(lr_nats, od->index);
-        ovs_assert(lrnat_rec);
+        const struct lr_stateful_record *lr_stateful_rec =
+            lr_stateful_table_find_by_index(lr_stateful_table, od->index);
+        ovs_assert(lr_stateful_rec);
+
+        const struct lr_nat_record *lrnat_rec = lr_stateful_rec->lrnat_rec;
         if (lb->skip_snat) {
             type = LROUTER_NAT_LB_FLOW_SKIP_SNAT;
         } else if (!lport_addresses_is_empty(&lrnat_rec->lb_force_snat_addrs)
@@ -11973,7 +12008,7 @@  build_lrouter_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
                            struct hmap *lflows,
                            const struct shash *meter_groups,
                            const struct ovn_datapaths *lr_datapaths,
-                           const struct lr_nat_table *lr_nats,
+                           const struct lr_stateful_table *lr_stateful_table,
                            const struct chassis_features *features,
                            const struct hmap *svc_monitor_map,
                            struct ds *match, struct ds *action)
@@ -11989,8 +12024,8 @@  build_lrouter_flows_for_lb(struct ovn_lb_datapaths *lb_dps,
         struct ovn_lb_vip *lb_vip = &lb->vips[i];
 
         build_lrouter_nat_flows_for_lb(lb_vip, lb_dps, &lb->vips_nb[i],
-                                       lr_datapaths, lr_nats, lflows, match,
-                                       action, meter_groups, features,
+                                       lr_datapaths, lr_stateful_table, lflows,
+                                       match, action, meter_groups, features,
                                        svc_monitor_map);
 
         if (!build_empty_lb_event_flow(lb_vip, lb, match, action)) {
@@ -12127,7 +12162,7 @@  lrouter_dnat_and_snat_is_stateless(const struct nbrec_nat *nat)
  * and action says "next" instead of ct*.
  */
 static inline void
-lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
+lrouter_nat_add_ext_ip_match(const struct ovn_datapath *od,
                              struct hmap *lflows, struct ds *match,
                              const struct nbrec_nat *nat,
                              bool is_v6, bool is_src, int cidr_bits)
@@ -12191,7 +12226,7 @@  lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
  * with the given priority.
  */
 static void
-build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
+build_lrouter_arp_flow(const struct ovn_datapath *od, struct ovn_port *op,
                        const char *ip_address, const char *eth_addr,
                        struct ds *extra_match, bool drop, uint16_t priority,
                        const struct ovsdb_idl_row *hint,
@@ -12240,7 +12275,7 @@  build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op,
  * 'sn_ip_address'.
  */
 static void
-build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
+build_lrouter_nd_flow(const struct ovn_datapath *od, struct ovn_port *op,
                       const char *action, const char *ip_address,
                       const char *sn_ip_address, const char *eth_addr,
                       struct ds *extra_match, bool drop, uint16_t priority,
@@ -12294,7 +12329,7 @@  build_lrouter_nd_flow(struct ovn_datapath *od, struct ovn_port *op,
 }
 
 static void
-build_lrouter_nat_arp_nd_flow(struct ovn_datapath *od,
+build_lrouter_nat_arp_nd_flow(const struct ovn_datapath *od,
                               struct ovn_nat *nat_entry,
                               struct hmap *lflows,
                               const struct shash *meter_groups)
@@ -12390,7 +12425,6 @@  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,
@@ -12402,9 +12436,9 @@  build_lrouter_drop_own_dest(struct ovn_port *op,
         for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
             const char *ip = op->lrp_networks.ipv4_addrs[i].addr_s;
 
-            bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
-                                                      ip);
-            bool router_ip_in_lb_ips = lr_stateful_rec &&
+            bool router_ip_in_snat_ips =
+                !!shash_find(&lr_stateful_rec->lrnat_rec->snat_ips, ip);
+            bool router_ip_in_lb_ips =
                 !!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));
@@ -12432,9 +12466,9 @@  build_lrouter_drop_own_dest(struct ovn_port *op,
         for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
             const char *ip = op->lrp_networks.ipv6_addrs[i].addr_s;
 
-            bool router_ip_in_snat_ips = !!shash_find(&lrnat_rec->snat_ips,
-                                                      ip);
-            bool router_ip_in_lb_ips = lr_stateful_rec &&
+            bool router_ip_in_snat_ips =
+                !!shash_find(&lr_stateful_rec->lrnat_rec->snat_ips, ip);
+            bool router_ip_in_lb_ips =
                 !!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));
@@ -12459,7 +12493,8 @@  build_lrouter_drop_own_dest(struct ovn_port *op,
 }
 
 static void
-build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_force_snat_flows(struct hmap *lflows,
+                               const struct ovn_datapath *od,
                                const char *ip_version, const char *ip_addr,
                                const char *context)
 {
@@ -13530,10 +13565,8 @@  routable_addresses_to_lflows(struct hmap *lflows, struct ovn_port *router_port,
 
 /* This function adds ARP resolve flows related to a LRP. */
 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)
+build_arp_resolve_flows_for_lrp(struct ovn_port *op, struct hmap *lflows,
+                                struct ds *match, struct ds *actions)
 {
     ovs_assert(op->nbrp);
     /* This is a logical router port. If next-hop IP address in
@@ -13602,15 +13635,6 @@  build_arp_resolve_flows_for_lrp(
                                     &op->nbrp->header_);
         }
     }
-
-    /* Drop IP traffic destined to router owned IPs. Part of it is dropped
-     * in stage "lr_in_ip_input" but traffic that could have been unSNATed
-     * but didn't match any existing session might still end up here.
-     *
-     * Priority 2.
-     */
-    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. */
@@ -13618,7 +13642,6 @@  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);
@@ -13759,15 +13782,50 @@  build_arp_resolve_flows_for_lsp(
                                         ds_cstr(match), ds_cstr(actions),
                                         &op->nbsp->header_);
             }
+        }
+    }
+}
 
-            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,
-                                             lr_stateful_rec, match, actions);
-            }
+static void
+build_arp_resolve_flows_for_lsp_routable_addresses(
+        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)
+{
+    if (!lsp_is_router(op->nbsp)) {
+        return;
+    }
+
+    struct ovn_port *peer = ovn_port_get_peer(lr_ports, op);
+    if (!peer || !peer->nbrp) {
+        return;
+    }
+
+    if (peer->od->nbr &&
+        smap_get_bool(&peer->od->nbr->options,
+                      "dynamic_neigh_routers", false)) {
+        return;
+    }
+
+    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) {
+            continue;
+        }
+
+        /* Skip the router port under consideration. */
+        if (router_port == peer) {
+            continue;
+        }
+
+        if (smap_get(&peer->od->nbr->options, "chassis") || peer->cr_port) {
+            const struct lr_stateful_record *lr_stateful_rec;
+            lr_stateful_rec = lr_stateful_table_find_by_index(
+                lr_stateful_table, router_port->od->index);
+            routable_addresses_to_lflows(lflows, router_port, peer,
+                                         lr_stateful_rec, match, actions);
         }
     }
 }
@@ -13944,7 +14002,6 @@  build_check_pkt_len_flows_for_lrouter(
 static void
 build_gateway_redirect_flows_for_lrouter(
         struct ovn_datapath *od, struct hmap *lflows,
-        const struct lr_nat_table *lr_nats,
         struct ds *match, struct ds *actions)
 {
     ovs_assert(od->nbr);
@@ -13961,7 +14018,6 @@  build_gateway_redirect_flows_for_lrouter(
         }
 
         const struct ovsdb_idl_row *stage_hint = NULL;
-        bool add_def_flow = true;
 
         if (od->l3dgw_ports[i]->nbrp) {
             stage_hint = &od->l3dgw_ports[i]->nbrp->header_;
@@ -13980,14 +14036,33 @@  build_gateway_redirect_flows_for_lrouter(
         ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
                                 ds_cstr(match), ds_cstr(actions),
                                 stage_hint);
+    }
 
-        const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
-            lr_nats, od->index);
+    /* Packets are allowed by default. */
+    ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
+}
 
-        if (!lrnat_rec) {
+/* Logical router ingress table GW_REDIRECT: Gateway redirect. */
+static void
+build_lr_gateway_redirect_flows_for_nats(
+    const struct ovn_datapath *od, const struct lr_nat_record *lrnat_rec,
+    struct hmap *lflows, struct ds *match, struct ds *actions)
+{
+    ovs_assert(od->nbr);
+    for (size_t i = 0; i < od->n_l3dgw_ports; i++) {
+        if (l3dgw_port_has_associated_vtep_lports(od->l3dgw_ports[i])) {
+            /* Skip adding redirect lflow for vtep-enabled l3dgw ports.
+             * Traffic from hypervisor to VTEP (ramp) switch should go in
+             * distributed manner. Only returning routed traffic must go
+             * through centralized gateway (or ha-chassis-group).
+             * This assumes that attached logical switch with vtep lport(s) has
+             * no localnet port(s) for NAT. Otherwise centralized NAT will not
+             * work. */
             continue;
         }
 
+        bool add_def_flow = true;
+
         for (int j = 0; j < lrnat_rec->n_nat_entries; j++) {
             const struct ovn_nat *nat = &lrnat_rec->nat_entries[j];
 
@@ -13996,6 +14071,12 @@  build_gateway_redirect_flows_for_lrouter(
                 continue;
             }
 
+            const struct ovsdb_idl_row *stage_hint = NULL;
+
+            if (od->l3dgw_ports[i]->nbrp) {
+                stage_hint = &od->l3dgw_ports[i]->nbrp->header_;
+            }
+
             struct ds match_ext = DS_EMPTY_INITIALIZER;
             struct nbrec_address_set  *as = nat->nb->allowed_ext_ips
                 ? nat->nb->allowed_ext_ips : nat->nb->exempted_ext_ips;
@@ -14025,9 +14106,6 @@  build_gateway_redirect_flows_for_lrouter(
             ds_destroy(&match_ext);
         }
     }
-
-    /* Packets are allowed by default. */
-    ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;");
 }
 
 /* Local router ingress table ARP_REQUEST: ARP request.
@@ -14426,8 +14504,8 @@  build_ipv6_input_flows_for_lrouter_port(
 }
 
 static void
-build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
-                                  const struct lr_nat_table *lr_nats,
+build_lrouter_arp_nd_for_datapath(const struct ovn_datapath *od,
+                                  const struct lr_nat_record *lrnat_rec,
                                   struct hmap *lflows,
                                   const struct shash *meter_groups)
 {
@@ -14444,10 +14522,6 @@  build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
      * port to handle the special cases. In case we get the packet
      * on a regular port, just reply with the port's ETH address.
      */
-    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(
-        lr_nats, od->index);
-    ovs_assert(lrnat_rec);
-
     for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
         struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
 
@@ -14485,8 +14559,6 @@  build_lrouter_arp_nd_for_datapath(struct ovn_datapath *od,
 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)
 {
@@ -14611,41 +14683,6 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
                                &op->nbrp->header_, lflows);
     }
 
-    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)",
-                          op->cr_port->json_key);
-        }
-
-        /* Create a single ARP rule for all IPs that are used as VIPs. */
-        char *lb_ips_v4_as = lr_lb_address_set_ref(op->od->tunnel_key,
-                                                   AF_INET);
-        build_lrouter_arp_flow(op->od, op, lb_ips_v4_as,
-                               REG_INPORT_ETH_ADDR,
-                               match, false, 90, NULL, lflows);
-        free(lb_ips_v4_as);
-    }
-
-    if (lr_stateful_rec && sset_count(
-            &lr_stateful_rec->lb_ips->ips_v6_reachable)) {
-        ds_clear(match);
-
-        if (is_l3dgw_port(op)) {
-            ds_put_format(match, "is_chassis_resident(%s)",
-                          op->cr_port->json_key);
-        }
-
-        /* Create a single ND rule for all IPs that are used as VIPs. */
-        char *lb_ips_v6_as = lr_lb_address_set_ref(op->od->tunnel_key,
-                                                   AF_INET6);
-        build_lrouter_nd_flow(op->od, op, "nd_na", lb_ips_v6_as, NULL,
-                              REG_INPORT_ETH_ADDR, match, false, 90,
-                              NULL, lflows, meter_groups);
-        free(lb_ips_v6_as);
-    }
-
     if (!op->od->is_gw_router && !op->od->n_l3dgw_ports) {
         /* UDP/TCP/SCTP port unreachable. */
         for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
@@ -14720,20 +14757,55 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
                                       &op->nbrp->header_);
         }
     }
+}
 
-    /* Drop IP traffic destined to router owned IPs except if the IP is
-     * also a SNAT IP. Those are dropped later, in stage
-     * "lr_in_arp_resolve", if unSNAT was unsuccessful.
-     *
-     * If lrnat_rec->lb_force_snat_router_ip is true, it means the IP of the
-     * router port is also SNAT IP.
-     *
-     * Priority 60.
-     */
-    if (!lrnat_rec->lb_force_snat_router_ip) {
-        build_lrouter_drop_own_dest(op, lrnat_rec, lr_stateful_rec,
-                                    S_ROUTER_IN_IP_INPUT, 60, false, lflows);
+/* Logical router ingress table 3: IP Input for IPv4. */
+static void
+build_lrouter_ipv4_ip_input_for_lbnats(
+    struct ovn_port *op, struct hmap *lflows,
+    const struct lr_stateful_record *lr_stateful_rec,
+    struct ds *match, const struct shash *meter_groups)
+{
+    ovs_assert(op->nbrp);
+    /* No ingress packets are accepted on a chassisredirect
+     * port, so no need to program flows for that port. */
+    if (is_cr_port(op)) {
+        return;
+    }
+
+    if (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)",
+                          op->cr_port->json_key);
+        }
+
+        /* Create a single ARP rule for all IPs that are used as VIPs. */
+        char *lb_ips_v4_as = lr_lb_address_set_ref(op->od->tunnel_key,
+                                                   AF_INET);
+        build_lrouter_arp_flow(op->od, op, lb_ips_v4_as,
+                               REG_INPORT_ETH_ADDR,
+                               match, false, 90, NULL, lflows);
+        free(lb_ips_v4_as);
+    }
+
+    if (sset_count(&lr_stateful_rec->lb_ips->ips_v6_reachable)) {
+        ds_clear(match);
+
+        if (is_l3dgw_port(op)) {
+            ds_put_format(match, "is_chassis_resident(%s)",
+                          op->cr_port->json_key);
+        }
+
+        /* Create a single ND rule for all IPs that are used as VIPs. */
+        char *lb_ips_v6_as = lr_lb_address_set_ref(op->od->tunnel_key,
+                                                   AF_INET6);
+        build_lrouter_nd_flow(op->od, op, "nd_na", lb_ips_v6_as, NULL,
+                              REG_INPORT_ETH_ADDR, match, false, 90,
+                              NULL, lflows, meter_groups);
+        free(lb_ips_v6_as);
     }
+
     /* ARP / ND handling for external IP addresses.
      *
      * DNAT and SNAT IP addresses are external IP addresses that need ARP
@@ -14747,8 +14819,9 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
         return;
     }
 
-    for (size_t i = 0; i < lrnat_rec->n_nat_entries; i++) {
-        struct ovn_nat *nat_entry = &lrnat_rec->nat_entries[i];
+    for (size_t i = 0; i < lr_stateful_rec->lrnat_rec->n_nat_entries; i++) {
+        struct ovn_nat *nat_entry =
+            &lr_stateful_rec->lrnat_rec->nat_entries[i];
 
         /* Skip entries we failed to parse. */
         if (!nat_entry_is_valid(nat_entry)) {
@@ -14767,7 +14840,7 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
 
     /* Now handle SNAT entries too, one per unique SNAT IP. */
     struct shash_node *snat_snode;
-    SHASH_FOR_EACH (snat_snode, &lrnat_rec->snat_ips) {
+    SHASH_FOR_EACH (snat_snode, &lr_stateful_rec->lrnat_rec->snat_ips) {
         struct ovn_snat_ip *snat_ip = snat_snode->data;
 
         if (ovs_list_is_empty(&snat_ip->snat_entries)) {
@@ -14783,7 +14856,7 @@  build_lrouter_ipv4_ip_input(struct ovn_port *op,
 }
 
 static void
-build_lrouter_in_unsnat_match(struct ovn_datapath *od,
+build_lrouter_in_unsnat_match(const struct ovn_datapath *od,
                               const struct nbrec_nat *nat, struct ds *match,
                               bool distributed_nat, bool is_v6,
                               struct ovn_port *l3dgw_port)
@@ -14810,7 +14883,7 @@  build_lrouter_in_unsnat_match(struct ovn_datapath *od,
 
 static void
 build_lrouter_in_unsnat_stateless_flow(struct hmap *lflows,
-                                       struct ovn_datapath *od,
+                                       const struct ovn_datapath *od,
                                        const struct nbrec_nat *nat,
                                        struct ds *match,
                                        bool distributed_nat, bool is_v6,
@@ -14832,7 +14905,7 @@  build_lrouter_in_unsnat_stateless_flow(struct hmap *lflows,
 
 static void
 build_lrouter_in_unsnat_in_czone_flow(struct hmap *lflows,
-                                      struct ovn_datapath *od,
+                                      const struct ovn_datapath *od,
                                       const struct nbrec_nat *nat,
                                       struct ds *match, bool distributed_nat,
                                       bool is_v6, struct ovn_port *l3dgw_port)
@@ -14865,7 +14938,8 @@  build_lrouter_in_unsnat_in_czone_flow(struct hmap *lflows,
 }
 
 static void
-build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_in_unsnat_flow(struct hmap *lflows,
+                             const struct ovn_datapath *od,
                              const struct nbrec_nat *nat, struct ds *match,
                              bool distributed_nat, bool is_v6,
                              struct ovn_port *l3dgw_port)
@@ -14886,7 +14960,8 @@  build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
 }
 
 static void
-build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_in_dnat_flow(struct hmap *lflows,
+                           const struct ovn_datapath *od,
                            const struct lr_nat_record *lrnat_rec,
                            const struct nbrec_nat *nat, struct ds *match,
                            struct ds *actions, bool distributed_nat,
@@ -14957,7 +15032,8 @@  build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
 }
 
 static void
-build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_out_undnat_flow(struct hmap *lflows,
+                              const struct ovn_datapath *od,
                               const struct nbrec_nat *nat, struct ds *match,
                               struct ds *actions, bool distributed_nat,
                               struct eth_addr mac, bool is_v6,
@@ -15007,7 +15083,8 @@  build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
 }
 
 static void
-build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_out_is_dnat_local(struct hmap *lflows,
+                                const struct ovn_datapath *od,
                                 const struct nbrec_nat *nat, struct ds *match,
                                 struct ds *actions, bool distributed_nat,
                                 bool is_v6, struct ovn_port *l3dgw_port)
@@ -15037,7 +15114,8 @@  build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
 }
 
 static void
-build_lrouter_out_snat_match(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_out_snat_match(struct hmap *lflows,
+                             const struct ovn_datapath *od,
                              const struct nbrec_nat *nat, struct ds *match,
                              bool distributed_nat, int cidr_bits, bool is_v6,
                              struct ovn_port *l3dgw_port)
@@ -15066,7 +15144,7 @@  build_lrouter_out_snat_match(struct hmap *lflows, struct ovn_datapath *od,
 
 static void
 build_lrouter_out_snat_stateless_flow(struct hmap *lflows,
-                                      struct ovn_datapath *od,
+                                      const struct ovn_datapath *od,
                                       const struct nbrec_nat *nat,
                                       struct ds *match, struct ds *actions,
                                       bool distributed_nat,
@@ -15109,7 +15187,7 @@  build_lrouter_out_snat_stateless_flow(struct hmap *lflows,
 
 static void
 build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
-                                     struct ovn_datapath *od,
+                                     const struct ovn_datapath *od,
                                      const struct nbrec_nat *nat,
                                      struct ds *match,
                                      struct ds *actions, bool distributed_nat,
@@ -15170,7 +15248,8 @@  build_lrouter_out_snat_in_czone_flow(struct hmap *lflows,
 }
 
 static void
-build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_out_snat_flow(struct hmap *lflows,
+                            const struct ovn_datapath *od,
                             const struct nbrec_nat *nat, struct ds *match,
                             struct ds *actions, bool distributed_nat,
                             struct eth_addr mac, int cidr_bits, bool is_v6,
@@ -15217,9 +15296,10 @@  build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od,
 static void
 build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
                                         const struct nbrec_nat *nat,
-                                        struct ovn_datapath *od, bool is_v6,
-                                        struct ds *match, struct ds *actions,
-                                        int mtu, struct ovn_port *l3dgw_port,
+                                        const struct ovn_datapath *od,
+                                        bool is_v6, struct ds *match,
+                                        struct ds *actions, int mtu,
+                                        struct ovn_port *l3dgw_port,
                                         const struct shash *meter_groups)
 {
         ds_clear(match);
@@ -15286,7 +15366,8 @@  build_lrouter_ingress_nat_check_pkt_len(struct hmap *lflows,
 }
 
 static void
-build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
+build_lrouter_ingress_flow(struct hmap *lflows,
+                           const struct ovn_datapath *od,
                            const struct nbrec_nat *nat, struct ds *match,
                            struct ds *actions, struct eth_addr mac,
                            bool distributed_nat, bool is_v6,
@@ -15336,7 +15417,8 @@  build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od,
 }
 
 static int
-lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
+lrouter_check_nat_entry(const struct ovn_datapath *od,
+                        const struct nbrec_nat *nat,
                         const struct hmap *lr_ports, ovs_be32 *mask,
                         bool *is_v6, int *cidr_bits, struct eth_addr *mac,
                         bool *distributed, struct ovn_port **nat_l3dgw_port)
@@ -15463,15 +15545,8 @@  lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
 }
 
 /* NAT, Defrag and load balancing. */
-static void
-build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
-                                const struct hmap *ls_ports,
-                                const struct hmap *lr_ports,
-                                const struct lr_nat_table *lr_nats,
-                                struct ds *match,
-                                struct ds *actions,
-                                const struct shash *meter_groups,
-                                const struct chassis_features *features)
+static void build_lr_nat_defrag_and_lb_default_flows(struct ovn_datapath *od,
+                                                     struct hmap *lflows)
 {
     ovs_assert(od->nbr);
 
@@ -15488,6 +15563,21 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
     ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;");
     ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;");
 
+    /* Send the IPv6 NS packets to next table. When ovn-controller
+     * generates IPv6 NS (for the action - nd_ns{}), the injected
+     * packet would go through conntrack - which is not required. */
+    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
+}
+
+static void
+build_lrouter_nat_defrag_and_lb(
+    const struct lr_stateful_record *lr_stateful_rec,
+    const struct ovn_datapath *od, struct hmap *lflows,
+    const struct hmap *ls_ports, const struct hmap *lr_ports,
+    struct ds *match, struct ds *actions,
+    const struct shash *meter_groups,
+    const struct chassis_features *features)
+{
     const char *ct_flag_reg = features->ct_no_masked_label
                               ? "ct_mark"
                               : "ct_label";
@@ -15565,11 +15655,6 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
                       "ip && ct.new", "ct_commit { } ; next; ");
     }
 
-    /* Send the IPv6 NS packets to next table. When ovn-controller
-     * generates IPv6 NS (for the action - nd_ns{}), the injected
-     * packet would go through conntrack - which is not required. */
-    ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
-
     /* NAT rules are only valid on Gateway routers and routers with
      * l3dgw_ports (router has port(s) with gateway chassis
      * specified). */
@@ -15578,8 +15663,7 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
     }
 
     struct sset nat_entries = SSET_INITIALIZER(&nat_entries);
-    const struct lr_nat_record *lrnat_rec = lr_nat_table_find_by_index(lr_nats,
-                                                                    od->index);
+    const struct lr_nat_record *lrnat_rec = lr_stateful_rec->lrnat_rec;
     ovs_assert(lrnat_rec);
 
     bool dnat_force_snat_ip =
@@ -15862,7 +15946,129 @@  build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, struct hmap *lflows,
     sset_destroy(&nat_entries);
 }
 
+static void
+build_lsp_lflows_for_lbnats(struct ovn_port *lsp,
+                            const struct lr_stateful_record *lr_stateful_rec,
+                            const struct lr_stateful_table *lr_stateful_table,
+                            const struct hmap *lr_ports,
+                            struct hmap *lflows,
+                            struct ds *match,
+                            struct ds *actions)
+{
+    ovs_assert(lsp->nbsp);
+    ovs_assert(lsp->peer);
+    start_collecting_lflows();
+    build_lswitch_rport_arp_req_flows_for_lbnats(
+        lsp->peer, lr_stateful_rec, lsp->od, lsp,
+        lflows, &lsp->nbsp->header_);
+    build_ip_routing_flows_for_router_type_lsp(lsp, lr_stateful_table,
+                                               lr_ports, lflows);
+    build_arp_resolve_flows_for_lsp_routable_addresses(
+        lsp, lflows, lr_ports, lr_stateful_table, match, actions);
+    build_lswitch_ip_unicast_lookup_for_nats(lsp, lr_stateful_rec, lflows,
+                                             match, actions);
+    link_ovn_port_to_lflows(lsp, &collected_lflows);
+    end_collecting_lflows();
+}
+
+static void
+build_lbnat_lflows_iterate_by_lsp(
+    struct ovn_port *op, const struct lr_stateful_table *lr_stateful_table,
+    const struct hmap *lr_ports, struct ds *match, struct ds *actions,
+    struct hmap *lflows)
+{
+    ovs_assert(op->nbsp);
+
+    if (!lsp_is_router(op->nbsp) || !op->peer) {
+        return;
+    }
+
+    const struct lr_stateful_record *lr_stateful_rec;
+    lr_stateful_rec = lr_stateful_table_find_by_index(lr_stateful_table,
+                                                      op->peer->od->index);
+    ovs_assert(lr_stateful_rec);
+
+    build_lsp_lflows_for_lbnats(op, lr_stateful_rec, lr_stateful_table,
+                                lr_ports, lflows, match, actions);
+}
+
+static void
+build_lrp_lflows_for_lbnats(struct ovn_port *op,
+                            const struct lr_stateful_record *lr_stateful_rec,
+                            const struct shash *meter_groups,
+                            struct ds *match, struct ds *actions,
+                            struct hmap *lflows)
+{
+    /* Drop IP traffic destined to router owned IPs except if the IP is
+     * also a SNAT IP. Those are dropped later, in stage
+     * "lr_in_arp_resolve", if unSNAT was unsuccessful.
+     *
+     * If lrnat_rec->lb_force_snat_router_ip is true, it means the IP of the
+     * router port is also SNAT IP.
+     *
+     * Priority 60.
+     */
+    if (!lr_stateful_rec->lrnat_rec->lb_force_snat_router_ip) {
+        build_lrouter_drop_own_dest(op, lr_stateful_rec,
+                                    S_ROUTER_IN_IP_INPUT, 60, false, lflows);
+    }
+
+    /* Drop IP traffic destined to router owned IPs. Part of it is dropped
+     * in stage "lr_in_ip_input" but traffic that could have been unSNATed
+     * but didn't match any existing session might still end up here.
+     *
+     * Priority 2.
+     */
+    build_lrouter_drop_own_dest(op, lr_stateful_rec,
+                                S_ROUTER_IN_ARP_RESOLVE, 2, true, lflows);
+
+    build_lrouter_ipv4_ip_input_for_lbnats(op, lflows, lr_stateful_rec,
+                                           match, meter_groups);
+    build_lrouter_force_snat_flows_op(op, lr_stateful_rec->lrnat_rec, lflows,
+                                      match, actions);
+}
+
+static void
+build_lbnat_lflows_iterate_by_lrp(
+    struct ovn_port *op, 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->nbrp);
+
+    const struct lr_stateful_record *lr_stateful_rec;
+    lr_stateful_rec = lr_stateful_table_find_by_index(lr_stateful_table,
+                                                      op->od->index);
+    ovs_assert(lr_stateful_rec);
 
+    build_lrp_lflows_for_lbnats(op, lr_stateful_rec, meter_groups, match,
+                                actions, lflows);
+}
+
+static void
+build_lr_stateful_flows(const struct lr_stateful_record *lr_stateful_rec,
+                        const struct ovn_datapaths *lr_datapaths,
+                        struct hmap *lflows,
+                        const struct hmap *ls_ports,
+                        const struct hmap *lr_ports,
+                        struct ds *match,
+                        struct ds *actions,
+                        const struct shash *meter_groups,
+                        const struct chassis_features *features)
+{
+    const struct ovn_datapath *od =
+        ovn_datapaths_find_by_index(lr_datapaths, lr_stateful_rec->lr_index);
+    ovs_assert(od->nbr);
+    ovs_assert(uuid_equals(&od->nbr->header_.uuid,
+                           &lr_stateful_rec->nbr_uuid));
+    build_lrouter_nat_defrag_and_lb(lr_stateful_rec, od, lflows, ls_ports,
+                                    lr_ports, match, actions,
+                                    meter_groups, features);
+    build_lr_gateway_redirect_flows_for_nats(od, lr_stateful_rec->lrnat_rec,
+                                             lflows, match, actions);
+    build_lrouter_arp_nd_for_datapath(od, lr_stateful_rec->lrnat_rec, lflows,
+                                      meter_groups);
+}
 
 struct lswitch_flow_build_info {
     const struct ovn_datapaths *ls_datapaths;
@@ -15870,7 +16076,6 @@  struct lswitch_flow_build_info {
     const struct hmap *ls_ports;
     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;
@@ -15937,17 +16142,13 @@  build_lswitch_and_lrouter_iterate_by_lr(struct ovn_datapath *od,
     build_check_pkt_len_flows_for_lrouter(od, lsi->lflows, lsi->lr_ports,
                                           &lsi->match, &lsi->actions,
                                           lsi->meter_groups);
-    build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, lsi->lr_nats,
-                                             &lsi->match, &lsi->actions);
+    build_gateway_redirect_flows_for_lrouter(od, lsi->lflows, &lsi->match,
+                                             &lsi->actions);
     build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match,
                                         &lsi->actions, lsi->meter_groups);
     build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows);
-    build_lrouter_arp_nd_for_datapath(od, lsi->lr_nats, lsi->lflows,
-                                      lsi->meter_groups);
-    build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ls_ports,
-                                    lsi->lr_ports,lsi->lr_nats, &lsi->match,
-                                    &lsi->actions, lsi->meter_groups,
-                                    lsi->features);
+
+    build_lr_nat_defrag_and_lb_default_flows(od, lsi->lflows);
     build_lrouter_lb_affinity_default_flows(od, lsi->lflows);
 }
 
@@ -15955,15 +16156,13 @@  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 lr_stateful_table *lr_stateful_table,
-    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 shash *meter_groups,
+                                         struct ds *match,
+                                         struct ds *actions,
+                                         struct hmap *lflows)
 {
     ovs_assert(op->nbsp);
     start_collecting_lflows();
@@ -15977,14 +16176,11 @@  build_lswitch_and_lrouter_iterate_by_lsp(
     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, lr_stateful_table, lflows,
-                                    actions, match);
+    build_lswitch_ip_unicast_lookup(op, lflows, actions,
+                                    match);
 
     /* Build Logical Router Flows. */
-    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);
+    build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
 
     link_ovn_port_to_lflows(op, &collected_lflows);
     end_collecting_lflows();
@@ -15999,12 +16195,6 @@  build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
 {
     ovs_assert(op->nbrp);
 
-    const struct lr_nat_record *lrnet_rec = lr_nat_table_find_by_index(
-        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,
@@ -16012,18 +16202,16 @@  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, lr_stateful_rec,
-                                    lsi->lflows, &lsi->match, &lsi->actions);
+    build_arp_resolve_flows_for_lrp(op, 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, 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);
+    build_lrouter_ipv4_ip_input(op, lsi->lflows, &lsi->match, &lsi->actions,
+                                lsi->meter_groups);
     build_lrouter_icmp_packet_toobig_admin_flows(op, lsi->lflows, &lsi->match,
                                                  &lsi->actions);
 }
@@ -16032,12 +16220,12 @@  static void *
 build_lflows_thread(void *arg)
 {
     struct worker_control *control = (struct worker_control *) arg;
+    const struct lr_stateful_record *lr_stateful_rec;
     struct lswitch_flow_build_info *lsi;
-
+    struct ovn_igmp_group *igmp_group;
+    struct ovn_lb_datapaths *lb_dps;
     struct ovn_datapath *od;
     struct ovn_port *op;
-    struct ovn_lb_datapaths *lb_dps;
-    struct ovn_igmp_group *igmp_group;
     int bnum;
 
     while (!stop_parallel_processing()) {
@@ -16082,10 +16270,15 @@  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->lr_stateful_table, lsi->meter_groups,
-                        &lsi->match, &lsi->actions, lsi->lflows);
+                    build_lswitch_and_lrouter_iterate_by_lsp(op, lsi->ls_ports,
+                                                             lsi->lr_ports,
+                                                             lsi->meter_groups,
+                                                             &lsi->match,
+                                                             &lsi->actions,
+                                                             lsi->lflows);
+                    build_lbnat_lflows_iterate_by_lsp(
+                        op, lsi->lr_stateful_table, lsi->lr_ports, &lsi->match,
+                        &lsi->actions, lsi->lflows);
                 }
             }
             for (bnum = control->id;
@@ -16098,6 +16291,9 @@  build_lflows_thread(void *arg)
                         return NULL;
                     }
                     build_lswitch_and_lrouter_iterate_by_lrp(op, lsi);
+                    build_lbnat_lflows_iterate_by_lrp(
+                        op, lsi->lr_stateful_table, lsi->meter_groups,
+                        &lsi->match, &lsi->actions, lsi->lflows);
                 }
             }
             for (bnum = control->id;
@@ -16120,7 +16316,7 @@  build_lflows_thread(void *arg)
                     build_lrouter_flows_for_lb(lb_dps, lsi->lflows,
                                                lsi->meter_groups,
                                                lsi->lr_datapaths,
-                                               lsi->lr_nats,
+                                               lsi->lr_stateful_table,
                                                lsi->features,
                                                lsi->svc_monitor_map,
                                                &lsi->match, &lsi->actions);
@@ -16132,6 +16328,23 @@  build_lflows_thread(void *arg)
                                                &lsi->match, &lsi->actions);
                 }
             }
+            for (bnum = control->id;
+                    bnum <= lsi->lr_stateful_table->entries.mask;
+                    bnum += control->pool->size)
+            {
+                LR_STATEFUL_TABLE_FOR_EACH_IN_P (lr_stateful_rec, bnum,
+                                                 lsi->lr_stateful_table) {
+                    if (stop_parallel_processing()) {
+                        return NULL;
+                    }
+                    build_lr_stateful_flows(lr_stateful_rec, lsi->lr_datapaths,
+                                            lsi->lflows, lsi->ls_ports,
+                                            lsi->lr_ports, &lsi->match,
+                                            &lsi->actions,
+                                            lsi->meter_groups,
+                                            lsi->features);
+                }
+            }
             for (bnum = control->id;
                     bnum <= lsi->igmp_groups->mask;
                     bnum += control->pool->size)
@@ -16192,7 +16405,6 @@  build_lswitch_and_lrouter_flows(
     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,
@@ -16223,7 +16435,6 @@  build_lswitch_and_lrouter_flows(
             lsiv[index].ls_ports = ls_ports;
             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;
@@ -16249,17 +16460,18 @@  build_lswitch_and_lrouter_flows(
         }
         free(lsiv);
     } else {
+        const struct lr_stateful_record *lr_stateful_rec;
+        struct ovn_igmp_group *igmp_group;
+        struct ovn_lb_datapaths *lb_dps;
         struct ovn_datapath *od;
         struct ovn_port *op;
-        struct ovn_lb_datapaths *lb_dps;
-        struct ovn_igmp_group *igmp_group;
+
         struct lswitch_flow_build_info lsi = {
             .ls_datapaths = ls_datapaths,
             .lr_datapaths = lr_datapaths,
             .ls_ports = ls_ports,
             .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,
@@ -16288,14 +16500,21 @@  build_lswitch_and_lrouter_flows(
         HMAP_FOR_EACH (op, key_node, ls_ports) {
             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.match,
+                                                     &lsi.actions,
                                                      lsi.lflows);
+            build_lbnat_lflows_iterate_by_lsp(op, lsi.lr_stateful_table,
+                                              lsi.lr_ports, &lsi.match,
+                                              &lsi.actions, lsi.lflows);
         }
         HMAP_FOR_EACH (op, key_node, lr_ports) {
             build_lswitch_and_lrouter_iterate_by_lrp(op, &lsi);
+            build_lbnat_lflows_iterate_by_lrp(op, lsi.lr_stateful_table,
+                                              lsi.meter_groups,
+                                              &lsi.match,
+                                              &lsi.actions,
+                                              lsi.lflows);
         }
         stopwatch_stop(LFLOWS_PORTS_STOPWATCH_NAME, time_msec());
         stopwatch_start(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
@@ -16306,7 +16525,7 @@  build_lswitch_and_lrouter_flows(
             build_lrouter_defrag_flows_for_lb(lb_dps, lsi.lflows,
                                               lsi.lr_datapaths, &lsi.match);
             build_lrouter_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
-                                       lsi.lr_datapaths, lsi.lr_nats,
+                                       lsi.lr_datapaths, lsi.lr_stateful_table,
                                        lsi.features, lsi.svc_monitor_map,
                                        &lsi.match, &lsi.actions);
             build_lswitch_flows_for_lb(lb_dps, lsi.lflows, lsi.meter_groups,
@@ -16315,6 +16534,14 @@  build_lswitch_and_lrouter_flows(
                                        &lsi.match, &lsi.actions);
         }
         stopwatch_stop(LFLOWS_LBS_STOPWATCH_NAME, time_msec());
+        stopwatch_start(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, time_msec());
+        LR_STATEFUL_TABLE_FOR_EACH (lr_stateful_rec, lr_stateful_table) {
+            build_lr_stateful_flows(lr_stateful_rec, lsi.lr_datapaths,
+                                    lsi.lflows, lsi.ls_ports, lsi.lr_ports,
+                                    &lsi.match, &lsi.actions,
+                                    lsi.meter_groups, lsi.features);
+        }
+        stopwatch_stop(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, time_msec());
         stopwatch_start(LFLOWS_IGMP_STOPWATCH_NAME, time_msec());
         HMAP_FOR_EACH (igmp_group, hmap_node, igmp_groups) {
             build_lswitch_ip_mcast_igmp_mld(igmp_group,
@@ -16410,7 +16637,6 @@  void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                                     input_data->ls_ports,
                                     input_data->lr_ports,
                                     input_data->ls_port_groups,
-                                    input_data->lr_nats,
                                     input_data->lr_stateful_table,
                                     lflows,
                                     &igmp_groups,
@@ -16887,10 +17113,14 @@  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->lr_stateful_table,
-            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->meter_groups,
+                                                 &match, &actions,
+                                                 lflows);
+        build_lbnat_lflows_iterate_by_lsp(op, lflow_input->lr_stateful_table,
+                                          lflow_input->lr_ports, &match,
+                                          &actions, lflows);
         ds_destroy(&match);
         ds_destroy(&actions);
 
@@ -16922,10 +17152,14 @@  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->lr_stateful_table,
-            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->meter_groups,
+                                                 &match, &actions, lflows);
+
+        build_lbnat_lflows_iterate_by_lsp(op, lflow_input->lr_stateful_table,
+                                          lflow_input->lr_ports, &match,
+                                          &actions, lflows);
         ds_destroy(&match);
         ds_destroy(&actions);
 
diff --git a/northd/northd.h b/northd/northd.h
index a6733b0f8f..9291e97abd 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -179,7 +179,6 @@  struct lflow_input {
     const struct hmap *ls_ports;
     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;
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index 5c68350442..0ad160b9ff 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -873,6 +873,7 @@  main(int argc, char *argv[])
     stopwatch_create(LFLOWS_DATAPATHS_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_PORTS_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_LBS_STOPWATCH_NAME, SW_MS);
+    stopwatch_create(LFLOWS_LR_STATEFUL_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_IGMP_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_DP_GROUPS_STOPWATCH_NAME, SW_MS);
     stopwatch_create(LFLOWS_TO_SB_STOPWATCH_NAME, SW_MS);