diff mbox series

[ovs-dev,v6,07/13] northd: Move ovn_lb_datapaths from lib to northd module.

Message ID 20240130212218.1483304-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:22 p.m. UTC
From: Numan Siddique <numans@ovn.org>

It also moves the ovn-controller specific code from lib/lb.c
and lib/lb.h to controller/lb.c and controller/lb.h.

Acked-by: Han Zhou <hzhou@ovn.org>
Co-authored-by: Dumitru Ceara <dceara@redhat.com>
Signed-off-by: Dumitru Ceara <dceara@redhat.com>
Signed-off-by: Numan Siddique <numans@ovn.org>
---
 controller/automake.mk  |   2 +
 controller/lb.c         | 146 ++++++++
 controller/lb.h         |  55 +++
 controller/lflow.c      |   1 +
 lib/lb.c                | 771 +---------------------------------------
 lib/lb.h                | 199 +----------
 northd/automake.mk      |   4 +-
 northd/en-lb-data.c     |   1 +
 northd/en-lr-stateful.c |   1 +
 northd/en-sync-sb.c     |   1 +
 northd/lb.c             | 651 +++++++++++++++++++++++++++++++++
 northd/lb.h             | 189 ++++++++++
 northd/northd.c         |   1 +
 13 files changed, 1068 insertions(+), 954 deletions(-)
 create mode 100644 controller/lb.c
 create mode 100644 controller/lb.h
 create mode 100644 northd/lb.c
 create mode 100644 northd/lb.h

Comments

Numan Siddique Feb. 1, 2024, 3:12 p.m. UTC | #1
On Tue, Jan 30, 2024 at 4:23 PM <numans@ovn.org> wrote:
>
> From: Numan Siddique <numans@ovn.org>
>
> It also moves the ovn-controller specific code from lib/lb.c
> and lib/lb.h to controller/lb.c and controller/lb.h.
>
> Acked-by: Han Zhou <hzhou@ovn.org>
> Co-authored-by: Dumitru Ceara <dceara@redhat.com>
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
> Signed-off-by: Numan Siddique <numans@ovn.org>

Recheck-request: github-robot-_Build_and_Test

> ---
>  controller/automake.mk  |   2 +
>  controller/lb.c         | 146 ++++++++
>  controller/lb.h         |  55 +++
>  controller/lflow.c      |   1 +
>  lib/lb.c                | 771 +---------------------------------------
>  lib/lb.h                | 199 +----------
>  northd/automake.mk      |   4 +-
>  northd/en-lb-data.c     |   1 +
>  northd/en-lr-stateful.c |   1 +
>  northd/en-sync-sb.c     |   1 +
>  northd/lb.c             | 651 +++++++++++++++++++++++++++++++++
>  northd/lb.h             | 189 ++++++++++
>  northd/northd.c         |   1 +
>  13 files changed, 1068 insertions(+), 954 deletions(-)
>  create mode 100644 controller/lb.c
>  create mode 100644 controller/lb.h
>  create mode 100644 northd/lb.c
>  create mode 100644 northd/lb.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index 0dbbd5d26b..a17ff0d60b 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -14,6 +14,8 @@ controller_ovn_controller_SOURCES = \
>         controller/if-status.h \
>         controller/ip-mcast.c \
>         controller/ip-mcast.h \
> +       controller/lb.c \
> +       controller/lb.h \
>         controller/lflow.c \
>         controller/lflow.h \
>         controller/lflow-cache.c \
> diff --git a/controller/lb.c b/controller/lb.c
> new file mode 100644
> index 0000000000..8f9f20ed54
> --- /dev/null
> +++ b/controller/lb.c
> @@ -0,0 +1,146 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +/* OpenvSwitch lib includes. */
> +#include "openvswitch/vlog.h"
> +#include "lib/smap.h"
> +
> +/* OVN includes */
> +#include "lb.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "ovn/lex.h"
> +
> +VLOG_DEFINE_THIS_MODULE(controller_lb);
> +
> +static void
> +ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
> +                           const struct smap *lb_options,
> +                           struct lport_addresses *hairpin_addrs)
> +{
> +    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
> +
> +    if (!addresses) {
> +        return;
> +    }
> +
> +    if (!extract_ip_address(addresses, hairpin_addrs)) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
> +                     addresses, UUID_ARGS(lb_uuid));
> +    }
> +}
> +
> +struct ovn_controller_lb *
> +ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
> +                         const struct smap *template_vars,
> +                         struct sset *template_vars_ref)
> +{
> +    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
> +    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
> +
> +    lb->slb = sbrec_lb;
> +    lb->n_vips = smap_count(&sbrec_lb->vips);
> +    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> +
> +    struct smap_node *node;
> +    size_t n_vips = 0;
> +
> +    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
> +        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> +
> +        struct lex_str key_s = template
> +                               ? lexer_parse_template_string(node->key,
> +                                                             template_vars,
> +                                                             template_vars_ref)
> +                               : lex_str_use(node->key);
> +        struct lex_str value_s = template
> +                               ? lexer_parse_template_string(node->value,
> +                                                             template_vars,
> +                                                             template_vars_ref)
> +                               : lex_str_use(node->value);
> +        char *error = ovn_lb_vip_init_explicit(lb_vip,
> +                                               lex_str_get(&key_s),
> +                                               lex_str_get(&value_s));
> +        if (error) {
> +            free(error);
> +        } else {
> +            n_vips++;
> +        }
> +        lex_str_free(&key_s);
> +        lex_str_free(&value_s);
> +    }
> +
> +    lb->proto = IPPROTO_TCP;
> +    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
> +        if (!strcmp(sbrec_lb->protocol, "udp")) {
> +            lb->proto = IPPROTO_UDP;
> +        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
> +            lb->proto = IPPROTO_SCTP;
> +        }
> +    }
> +
> +    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> +     * correct value.
> +     */
> +    lb->n_vips = n_vips;
> +
> +    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
> +                                           "hairpin_orig_tuple",
> +                                           false);
> +    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
> +    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
> +                               &lb->hairpin_snat_ips);
> +    return lb;
> +}
> +
> +void
> +ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
> +{
> +    for (size_t i = 0; i < lb->n_vips; i++) {
> +        ovn_lb_vip_destroy(&lb->vips[i]);
> +    }
> +    free(lb->vips);
> +    destroy_lport_addresses(&lb->hairpin_snat_ips);
> +    free(lb);
> +}
> +
> +void
> +ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
> +{
> +    struct ovn_controller_lb *lb;
> +    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
> +        ovn_controller_lb_destroy(lb);
> +    }
> +
> +    hmap_destroy(ovn_controller_lbs);
> +}
> +
> +struct ovn_controller_lb *
> +ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
> +                       const struct uuid *uuid)
> +{
> +    struct ovn_controller_lb *lb;
> +    size_t hash = uuid_hash(uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
> +        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
> +            return lb;
> +        }
> +    }
> +    return NULL;
> +}
> +
> diff --git a/controller/lb.h b/controller/lb.h
> new file mode 100644
> index 0000000000..84d51c3329
> --- /dev/null
> +++ b/controller/lb.h
> @@ -0,0 +1,55 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_CONTROLLER_LB_H
> +#define OVN_CONTROLLER_LB_H 1
> +
> +#include "lib/lb.h"
> +
> +struct sbrec_load_balancer;
> +
> +struct ovn_controller_lb {
> +    struct hmap_node hmap_node;
> +
> +    const struct sbrec_load_balancer *slb; /* May be NULL. */
> +
> +    uint8_t proto;
> +
> +    struct ovn_lb_vip *vips;
> +    size_t n_vips;
> +    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
> +                              * destination tuple in registers.
> +                              */
> +    bool ct_flush; /* True if we should flush CT after backend removal. */
> +
> +    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
> +                                              * as source for hairpinned
> +                                              * traffic.
> +                                              */
> +};
> +
> +struct ovn_controller_lb *ovn_controller_lb_create(
> +    const struct sbrec_load_balancer *,
> +    const struct smap *template_vars,
> +    struct sset *template_vars_ref);
> +void ovn_controller_lb_destroy(struct ovn_controller_lb *);
> +void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
> +struct ovn_controller_lb *ovn_controller_lb_find(
> +    const struct hmap *ovn_controller_lbs,
> +    const struct uuid *uuid);
> +
> +#endif /* OVN_CONTROLLER_LB_H */
> +
> diff --git a/controller/lflow.c b/controller/lflow.c
> index c0cf0aa106..895d17d193 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -18,6 +18,7 @@
>  #include "lflow.h"
>  #include "coverage.h"
>  #include "ha-chassis.h"
> +#include "lb.h"
>  #include "lflow-cache.h"
>  #include "local_data.h"
>  #include "lport.h"
> diff --git a/lib/lb.c b/lib/lb.c
> index d0d562b6fb..e67a5fcfd0 100644
> --- a/lib/lb.c
> +++ b/lib/lb.c
> @@ -16,76 +16,16 @@
>  #include <config.h>
>
>  #include "lb.h"
> -#include "lib/ovn-nb-idl.h"
> -#include "lib/ovn-sb-idl.h"
>  #include "lib/ovn-util.h"
> -#include "northd/northd.h"
>  #include "ovn/lex.h"
>
>  /* OpenvSwitch lib includes. */
>  #include "openvswitch/vlog.h"
> -#include "lib/bitmap.h"
> -#include "lib/smap.h"
> -#include "socket-util.h"
>
>  VLOG_DEFINE_THIS_MODULE(lb);
>
> -static const char *lb_neighbor_responder_mode_names[] = {
> -    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
> -    [LB_NEIGH_RESPOND_ALL] = "all",
> -    [LB_NEIGH_RESPOND_NONE] = "none",
> -};
> -
> -static struct nbrec_load_balancer_health_check *
> -ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> -                        const char *vip_port_str, bool template);
>  static void ovn_lb_backends_clear(struct ovn_lb_vip *vip);
>
> -struct ovn_lb_ip_set *
> -ovn_lb_ip_set_create(void)
> -{
> -    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
> -
> -    sset_init(&lb_ip_set->ips_v4);
> -    sset_init(&lb_ip_set->ips_v4_routable);
> -    sset_init(&lb_ip_set->ips_v4_reachable);
> -    sset_init(&lb_ip_set->ips_v6);
> -    sset_init(&lb_ip_set->ips_v6_routable);
> -    sset_init(&lb_ip_set->ips_v6_reachable);
> -
> -    return lb_ip_set;
> -}
> -
> -void
> -ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
> -{
> -    if (!lb_ip_set) {
> -        return;
> -    }
> -    sset_destroy(&lb_ip_set->ips_v4);
> -    sset_destroy(&lb_ip_set->ips_v4_routable);
> -    sset_destroy(&lb_ip_set->ips_v4_reachable);
> -    sset_destroy(&lb_ip_set->ips_v6);
> -    sset_destroy(&lb_ip_set->ips_v6_routable);
> -    sset_destroy(&lb_ip_set->ips_v6_reachable);
> -    free(lb_ip_set);
> -}
> -
> -struct ovn_lb_ip_set *
> -ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
> -{
> -    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
> -
> -    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
> -    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
> -    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
> -    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
> -    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
> -    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
> -
> -    return clone;
> -}
> -
>  /* Format for backend ips: "IP1:port1,IP2:port2,...". */
>  static char *
>  ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value)
> @@ -160,9 +100,9 @@ ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value)
>      return NULL;
>  }
>
> -static
> -char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> -                               const char *lb_value)
> +char *
> +ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> +                         const char *lb_value)
>  {
>      if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str,
>                                           &lb_vip->vip, &lb_vip->vip_port,
> @@ -369,22 +309,6 @@ ovn_lb_vip_format__(const struct ovn_lb_vip *vip, struct ds *s,
>      }
>  }
>
> -/* Formats the VIP in the way the ovn-controller expects it, that is,
> - * template IPv6 variables need to be between brackets too.
> - */
> -static char *
> -ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
> -{
> -    struct ds s = DS_EMPTY_INITIALIZER;
> -
> -    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
> -        ovn_lb_vip_format__(vip, &s, true);
> -    } else {
> -        ovn_lb_vip_format(vip, &s, !!vip->port_str);
> -    }
> -    return ds_steal_cstr(&s);
> -}
> -
>  void
>  ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
>  {
> @@ -417,540 +341,20 @@ ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s)
>      }
>  }
>
> -static
> -void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
> -                            const struct ovn_lb_vip *lb_vip,
> -                            const struct nbrec_load_balancer *nbrec_lb,
> -                            const char *vip_port_str, const char *backend_ips,
> -                            bool template)
> -{
> -    lb_vip_nb->backend_ips = xstrdup(backend_ips);
> -    lb_vip_nb->n_backends = lb_vip->n_backends;
> -    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
> -                                     sizeof *lb_vip_nb->backends_nb);
> -    lb_vip_nb->lb_health_check =
> -        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
> -}
> -
> -static void
> -ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
> -                                      const struct ovn_lb_vip *lb_vip,
> -                                      struct ovn_northd_lb_vip *lb_vip_nb)
> -{
> -    struct ds key = DS_EMPTY_INITIALIZER;
> -
> -    for (size_t j = 0; j < lb_vip->n_backends; j++) {
> -        struct ovn_lb_backend *backend = &lb_vip->backends[j];
> -        ds_clear(&key);
> -        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
> -                      ? "%s" : "[%s]", backend->ip_str);
> -
> -        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
> -        if (!s) {
> -            continue;
> -        }
> -
> -        char *svc_mon_src_ip = NULL;
> -        char *port_name = xstrdup(s);
> -        char *p = strstr(port_name, ":");
> -        if (p) {
> -            *p = 0;
> -            p++;
> -            struct sockaddr_storage svc_mon_src_addr;
> -            if (!inet_parse_address(p, &svc_mon_src_addr)) {
> -                static struct vlog_rate_limit rl =
> -                    VLOG_RATE_LIMIT_INIT(5, 1);
> -                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
> -            } else {
> -                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
> -                ss_format_address_nobracks(&svc_mon_src_addr,
> -                                            &src_ip_s);
> -                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
> -            }
> -        }
> -
> -        if (svc_mon_src_ip) {
> -            struct ovn_northd_lb_backend *backend_nb =
> -                &lb_vip_nb->backends_nb[j];
> -            backend_nb->health_check = true;
> -            backend_nb->logical_port = xstrdup(port_name);
> -            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> -        }
> -        free(port_name);
> -    }
> -
> -    ds_destroy(&key);
> -}
> -
> -static
> -void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
> -{
> -    free(vip->backend_ips);
> -    for (size_t i = 0; i < vip->n_backends; i++) {
> -        free(vip->backends_nb[i].logical_port);
> -        free(vip->backends_nb[i].svc_mon_src_ip);
> -    }
> -    free(vip->backends_nb);
> -}
> -
> -static void
> -ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
> -                           const struct smap *lb_options,
> -                           struct lport_addresses *hairpin_addrs)
> -{
> -    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
> -
> -    if (!addresses) {
> -        return;
> -    }
> -
> -    if (!extract_ip_address(addresses, hairpin_addrs)) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
> -                     addresses, UUID_ARGS(lb_uuid));
> -    }
> -}
> -
> -static bool
> -ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
> -                         bool routable, bool template)
> -{
> -    if (template && routable) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport "
> -                           "option 'add_route'.  Forcing it to disabled.",
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return false;
> -    }
> -    return routable;
> -}
> -
> -static bool
> -ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool template)
> -{
> -    if (!template) {
> -        return true;
> -    }
> -
> -    switch (mode) {
> -    case LB_NEIGH_RESPOND_REACHABLE:
> -        return false;
> -    case LB_NEIGH_RESPOND_ALL:
> -    case LB_NEIGH_RESPOND_NONE:
> -        return true;
> -    }
> -    return false;
> -}
> -
> -static enum lb_neighbor_responder_mode
> -ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
> -                      const char *mode, bool template)
> -{
> -    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -    enum lb_neighbor_responder_mode default_mode =
> -        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
> -
> -    if (!mode) {
> -        mode = lb_neighbor_responder_mode_names[default_mode];
> -    }
> -
> -    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); i++) {
> -        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
> -            if (ovn_lb_neigh_mode_is_valid(i, template)) {
> -                return i;
> -            }
> -            break;
> -        }
> -    }
> -
> -    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
> -                       UUID_FMT", forcing it to %s",
> -                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
> -                 lb_neighbor_responder_mode_names[default_mode]);
> -    return default_mode;
> -}
> -
> -static struct nbrec_load_balancer_health_check *
> -ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> -                        const char *vip_port_str, bool template)
> -{
> -    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -
> -    if (!nbrec_lb->n_health_check) {
> -        return NULL;
> -    }
> -
> -    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
> -        VLOG_WARN_RL(&rl,
> -                     "SCTP load balancers do not currently support "
> -                     "health checks. Not creating health checks for "
> -                     "load balancer " UUID_FMT,
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return NULL;
> -    }
> -
> -    if (template) {
> -        VLOG_WARN_RL(&rl,
> -                     "Template load balancers do not currently support "
> -                     "health checks. Not creating health checks for "
> -                     "load balancer " UUID_FMT,
> -                     UUID_ARGS(&nbrec_lb->header_.uuid));
> -        return NULL;
> -    }
> -
> -    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
> -        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
> -            return nbrec_lb->health_check[i];
> -        }
> -    }
> -    return NULL;
> -}
> -
> -static void
> -ovn_northd_lb_init(struct ovn_northd_lb *lb,
> -                   const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
> -    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
> -    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
> -    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
> -                                              "address-family", "ipv4"),
> -                                 "ipv4")
> -                         ? AF_INET
> -                         : AF_INET6;
> -
> -    lb->nlb = nbrec_lb;
> -    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
> -    lb->n_vips = smap_count(&nbrec_lb->vips);
> -    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> -    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
> -    smap_init(&lb->template_vips);
> -    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
> -
> -    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
> -    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
> -
> -    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
> -    lb->template = template;
> -
> -    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
> -    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
> -
> -    uint32_t affinity_timeout =
> -        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
> -    if (affinity_timeout > UINT16_MAX) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
> -                     UINT16_MAX);
> -        affinity_timeout = UINT16_MAX;
> -    }
> -    lb->affinity_timeout = affinity_timeout;
> -
> -    sset_init(&lb->ips_v4);
> -    sset_init(&lb->ips_v6);
> -    struct smap_node *node;
> -    size_t n_vips = 0;
> -
> -    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
> -        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> -        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
> -
> -        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
> -                                      template, address_family);
> -        if (error) {
> -            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> -            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
> -            ovn_lb_vip_destroy(lb_vip);
> -            free(error);
> -            continue;
> -        }
> -        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
> -                                                  "reject", false);
> -        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
> -                               node->key, node->value, template);
> -        if (lb_vip_nb->lb_health_check) {
> -            lb->health_checks = true;
> -        }
> -
> -        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> -            sset_add(&lb->ips_v4, lb_vip->vip_str);
> -        } else {
> -            sset_add(&lb->ips_v6, lb_vip->vip_str);
> -        }
> -
> -        if (lb->template && address_family == AF_INET6) {
> -            smap_add_nocopy(&lb->template_vips,
> -                            ovn_lb_vip6_template_format_internal(lb_vip),
> -                            xstrdup(node->value));
> -        }
> -        n_vips++;
> -
> -        if (lb_vip_nb->lb_health_check) {
> -            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
> -        }
> -    }
> -
> -    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> -     * correct value.
> -     */
> -    lb->n_vips = n_vips;
> -
> -    if (nbrec_lb->n_selection_fields) {
> -        char *proto = NULL;
> -        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
> -            proto = nbrec_lb->protocol;
> -        }
> -
> -        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> -        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> -            char *field = lb->nlb->selection_fields[i];
> -            if (!strcmp(field, "tp_src") && proto) {
> -                ds_put_format(&sel_fields, "%s_src,", proto);
> -            } else if (!strcmp(field, "tp_dst") && proto) {
> -                ds_put_format(&sel_fields, "%s_dst,", proto);
> -            } else {
> -                ds_put_format(&sel_fields, "%s,", field);
> -            }
> -        }
> -        ds_chomp(&sel_fields, ',');
> -        lb->selection_fields = ds_steal_cstr(&sel_fields);
> -    }
> -}
> -
> -struct ovn_northd_lb *
> -ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
> -    ovn_northd_lb_init(lb, nbrec_lb);
> -    return lb;
> -}
> -
> -struct ovn_northd_lb *
> -ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
> -{
> -    struct ovn_northd_lb *lb;
> -    size_t hash = uuid_hash(uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
> -        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
> -            return lb;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -const struct smap *
> -ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
> -{
> -    if (!smap_is_empty(&lb->template_vips)) {
> -        return &lb->template_vips;
> -    }
> -    return &lb->nlb->vips;
> -}
> -
> -static void
> -ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
> -{
> -    for (size_t i = 0; i < lb->n_vips; i++) {
> -        ovn_lb_vip_destroy(&lb->vips[i]);
> -        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
> -    }
> -    free(lb->vips);
> -    free(lb->vips_nb);
> -    lb->vips = NULL;
> -    lb->vips_nb = NULL;
> -    smap_destroy(&lb->template_vips);
> -    sset_destroy(&lb->ips_v4);
> -    sset_destroy(&lb->ips_v6);
> -    free(lb->selection_fields);
> -    lb->selection_fields = NULL;
> -    lb->health_checks = false;
> -}
> -
> -void
> -ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
> -{
> -    ovn_northd_lb_cleanup(lb);
> -    free(lb);
> -}
> -
> -void
> -ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
> -                     const struct nbrec_load_balancer *nbrec_lb)
> -{
> -    ovn_northd_lb_cleanup(lb);
> -    ovn_northd_lb_init(lb, nbrec_lb);
> -}
> -
> -static void
> -ovn_lb_group_init(struct ovn_lb_group *lb_group,
> -                  const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                  const struct hmap *lbs)
> -{
> -    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
> -    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
> -    lb_group->lb_ips = ovn_lb_ip_set_create();
> -
> -    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
> -        const struct uuid *lb_uuid =
> -            &nbrec_lb_group->load_balancer[i]->header_.uuid;
> -        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
> -        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
> -    }
> -}
> -
> -/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
> - * and an array of 'struct ovn_northd_lb' objects for its associated
> - * load balancers. */
> -struct ovn_lb_group *
> -ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                    const struct hmap *lbs)
> -{
> -    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
> -    lb_group->uuid = nbrec_lb_group->header_.uuid;
> -    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> -    return lb_group;
> -}
> -
> -static void
> -ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
> -{
> -    ovn_lb_ip_set_destroy(lb_group->lb_ips);
> -    lb_group->lb_ips = NULL;
> -    lb_group->has_routable_lb = false;
> -    free(lb_group->lbs);
> -}
> -
> -void
> -ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
> -{
> -    if (!lb_group) {
> -        return;
> -    }
> -
> -    ovn_lb_group_cleanup(lb_group);
> -    free(lb_group);
> -}
> -
> -void
> -ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
> -                    const struct nbrec_load_balancer_group *nbrec_lb_group,
> -                    const struct hmap *lbs)
> -{
> -    ovn_lb_group_cleanup(lb_group);
> -    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> -}
> -
> -struct ovn_lb_group *
> -ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
> -{
> -    struct ovn_lb_group *lb_group;
> -    size_t hash = uuid_hash(uuid);
> -
> -    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
> -        if (uuid_equals(&lb_group->uuid, uuid)) {
> -            return lb_group;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -struct ovn_controller_lb *
> -ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
> -                         const struct smap *template_vars,
> -                         struct sset *template_vars_ref)
> -{
> -    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
> -    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
> -
> -    lb->slb = sbrec_lb;
> -    lb->n_vips = smap_count(&sbrec_lb->vips);
> -    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> -
> -    struct smap_node *node;
> -    size_t n_vips = 0;
> -
> -    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
> -        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> -
> -        struct lex_str key_s = template
> -                               ? lexer_parse_template_string(node->key,
> -                                                             template_vars,
> -                                                             template_vars_ref)
> -                               : lex_str_use(node->key);
> -        struct lex_str value_s = template
> -                               ? lexer_parse_template_string(node->value,
> -                                                             template_vars,
> -                                                             template_vars_ref)
> -                               : lex_str_use(node->value);
> -        char *error = ovn_lb_vip_init_explicit(lb_vip,
> -                                               lex_str_get(&key_s),
> -                                               lex_str_get(&value_s));
> -        if (error) {
> -            free(error);
> -        } else {
> -            n_vips++;
> -        }
> -        lex_str_free(&key_s);
> -        lex_str_free(&value_s);
> -    }
> -
> -    lb->proto = IPPROTO_TCP;
> -    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
> -        if (!strcmp(sbrec_lb->protocol, "udp")) {
> -            lb->proto = IPPROTO_UDP;
> -        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
> -            lb->proto = IPPROTO_SCTP;
> -        }
> -    }
> -
> -    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> -     * correct value.
> -     */
> -    lb->n_vips = n_vips;
> -
> -    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
> -                                           "hairpin_orig_tuple",
> -                                           false);
> -    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
> -    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
> -                               &lb->hairpin_snat_ips);
> -    return lb;
> -}
> -
> -void
> -ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
> -{
> -    for (size_t i = 0; i < lb->n_vips; i++) {
> -        ovn_lb_vip_destroy(&lb->vips[i]);
> -    }
> -    free(lb->vips);
> -    destroy_lport_addresses(&lb->hairpin_snat_ips);
> -    free(lb);
> -}
> -
> -void
> -ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
> +/* Formats the VIP in the way the ovn-controller expects it, that is,
> + * template IPv6 variables need to be between brackets too.
> + */
> +char *
> +ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
>  {
> -    struct ovn_controller_lb *lb;
> -    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
> -        ovn_controller_lb_destroy(lb);
> -    }
> -
> -    hmap_destroy(ovn_controller_lbs);
> -}
> +    struct ds s = DS_EMPTY_INITIALIZER;
>
> -struct ovn_controller_lb *
> -ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
> -                       const struct uuid *uuid)
> -{
> -    struct ovn_controller_lb *lb;
> -    size_t hash = uuid_hash(uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
> -        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
> -            return lb;
> -        }
> +    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
> +        ovn_lb_vip_format__(vip, &s, true);
> +    } else {
> +        ovn_lb_vip_format(vip, &s, !!vip->port_str);
>      }
> -    return NULL;
> +    return ds_steal_cstr(&s);
>  }
>
>  static uint32_t
> @@ -1020,150 +424,3 @@ ovn_lb_5tuples_destroy(struct hmap *tuples)
>
>      hmap_destroy(tuples);
>  }
> -
> -void
> -build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
> -                     const struct ovn_northd_lb *lb)
> -{
> -    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
> -}
> -
> -void
> -add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                     bool is_routable,
> -                     const struct sset *lb_ips_v4,
> -                     const struct sset *lb_ips_v6)
> -{
> -    const char *ip_address;
> -
> -    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> -        sset_add(&lb_ips->ips_v4, ip_address);
> -        if (is_routable) {
> -            sset_add(&lb_ips->ips_v4_routable, ip_address);
> -        }
> -    }
> -    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> -        sset_add(&lb_ips->ips_v6, ip_address);
> -        if (is_routable) {
> -            sset_add(&lb_ips->ips_v6_routable, ip_address);
> -        }
> -    }
> -}
> -
> -void
> -remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                          bool is_routable,
> -                          const struct sset *lb_ips_v4,
> -                          const struct sset *lb_ips_v6)
> -{
> -    const char *ip_address;
> -
> -    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> -        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
> -        if (is_routable) {
> -            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
> -        }
> -    }
> -    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> -        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
> -        if (is_routable) {
> -            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
> -        }
> -    }
> -}
> -
> -/* lb datapaths functions */
> -struct  ovn_lb_datapaths *
> -ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t n_ls_datapaths,
> -                        size_t n_lr_datapaths)
> -{
> -    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
> -    lb_dps->lb = lb;
> -    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
> -    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
> -
> -    return lb_dps;
> -}
> -
> -struct ovn_lb_datapaths *
> -ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
> -                      const struct uuid *lb_uuid)
> -{
> -    struct ovn_lb_datapaths *lb_dps;
> -    size_t hash = uuid_hash(lb_uuid);
> -    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
> -        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
> -            return lb_dps;
> -        }
> -    }
> -    return NULL;
> -}
> -
> -void
> -ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
> -{
> -    bitmap_free(lb_dps->nb_lr_map);
> -    bitmap_free(lb_dps->nb_ls_map);
> -    free(lb_dps);
> -}
> -
> -void
> -ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
> -                        struct ovn_datapath **ods)
> -{
> -    for (size_t i = 0; i < n; i++) {
> -        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
> -            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
> -            lb_dps->n_nb_lr++;
> -        }
> -    }
> -}
> -
> -void
> -ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
> -                        struct ovn_datapath **ods)
> -{
> -    for (size_t i = 0; i < n; i++) {
> -        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
> -            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
> -            lb_dps->n_nb_ls++;
> -        }
> -    }
> -}
> -
> -struct ovn_lb_group_datapaths *
> -ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
> -                              size_t max_ls_datapaths,
> -                              size_t max_lr_datapaths)
> -{
> -    struct ovn_lb_group_datapaths *lb_group_dps =
> -        xzalloc(sizeof *lb_group_dps);
> -    lb_group_dps->lb_group = lb_group;
> -    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
> -    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
> -
> -    return lb_group_dps;
> -}
> -
> -void
> -ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
> -{
> -    free(lb_group_dps->ls);
> -    free(lb_group_dps->lr);
> -    free(lb_group_dps);
> -}
> -
> -struct ovn_lb_group_datapaths *
> -ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
> -                            const struct uuid *lb_group_uuid)
> -{
> -    struct ovn_lb_group_datapaths *lb_group_dps;
> -    size_t hash = uuid_hash(lb_group_uuid);
> -
> -    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, lb_group_dps_map) {
> -        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
> -            return lb_group_dps;
> -        }
> -    }
> -    return NULL;
> -}
> diff --git a/lib/lb.h b/lib/lb.h
> index b8e3c1e8fb..bcc677f82e 100644
> --- a/lib/lb.h
> +++ b/lib/lb.h
> @@ -25,63 +25,8 @@
>  #include "sset.h"
>  #include "uuid.h"
>
> -struct nbrec_load_balancer;
> -struct nbrec_load_balancer_group;
> -struct sbrec_load_balancer;
> -struct sbrec_datapath_binding;
> -struct ovn_datapath;
> -struct ovn_dp_group;
> -struct ovn_port;
>  struct uuid;
>
> -enum lb_neighbor_responder_mode {
> -    LB_NEIGH_RESPOND_REACHABLE,
> -    LB_NEIGH_RESPOND_ALL,
> -    LB_NEIGH_RESPOND_NONE,
> -};
> -
> -/* The "routable" ssets are subsets of the load balancer IPs for which IP
> - * routes and ARP resolution flows are automatically added. */
> -struct ovn_lb_ip_set {
> -    struct sset ips_v4;
> -    struct sset ips_v4_routable;
> -    struct sset ips_v4_reachable;
> -    struct sset ips_v6;
> -    struct sset ips_v6_routable;
> -    struct sset ips_v6_reachable;
> -};
> -
> -struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
> -void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
> -struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
> -
> -struct ovn_northd_lb {
> -    struct hmap_node hmap_node;
> -
> -    const struct nbrec_load_balancer *nlb; /* May be NULL. */
> -    const char *proto;
> -    char *selection_fields;
> -    struct ovn_lb_vip *vips;
> -    struct ovn_northd_lb_vip *vips_nb;
> -    struct smap template_vips; /* Slightly changed template VIPs, populated
> -                                * if needed.  Until now it's only required
> -                                * for IPv6 template load balancers. */
> -    size_t n_vips;
> -
> -    enum lb_neighbor_responder_mode neigh_mode;
> -    bool controller_event;
> -    bool routable;
> -    bool skip_snat;
> -    bool template;
> -    uint16_t affinity_timeout;
> -
> -    struct sset ips_v4;
> -    struct sset ips_v6;
> -
> -    /* Indicates if the load balancer has health checks configured. */
> -    bool health_checks;
> -};
> -
>  struct ovn_lb_vip {
>      struct in6_addr vip; /* Only used in ovn-controller. */
>      char *vip_str;       /* Actual VIP string representation (without port).
> @@ -113,153 +58,15 @@ struct ovn_lb_backend {
>                            */
>  };
>
> -/* ovn-northd specific backend information. */
> -struct ovn_northd_lb_vip {
> -    char *backend_ips;
> -    struct ovn_northd_lb_backend *backends_nb;
> -    size_t n_backends;
> -
> -    struct nbrec_load_balancer_health_check *lb_health_check;
> -};
> -
> -struct ovn_northd_lb_backend {
> -    bool health_check;
> -    char *logical_port; /* Logical port to which the ip belong to. */
> -    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
> -};
> -
> -struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
> -struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
> -                                         const struct uuid *);
> -const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
> -void ovn_northd_lb_destroy(struct ovn_northd_lb *);
> -void ovn_northd_lb_reinit(struct ovn_northd_lb *,
> -                          const struct nbrec_load_balancer *);
> -
> -void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
> -                          const struct ovn_northd_lb *);
> -void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                          bool is_routable,
> -                          const struct sset *lb_ips_v4,
> -                          const struct sset *lb_ips_v6);
> -void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> -                               bool is_routable,
> -                               const struct sset *lb_ips_v4,
> -                               const struct sset *lb_ips_v6);
> -
> -struct ovn_lb_group {
> -    struct hmap_node hmap_node;
> -    struct uuid uuid;
> -    size_t n_lbs;
> -    struct ovn_northd_lb **lbs;
> -    struct ovn_lb_ip_set *lb_ips;
> -    bool has_routable_lb;
> -};
> -
> -struct ovn_lb_group *ovn_lb_group_create(
> -    const struct nbrec_load_balancer_group *,
> -    const struct hmap *lbs);
> -void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
> -struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
> -                                       const struct uuid *);
> -void ovn_lb_group_reinit(
> -    struct ovn_lb_group *lb_group,
> -    const struct nbrec_load_balancer_group *,
> -    const struct hmap *lbs);
> -
> -struct ovn_lb_datapaths {
> -    struct hmap_node hmap_node;
> -
> -    const struct ovn_northd_lb *lb;
> -    size_t n_nb_ls;
> -    unsigned long *nb_ls_map;
> -
> -    size_t n_nb_lr;
> -    unsigned long *nb_lr_map;
> -};
> -
> -struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb *,
> -                                                 size_t n_ls_datapaths,
> -                                                 size_t n_lr_datapaths);
> -struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
> -                                               const struct uuid *);
> -void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
> -void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
> -                             struct ovn_datapath **);
> -void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
> -                             struct ovn_datapath **);
> -
> -struct ovn_lb_group_datapaths {
> -    struct hmap_node hmap_node;
> -
> -    const struct ovn_lb_group *lb_group;
> -
> -    /* Datapaths to which 'lb_group' is applied. */
> -    size_t n_ls;
> -    struct ovn_datapath **ls;
> -    size_t n_lr;
> -    struct ovn_datapath **lr;
> -};
> -
> -struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
> -    const struct ovn_lb_group *, size_t max_ls_datapaths,
> -    size_t max_lr_datapaths);
> -
> -void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
> -struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
> -    const struct hmap *lb_group_dps, const struct uuid *);
> -
> -static inline void
> -ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t n,
> -                               struct ovn_datapath **ods)
> -{
> -    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
> -    lbg_dps->n_ls += n;
> -}
> -
> -static inline void
> -ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
> -                               struct ovn_datapath *lr)
> -{
> -    lbg_dps->lr[lbg_dps->n_lr++] = lr;
> -}
> -
> -struct ovn_controller_lb {
> -    struct hmap_node hmap_node;
> -
> -    const struct sbrec_load_balancer *slb; /* May be NULL. */
> -
> -    uint8_t proto;
> -
> -    struct ovn_lb_vip *vips;
> -    size_t n_vips;
> -    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
> -                              * destination tuple in registers.
> -                              */
> -    bool ct_flush; /* True if we should flush CT after backend removal. */
> -
> -    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
> -                                              * as source for hairpinned
> -                                              * traffic.
> -                                              */
> -};
> -
> -struct ovn_controller_lb *ovn_controller_lb_create(
> -    const struct sbrec_load_balancer *,
> -    const struct smap *template_vars,
> -    struct sset *template_vars_ref);
> -void ovn_controller_lb_destroy(struct ovn_controller_lb *);
> -void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
> -struct ovn_controller_lb *ovn_controller_lb_find(
> -    const struct hmap *ovn_controller_lbs,
> -    const struct uuid *uuid);
> -
>  char *ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key,
>                        const char *lb_value, bool template, int address_family);
> +char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
> +                               const char *lb_value);
>  void ovn_lb_vip_destroy(struct ovn_lb_vip *vip);
>  void ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s,
>                         bool template);
>  void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s);
> +char *ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip);
>
>  struct ovn_lb_5tuple {
>      struct hmap_node hmap_node;
> diff --git a/northd/automake.mk b/northd/automake.mk
> index 7c6d56a4ff..19abb0dece 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -35,7 +35,9 @@ northd_ovn_northd_SOURCES = \
>         northd/ipam.c \
>         northd/ipam.h \
>         northd/lflow-mgr.c \
> -       northd/lflow-mgr.h
> +       northd/lflow-mgr.h \
> +       northd/lb.c \
> +       northd/lb.h
>  northd_ovn_northd_LDADD = \
>         lib/libovn.la \
>         $(OVSDB_LIBDIR)/libovsdb.la \
> diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
> index d06f46a54b..6ad3fbb35f 100644
> --- a/northd/en-lb-data.c
> +++ b/northd/en-lb-data.c
> @@ -25,6 +25,7 @@
>
>  /* OVN includes */
>  #include "en-lb-data.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
> index 8665b3c791..3e2a6c254e 100644
> --- a/northd/en-lr-stateful.c
> +++ b/northd/en-lr-stateful.c
> @@ -33,6 +33,7 @@
>  #include "en-lb-data.h"
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
> index 53f687f220..9ca59d4338 100644
> --- a/northd/en-sync-sb.c
> +++ b/northd/en-sync-sb.c
> @@ -24,6 +24,7 @@
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
>  #include "en-sync-sb.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
> diff --git a/northd/lb.c b/northd/lb.c
> new file mode 100644
> index 0000000000..e35569cb70
> --- /dev/null
> +++ b/northd/lb.c
> @@ -0,0 +1,651 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +/* OVS includes */
> +#include "lib/bitmap.h"
> +#include "openvswitch/vlog.h"
> +#include "socket-util.h"
> +
> +/* OVN includes */
> +#include "lb.h"
> +#include "lib/ovn-nb-idl.h"
> +#include "northd.h"
> +#include "ovn/lex.h"
> +
> +VLOG_DEFINE_THIS_MODULE(northd_lb);
> +
> +static const char *lb_neighbor_responder_mode_names[] = {
> +    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
> +    [LB_NEIGH_RESPOND_ALL] = "all",
> +    [LB_NEIGH_RESPOND_NONE] = "none",
> +};
> +
> +static struct nbrec_load_balancer_health_check *
> +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> +                        const char *vip_port_str, bool template);
> +
> +struct ovn_lb_ip_set *
> +ovn_lb_ip_set_create(void)
> +{
> +    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
> +
> +    sset_init(&lb_ip_set->ips_v4);
> +    sset_init(&lb_ip_set->ips_v4_routable);
> +    sset_init(&lb_ip_set->ips_v4_reachable);
> +    sset_init(&lb_ip_set->ips_v6);
> +    sset_init(&lb_ip_set->ips_v6_routable);
> +    sset_init(&lb_ip_set->ips_v6_reachable);
> +
> +    return lb_ip_set;
> +}
> +
> +void
> +ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
> +{
> +    if (!lb_ip_set) {
> +        return;
> +    }
> +    sset_destroy(&lb_ip_set->ips_v4);
> +    sset_destroy(&lb_ip_set->ips_v4_routable);
> +    sset_destroy(&lb_ip_set->ips_v4_reachable);
> +    sset_destroy(&lb_ip_set->ips_v6);
> +    sset_destroy(&lb_ip_set->ips_v6_routable);
> +    sset_destroy(&lb_ip_set->ips_v6_reachable);
> +    free(lb_ip_set);
> +}
> +
> +struct ovn_lb_ip_set *
> +ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
> +{
> +    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
> +
> +    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
> +    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
> +    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
> +    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
> +    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
> +    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
> +
> +    return clone;
> +}
> +
> +static
> +void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
> +                            const struct ovn_lb_vip *lb_vip,
> +                            const struct nbrec_load_balancer *nbrec_lb,
> +                            const char *vip_port_str, const char *backend_ips,
> +                            bool template)
> +{
> +    lb_vip_nb->backend_ips = xstrdup(backend_ips);
> +    lb_vip_nb->n_backends = lb_vip->n_backends;
> +    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
> +                                     sizeof *lb_vip_nb->backends_nb);
> +    lb_vip_nb->lb_health_check =
> +        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
> +}
> +
> +static void
> +ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
> +                                      const struct ovn_lb_vip *lb_vip,
> +                                      struct ovn_northd_lb_vip *lb_vip_nb)
> +{
> +    struct ds key = DS_EMPTY_INITIALIZER;
> +
> +    for (size_t j = 0; j < lb_vip->n_backends; j++) {
> +        struct ovn_lb_backend *backend = &lb_vip->backends[j];
> +        ds_clear(&key);
> +        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
> +                      ? "%s" : "[%s]", backend->ip_str);
> +
> +        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
> +        if (!s) {
> +            continue;
> +        }
> +
> +        char *svc_mon_src_ip = NULL;
> +        char *port_name = xstrdup(s);
> +        char *p = strstr(port_name, ":");
> +        if (p) {
> +            *p = 0;
> +            p++;
> +            struct sockaddr_storage svc_mon_src_addr;
> +            if (!inet_parse_address(p, &svc_mon_src_addr)) {
> +                static struct vlog_rate_limit rl =
> +                    VLOG_RATE_LIMIT_INIT(5, 1);
> +                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
> +            } else {
> +                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
> +                ss_format_address_nobracks(&svc_mon_src_addr,
> +                                            &src_ip_s);
> +                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
> +            }
> +        }
> +
> +        if (svc_mon_src_ip) {
> +            struct ovn_northd_lb_backend *backend_nb =
> +                &lb_vip_nb->backends_nb[j];
> +            backend_nb->health_check = true;
> +            backend_nb->logical_port = xstrdup(port_name);
> +            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
> +        }
> +        free(port_name);
> +    }
> +
> +    ds_destroy(&key);
> +}
> +
> +static
> +void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
> +{
> +    free(vip->backend_ips);
> +    for (size_t i = 0; i < vip->n_backends; i++) {
> +        free(vip->backends_nb[i].logical_port);
> +        free(vip->backends_nb[i].svc_mon_src_ip);
> +    }
> +    free(vip->backends_nb);
> +}
> +
> +static bool
> +ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
> +                         bool routable, bool template)
> +{
> +    if (template && routable) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport "
> +                           "option 'add_route'.  Forcing it to disabled.",
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return false;
> +    }
> +    return routable;
> +}
> +
> +static bool
> +ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool template)
> +{
> +    if (!template) {
> +        return true;
> +    }
> +
> +    switch (mode) {
> +    case LB_NEIGH_RESPOND_REACHABLE:
> +        return false;
> +    case LB_NEIGH_RESPOND_ALL:
> +    case LB_NEIGH_RESPOND_NONE:
> +        return true;
> +    }
> +    return false;
> +}
> +
> +static enum lb_neighbor_responder_mode
> +ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
> +                      const char *mode, bool template)
> +{
> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +    enum lb_neighbor_responder_mode default_mode =
> +        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
> +
> +    if (!mode) {
> +        mode = lb_neighbor_responder_mode_names[default_mode];
> +    }
> +
> +    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); i++) {
> +        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
> +            if (ovn_lb_neigh_mode_is_valid(i, template)) {
> +                return i;
> +            }
> +            break;
> +        }
> +    }
> +
> +    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
> +                       UUID_FMT", forcing it to %s",
> +                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
> +                 lb_neighbor_responder_mode_names[default_mode]);
> +    return default_mode;
> +}
> +
> +static struct nbrec_load_balancer_health_check *
> +ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
> +                        const char *vip_port_str, bool template)
> +{
> +    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +
> +    if (!nbrec_lb->n_health_check) {
> +        return NULL;
> +    }
> +
> +    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
> +        VLOG_WARN_RL(&rl,
> +                     "SCTP load balancers do not currently support "
> +                     "health checks. Not creating health checks for "
> +                     "load balancer " UUID_FMT,
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return NULL;
> +    }
> +
> +    if (template) {
> +        VLOG_WARN_RL(&rl,
> +                     "Template load balancers do not currently support "
> +                     "health checks. Not creating health checks for "
> +                     "load balancer " UUID_FMT,
> +                     UUID_ARGS(&nbrec_lb->header_.uuid));
> +        return NULL;
> +    }
> +
> +    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
> +        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
> +            return nbrec_lb->health_check[i];
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void
> +ovn_northd_lb_init(struct ovn_northd_lb *lb,
> +                   const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
> +    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
> +    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
> +    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
> +                                              "address-family", "ipv4"),
> +                                 "ipv4")
> +                         ? AF_INET
> +                         : AF_INET6;
> +
> +    lb->nlb = nbrec_lb;
> +    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
> +    lb->n_vips = smap_count(&nbrec_lb->vips);
> +    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
> +    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
> +    smap_init(&lb->template_vips);
> +    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
> +
> +    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
> +    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
> +
> +    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
> +    lb->template = template;
> +
> +    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
> +    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
> +
> +    uint32_t affinity_timeout =
> +        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
> +    if (affinity_timeout > UINT16_MAX) {
> +        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
> +                     UINT16_MAX);
> +        affinity_timeout = UINT16_MAX;
> +    }
> +    lb->affinity_timeout = affinity_timeout;
> +
> +    sset_init(&lb->ips_v4);
> +    sset_init(&lb->ips_v6);
> +    struct smap_node *node;
> +    size_t n_vips = 0;
> +
> +    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
> +        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
> +        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
> +
> +        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
> +                                      template, address_family);
> +        if (error) {
> +            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> +            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
> +            ovn_lb_vip_destroy(lb_vip);
> +            free(error);
> +            continue;
> +        }
> +        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
> +                                                  "reject", false);
> +        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
> +                               node->key, node->value, template);
> +        if (lb_vip_nb->lb_health_check) {
> +            lb->health_checks = true;
> +        }
> +
> +        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
> +            sset_add(&lb->ips_v4, lb_vip->vip_str);
> +        } else {
> +            sset_add(&lb->ips_v6, lb_vip->vip_str);
> +        }
> +
> +        if (lb->template && address_family == AF_INET6) {
> +            smap_add_nocopy(&lb->template_vips,
> +                            ovn_lb_vip6_template_format_internal(lb_vip),
> +                            xstrdup(node->value));
> +        }
> +        n_vips++;
> +
> +        if (lb_vip_nb->lb_health_check) {
> +            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
> +        }
> +    }
> +
> +    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
> +     * correct value.
> +     */
> +    lb->n_vips = n_vips;
> +
> +    if (nbrec_lb->n_selection_fields) {
> +        char *proto = NULL;
> +        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
> +            proto = nbrec_lb->protocol;
> +        }
> +
> +        struct ds sel_fields = DS_EMPTY_INITIALIZER;
> +        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
> +            char *field = lb->nlb->selection_fields[i];
> +            if (!strcmp(field, "tp_src") && proto) {
> +                ds_put_format(&sel_fields, "%s_src,", proto);
> +            } else if (!strcmp(field, "tp_dst") && proto) {
> +                ds_put_format(&sel_fields, "%s_dst,", proto);
> +            } else {
> +                ds_put_format(&sel_fields, "%s,", field);
> +            }
> +        }
> +        ds_chomp(&sel_fields, ',');
> +        lb->selection_fields = ds_steal_cstr(&sel_fields);
> +    }
> +}
> +
> +struct ovn_northd_lb *
> +ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
> +    ovn_northd_lb_init(lb, nbrec_lb);
> +    return lb;
> +}
> +
> +struct ovn_northd_lb *
> +ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
> +{
> +    struct ovn_northd_lb *lb;
> +    size_t hash = uuid_hash(uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
> +        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
> +            return lb;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +const struct smap *
> +ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
> +{
> +    if (!smap_is_empty(&lb->template_vips)) {
> +        return &lb->template_vips;
> +    }
> +    return &lb->nlb->vips;
> +}
> +
> +static void
> +ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
> +{
> +    for (size_t i = 0; i < lb->n_vips; i++) {
> +        ovn_lb_vip_destroy(&lb->vips[i]);
> +        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
> +    }
> +    free(lb->vips);
> +    free(lb->vips_nb);
> +    lb->vips = NULL;
> +    lb->vips_nb = NULL;
> +    smap_destroy(&lb->template_vips);
> +    sset_destroy(&lb->ips_v4);
> +    sset_destroy(&lb->ips_v6);
> +    free(lb->selection_fields);
> +    lb->selection_fields = NULL;
> +    lb->health_checks = false;
> +}
> +
> +void
> +ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
> +{
> +    ovn_northd_lb_cleanup(lb);
> +    free(lb);
> +}
> +
> +void
> +ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
> +                     const struct nbrec_load_balancer *nbrec_lb)
> +{
> +    ovn_northd_lb_cleanup(lb);
> +    ovn_northd_lb_init(lb, nbrec_lb);
> +}
> +
> +static void
> +ovn_lb_group_init(struct ovn_lb_group *lb_group,
> +                  const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                  const struct hmap *lbs)
> +{
> +    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
> +    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
> +    lb_group->lb_ips = ovn_lb_ip_set_create();
> +
> +    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
> +        const struct uuid *lb_uuid =
> +            &nbrec_lb_group->load_balancer[i]->header_.uuid;
> +        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
> +        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
> +    }
> +}
> +
> +/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
> + * and an array of 'struct ovn_northd_lb' objects for its associated
> + * load balancers. */
> +struct ovn_lb_group *
> +ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                    const struct hmap *lbs)
> +{
> +    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
> +    lb_group->uuid = nbrec_lb_group->header_.uuid;
> +    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> +    return lb_group;
> +}
> +
> +static void
> +ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
> +{
> +    ovn_lb_ip_set_destroy(lb_group->lb_ips);
> +    lb_group->lb_ips = NULL;
> +    lb_group->has_routable_lb = false;
> +    free(lb_group->lbs);
> +}
> +
> +void
> +ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
> +{
> +    if (!lb_group) {
> +        return;
> +    }
> +
> +    ovn_lb_group_cleanup(lb_group);
> +    free(lb_group);
> +}
> +
> +void
> +ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
> +                    const struct nbrec_load_balancer_group *nbrec_lb_group,
> +                    const struct hmap *lbs)
> +{
> +    ovn_lb_group_cleanup(lb_group);
> +    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
> +}
> +
> +struct ovn_lb_group *
> +ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
> +{
> +    struct ovn_lb_group *lb_group;
> +    size_t hash = uuid_hash(uuid);
> +
> +    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
> +        if (uuid_equals(&lb_group->uuid, uuid)) {
> +            return lb_group;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +void
> +build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
> +                     const struct ovn_northd_lb *lb)
> +{
> +    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
> +}
> +
> +void
> +add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                     bool is_routable,
> +                     const struct sset *lb_ips_v4,
> +                     const struct sset *lb_ips_v6)
> +{
> +    const char *ip_address;
> +
> +    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> +        sset_add(&lb_ips->ips_v4, ip_address);
> +        if (is_routable) {
> +            sset_add(&lb_ips->ips_v4_routable, ip_address);
> +        }
> +    }
> +    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> +        sset_add(&lb_ips->ips_v6, ip_address);
> +        if (is_routable) {
> +            sset_add(&lb_ips->ips_v6_routable, ip_address);
> +        }
> +    }
> +}
> +
> +void
> +remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                          bool is_routable,
> +                          const struct sset *lb_ips_v4,
> +                          const struct sset *lb_ips_v6)
> +{
> +    const char *ip_address;
> +
> +    SSET_FOR_EACH (ip_address, lb_ips_v4) {
> +        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
> +        if (is_routable) {
> +            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
> +        }
> +    }
> +    SSET_FOR_EACH (ip_address, lb_ips_v6) {
> +        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
> +        if (is_routable) {
> +            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
> +        }
> +    }
> +}
> +
> +/* lb datapaths functions */
> +struct  ovn_lb_datapaths *
> +ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t n_ls_datapaths,
> +                        size_t n_lr_datapaths)
> +{
> +    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
> +    lb_dps->lb = lb;
> +    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
> +    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
> +
> +    return lb_dps;
> +}
> +
> +void
> +ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
> +{
> +    bitmap_free(lb_dps->nb_lr_map);
> +    bitmap_free(lb_dps->nb_ls_map);
> +    free(lb_dps);
> +}
> +
> +void
> +ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
> +                        struct ovn_datapath **ods)
> +{
> +    for (size_t i = 0; i < n; i++) {
> +        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
> +            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
> +            lb_dps->n_nb_lr++;
> +        }
> +    }
> +}
> +
> +void
> +ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
> +                        struct ovn_datapath **ods)
> +{
> +    for (size_t i = 0; i < n; i++) {
> +        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
> +            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
> +            lb_dps->n_nb_ls++;
> +        }
> +    }
> +}
> +
> +struct ovn_lb_datapaths *
> +ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
> +                      const struct uuid *lb_uuid)
> +{
> +    struct ovn_lb_datapaths *lb_dps;
> +    size_t hash = uuid_hash(lb_uuid);
> +    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
> +        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
> +            return lb_dps;
> +        }
> +    }
> +    return NULL;
> +}
> +
> +struct ovn_lb_group_datapaths *
> +ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
> +                              size_t max_ls_datapaths,
> +                              size_t max_lr_datapaths)
> +{
> +    struct ovn_lb_group_datapaths *lb_group_dps =
> +        xzalloc(sizeof *lb_group_dps);
> +    lb_group_dps->lb_group = lb_group;
> +    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
> +    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
> +
> +    return lb_group_dps;
> +}
> +
> +void
> +ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
> +{
> +    free(lb_group_dps->ls);
> +    free(lb_group_dps->lr);
> +    free(lb_group_dps);
> +}
> +
> +struct ovn_lb_group_datapaths *
> +ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
> +                            const struct uuid *lb_group_uuid)
> +{
> +    struct ovn_lb_group_datapaths *lb_group_dps;
> +    size_t hash = uuid_hash(lb_group_uuid);
> +
> +    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, lb_group_dps_map) {
> +        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
> +            return lb_group_dps;
> +        }
> +    }
> +    return NULL;
> +}
> diff --git a/northd/lb.h b/northd/lb.h
> new file mode 100644
> index 0000000000..00f81c3533
> --- /dev/null
> +++ b/northd/lb.h
> @@ -0,0 +1,189 @@
> +/*
> + * Copyright (c) 2024, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_NORTHD_LB_H
> +#define OVN_NORTHD_LB_H 1
> +
> +#include "openvswitch/hmap.h"
> +#include "uuid.h"
> +
> +#include "lib/lb.h"
> +
> +struct nbrec_load_balancer;
> +struct nbrec_load_balancer_group;
> +struct ovn_datapath;
> +
> +enum lb_neighbor_responder_mode {
> +    LB_NEIGH_RESPOND_REACHABLE,
> +    LB_NEIGH_RESPOND_ALL,
> +    LB_NEIGH_RESPOND_NONE,
> +};
> +
> +/* The "routable" ssets are subsets of the load balancer IPs for which IP
> + * routes and ARP resolution flows are automatically added. */
> +struct ovn_lb_ip_set {
> +    struct sset ips_v4;
> +    struct sset ips_v4_routable;
> +    struct sset ips_v4_reachable;
> +    struct sset ips_v6;
> +    struct sset ips_v6_routable;
> +    struct sset ips_v6_reachable;
> +};
> +
> +struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
> +void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
> +struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
> +
> +struct ovn_northd_lb {
> +    struct hmap_node hmap_node;
> +
> +    const struct nbrec_load_balancer *nlb; /* May be NULL. */
> +    const char *proto;
> +    char *selection_fields;
> +    struct ovn_lb_vip *vips;
> +    struct ovn_northd_lb_vip *vips_nb;
> +    struct smap template_vips; /* Slightly changed template VIPs, populated
> +                                * if needed.  Until now it's only required
> +                                * for IPv6 template load balancers. */
> +    size_t n_vips;
> +
> +    enum lb_neighbor_responder_mode neigh_mode;
> +    bool controller_event;
> +    bool routable;
> +    bool skip_snat;
> +    bool template;
> +    uint16_t affinity_timeout;
> +
> +    struct sset ips_v4;
> +    struct sset ips_v6;
> +
> +    /* Indicates if the load balancer has health checks configured. */
> +    bool health_checks;
> +};
> +
> +/* ovn-northd specific backend information. */
> +struct ovn_northd_lb_vip {
> +    char *backend_ips;
> +    struct ovn_northd_lb_backend *backends_nb;
> +    size_t n_backends;
> +
> +    struct nbrec_load_balancer_health_check *lb_health_check;
> +};
> +
> +struct ovn_northd_lb_backend {
> +    bool health_check;
> +    char *logical_port; /* Logical port to which the ip belong to. */
> +    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
> +};
> +
> +struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
> +struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
> +                                         const struct uuid *);
> +const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
> +void ovn_northd_lb_destroy(struct ovn_northd_lb *);
> +void ovn_northd_lb_reinit(struct ovn_northd_lb *,
> +                          const struct nbrec_load_balancer *);
> +
> +void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
> +                          const struct ovn_northd_lb *);
> +void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                          bool is_routable,
> +                          const struct sset *lb_ips_v4,
> +                          const struct sset *lb_ips_v6);
> +void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
> +                               bool is_routable,
> +                               const struct sset *lb_ips_v4,
> +                               const struct sset *lb_ips_v6);
> +
> +struct ovn_lb_group {
> +    struct hmap_node hmap_node;
> +    struct uuid uuid;
> +    size_t n_lbs;
> +    struct ovn_northd_lb **lbs;
> +    struct ovn_lb_ip_set *lb_ips;
> +    bool has_routable_lb;
> +};
> +
> +struct ovn_lb_group *ovn_lb_group_create(
> +    const struct nbrec_load_balancer_group *,
> +    const struct hmap *lbs);
> +void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
> +struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
> +                                       const struct uuid *);
> +void ovn_lb_group_reinit(
> +    struct ovn_lb_group *lb_group,
> +    const struct nbrec_load_balancer_group *,
> +    const struct hmap *lbs);
> +
> +struct ovn_lb_datapaths {
> +    struct hmap_node hmap_node;
> +
> +    const struct ovn_northd_lb *lb;
> +    size_t n_nb_ls;
> +    unsigned long *nb_ls_map;
> +
> +    size_t n_nb_lr;
> +    unsigned long *nb_lr_map;
> +};
> +
> +struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb *,
> +                                                 size_t n_ls_datapaths,
> +                                                 size_t n_lr_datapaths);
> +struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
> +                                               const struct uuid *);
> +void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
> +
> +void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
> +                             struct ovn_datapath **);
> +void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
> +                             struct ovn_datapath **);
> +
> +struct ovn_lb_group_datapaths {
> +    struct hmap_node hmap_node;
> +
> +    const struct ovn_lb_group *lb_group;
> +
> +    /* Datapaths to which 'lb_group' is applied. */
> +    size_t n_ls;
> +    struct ovn_datapath **ls;
> +    size_t n_lr;
> +    struct ovn_datapath **lr;
> +};
> +
> +struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
> +    const struct ovn_lb_group *, size_t max_ls_datapaths,
> +    size_t max_lr_datapaths);
> +
> +void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
> +struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
> +    const struct hmap *lb_group_dps, const struct uuid *);
> +
> +static inline void
> +ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t n,
> +                               struct ovn_datapath **ods)
> +{
> +    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
> +    lbg_dps->n_ls += n;
> +}
> +
> +static inline void
> +ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
> +                               struct ovn_datapath *lr)
> +{
> +    lbg_dps->lr[lbg_dps->n_lr++] = lr;
> +}
> +
> +#endif /* OVN_NORTHD_LB_H */
> diff --git a/northd/northd.c b/northd/northd.c
> index cb53ec9716..00899e3d1a 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -31,6 +31,7 @@
>  #include "openvswitch/hmap.h"
>  #include "openvswitch/json.h"
>  #include "ovn/lex.h"
> +#include "lb.h"
>  #include "lib/chassis-index.h"
>  #include "lib/ip-mcast-index.h"
>  #include "lib/static-mac-binding-index.h"
> --
> 2.43.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/automake.mk b/controller/automake.mk
index 0dbbd5d26b..a17ff0d60b 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -14,6 +14,8 @@  controller_ovn_controller_SOURCES = \
 	controller/if-status.h \
 	controller/ip-mcast.c \
 	controller/ip-mcast.h \
+	controller/lb.c \
+	controller/lb.h \
 	controller/lflow.c \
 	controller/lflow.h \
 	controller/lflow-cache.c \
diff --git a/controller/lb.c b/controller/lb.c
new file mode 100644
index 0000000000..8f9f20ed54
--- /dev/null
+++ b/controller/lb.c
@@ -0,0 +1,146 @@ 
+/*
+ * Copyright (c) 2024, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+/* OpenvSwitch lib includes. */
+#include "openvswitch/vlog.h"
+#include "lib/smap.h"
+
+/* OVN includes */
+#include "lb.h"
+#include "lib/ovn-sb-idl.h"
+#include "ovn/lex.h"
+
+VLOG_DEFINE_THIS_MODULE(controller_lb);
+
+static void
+ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
+                           const struct smap *lb_options,
+                           struct lport_addresses *hairpin_addrs)
+{
+    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
+
+    if (!addresses) {
+        return;
+    }
+
+    if (!extract_ip_address(addresses, hairpin_addrs)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
+                     addresses, UUID_ARGS(lb_uuid));
+    }
+}
+
+struct ovn_controller_lb *
+ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
+                         const struct smap *template_vars,
+                         struct sset *template_vars_ref)
+{
+    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
+    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
+
+    lb->slb = sbrec_lb;
+    lb->n_vips = smap_count(&sbrec_lb->vips);
+    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
+
+    struct smap_node *node;
+    size_t n_vips = 0;
+
+    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
+        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
+
+        struct lex_str key_s = template
+                               ? lexer_parse_template_string(node->key,
+                                                             template_vars,
+                                                             template_vars_ref)
+                               : lex_str_use(node->key);
+        struct lex_str value_s = template
+                               ? lexer_parse_template_string(node->value,
+                                                             template_vars,
+                                                             template_vars_ref)
+                               : lex_str_use(node->value);
+        char *error = ovn_lb_vip_init_explicit(lb_vip,
+                                               lex_str_get(&key_s),
+                                               lex_str_get(&value_s));
+        if (error) {
+            free(error);
+        } else {
+            n_vips++;
+        }
+        lex_str_free(&key_s);
+        lex_str_free(&value_s);
+    }
+
+    lb->proto = IPPROTO_TCP;
+    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
+        if (!strcmp(sbrec_lb->protocol, "udp")) {
+            lb->proto = IPPROTO_UDP;
+        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
+            lb->proto = IPPROTO_SCTP;
+        }
+    }
+
+    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
+     * correct value.
+     */
+    lb->n_vips = n_vips;
+
+    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
+                                           "hairpin_orig_tuple",
+                                           false);
+    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
+    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
+                               &lb->hairpin_snat_ips);
+    return lb;
+}
+
+void
+ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
+{
+    for (size_t i = 0; i < lb->n_vips; i++) {
+        ovn_lb_vip_destroy(&lb->vips[i]);
+    }
+    free(lb->vips);
+    destroy_lport_addresses(&lb->hairpin_snat_ips);
+    free(lb);
+}
+
+void
+ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
+{
+    struct ovn_controller_lb *lb;
+    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
+        ovn_controller_lb_destroy(lb);
+    }
+
+    hmap_destroy(ovn_controller_lbs);
+}
+
+struct ovn_controller_lb *
+ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
+                       const struct uuid *uuid)
+{
+    struct ovn_controller_lb *lb;
+    size_t hash = uuid_hash(uuid);
+    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
+        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
+            return lb;
+        }
+    }
+    return NULL;
+}
+
diff --git a/controller/lb.h b/controller/lb.h
new file mode 100644
index 0000000000..84d51c3329
--- /dev/null
+++ b/controller/lb.h
@@ -0,0 +1,55 @@ 
+/*
+ * Copyright (c) 2024, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_CONTROLLER_LB_H
+#define OVN_CONTROLLER_LB_H 1
+
+#include "lib/lb.h"
+
+struct sbrec_load_balancer;
+
+struct ovn_controller_lb {
+    struct hmap_node hmap_node;
+
+    const struct sbrec_load_balancer *slb; /* May be NULL. */
+
+    uint8_t proto;
+
+    struct ovn_lb_vip *vips;
+    size_t n_vips;
+    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
+                              * destination tuple in registers.
+                              */
+    bool ct_flush; /* True if we should flush CT after backend removal. */
+
+    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
+                                              * as source for hairpinned
+                                              * traffic.
+                                              */
+};
+
+struct ovn_controller_lb *ovn_controller_lb_create(
+    const struct sbrec_load_balancer *,
+    const struct smap *template_vars,
+    struct sset *template_vars_ref);
+void ovn_controller_lb_destroy(struct ovn_controller_lb *);
+void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
+struct ovn_controller_lb *ovn_controller_lb_find(
+    const struct hmap *ovn_controller_lbs,
+    const struct uuid *uuid);
+
+#endif /* OVN_CONTROLLER_LB_H */
+
diff --git a/controller/lflow.c b/controller/lflow.c
index c0cf0aa106..895d17d193 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -18,6 +18,7 @@ 
 #include "lflow.h"
 #include "coverage.h"
 #include "ha-chassis.h"
+#include "lb.h"
 #include "lflow-cache.h"
 #include "local_data.h"
 #include "lport.h"
diff --git a/lib/lb.c b/lib/lb.c
index d0d562b6fb..e67a5fcfd0 100644
--- a/lib/lb.c
+++ b/lib/lb.c
@@ -16,76 +16,16 @@ 
 #include <config.h>
 
 #include "lb.h"
-#include "lib/ovn-nb-idl.h"
-#include "lib/ovn-sb-idl.h"
 #include "lib/ovn-util.h"
-#include "northd/northd.h"
 #include "ovn/lex.h"
 
 /* OpenvSwitch lib includes. */
 #include "openvswitch/vlog.h"
-#include "lib/bitmap.h"
-#include "lib/smap.h"
-#include "socket-util.h"
 
 VLOG_DEFINE_THIS_MODULE(lb);
 
-static const char *lb_neighbor_responder_mode_names[] = {
-    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
-    [LB_NEIGH_RESPOND_ALL] = "all",
-    [LB_NEIGH_RESPOND_NONE] = "none",
-};
-
-static struct nbrec_load_balancer_health_check *
-ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
-                        const char *vip_port_str, bool template);
 static void ovn_lb_backends_clear(struct ovn_lb_vip *vip);
 
-struct ovn_lb_ip_set *
-ovn_lb_ip_set_create(void)
-{
-    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
-
-    sset_init(&lb_ip_set->ips_v4);
-    sset_init(&lb_ip_set->ips_v4_routable);
-    sset_init(&lb_ip_set->ips_v4_reachable);
-    sset_init(&lb_ip_set->ips_v6);
-    sset_init(&lb_ip_set->ips_v6_routable);
-    sset_init(&lb_ip_set->ips_v6_reachable);
-
-    return lb_ip_set;
-}
-
-void
-ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
-{
-    if (!lb_ip_set) {
-        return;
-    }
-    sset_destroy(&lb_ip_set->ips_v4);
-    sset_destroy(&lb_ip_set->ips_v4_routable);
-    sset_destroy(&lb_ip_set->ips_v4_reachable);
-    sset_destroy(&lb_ip_set->ips_v6);
-    sset_destroy(&lb_ip_set->ips_v6_routable);
-    sset_destroy(&lb_ip_set->ips_v6_reachable);
-    free(lb_ip_set);
-}
-
-struct ovn_lb_ip_set *
-ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
-{
-    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
-
-    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
-    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
-    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
-    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
-    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
-    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
-
-    return clone;
-}
-
 /* Format for backend ips: "IP1:port1,IP2:port2,...". */
 static char *
 ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value)
@@ -160,9 +100,9 @@  ovn_lb_backends_init_explicit(struct ovn_lb_vip *lb_vip, const char *value)
     return NULL;
 }
 
-static
-char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
-                               const char *lb_value)
+char *
+ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
+                         const char *lb_value)
 {
     if (!ip_address_and_port_from_lb_key(lb_key, &lb_vip->vip_str,
                                          &lb_vip->vip, &lb_vip->vip_port,
@@ -369,22 +309,6 @@  ovn_lb_vip_format__(const struct ovn_lb_vip *vip, struct ds *s,
     }
 }
 
-/* Formats the VIP in the way the ovn-controller expects it, that is,
- * template IPv6 variables need to be between brackets too.
- */
-static char *
-ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
-{
-    struct ds s = DS_EMPTY_INITIALIZER;
-
-    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
-        ovn_lb_vip_format__(vip, &s, true);
-    } else {
-        ovn_lb_vip_format(vip, &s, !!vip->port_str);
-    }
-    return ds_steal_cstr(&s);
-}
-
 void
 ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s, bool template)
 {
@@ -417,540 +341,20 @@  ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s)
     }
 }
 
-static
-void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
-                            const struct ovn_lb_vip *lb_vip,
-                            const struct nbrec_load_balancer *nbrec_lb,
-                            const char *vip_port_str, const char *backend_ips,
-                            bool template)
-{
-    lb_vip_nb->backend_ips = xstrdup(backend_ips);
-    lb_vip_nb->n_backends = lb_vip->n_backends;
-    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
-                                     sizeof *lb_vip_nb->backends_nb);
-    lb_vip_nb->lb_health_check =
-        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
-}
-
-static void
-ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
-                                      const struct ovn_lb_vip *lb_vip,
-                                      struct ovn_northd_lb_vip *lb_vip_nb)
-{
-    struct ds key = DS_EMPTY_INITIALIZER;
-
-    for (size_t j = 0; j < lb_vip->n_backends; j++) {
-        struct ovn_lb_backend *backend = &lb_vip->backends[j];
-        ds_clear(&key);
-        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
-                      ? "%s" : "[%s]", backend->ip_str);
-
-        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
-        if (!s) {
-            continue;
-        }
-
-        char *svc_mon_src_ip = NULL;
-        char *port_name = xstrdup(s);
-        char *p = strstr(port_name, ":");
-        if (p) {
-            *p = 0;
-            p++;
-            struct sockaddr_storage svc_mon_src_addr;
-            if (!inet_parse_address(p, &svc_mon_src_addr)) {
-                static struct vlog_rate_limit rl =
-                    VLOG_RATE_LIMIT_INIT(5, 1);
-                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
-            } else {
-                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
-                ss_format_address_nobracks(&svc_mon_src_addr,
-                                            &src_ip_s);
-                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
-            }
-        }
-
-        if (svc_mon_src_ip) {
-            struct ovn_northd_lb_backend *backend_nb =
-                &lb_vip_nb->backends_nb[j];
-            backend_nb->health_check = true;
-            backend_nb->logical_port = xstrdup(port_name);
-            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
-        }
-        free(port_name);
-    }
-
-    ds_destroy(&key);
-}
-
-static
-void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
-{
-    free(vip->backend_ips);
-    for (size_t i = 0; i < vip->n_backends; i++) {
-        free(vip->backends_nb[i].logical_port);
-        free(vip->backends_nb[i].svc_mon_src_ip);
-    }
-    free(vip->backends_nb);
-}
-
-static void
-ovn_lb_get_hairpin_snat_ip(const struct uuid *lb_uuid,
-                           const struct smap *lb_options,
-                           struct lport_addresses *hairpin_addrs)
-{
-    const char *addresses = smap_get(lb_options, "hairpin_snat_ip");
-
-    if (!addresses) {
-        return;
-    }
-
-    if (!extract_ip_address(addresses, hairpin_addrs)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-        VLOG_WARN_RL(&rl, "bad hairpin_snat_ip %s in load balancer "UUID_FMT,
-                     addresses, UUID_ARGS(lb_uuid));
-    }
-}
-
-static bool
-ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
-                         bool routable, bool template)
-{
-    if (template && routable) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport "
-                           "option 'add_route'.  Forcing it to disabled.",
-                     UUID_ARGS(&nbrec_lb->header_.uuid));
-        return false;
-    }
-    return routable;
-}
-
-static bool
-ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool template)
-{
-    if (!template) {
-        return true;
-    }
-
-    switch (mode) {
-    case LB_NEIGH_RESPOND_REACHABLE:
-        return false;
-    case LB_NEIGH_RESPOND_ALL:
-    case LB_NEIGH_RESPOND_NONE:
-        return true;
-    }
-    return false;
-}
-
-static enum lb_neighbor_responder_mode
-ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
-                      const char *mode, bool template)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-    enum lb_neighbor_responder_mode default_mode =
-        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
-
-    if (!mode) {
-        mode = lb_neighbor_responder_mode_names[default_mode];
-    }
-
-    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); i++) {
-        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
-            if (ovn_lb_neigh_mode_is_valid(i, template)) {
-                return i;
-            }
-            break;
-        }
-    }
-
-    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
-                       UUID_FMT", forcing it to %s",
-                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
-                 lb_neighbor_responder_mode_names[default_mode]);
-    return default_mode;
-}
-
-static struct nbrec_load_balancer_health_check *
-ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
-                        const char *vip_port_str, bool template)
-{
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-
-    if (!nbrec_lb->n_health_check) {
-        return NULL;
-    }
-
-    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
-        VLOG_WARN_RL(&rl,
-                     "SCTP load balancers do not currently support "
-                     "health checks. Not creating health checks for "
-                     "load balancer " UUID_FMT,
-                     UUID_ARGS(&nbrec_lb->header_.uuid));
-        return NULL;
-    }
-
-    if (template) {
-        VLOG_WARN_RL(&rl,
-                     "Template load balancers do not currently support "
-                     "health checks. Not creating health checks for "
-                     "load balancer " UUID_FMT,
-                     UUID_ARGS(&nbrec_lb->header_.uuid));
-        return NULL;
-    }
-
-    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
-        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
-            return nbrec_lb->health_check[i];
-        }
-    }
-    return NULL;
-}
-
-static void
-ovn_northd_lb_init(struct ovn_northd_lb *lb,
-                   const struct nbrec_load_balancer *nbrec_lb)
-{
-    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
-    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
-    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
-    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
-                                              "address-family", "ipv4"),
-                                 "ipv4")
-                         ? AF_INET
-                         : AF_INET6;
-
-    lb->nlb = nbrec_lb;
-    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
-    lb->n_vips = smap_count(&nbrec_lb->vips);
-    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
-    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
-    smap_init(&lb->template_vips);
-    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
-
-    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
-    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
-
-    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
-    lb->template = template;
-
-    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
-    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
-
-    uint32_t affinity_timeout =
-        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
-    if (affinity_timeout > UINT16_MAX) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
-                     UINT16_MAX);
-        affinity_timeout = UINT16_MAX;
-    }
-    lb->affinity_timeout = affinity_timeout;
-
-    sset_init(&lb->ips_v4);
-    sset_init(&lb->ips_v6);
-    struct smap_node *node;
-    size_t n_vips = 0;
-
-    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
-        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
-        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
-
-        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
-                                      template, address_family);
-        if (error) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
-            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
-            ovn_lb_vip_destroy(lb_vip);
-            free(error);
-            continue;
-        }
-        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
-                                                  "reject", false);
-        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
-                               node->key, node->value, template);
-        if (lb_vip_nb->lb_health_check) {
-            lb->health_checks = true;
-        }
-
-        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
-            sset_add(&lb->ips_v4, lb_vip->vip_str);
-        } else {
-            sset_add(&lb->ips_v6, lb_vip->vip_str);
-        }
-
-        if (lb->template && address_family == AF_INET6) {
-            smap_add_nocopy(&lb->template_vips,
-                            ovn_lb_vip6_template_format_internal(lb_vip),
-                            xstrdup(node->value));
-        }
-        n_vips++;
-
-        if (lb_vip_nb->lb_health_check) {
-            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
-        }
-    }
-
-    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
-     * correct value.
-     */
-    lb->n_vips = n_vips;
-
-    if (nbrec_lb->n_selection_fields) {
-        char *proto = NULL;
-        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
-            proto = nbrec_lb->protocol;
-        }
-
-        struct ds sel_fields = DS_EMPTY_INITIALIZER;
-        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
-            char *field = lb->nlb->selection_fields[i];
-            if (!strcmp(field, "tp_src") && proto) {
-                ds_put_format(&sel_fields, "%s_src,", proto);
-            } else if (!strcmp(field, "tp_dst") && proto) {
-                ds_put_format(&sel_fields, "%s_dst,", proto);
-            } else {
-                ds_put_format(&sel_fields, "%s,", field);
-            }
-        }
-        ds_chomp(&sel_fields, ',');
-        lb->selection_fields = ds_steal_cstr(&sel_fields);
-    }
-}
-
-struct ovn_northd_lb *
-ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
-{
-    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
-    ovn_northd_lb_init(lb, nbrec_lb);
-    return lb;
-}
-
-struct ovn_northd_lb *
-ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
-{
-    struct ovn_northd_lb *lb;
-    size_t hash = uuid_hash(uuid);
-    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
-        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
-            return lb;
-        }
-    }
-    return NULL;
-}
-
-const struct smap *
-ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
-{
-    if (!smap_is_empty(&lb->template_vips)) {
-        return &lb->template_vips;
-    }
-    return &lb->nlb->vips;
-}
-
-static void
-ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
-{
-    for (size_t i = 0; i < lb->n_vips; i++) {
-        ovn_lb_vip_destroy(&lb->vips[i]);
-        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
-    }
-    free(lb->vips);
-    free(lb->vips_nb);
-    lb->vips = NULL;
-    lb->vips_nb = NULL;
-    smap_destroy(&lb->template_vips);
-    sset_destroy(&lb->ips_v4);
-    sset_destroy(&lb->ips_v6);
-    free(lb->selection_fields);
-    lb->selection_fields = NULL;
-    lb->health_checks = false;
-}
-
-void
-ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
-{
-    ovn_northd_lb_cleanup(lb);
-    free(lb);
-}
-
-void
-ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
-                     const struct nbrec_load_balancer *nbrec_lb)
-{
-    ovn_northd_lb_cleanup(lb);
-    ovn_northd_lb_init(lb, nbrec_lb);
-}
-
-static void
-ovn_lb_group_init(struct ovn_lb_group *lb_group,
-                  const struct nbrec_load_balancer_group *nbrec_lb_group,
-                  const struct hmap *lbs)
-{
-    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
-    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
-    lb_group->lb_ips = ovn_lb_ip_set_create();
-
-    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
-        const struct uuid *lb_uuid =
-            &nbrec_lb_group->load_balancer[i]->header_.uuid;
-        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
-        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
-    }
-}
-
-/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
- * and an array of 'struct ovn_northd_lb' objects for its associated
- * load balancers. */
-struct ovn_lb_group *
-ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
-                    const struct hmap *lbs)
-{
-    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
-    lb_group->uuid = nbrec_lb_group->header_.uuid;
-    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
-    return lb_group;
-}
-
-static void
-ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
-{
-    ovn_lb_ip_set_destroy(lb_group->lb_ips);
-    lb_group->lb_ips = NULL;
-    lb_group->has_routable_lb = false;
-    free(lb_group->lbs);
-}
-
-void
-ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
-{
-    if (!lb_group) {
-        return;
-    }
-
-    ovn_lb_group_cleanup(lb_group);
-    free(lb_group);
-}
-
-void
-ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
-                    const struct nbrec_load_balancer_group *nbrec_lb_group,
-                    const struct hmap *lbs)
-{
-    ovn_lb_group_cleanup(lb_group);
-    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
-}
-
-struct ovn_lb_group *
-ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
-{
-    struct ovn_lb_group *lb_group;
-    size_t hash = uuid_hash(uuid);
-
-    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
-        if (uuid_equals(&lb_group->uuid, uuid)) {
-            return lb_group;
-        }
-    }
-    return NULL;
-}
-
-struct ovn_controller_lb *
-ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb,
-                         const struct smap *template_vars,
-                         struct sset *template_vars_ref)
-{
-    struct ovn_controller_lb *lb = xzalloc(sizeof *lb);
-    bool template = smap_get_bool(&sbrec_lb->options, "template", false);
-
-    lb->slb = sbrec_lb;
-    lb->n_vips = smap_count(&sbrec_lb->vips);
-    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
-
-    struct smap_node *node;
-    size_t n_vips = 0;
-
-    SMAP_FOR_EACH (node, &sbrec_lb->vips) {
-        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
-
-        struct lex_str key_s = template
-                               ? lexer_parse_template_string(node->key,
-                                                             template_vars,
-                                                             template_vars_ref)
-                               : lex_str_use(node->key);
-        struct lex_str value_s = template
-                               ? lexer_parse_template_string(node->value,
-                                                             template_vars,
-                                                             template_vars_ref)
-                               : lex_str_use(node->value);
-        char *error = ovn_lb_vip_init_explicit(lb_vip,
-                                               lex_str_get(&key_s),
-                                               lex_str_get(&value_s));
-        if (error) {
-            free(error);
-        } else {
-            n_vips++;
-        }
-        lex_str_free(&key_s);
-        lex_str_free(&value_s);
-    }
-
-    lb->proto = IPPROTO_TCP;
-    if (sbrec_lb->protocol && sbrec_lb->protocol[0]) {
-        if (!strcmp(sbrec_lb->protocol, "udp")) {
-            lb->proto = IPPROTO_UDP;
-        } else if (!strcmp(sbrec_lb->protocol, "sctp")) {
-            lb->proto = IPPROTO_SCTP;
-        }
-    }
-
-    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
-     * correct value.
-     */
-    lb->n_vips = n_vips;
-
-    lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
-                                           "hairpin_orig_tuple",
-                                           false);
-    lb->ct_flush = smap_get_bool(&sbrec_lb->options, "ct_flush", false);
-    ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid, &sbrec_lb->options,
-                               &lb->hairpin_snat_ips);
-    return lb;
-}
-
-void
-ovn_controller_lb_destroy(struct ovn_controller_lb *lb)
-{
-    for (size_t i = 0; i < lb->n_vips; i++) {
-        ovn_lb_vip_destroy(&lb->vips[i]);
-    }
-    free(lb->vips);
-    destroy_lport_addresses(&lb->hairpin_snat_ips);
-    free(lb);
-}
-
-void
-ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs)
+/* Formats the VIP in the way the ovn-controller expects it, that is,
+ * template IPv6 variables need to be between brackets too.
+ */
+char *
+ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip)
 {
-    struct ovn_controller_lb *lb;
-    HMAP_FOR_EACH_POP (lb, hmap_node, ovn_controller_lbs) {
-        ovn_controller_lb_destroy(lb);
-    }
-
-    hmap_destroy(ovn_controller_lbs);
-}
+    struct ds s = DS_EMPTY_INITIALIZER;
 
-struct ovn_controller_lb *
-ovn_controller_lb_find(const struct hmap *ovn_controller_lbs,
-                       const struct uuid *uuid)
-{
-    struct ovn_controller_lb *lb;
-    size_t hash = uuid_hash(uuid);
-    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, ovn_controller_lbs) {
-        if (uuid_equals(&lb->slb->header_.uuid, uuid)) {
-            return lb;
-        }
+    if (vip->vip_str && *vip->vip_str == LEX_TEMPLATE_PREFIX) {
+        ovn_lb_vip_format__(vip, &s, true);
+    } else {
+        ovn_lb_vip_format(vip, &s, !!vip->port_str);
     }
-    return NULL;
+    return ds_steal_cstr(&s);
 }
 
 static uint32_t
@@ -1020,150 +424,3 @@  ovn_lb_5tuples_destroy(struct hmap *tuples)
 
     hmap_destroy(tuples);
 }
-
-void
-build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
-                     const struct ovn_northd_lb *lb)
-{
-    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
-}
-
-void
-add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
-                     bool is_routable,
-                     const struct sset *lb_ips_v4,
-                     const struct sset *lb_ips_v6)
-{
-    const char *ip_address;
-
-    SSET_FOR_EACH (ip_address, lb_ips_v4) {
-        sset_add(&lb_ips->ips_v4, ip_address);
-        if (is_routable) {
-            sset_add(&lb_ips->ips_v4_routable, ip_address);
-        }
-    }
-    SSET_FOR_EACH (ip_address, lb_ips_v6) {
-        sset_add(&lb_ips->ips_v6, ip_address);
-        if (is_routable) {
-            sset_add(&lb_ips->ips_v6_routable, ip_address);
-        }
-    }
-}
-
-void
-remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
-                          bool is_routable,
-                          const struct sset *lb_ips_v4,
-                          const struct sset *lb_ips_v6)
-{
-    const char *ip_address;
-
-    SSET_FOR_EACH (ip_address, lb_ips_v4) {
-        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
-        if (is_routable) {
-            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
-        }
-    }
-    SSET_FOR_EACH (ip_address, lb_ips_v6) {
-        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
-        if (is_routable) {
-            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
-        }
-    }
-}
-
-/* lb datapaths functions */
-struct  ovn_lb_datapaths *
-ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t n_ls_datapaths,
-                        size_t n_lr_datapaths)
-{
-    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
-    lb_dps->lb = lb;
-    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
-    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
-
-    return lb_dps;
-}
-
-struct ovn_lb_datapaths *
-ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
-                      const struct uuid *lb_uuid)
-{
-    struct ovn_lb_datapaths *lb_dps;
-    size_t hash = uuid_hash(lb_uuid);
-    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
-        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
-            return lb_dps;
-        }
-    }
-    return NULL;
-}
-
-void
-ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
-{
-    bitmap_free(lb_dps->nb_lr_map);
-    bitmap_free(lb_dps->nb_ls_map);
-    free(lb_dps);
-}
-
-void
-ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
-                        struct ovn_datapath **ods)
-{
-    for (size_t i = 0; i < n; i++) {
-        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
-            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
-            lb_dps->n_nb_lr++;
-        }
-    }
-}
-
-void
-ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
-                        struct ovn_datapath **ods)
-{
-    for (size_t i = 0; i < n; i++) {
-        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
-            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
-            lb_dps->n_nb_ls++;
-        }
-    }
-}
-
-struct ovn_lb_group_datapaths *
-ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
-                              size_t max_ls_datapaths,
-                              size_t max_lr_datapaths)
-{
-    struct ovn_lb_group_datapaths *lb_group_dps =
-        xzalloc(sizeof *lb_group_dps);
-    lb_group_dps->lb_group = lb_group;
-    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
-    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
-
-    return lb_group_dps;
-}
-
-void
-ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
-{
-    free(lb_group_dps->ls);
-    free(lb_group_dps->lr);
-    free(lb_group_dps);
-}
-
-struct ovn_lb_group_datapaths *
-ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
-                            const struct uuid *lb_group_uuid)
-{
-    struct ovn_lb_group_datapaths *lb_group_dps;
-    size_t hash = uuid_hash(lb_group_uuid);
-
-    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, lb_group_dps_map) {
-        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
-            return lb_group_dps;
-        }
-    }
-    return NULL;
-}
diff --git a/lib/lb.h b/lib/lb.h
index b8e3c1e8fb..bcc677f82e 100644
--- a/lib/lb.h
+++ b/lib/lb.h
@@ -25,63 +25,8 @@ 
 #include "sset.h"
 #include "uuid.h"
 
-struct nbrec_load_balancer;
-struct nbrec_load_balancer_group;
-struct sbrec_load_balancer;
-struct sbrec_datapath_binding;
-struct ovn_datapath;
-struct ovn_dp_group;
-struct ovn_port;
 struct uuid;
 
-enum lb_neighbor_responder_mode {
-    LB_NEIGH_RESPOND_REACHABLE,
-    LB_NEIGH_RESPOND_ALL,
-    LB_NEIGH_RESPOND_NONE,
-};
-
-/* The "routable" ssets are subsets of the load balancer IPs for which IP
- * routes and ARP resolution flows are automatically added. */
-struct ovn_lb_ip_set {
-    struct sset ips_v4;
-    struct sset ips_v4_routable;
-    struct sset ips_v4_reachable;
-    struct sset ips_v6;
-    struct sset ips_v6_routable;
-    struct sset ips_v6_reachable;
-};
-
-struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
-void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
-struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
-
-struct ovn_northd_lb {
-    struct hmap_node hmap_node;
-
-    const struct nbrec_load_balancer *nlb; /* May be NULL. */
-    const char *proto;
-    char *selection_fields;
-    struct ovn_lb_vip *vips;
-    struct ovn_northd_lb_vip *vips_nb;
-    struct smap template_vips; /* Slightly changed template VIPs, populated
-                                * if needed.  Until now it's only required
-                                * for IPv6 template load balancers. */
-    size_t n_vips;
-
-    enum lb_neighbor_responder_mode neigh_mode;
-    bool controller_event;
-    bool routable;
-    bool skip_snat;
-    bool template;
-    uint16_t affinity_timeout;
-
-    struct sset ips_v4;
-    struct sset ips_v6;
-
-    /* Indicates if the load balancer has health checks configured. */
-    bool health_checks;
-};
-
 struct ovn_lb_vip {
     struct in6_addr vip; /* Only used in ovn-controller. */
     char *vip_str;       /* Actual VIP string representation (without port).
@@ -113,153 +58,15 @@  struct ovn_lb_backend {
                           */
 };
 
-/* ovn-northd specific backend information. */
-struct ovn_northd_lb_vip {
-    char *backend_ips;
-    struct ovn_northd_lb_backend *backends_nb;
-    size_t n_backends;
-
-    struct nbrec_load_balancer_health_check *lb_health_check;
-};
-
-struct ovn_northd_lb_backend {
-    bool health_check;
-    char *logical_port; /* Logical port to which the ip belong to. */
-    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
-};
-
-struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
-struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
-                                         const struct uuid *);
-const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
-void ovn_northd_lb_destroy(struct ovn_northd_lb *);
-void ovn_northd_lb_reinit(struct ovn_northd_lb *,
-                          const struct nbrec_load_balancer *);
-
-void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
-                          const struct ovn_northd_lb *);
-void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
-                          bool is_routable,
-                          const struct sset *lb_ips_v4,
-                          const struct sset *lb_ips_v6);
-void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
-                               bool is_routable,
-                               const struct sset *lb_ips_v4,
-                               const struct sset *lb_ips_v6);
-
-struct ovn_lb_group {
-    struct hmap_node hmap_node;
-    struct uuid uuid;
-    size_t n_lbs;
-    struct ovn_northd_lb **lbs;
-    struct ovn_lb_ip_set *lb_ips;
-    bool has_routable_lb;
-};
-
-struct ovn_lb_group *ovn_lb_group_create(
-    const struct nbrec_load_balancer_group *,
-    const struct hmap *lbs);
-void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
-struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
-                                       const struct uuid *);
-void ovn_lb_group_reinit(
-    struct ovn_lb_group *lb_group,
-    const struct nbrec_load_balancer_group *,
-    const struct hmap *lbs);
-
-struct ovn_lb_datapaths {
-    struct hmap_node hmap_node;
-
-    const struct ovn_northd_lb *lb;
-    size_t n_nb_ls;
-    unsigned long *nb_ls_map;
-
-    size_t n_nb_lr;
-    unsigned long *nb_lr_map;
-};
-
-struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb *,
-                                                 size_t n_ls_datapaths,
-                                                 size_t n_lr_datapaths);
-struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
-                                               const struct uuid *);
-void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
-void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
-                             struct ovn_datapath **);
-void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
-                             struct ovn_datapath **);
-
-struct ovn_lb_group_datapaths {
-    struct hmap_node hmap_node;
-
-    const struct ovn_lb_group *lb_group;
-
-    /* Datapaths to which 'lb_group' is applied. */
-    size_t n_ls;
-    struct ovn_datapath **ls;
-    size_t n_lr;
-    struct ovn_datapath **lr;
-};
-
-struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
-    const struct ovn_lb_group *, size_t max_ls_datapaths,
-    size_t max_lr_datapaths);
-
-void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
-struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
-    const struct hmap *lb_group_dps, const struct uuid *);
-
-static inline void
-ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t n,
-                               struct ovn_datapath **ods)
-{
-    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
-    lbg_dps->n_ls += n;
-}
-
-static inline void
-ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
-                               struct ovn_datapath *lr)
-{
-    lbg_dps->lr[lbg_dps->n_lr++] = lr;
-}
-
-struct ovn_controller_lb {
-    struct hmap_node hmap_node;
-
-    const struct sbrec_load_balancer *slb; /* May be NULL. */
-
-    uint8_t proto;
-
-    struct ovn_lb_vip *vips;
-    size_t n_vips;
-    bool hairpin_orig_tuple; /* True if ovn-northd stores the original
-                              * destination tuple in registers.
-                              */
-    bool ct_flush; /* True if we should flush CT after backend removal. */
-
-    struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
-                                              * as source for hairpinned
-                                              * traffic.
-                                              */
-};
-
-struct ovn_controller_lb *ovn_controller_lb_create(
-    const struct sbrec_load_balancer *,
-    const struct smap *template_vars,
-    struct sset *template_vars_ref);
-void ovn_controller_lb_destroy(struct ovn_controller_lb *);
-void ovn_controller_lbs_destroy(struct hmap *ovn_controller_lbs);
-struct ovn_controller_lb *ovn_controller_lb_find(
-    const struct hmap *ovn_controller_lbs,
-    const struct uuid *uuid);
-
 char *ovn_lb_vip_init(struct ovn_lb_vip *lb_vip, const char *lb_key,
                       const char *lb_value, bool template, int address_family);
+char *ovn_lb_vip_init_explicit(struct ovn_lb_vip *lb_vip, const char *lb_key,
+                               const char *lb_value);
 void ovn_lb_vip_destroy(struct ovn_lb_vip *vip);
 void ovn_lb_vip_format(const struct ovn_lb_vip *vip, struct ds *s,
                        bool template);
 void ovn_lb_vip_backends_format(const struct ovn_lb_vip *vip, struct ds *s);
+char *ovn_lb_vip6_template_format_internal(const struct ovn_lb_vip *vip);
 
 struct ovn_lb_5tuple {
     struct hmap_node hmap_node;
diff --git a/northd/automake.mk b/northd/automake.mk
index 7c6d56a4ff..19abb0dece 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -35,7 +35,9 @@  northd_ovn_northd_SOURCES = \
 	northd/ipam.c \
 	northd/ipam.h \
 	northd/lflow-mgr.c \
-	northd/lflow-mgr.h
+	northd/lflow-mgr.h \
+	northd/lb.c \
+	northd/lb.h
 northd_ovn_northd_LDADD = \
 	lib/libovn.la \
 	$(OVSDB_LIBDIR)/libovsdb.la \
diff --git a/northd/en-lb-data.c b/northd/en-lb-data.c
index d06f46a54b..6ad3fbb35f 100644
--- a/northd/en-lb-data.c
+++ b/northd/en-lb-data.c
@@ -25,6 +25,7 @@ 
 
 /* OVN includes */
 #include "en-lb-data.h"
+#include "lb.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/lb.h"
 #include "lib/ovn-nb-idl.h"
diff --git a/northd/en-lr-stateful.c b/northd/en-lr-stateful.c
index 8665b3c791..3e2a6c254e 100644
--- a/northd/en-lr-stateful.c
+++ b/northd/en-lr-stateful.c
@@ -33,6 +33,7 @@ 
 #include "en-lb-data.h"
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
+#include "lb.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/lb.h"
 #include "lib/ovn-nb-idl.h"
diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index 53f687f220..9ca59d4338 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -24,6 +24,7 @@ 
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
 #include "en-sync-sb.h"
+#include "lb.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/lb.h"
 #include "lib/ovn-nb-idl.h"
diff --git a/northd/lb.c b/northd/lb.c
new file mode 100644
index 0000000000..e35569cb70
--- /dev/null
+++ b/northd/lb.c
@@ -0,0 +1,651 @@ 
+/*
+ * Copyright (c) 2024, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+/* OVS includes */
+#include "lib/bitmap.h"
+#include "openvswitch/vlog.h"
+#include "socket-util.h"
+
+/* OVN includes */
+#include "lb.h"
+#include "lib/ovn-nb-idl.h"
+#include "northd.h"
+#include "ovn/lex.h"
+
+VLOG_DEFINE_THIS_MODULE(northd_lb);
+
+static const char *lb_neighbor_responder_mode_names[] = {
+    [LB_NEIGH_RESPOND_REACHABLE] = "reachable",
+    [LB_NEIGH_RESPOND_ALL] = "all",
+    [LB_NEIGH_RESPOND_NONE] = "none",
+};
+
+static struct nbrec_load_balancer_health_check *
+ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
+                        const char *vip_port_str, bool template);
+
+struct ovn_lb_ip_set *
+ovn_lb_ip_set_create(void)
+{
+    struct ovn_lb_ip_set *lb_ip_set = xzalloc(sizeof *lb_ip_set);
+
+    sset_init(&lb_ip_set->ips_v4);
+    sset_init(&lb_ip_set->ips_v4_routable);
+    sset_init(&lb_ip_set->ips_v4_reachable);
+    sset_init(&lb_ip_set->ips_v6);
+    sset_init(&lb_ip_set->ips_v6_routable);
+    sset_init(&lb_ip_set->ips_v6_reachable);
+
+    return lb_ip_set;
+}
+
+void
+ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *lb_ip_set)
+{
+    if (!lb_ip_set) {
+        return;
+    }
+    sset_destroy(&lb_ip_set->ips_v4);
+    sset_destroy(&lb_ip_set->ips_v4_routable);
+    sset_destroy(&lb_ip_set->ips_v4_reachable);
+    sset_destroy(&lb_ip_set->ips_v6);
+    sset_destroy(&lb_ip_set->ips_v6_routable);
+    sset_destroy(&lb_ip_set->ips_v6_reachable);
+    free(lb_ip_set);
+}
+
+struct ovn_lb_ip_set *
+ovn_lb_ip_set_clone(struct ovn_lb_ip_set *lb_ip_set)
+{
+    struct ovn_lb_ip_set *clone = ovn_lb_ip_set_create();
+
+    sset_clone(&clone->ips_v4, &lb_ip_set->ips_v4);
+    sset_clone(&clone->ips_v4_routable, &lb_ip_set->ips_v4_routable);
+    sset_clone(&clone->ips_v4_reachable, &lb_ip_set->ips_v4_reachable);
+    sset_clone(&clone->ips_v6, &lb_ip_set->ips_v6);
+    sset_clone(&clone->ips_v6_routable, &lb_ip_set->ips_v6_routable);
+    sset_clone(&clone->ips_v6_reachable, &lb_ip_set->ips_v6_reachable);
+
+    return clone;
+}
+
+static
+void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb,
+                            const struct ovn_lb_vip *lb_vip,
+                            const struct nbrec_load_balancer *nbrec_lb,
+                            const char *vip_port_str, const char *backend_ips,
+                            bool template)
+{
+    lb_vip_nb->backend_ips = xstrdup(backend_ips);
+    lb_vip_nb->n_backends = lb_vip->n_backends;
+    lb_vip_nb->backends_nb = xcalloc(lb_vip_nb->n_backends,
+                                     sizeof *lb_vip_nb->backends_nb);
+    lb_vip_nb->lb_health_check =
+        ovn_lb_get_health_check(nbrec_lb, vip_port_str, template);
+}
+
+static void
+ovn_lb_vip_backends_health_check_init(const struct ovn_northd_lb *lb,
+                                      const struct ovn_lb_vip *lb_vip,
+                                      struct ovn_northd_lb_vip *lb_vip_nb)
+{
+    struct ds key = DS_EMPTY_INITIALIZER;
+
+    for (size_t j = 0; j < lb_vip->n_backends; j++) {
+        struct ovn_lb_backend *backend = &lb_vip->backends[j];
+        ds_clear(&key);
+        ds_put_format(&key, IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)
+                      ? "%s" : "[%s]", backend->ip_str);
+
+        const char *s = smap_get(&lb->nlb->ip_port_mappings, ds_cstr(&key));
+        if (!s) {
+            continue;
+        }
+
+        char *svc_mon_src_ip = NULL;
+        char *port_name = xstrdup(s);
+        char *p = strstr(port_name, ":");
+        if (p) {
+            *p = 0;
+            p++;
+            struct sockaddr_storage svc_mon_src_addr;
+            if (!inet_parse_address(p, &svc_mon_src_addr)) {
+                static struct vlog_rate_limit rl =
+                    VLOG_RATE_LIMIT_INIT(5, 1);
+                VLOG_WARN_RL(&rl, "Invalid svc mon src IP %s", p);
+            } else {
+                struct ds src_ip_s = DS_EMPTY_INITIALIZER;
+                ss_format_address_nobracks(&svc_mon_src_addr,
+                                            &src_ip_s);
+                svc_mon_src_ip = ds_steal_cstr(&src_ip_s);
+            }
+        }
+
+        if (svc_mon_src_ip) {
+            struct ovn_northd_lb_backend *backend_nb =
+                &lb_vip_nb->backends_nb[j];
+            backend_nb->health_check = true;
+            backend_nb->logical_port = xstrdup(port_name);
+            backend_nb->svc_mon_src_ip = svc_mon_src_ip;
+        }
+        free(port_name);
+    }
+
+    ds_destroy(&key);
+}
+
+static
+void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip)
+{
+    free(vip->backend_ips);
+    for (size_t i = 0; i < vip->n_backends; i++) {
+        free(vip->backends_nb[i].logical_port);
+        free(vip->backends_nb[i].svc_mon_src_ip);
+    }
+    free(vip->backends_nb);
+}
+
+static bool
+ovn_lb_get_routable_mode(const struct nbrec_load_balancer *nbrec_lb,
+                         bool routable, bool template)
+{
+    if (template && routable) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_WARN_RL(&rl, "Template load balancer "UUID_FMT" does not suport "
+                           "option 'add_route'.  Forcing it to disabled.",
+                     UUID_ARGS(&nbrec_lb->header_.uuid));
+        return false;
+    }
+    return routable;
+}
+
+static bool
+ovn_lb_neigh_mode_is_valid(enum lb_neighbor_responder_mode mode, bool template)
+{
+    if (!template) {
+        return true;
+    }
+
+    switch (mode) {
+    case LB_NEIGH_RESPOND_REACHABLE:
+        return false;
+    case LB_NEIGH_RESPOND_ALL:
+    case LB_NEIGH_RESPOND_NONE:
+        return true;
+    }
+    return false;
+}
+
+static enum lb_neighbor_responder_mode
+ovn_lb_get_neigh_mode(const struct nbrec_load_balancer *nbrec_lb,
+                      const char *mode, bool template)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+    enum lb_neighbor_responder_mode default_mode =
+        template ? LB_NEIGH_RESPOND_NONE : LB_NEIGH_RESPOND_REACHABLE;
+
+    if (!mode) {
+        mode = lb_neighbor_responder_mode_names[default_mode];
+    }
+
+    for (size_t i = 0; i < ARRAY_SIZE(lb_neighbor_responder_mode_names); i++) {
+        if (!strcmp(mode, lb_neighbor_responder_mode_names[i])) {
+            if (ovn_lb_neigh_mode_is_valid(i, template)) {
+                return i;
+            }
+            break;
+        }
+    }
+
+    VLOG_WARN_RL(&rl, "Invalid neighbor responder mode %s for load balancer "
+                       UUID_FMT", forcing it to %s",
+                 mode, UUID_ARGS(&nbrec_lb->header_.uuid),
+                 lb_neighbor_responder_mode_names[default_mode]);
+    return default_mode;
+}
+
+static struct nbrec_load_balancer_health_check *
+ovn_lb_get_health_check(const struct nbrec_load_balancer *nbrec_lb,
+                        const char *vip_port_str, bool template)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+
+    if (!nbrec_lb->n_health_check) {
+        return NULL;
+    }
+
+    if (nbrec_lb->protocol && !strcmp(nbrec_lb->protocol, "sctp")) {
+        VLOG_WARN_RL(&rl,
+                     "SCTP load balancers do not currently support "
+                     "health checks. Not creating health checks for "
+                     "load balancer " UUID_FMT,
+                     UUID_ARGS(&nbrec_lb->header_.uuid));
+        return NULL;
+    }
+
+    if (template) {
+        VLOG_WARN_RL(&rl,
+                     "Template load balancers do not currently support "
+                     "health checks. Not creating health checks for "
+                     "load balancer " UUID_FMT,
+                     UUID_ARGS(&nbrec_lb->header_.uuid));
+        return NULL;
+    }
+
+    for (size_t i = 0; i < nbrec_lb->n_health_check; i++) {
+        if (!strcmp(nbrec_lb->health_check[i]->vip, vip_port_str)) {
+            return nbrec_lb->health_check[i];
+        }
+    }
+    return NULL;
+}
+
+static void
+ovn_northd_lb_init(struct ovn_northd_lb *lb,
+                   const struct nbrec_load_balancer *nbrec_lb)
+{
+    bool template = smap_get_bool(&nbrec_lb->options, "template", false);
+    bool is_udp = nullable_string_is_equal(nbrec_lb->protocol, "udp");
+    bool is_sctp = nullable_string_is_equal(nbrec_lb->protocol, "sctp");
+    int address_family = !strcmp(smap_get_def(&nbrec_lb->options,
+                                              "address-family", "ipv4"),
+                                 "ipv4")
+                         ? AF_INET
+                         : AF_INET6;
+
+    lb->nlb = nbrec_lb;
+    lb->proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp";
+    lb->n_vips = smap_count(&nbrec_lb->vips);
+    lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips);
+    lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb);
+    smap_init(&lb->template_vips);
+    lb->controller_event = smap_get_bool(&nbrec_lb->options, "event", false);
+
+    bool routable = smap_get_bool(&nbrec_lb->options, "add_route", false);
+    lb->routable = ovn_lb_get_routable_mode(nbrec_lb, routable, template);
+
+    lb->skip_snat = smap_get_bool(&nbrec_lb->options, "skip_snat", false);
+    lb->template = template;
+
+    const char *mode = smap_get(&nbrec_lb->options, "neighbor_responder");
+    lb->neigh_mode = ovn_lb_get_neigh_mode(nbrec_lb, mode, template);
+
+    uint32_t affinity_timeout =
+        smap_get_uint(&nbrec_lb->options, "affinity_timeout", 0);
+    if (affinity_timeout > UINT16_MAX) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_WARN_RL(&rl, "max affinity_timeout timeout value is %u",
+                     UINT16_MAX);
+        affinity_timeout = UINT16_MAX;
+    }
+    lb->affinity_timeout = affinity_timeout;
+
+    sset_init(&lb->ips_v4);
+    sset_init(&lb->ips_v6);
+    struct smap_node *node;
+    size_t n_vips = 0;
+
+    SMAP_FOR_EACH (node, &nbrec_lb->vips) {
+        struct ovn_lb_vip *lb_vip = &lb->vips[n_vips];
+        struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips];
+
+        char *error = ovn_lb_vip_init(lb_vip, node->key, node->value,
+                                      template, address_family);
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+            VLOG_WARN_RL(&rl, "Failed to initialize LB VIP: %s", error);
+            ovn_lb_vip_destroy(lb_vip);
+            free(error);
+            continue;
+        }
+        lb_vip->empty_backend_rej = smap_get_bool(&nbrec_lb->options,
+                                                  "reject", false);
+        ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb,
+                               node->key, node->value, template);
+        if (lb_vip_nb->lb_health_check) {
+            lb->health_checks = true;
+        }
+
+        if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+            sset_add(&lb->ips_v4, lb_vip->vip_str);
+        } else {
+            sset_add(&lb->ips_v6, lb_vip->vip_str);
+        }
+
+        if (lb->template && address_family == AF_INET6) {
+            smap_add_nocopy(&lb->template_vips,
+                            ovn_lb_vip6_template_format_internal(lb_vip),
+                            xstrdup(node->value));
+        }
+        n_vips++;
+
+        if (lb_vip_nb->lb_health_check) {
+            ovn_lb_vip_backends_health_check_init(lb, lb_vip, lb_vip_nb);
+        }
+    }
+
+    /* It's possible that parsing VIPs fails.  Update the lb->n_vips to the
+     * correct value.
+     */
+    lb->n_vips = n_vips;
+
+    if (nbrec_lb->n_selection_fields) {
+        char *proto = NULL;
+        if (nbrec_lb->protocol && nbrec_lb->protocol[0]) {
+            proto = nbrec_lb->protocol;
+        }
+
+        struct ds sel_fields = DS_EMPTY_INITIALIZER;
+        for (size_t i = 0; i < lb->nlb->n_selection_fields; i++) {
+            char *field = lb->nlb->selection_fields[i];
+            if (!strcmp(field, "tp_src") && proto) {
+                ds_put_format(&sel_fields, "%s_src,", proto);
+            } else if (!strcmp(field, "tp_dst") && proto) {
+                ds_put_format(&sel_fields, "%s_dst,", proto);
+            } else {
+                ds_put_format(&sel_fields, "%s,", field);
+            }
+        }
+        ds_chomp(&sel_fields, ',');
+        lb->selection_fields = ds_steal_cstr(&sel_fields);
+    }
+}
+
+struct ovn_northd_lb *
+ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb)
+{
+    struct ovn_northd_lb *lb = xzalloc(sizeof *lb);
+    ovn_northd_lb_init(lb, nbrec_lb);
+    return lb;
+}
+
+struct ovn_northd_lb *
+ovn_northd_lb_find(const struct hmap *lbs, const struct uuid *uuid)
+{
+    struct ovn_northd_lb *lb;
+    size_t hash = uuid_hash(uuid);
+    HMAP_FOR_EACH_WITH_HASH (lb, hmap_node, hash, lbs) {
+        if (uuid_equals(&lb->nlb->header_.uuid, uuid)) {
+            return lb;
+        }
+    }
+    return NULL;
+}
+
+const struct smap *
+ovn_northd_lb_get_vips(const struct ovn_northd_lb *lb)
+{
+    if (!smap_is_empty(&lb->template_vips)) {
+        return &lb->template_vips;
+    }
+    return &lb->nlb->vips;
+}
+
+static void
+ovn_northd_lb_cleanup(struct ovn_northd_lb *lb)
+{
+    for (size_t i = 0; i < lb->n_vips; i++) {
+        ovn_lb_vip_destroy(&lb->vips[i]);
+        ovn_northd_lb_vip_destroy(&lb->vips_nb[i]);
+    }
+    free(lb->vips);
+    free(lb->vips_nb);
+    lb->vips = NULL;
+    lb->vips_nb = NULL;
+    smap_destroy(&lb->template_vips);
+    sset_destroy(&lb->ips_v4);
+    sset_destroy(&lb->ips_v6);
+    free(lb->selection_fields);
+    lb->selection_fields = NULL;
+    lb->health_checks = false;
+}
+
+void
+ovn_northd_lb_destroy(struct ovn_northd_lb *lb)
+{
+    ovn_northd_lb_cleanup(lb);
+    free(lb);
+}
+
+void
+ovn_northd_lb_reinit(struct ovn_northd_lb *lb,
+                     const struct nbrec_load_balancer *nbrec_lb)
+{
+    ovn_northd_lb_cleanup(lb);
+    ovn_northd_lb_init(lb, nbrec_lb);
+}
+
+static void
+ovn_lb_group_init(struct ovn_lb_group *lb_group,
+                  const struct nbrec_load_balancer_group *nbrec_lb_group,
+                  const struct hmap *lbs)
+{
+    lb_group->n_lbs = nbrec_lb_group->n_load_balancer;
+    lb_group->lbs = xmalloc(lb_group->n_lbs * sizeof *lb_group->lbs);
+    lb_group->lb_ips = ovn_lb_ip_set_create();
+
+    for (size_t i = 0; i < nbrec_lb_group->n_load_balancer; i++) {
+        const struct uuid *lb_uuid =
+            &nbrec_lb_group->load_balancer[i]->header_.uuid;
+        lb_group->lbs[i] = ovn_northd_lb_find(lbs, lb_uuid);
+        lb_group->has_routable_lb |= lb_group->lbs[i]->routable;
+    }
+}
+
+/* Constructs a new 'struct ovn_lb_group' object from the Nb LB Group record
+ * and an array of 'struct ovn_northd_lb' objects for its associated
+ * load balancers. */
+struct ovn_lb_group *
+ovn_lb_group_create(const struct nbrec_load_balancer_group *nbrec_lb_group,
+                    const struct hmap *lbs)
+{
+    struct ovn_lb_group *lb_group = xzalloc(sizeof *lb_group);
+    lb_group->uuid = nbrec_lb_group->header_.uuid;
+    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
+    return lb_group;
+}
+
+static void
+ovn_lb_group_cleanup(struct ovn_lb_group *lb_group)
+{
+    ovn_lb_ip_set_destroy(lb_group->lb_ips);
+    lb_group->lb_ips = NULL;
+    lb_group->has_routable_lb = false;
+    free(lb_group->lbs);
+}
+
+void
+ovn_lb_group_destroy(struct ovn_lb_group *lb_group)
+{
+    if (!lb_group) {
+        return;
+    }
+
+    ovn_lb_group_cleanup(lb_group);
+    free(lb_group);
+}
+
+void
+ovn_lb_group_reinit(struct ovn_lb_group *lb_group,
+                    const struct nbrec_load_balancer_group *nbrec_lb_group,
+                    const struct hmap *lbs)
+{
+    ovn_lb_group_cleanup(lb_group);
+    ovn_lb_group_init(lb_group, nbrec_lb_group, lbs);
+}
+
+struct ovn_lb_group *
+ovn_lb_group_find(const struct hmap *lb_groups, const struct uuid *uuid)
+{
+    struct ovn_lb_group *lb_group;
+    size_t hash = uuid_hash(uuid);
+
+    HMAP_FOR_EACH_WITH_HASH (lb_group, hmap_node, hash, lb_groups) {
+        if (uuid_equals(&lb_group->uuid, uuid)) {
+            return lb_group;
+        }
+    }
+    return NULL;
+}
+
+void
+build_lrouter_lb_ips(struct ovn_lb_ip_set *lb_ips,
+                     const struct ovn_northd_lb *lb)
+{
+    add_ips_to_lb_ip_set(lb_ips, lb->routable, &lb->ips_v4, &lb->ips_v6);
+}
+
+void
+add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
+                     bool is_routable,
+                     const struct sset *lb_ips_v4,
+                     const struct sset *lb_ips_v6)
+{
+    const char *ip_address;
+
+    SSET_FOR_EACH (ip_address, lb_ips_v4) {
+        sset_add(&lb_ips->ips_v4, ip_address);
+        if (is_routable) {
+            sset_add(&lb_ips->ips_v4_routable, ip_address);
+        }
+    }
+    SSET_FOR_EACH (ip_address, lb_ips_v6) {
+        sset_add(&lb_ips->ips_v6, ip_address);
+        if (is_routable) {
+            sset_add(&lb_ips->ips_v6_routable, ip_address);
+        }
+    }
+}
+
+void
+remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
+                          bool is_routable,
+                          const struct sset *lb_ips_v4,
+                          const struct sset *lb_ips_v6)
+{
+    const char *ip_address;
+
+    SSET_FOR_EACH (ip_address, lb_ips_v4) {
+        sset_find_and_delete(&lb_ips->ips_v4, ip_address);
+        if (is_routable) {
+            sset_find_and_delete(&lb_ips->ips_v4_routable, ip_address);
+        }
+    }
+    SSET_FOR_EACH (ip_address, lb_ips_v6) {
+        sset_find_and_delete(&lb_ips->ips_v6, ip_address);
+        if (is_routable) {
+            sset_find_and_delete(&lb_ips->ips_v6_routable, ip_address);
+        }
+    }
+}
+
+/* lb datapaths functions */
+struct  ovn_lb_datapaths *
+ovn_lb_datapaths_create(const struct ovn_northd_lb *lb, size_t n_ls_datapaths,
+                        size_t n_lr_datapaths)
+{
+    struct ovn_lb_datapaths *lb_dps = xzalloc(sizeof *lb_dps);
+    lb_dps->lb = lb;
+    lb_dps->nb_ls_map = bitmap_allocate(n_ls_datapaths);
+    lb_dps->nb_lr_map = bitmap_allocate(n_lr_datapaths);
+
+    return lb_dps;
+}
+
+void
+ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *lb_dps)
+{
+    bitmap_free(lb_dps->nb_lr_map);
+    bitmap_free(lb_dps->nb_ls_map);
+    free(lb_dps);
+}
+
+void
+ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *lb_dps, size_t n,
+                        struct ovn_datapath **ods)
+{
+    for (size_t i = 0; i < n; i++) {
+        if (!bitmap_is_set(lb_dps->nb_lr_map, ods[i]->index)) {
+            bitmap_set1(lb_dps->nb_lr_map, ods[i]->index);
+            lb_dps->n_nb_lr++;
+        }
+    }
+}
+
+void
+ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *lb_dps, size_t n,
+                        struct ovn_datapath **ods)
+{
+    for (size_t i = 0; i < n; i++) {
+        if (!bitmap_is_set(lb_dps->nb_ls_map, ods[i]->index)) {
+            bitmap_set1(lb_dps->nb_ls_map, ods[i]->index);
+            lb_dps->n_nb_ls++;
+        }
+    }
+}
+
+struct ovn_lb_datapaths *
+ovn_lb_datapaths_find(const struct hmap *lb_dps_map,
+                      const struct uuid *lb_uuid)
+{
+    struct ovn_lb_datapaths *lb_dps;
+    size_t hash = uuid_hash(lb_uuid);
+    HMAP_FOR_EACH_WITH_HASH (lb_dps, hmap_node, hash, lb_dps_map) {
+        if (uuid_equals(&lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
+            return lb_dps;
+        }
+    }
+    return NULL;
+}
+
+struct ovn_lb_group_datapaths *
+ovn_lb_group_datapaths_create(const struct ovn_lb_group *lb_group,
+                              size_t max_ls_datapaths,
+                              size_t max_lr_datapaths)
+{
+    struct ovn_lb_group_datapaths *lb_group_dps =
+        xzalloc(sizeof *lb_group_dps);
+    lb_group_dps->lb_group = lb_group;
+    lb_group_dps->ls = xmalloc(max_ls_datapaths * sizeof *lb_group_dps->ls);
+    lb_group_dps->lr = xmalloc(max_lr_datapaths * sizeof *lb_group_dps->lr);
+
+    return lb_group_dps;
+}
+
+void
+ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *lb_group_dps)
+{
+    free(lb_group_dps->ls);
+    free(lb_group_dps->lr);
+    free(lb_group_dps);
+}
+
+struct ovn_lb_group_datapaths *
+ovn_lb_group_datapaths_find(const struct hmap *lb_group_dps_map,
+                            const struct uuid *lb_group_uuid)
+{
+    struct ovn_lb_group_datapaths *lb_group_dps;
+    size_t hash = uuid_hash(lb_group_uuid);
+
+    HMAP_FOR_EACH_WITH_HASH (lb_group_dps, hmap_node, hash, lb_group_dps_map) {
+        if (uuid_equals(&lb_group_dps->lb_group->uuid, lb_group_uuid)) {
+            return lb_group_dps;
+        }
+    }
+    return NULL;
+}
diff --git a/northd/lb.h b/northd/lb.h
new file mode 100644
index 0000000000..00f81c3533
--- /dev/null
+++ b/northd/lb.h
@@ -0,0 +1,189 @@ 
+/*
+ * Copyright (c) 2024, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_NORTHD_LB_H
+#define OVN_NORTHD_LB_H 1
+
+#include "openvswitch/hmap.h"
+#include "uuid.h"
+
+#include "lib/lb.h"
+
+struct nbrec_load_balancer;
+struct nbrec_load_balancer_group;
+struct ovn_datapath;
+
+enum lb_neighbor_responder_mode {
+    LB_NEIGH_RESPOND_REACHABLE,
+    LB_NEIGH_RESPOND_ALL,
+    LB_NEIGH_RESPOND_NONE,
+};
+
+/* The "routable" ssets are subsets of the load balancer IPs for which IP
+ * routes and ARP resolution flows are automatically added. */
+struct ovn_lb_ip_set {
+    struct sset ips_v4;
+    struct sset ips_v4_routable;
+    struct sset ips_v4_reachable;
+    struct sset ips_v6;
+    struct sset ips_v6_routable;
+    struct sset ips_v6_reachable;
+};
+
+struct ovn_lb_ip_set *ovn_lb_ip_set_create(void);
+void ovn_lb_ip_set_destroy(struct ovn_lb_ip_set *);
+struct ovn_lb_ip_set *ovn_lb_ip_set_clone(struct ovn_lb_ip_set *);
+
+struct ovn_northd_lb {
+    struct hmap_node hmap_node;
+
+    const struct nbrec_load_balancer *nlb; /* May be NULL. */
+    const char *proto;
+    char *selection_fields;
+    struct ovn_lb_vip *vips;
+    struct ovn_northd_lb_vip *vips_nb;
+    struct smap template_vips; /* Slightly changed template VIPs, populated
+                                * if needed.  Until now it's only required
+                                * for IPv6 template load balancers. */
+    size_t n_vips;
+
+    enum lb_neighbor_responder_mode neigh_mode;
+    bool controller_event;
+    bool routable;
+    bool skip_snat;
+    bool template;
+    uint16_t affinity_timeout;
+
+    struct sset ips_v4;
+    struct sset ips_v6;
+
+    /* Indicates if the load balancer has health checks configured. */
+    bool health_checks;
+};
+
+/* ovn-northd specific backend information. */
+struct ovn_northd_lb_vip {
+    char *backend_ips;
+    struct ovn_northd_lb_backend *backends_nb;
+    size_t n_backends;
+
+    struct nbrec_load_balancer_health_check *lb_health_check;
+};
+
+struct ovn_northd_lb_backend {
+    bool health_check;
+    char *logical_port; /* Logical port to which the ip belong to. */
+    char *svc_mon_src_ip; /* Source IP to use for monitoring. */
+};
+
+struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *);
+struct ovn_northd_lb *ovn_northd_lb_find(const struct hmap *,
+                                         const struct uuid *);
+const struct smap *ovn_northd_lb_get_vips(const struct ovn_northd_lb *);
+void ovn_northd_lb_destroy(struct ovn_northd_lb *);
+void ovn_northd_lb_reinit(struct ovn_northd_lb *,
+                          const struct nbrec_load_balancer *);
+
+void build_lrouter_lb_ips(struct ovn_lb_ip_set *,
+                          const struct ovn_northd_lb *);
+void add_ips_to_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
+                          bool is_routable,
+                          const struct sset *lb_ips_v4,
+                          const struct sset *lb_ips_v6);
+void remove_ips_from_lb_ip_set(struct ovn_lb_ip_set *lb_ips,
+                               bool is_routable,
+                               const struct sset *lb_ips_v4,
+                               const struct sset *lb_ips_v6);
+
+struct ovn_lb_group {
+    struct hmap_node hmap_node;
+    struct uuid uuid;
+    size_t n_lbs;
+    struct ovn_northd_lb **lbs;
+    struct ovn_lb_ip_set *lb_ips;
+    bool has_routable_lb;
+};
+
+struct ovn_lb_group *ovn_lb_group_create(
+    const struct nbrec_load_balancer_group *,
+    const struct hmap *lbs);
+void ovn_lb_group_destroy(struct ovn_lb_group *lb_group);
+struct ovn_lb_group *ovn_lb_group_find(const struct hmap *lb_groups,
+                                       const struct uuid *);
+void ovn_lb_group_reinit(
+    struct ovn_lb_group *lb_group,
+    const struct nbrec_load_balancer_group *,
+    const struct hmap *lbs);
+
+struct ovn_lb_datapaths {
+    struct hmap_node hmap_node;
+
+    const struct ovn_northd_lb *lb;
+    size_t n_nb_ls;
+    unsigned long *nb_ls_map;
+
+    size_t n_nb_lr;
+    unsigned long *nb_lr_map;
+};
+
+struct ovn_lb_datapaths *ovn_lb_datapaths_create(const struct ovn_northd_lb *,
+                                                 size_t n_ls_datapaths,
+                                                 size_t n_lr_datapaths);
+struct ovn_lb_datapaths *ovn_lb_datapaths_find(const struct hmap *,
+                                               const struct uuid *);
+void ovn_lb_datapaths_destroy(struct ovn_lb_datapaths *);
+
+void ovn_lb_datapaths_add_lr(struct ovn_lb_datapaths *, size_t n,
+                             struct ovn_datapath **);
+void ovn_lb_datapaths_add_ls(struct ovn_lb_datapaths *, size_t n,
+                             struct ovn_datapath **);
+
+struct ovn_lb_group_datapaths {
+    struct hmap_node hmap_node;
+
+    const struct ovn_lb_group *lb_group;
+
+    /* Datapaths to which 'lb_group' is applied. */
+    size_t n_ls;
+    struct ovn_datapath **ls;
+    size_t n_lr;
+    struct ovn_datapath **lr;
+};
+
+struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_create(
+    const struct ovn_lb_group *, size_t max_ls_datapaths,
+    size_t max_lr_datapaths);
+
+void ovn_lb_group_datapaths_destroy(struct ovn_lb_group_datapaths *);
+struct ovn_lb_group_datapaths *ovn_lb_group_datapaths_find(
+    const struct hmap *lb_group_dps, const struct uuid *);
+
+static inline void
+ovn_lb_group_datapaths_add_ls(struct ovn_lb_group_datapaths *lbg_dps, size_t n,
+                               struct ovn_datapath **ods)
+{
+    memcpy(&lbg_dps->ls[lbg_dps->n_ls], ods, n * sizeof *ods);
+    lbg_dps->n_ls += n;
+}
+
+static inline void
+ovn_lb_group_datapaths_add_lr(struct ovn_lb_group_datapaths *lbg_dps,
+                               struct ovn_datapath *lr)
+{
+    lbg_dps->lr[lbg_dps->n_lr++] = lr;
+}
+
+#endif /* OVN_NORTHD_LB_H */
diff --git a/northd/northd.c b/northd/northd.c
index cb53ec9716..00899e3d1a 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -31,6 +31,7 @@ 
 #include "openvswitch/hmap.h"
 #include "openvswitch/json.h"
 #include "ovn/lex.h"
+#include "lb.h"
 #include "lib/chassis-index.h"
 #include "lib/ip-mcast-index.h"
 #include "lib/static-mac-binding-index.h"