diff mbox series

[ovs-dev,v7,4/4] controller: Extract garp_rarp to engine node.

Message ID 823ecbce2df827c8a362883d3ba69c73f50fce84.1742465192.git.felix.huettner@stackit.cloud
State Changes Requested
Headers show
Series controller: Add garp_rarp engine. | expand

Checks

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

Commit Message

Felix Huettner March 20, 2025, noon UTC
Previously on each call to pinctrl_run all addresses that need to be
announced via garps or rarps where computed newly. On larger external
networks that might take significant time.
However the content of the data can only change in a run of the I+P
engine. So we move the whole logic to an engine node and only handle the
minimal needed set in pinctrl_run.

Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
Co-Authored-by: Max Lamprecht <max.lamprecht@mail.schwarz>
Signed-off-by: Max Lamprecht <max.lamprecht@mail.schwarz>
Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
---
v6->v7: added ack

 controller/automake.mk      |   4 +-
 controller/garp_rarp.c      | 626 ++++++++++++++++++++++++++++++++++++
 controller/garp_rarp.h      |  61 ++++
 controller/ovn-controller.c | 243 +++++++++++++-
 controller/pinctrl.c        | 559 ++------------------------------
 controller/pinctrl.h        |  16 +-
 6 files changed, 967 insertions(+), 542 deletions(-)
 create mode 100644 controller/garp_rarp.c
 create mode 100644 controller/garp_rarp.h

Comments

Ales Musil April 3, 2025, 11:25 a.m. UTC | #1
On Thu, Mar 20, 2025 at 1:02 PM Felix Huettner via dev <
ovs-dev@openvswitch.org> wrote:

> Previously on each call to pinctrl_run all addresses that need to be
> announced via garps or rarps where computed newly. On larger external
> networks that might take significant time.
> However the content of the data can only change in a run of the I+P
> engine. So we move the whole logic to an engine node and only handle the
> minimal needed set in pinctrl_run.
>
> Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> Co-Authored-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> Signed-off-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
> ---
>

Hi Felix,

thank you for the patch. I have a few comments down below.

v6->v7: added ack
>
>  controller/automake.mk      |   4 +-
>  controller/garp_rarp.c      | 626 ++++++++++++++++++++++++++++++++++++
>  controller/garp_rarp.h      |  61 ++++
>  controller/ovn-controller.c | 243 +++++++++++++-
>  controller/pinctrl.c        | 559 ++------------------------------
>  controller/pinctrl.h        |  16 +-
>  6 files changed, 967 insertions(+), 542 deletions(-)
>  create mode 100644 controller/garp_rarp.c
>  create mode 100644 controller/garp_rarp.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index 067c2e6e4..f0638ea97 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -59,7 +59,9 @@ controller_ovn_controller_SOURCES = \
>         controller/route-exchange.h \
>         controller/route-table-notify.h \
>         controller/route.h \
> -       controller/route.c
> +       controller/route.c \
> +       controller/garp_rarp.h \
> +       controller/garp_rarp.c
>
>  if HAVE_NETLINK
>  controller_ovn_controller_SOURCES += \
> diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
> new file mode 100644
> index 000000000..916ff896a
> --- /dev/null
> +++ b/controller/garp_rarp.c
> @@ -0,0 +1,626 @@
> +/*
> + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include <net/if.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +
> +#include "controller/encaps.h"
> +#include "controller/local_data.h"
> +#include "controller/lport.h"
> +#include "hash.h"
> +#include "mac-binding-index.h"
> +#include "openvswitch/hmap.h"
> +#include "openvswitch/vlog.h"
> +#include "ovn/lex.h"
> +#include "packets.h"
> +#include "smap.h"
> +#include "sset.h"
> +#include "openvswitch/shash.h"
> +#include "garp_rarp.h"
> +#include "ovn-sb-idl.h"
> +#include "vswitch-idl.h"
> +
> +VLOG_DEFINE_THIS_MODULE(garp_rarp);
> +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> +
> +/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> ports. */
> +static void
> +get_localnet_vifs_l3gwports(
> +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +    const struct ovsrec_bridge *br_int,
> +    const struct sbrec_chassis *chassis,
> +    const struct hmap *local_datapaths,
> +    struct sset *localnet_vifs,
> +    struct sset *local_l3gw_ports)
> +{
> +    for (int i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *port_rec = br_int->ports[i];
> +        if (!strcmp(port_rec->name, br_int->name)) {
> +            continue;
> +        }
> +        const char *tunnel_id = smap_get(&port_rec->external_ids,
> +                                          "ovn-chassis-id");
> +        if (tunnel_id &&
> +                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> NULL)) {
> +            continue;
> +        }
> +        const char *localnet = smap_get(&port_rec->external_ids,
> +                                        "ovn-localnet-port");
> +        if (localnet) {
> +            continue;
> +        }
> +        for (int j = 0; j < port_rec->n_interfaces; j++) {
> +            const struct ovsrec_interface *iface_rec =
> port_rec->interfaces[j];
> +            if (!iface_rec->n_ofport) {
> +                continue;
> +            }
> +            /* Get localnet vif. */
> +            const char *iface_id = smap_get(&iface_rec->external_ids,
> +                                            "iface-id");
> +            if (!iface_id) {
> +                continue;
> +            }
> +            const struct sbrec_port_binding *pb
> +                = lport_lookup_by_name(sbrec_port_binding_by_name,
> iface_id);
> +            if (!pb || pb->chassis != chassis) {
> +                continue;
> +            }
> +            if (!iface_rec->link_state ||
> +                    strcmp(iface_rec->link_state, "up")) {
> +                continue;
> +            }
> +            struct local_datapath *ld
> +                = get_local_datapath(local_datapaths,
> +                                     pb->datapath->tunnel_key);
> +            if (ld && ld->localnet_port) {
> +                sset_add(localnet_vifs, iface_id);
> +            }
> +        }
> +    }
>

I know that this is old code but it makes me wonder if there's a better
way to find local vifs that are connected to localnet port. I think
it should be doable through bindings.

+
> +    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
> +        sbrec_port_binding_by_datapath);
> +
> +    const struct local_datapath *ld;
> +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> +        const struct sbrec_port_binding *pb;
> +
> +        if (!ld->localnet_port) {
> +            continue;
> +        }
> +
> +        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
> +         * that connect to gateway routers (if local), and consider port
> +         * bindings of type "patch" since they might connect to
> +         * distributed gateway ports with NAT addresses. */
> +
> +        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> +        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> +
>  sbrec_port_binding_by_datapath) {
> +            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
> +                || !strcmp(pb->type, "patch")) {
> +                sset_add(local_l3gw_ports, pb->logical_port);
> +            }
> +        }
> +    }
> +    sbrec_port_binding_index_destroy_row(target);
> +}
> +
> +
> +/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> + * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> + * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
> + * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
> + * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> + *
> + * Returns true if at least 'MAC' is found in 'address', false otherwise.
> + *
> + * The caller must call destroy_lport_addresses() and free(*lport). */
> +static bool
> +extract_addresses_with_port(const char *addresses,
> +                            struct lport_addresses *laddrs,
> +                            char **lport)
> +{
> +    int ofs;
> +    if (!extract_addresses(addresses, laddrs, &ofs)) {
> +        return false;
> +    } else if (!addresses[ofs]) {
> +        return true;
> +    }
> +
> +    struct lexer lexer;
> +    lexer_init(&lexer, addresses + ofs);
> +    lexer_get(&lexer);
> +
> +    if (lexer.error || lexer.token.type != LEX_T_ID
> +        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> +        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
> +        lexer_destroy(&lexer);
> +        return true;
> +    }
> +
> +    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> +        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> +                          "'is_chassis_resident' in address '%s'",
> addresses);
> +        lexer_destroy(&lexer);
> +        return false;
> +    }
> +
> +    if (lexer.token.type != LEX_T_STRING) {
> +        VLOG_INFO_RL(&rl,
> +                    "Syntax error: expecting quoted string after "
> +                    "'is_chassis_resident' in address '%s'", addresses);
> +        lexer_destroy(&lexer);
> +        return false;
> +    }
> +
> +    *lport = xstrdup(lexer.token.s);
> +
> +    lexer_get(&lexer);
> +    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> +        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> string in "
> +                          "'is_chassis_resident()' in address '%s'",
> +                          addresses);
> +        lexer_destroy(&lexer);
> +        return false;
> +    }
> +
> +    lexer_destroy(&lexer);
> +    return true;
> +}
> +
> +static void
> +consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
> +                     const char *nat_address,
> +                     const struct sbrec_port_binding *pb,
> +                     struct sset *nat_address_keys,
> +                     const struct sbrec_chassis *chassis,
> +                     const struct sset *active_tunnels,
> +                     struct shash *nat_addresses,
> +                     struct sset *non_local_lports,
> +                     struct sset *local_lports)
> +{
> +    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> +    char *lport = NULL;
> +    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> +        || (!lport && !strcmp(pb->type, "patch"))) {
> +        destroy_lport_addresses(laddrs);
> +        free(laddrs);
> +        free(lport);
> +        return;
> +    }
> +    if (lport) {
> +        if (!lport_is_chassis_resident(sbrec_port_binding_by_name,
> +                chassis, active_tunnels, lport)) {
> +            sset_add(non_local_lports, lport);
> +            destroy_lport_addresses(laddrs);
> +            free(laddrs);
> +            free(lport);
> +            return;
> +        } else {
> +            sset_add(local_lports, lport);
> +        }
> +    }
> +    free(lport);
> +
> +    int i;
> +    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> +        char *name = xasprintf("%s-%s", pb->logical_port,
> +                                        laddrs->ipv4_addrs[i].addr_s);
> +        sset_add(nat_address_keys, name);
> +        free(name);
> +    }
> +    if (laddrs->n_ipv4_addrs == 0) {
> +        char *name = xasprintf("%s-noip", pb->logical_port);
> +        sset_add(nat_address_keys, name);
> +        free(name);
> +    }
> +    shash_add(nat_addresses, pb->logical_port, laddrs);
> +}
> +
> +static void
> +get_nat_addresses_and_keys(struct ovsdb_idl_index
> *sbrec_port_binding_by_name,
> +                           struct sset *nat_address_keys,
> +                           struct sset *local_l3gw_ports,
> +                           const struct sbrec_chassis *chassis,
> +                           const struct sset *active_tunnels,
> +                           struct shash *nat_addresses,
> +                           struct sset *non_local_lports,
> +                           struct sset *local_lports)
> +{
> +    const char *gw_port;
> +    SSET_FOR_EACH (gw_port, local_l3gw_ports) {
> +        const struct sbrec_port_binding *pb;
> +
> +        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> +        if (!pb) {
> +            continue;
> +        }
> +
> +        if (pb->n_nat_addresses) {
> +            for (int i = 0; i < pb->n_nat_addresses; i++) {
> +                consider_nat_address(sbrec_port_binding_by_name,
> +                                     pb->nat_addresses[i], pb,
> +                                     nat_address_keys, chassis,
> +                                     active_tunnels,
> +                                     nat_addresses,
> +                                     non_local_lports,
> +                                     local_lports);
> +            }
> +        } else {
> +            /* Continue to support options:nat-addresses for version
> +             * upgrade. */
> +            const char *nat_addresses_options = smap_get(&pb->options,
> +                                                         "nat-addresses");
> +            if (nat_addresses_options) {
> +                consider_nat_address(sbrec_port_binding_by_name,
> +                                     nat_addresses_options, pb,
> +                                     nat_address_keys, chassis,
> +                                     active_tunnels,
> +                                     nat_addresses,
> +                                     non_local_lports,
> +                                     local_lports);
> +            }
> +        }
> +    }
> +}
> +
> +static uint32_t
> +garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
> +                    uint32_t port_key)
> +{
> +    return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
> +}
> +
> +static uint32_t
> +garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
> +{
> +    return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
> +}
> +
> +static void
> +garp_rarp_node_add(struct hmap *map, const struct eth_addr ea, ovs_be32
> ip,
> +                   uint32_t dp_key, uint32_t port_key)
> +{
> +    struct garp_rarp_node *grn = xmalloc(sizeof *grn);
> +    grn->ea = ea;
> +    grn->ipv4 = ip;
> +    grn->announce_time = time_msec() + 1000;
> +    grn->backoff = 1000; /* msec. */
> +    grn->dp_key = dp_key;
> +    grn->port_key = port_key;
> +    hmap_insert(map, &grn->hmap_node, garp_rarp_node_hash_struct(grn));
> +}
> +
> +/* Searches for a given garp_rarp_node in a hmap. Ignores the
> announce_time
> + * and backoff field since they might be different based on runtime. */
> +static struct garp_rarp_node*
> +garp_rarp_lookup(const struct hmap *map, const struct garp_rarp_node
> *search)
> +{
> +    struct garp_rarp_node *n;
> +    uint32_t hash = garp_rarp_node_hash_struct(search);
> +    HMAP_FOR_EACH_WITH_HASH (n, hmap_node, hash, map) {
> +        if (!eth_addr_equals(search->ea, n->ea)) {
> +            continue;
> +        }
> +
> +        if (search->ipv4 != n->ipv4) {
> +            continue;
> +        }
> +
> +        if (search->dp_key != n->dp_key) {
> +            continue;
> +        }
> +
> +        if (search->port_key != n->port_key) {
> +            continue;
> +        }
> +
> +        return n;
> +    }
> +    return NULL;
> +}
> +
> +/* Update or add an IP-MAC binding for 'logical_port'.
> + * Caller should make sure that 'ovnsb_idl_txn' is valid. */
> +static void
> +mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
> +                      struct ovsdb_idl_index
> *sbrec_mac_binding_by_lport_ip,
> +                      const char *logical_port,
> +                      const struct sbrec_datapath_binding *dp,
> +                      struct eth_addr ea, const char *ip,
> +                      bool update_only)
> +{
> +    /* Convert ethernet argument to string form for database. */
> +    char mac_string[ETH_ADDR_STRLEN + 1];
> +    snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT,
> ETH_ADDR_ARGS(ea));
> +
> +    const struct sbrec_mac_binding *b =
> +            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
> +                               logical_port, ip);
> +    if (!b) {
> +        if (update_only) {
> +            return;
> +        }
> +        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
> +        sbrec_mac_binding_set_logical_port(b, logical_port);
> +        sbrec_mac_binding_set_ip(b, ip);
> +        sbrec_mac_binding_set_datapath(b, dp);
> +    }
> +
> +    if (strcmp(b->mac, mac_string)) {
> +        sbrec_mac_binding_set_mac(b, mac_string);
> +
> +        /* For backward compatibility check if timestamp column is
> available
> +         * in SB DB. */
> +        if (sbrec_server_has_mac_binding_table_col_timestamp(
> +                ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
> +            VLOG_DBG("Setting MAC binding timestamp for "
> +                     "ip:%s mac:%s port:%s to %lld",
> +                     b->ip, b->mac, logical_port, time_wall_msec());
> +            sbrec_mac_binding_set_timestamp(b, time_wall_msec());
> +        }
> +    }
> +}
>

Unless I've missed something this is duplicated from pinctrl.c, how about
moving
this function to a module where it could be used by both?


> +
> +/* Simulate the effect of a GARP on local datapaths, i.e., create
> MAC_Bindings
> + * on peer router datapaths.
> + */
> +static void
> +send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
> +                  const struct sbrec_port_binding *in_pb,
> +                  struct eth_addr ea, ovs_be32 ip)
> +{
> +    if (!r_ctx_in->ovnsb_idl_txn) {
> +        return;
> +    }
> +
> +    const struct local_datapath *ldp =
> +        get_local_datapath(r_ctx_in->local_datapaths,
> +                           in_pb->datapath->tunnel_key);
> +
> +    ovs_assert(ldp);
> +    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> +        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
> +        const struct sbrec_port_binding *remote =
> ldp->peer_ports[i].remote;
> +
> +        /* Skip "ingress" port. */
> +        if (local == in_pb) {
> +            continue;
> +        }
> +
> +        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
> +                                          "always_learn_from_arp_request",
> +                                          true);
> +
> +        struct ds ip_s = DS_EMPTY_INITIALIZER;
> +
> +        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> +        mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
> +                              r_ctx_in->sbrec_mac_binding_by_lport_ip,
> +                              remote->logical_port, remote->datapath,
> +                              ea, ds_cstr(&ip_s), update_only);
> +        ds_destroy(&ip_s);
> +    }
> +}
> +
> +/* Add or update a vif for which GARPs need to be announced. */
> +static void
> +send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
> +                      const struct sbrec_port_binding *binding_rec,
> +                      struct shash *nat_addresses,
> +                      struct hmap *garp_rarp_data)
> +{
> +    /* Skip localports as they don't need to be announced */
> +    if (!strcmp(binding_rec->type, "localport")) {
> +        return;
> +    }
> +
> +    /* Update GARP for NAT IP if it exists.  Consider port bindings with
> type
> +     * "l3gateway" for logical switch ports attached to gateway routers,
> and
> +     * port bindings with type "patch" for logical switch ports attached
> to
> +     * distributed gateway ports. */
> +    if (!strcmp(binding_rec->type, "l3gateway")
> +        || !strcmp(binding_rec->type, "patch")) {
> +        struct lport_addresses *laddrs = NULL;
> +        while ((laddrs = shash_find_and_delete(nat_addresses,
> +
>  binding_rec->logical_port))) {
> +            int i;
> +            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> +                              laddrs->ipv4_addrs[i].addr,
> +                              binding_rec->datapath->tunnel_key,
> +                              binding_rec->tunnel_key);
> +                send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
> +                                  laddrs->ipv4_addrs[i].addr);
> +            }
> +            /*
> +             * Send RARPs even if we do not have a ipv4 address as it e.g.
> +             * happens on ipv6 only ports.
> +             */
> +            if (laddrs->n_ipv4_addrs == 0) {
> +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> +                              0, binding_rec->datapath->tunnel_key,
> +                              binding_rec->tunnel_key);
> +            }
> +            destroy_lport_addresses(laddrs);
> +            free(laddrs);
> +        }
> +        return;
> +    }
> +
> +    /* Add GARP for new vif. */
> +    int i;
> +    for (i = 0; i < binding_rec->n_mac; i++) {
> +        struct lport_addresses laddrs;
> +        ovs_be32 ip = 0;
> +        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> +            continue;
> +        }
> +
> +        if (laddrs.n_ipv4_addrs) {
> +            ip = laddrs.ipv4_addrs[0].addr;
> +        }
> +
> +        garp_rarp_node_add(garp_rarp_data,
> +                      laddrs.ea, ip,
> +                      binding_rec->datapath->tunnel_key,
> +                      binding_rec->tunnel_key);
> +        if (ip) {
> +            send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
> +        }
> +
> +        destroy_lport_addresses(&laddrs);
> +        break;
> +    }
> +}
> +
> +static struct garp_rarp_node*
> +garp_rarp_node_clone(const struct garp_rarp_node *src)
> +{
> +    struct garp_rarp_node *dst = xzalloc(sizeof *dst);
> +    dst->ea = src->ea;
> +    dst->ipv4 = src->ipv4;
> +    dst->announce_time = time_msec() + 1000;
> +    dst->backoff = 1000;
> +    dst->dp_key = src->dp_key;
> +    dst->port_key = src->port_key;
> +    return dst;
> +}
> +
> +static void
> +garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in,
> +                struct garp_rarp_ctx_out *r_ctx_out)
> +{
> +    struct garp_rarp_node *grn;
> +    HMAP_FOR_EACH_POP (grn, hmap_node, r_ctx_out->garp_rarp_data) {
> +        garp_rarp_node_free(grn);
> +    }
> +    hmap_clear(r_ctx_out->garp_rarp_data);
> +    sset_clear(r_ctx_in->non_local_lports);
> +    sset_clear(r_ctx_in->local_lports);
> +}
> +
> +void
> +garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in,
> +              struct garp_rarp_ctx_out *r_ctx_out)
> +{
> +    garp_rarp_clear(r_ctx_in, r_ctx_out);
> +
> +    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> +    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
> +    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> +    struct shash nat_addresses;
> +
> +    shash_init(&nat_addresses);
> +
> +    get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
> +                                r_ctx_in->sbrec_port_binding_by_name,
> +                                r_ctx_in->br_int, r_ctx_in->chassis,
> +                                r_ctx_in->local_datapaths,
> +                                &localnet_vifs, &local_l3gw_ports);
> +
> +    get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
> +                               &nat_ip_keys, &local_l3gw_ports,
> +                               r_ctx_in->chassis,
> r_ctx_in->active_tunnels,
> +                               &nat_addresses, r_ctx_in->non_local_lports,
> +                               r_ctx_in->local_lports);
> +
> +    /* Update send_garp_rarp_data. */
> +    const char *iface_id;
> +    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> +            r_ctx_in->sbrec_port_binding_by_name, iface_id);
> +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> false)) {
> +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> +                                  r_ctx_out->garp_rarp_data);
> +        }
> +    }
> +
> +    /* Update send_garp_rarp_data for nat-addresses. */
> +    const char *gw_port;
> +    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> +            r_ctx_in->sbrec_port_binding_by_name, gw_port);
> +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> false)) {
> +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> +                                  r_ctx_out->garp_rarp_data);
> +        }
> +    }
> +
> +    sset_destroy(&localnet_vifs);
> +    sset_destroy(&local_l3gw_ports);
> +
> +    struct shash_node *iter;
> +    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> +        struct lport_addresses *laddrs = iter->data;
> +        destroy_lport_addresses(laddrs);
> +        shash_delete(&nat_addresses, iter);
> +        free(laddrs);
> +    }
> +    shash_destroy(&nat_addresses);
> +
> +    sset_destroy(&nat_ip_keys);
> +}
> +
> +/* Syncs a hmap of garp_rarp_node to another hmap. The elements in the
> + * destination hmap are initialized with valid announce times based on the
> + * current timestamp.
> + * If source is NULL then destination will at most change the timer if
> + * reset_timers is set.
> + * If reset_timers is true then the timers of all entries in the
> destination
> + * hmap will be reset to default values.
> + * Returns true if the destination hmap was chnaged. */
> +bool
> +garp_rarp_sync(const struct hmap *source, struct hmap *dest, bool
> reset_timers)
> +{
> +    struct garp_rarp_node *grn;
> +    bool changed = false;
> +
> +    HMAP_FOR_EACH_SAFE (grn, hmap_node, dest) {
> +        if (source && !garp_rarp_lookup(source, grn)) {
> +            hmap_remove(dest, &grn->hmap_node);
> +            garp_rarp_node_free(grn);
> +            changed = true;
> +        } else if (reset_timers) {
> +            grn->announce_time = time_msec() + 1000;
> +            grn->backoff = 1000;
> +            changed = true;
> +        }
> +    }
> +
> +    if (source) {
> +        HMAP_FOR_EACH (grn, hmap_node, source) {
> +            if (!garp_rarp_lookup(dest, grn)) {
> +                uint32_t hash = garp_rarp_node_hash_struct(grn);
> +                hmap_insert(dest, &garp_rarp_node_clone(grn)->hmap_node,
> hash);
> +                changed = true;
> +            }
> +        }
> +    }
> +
> +    return changed;
> +}
>

That's a lot of copying between the hmaps, why can't we indicate that the
announcement was sent for the last time and use just the status instead
of copying parts of the hmap?


> +
> +void
> +garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
> +{
> +    free(garp_rarp);
> +}
> diff --git a/controller/garp_rarp.h b/controller/garp_rarp.h
> new file mode 100644
> index 000000000..b85e75a7c
> --- /dev/null
> +++ b/controller/garp_rarp.h
> @@ -0,0 +1,61 @@
> +/*
> + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> + *
> + * 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 GARP_RARP_H
> +#define GARP_RARP_H 1
> +
> +#include "openvswitch/hmap.h"
> +#include "openvswitch/types.h"
> +
> +/* Contains a single mac and ip address that should be announced. */
> +struct garp_rarp_node {
> +    struct hmap_node hmap_node;
> +    struct eth_addr ea;          /* Ethernet address of port. */
> +    ovs_be32 ipv4;               /* Ipv4 address of port. */
> +    long long int announce_time; /* Next announcement in ms.
> +                                  * If LLONG_MAX there should be no
> +                                  * annoucement. */
> +    int backoff;                 /* Backoff timeout for the next
> +                                  * announcement (in msecs). */
> +    uint32_t dp_key;             /* Datapath used to output this GARP. */
> +    uint32_t port_key;           /* Port to inject the GARP into. */
> +};
> +
> +struct garp_rarp_ctx_in {
> +    struct ovsdb_idl_txn *ovnsb_idl_txn;
> +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name;
> +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
> +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
> +    const struct ovsrec_bridge *br_int;
> +    const struct sbrec_chassis *chassis;
> +    const struct hmap *local_datapaths;
> +    const struct sset *active_tunnels;
> +    struct sset *non_local_lports;
> +    struct sset *local_lports;
> +};
> +
> +struct garp_rarp_ctx_out {
> +    /* Contains struct garp_rarp_node. */
> +    struct hmap *garp_rarp_data;
> +};
> +
> +void garp_rarp_run(struct garp_rarp_ctx_in *, struct garp_rarp_ctx_out *);
> +bool garp_rarp_sync(const struct hmap *source, struct hmap *dest,
> +                    bool reset_timers);
> +void garp_rarp_node_free(struct garp_rarp_node *);
> +
> +#endif /* GARP_RARP_H */
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index cfed5282c..6f1b27876 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -92,6 +92,7 @@
>  #include "route.h"
>  #include "route-exchange.h"
>  #include "route-table-notify.h"
> +#include "garp_rarp.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -4952,6 +4953,14 @@ controller_output_route_exchange_handler(struct
> engine_node *node,
>      return true;
>  }
>
> +static bool
> +controller_output_garp_rarp_handler(struct engine_node *node,
> +                                    void *data OVS_UNUSED)
> +{
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
>  /* Handles sbrec_chassis changes.
>   * If a new chassis is added or removed return false, so that
>   * flows are recomputed.  For any updates, there is no need for
> @@ -5305,6 +5314,216 @@ en_route_table_notify_cleanup(void *data
> OVS_UNUSED)
>  {
>  }
>
> +struct ed_type_garp_rarp {
> +    struct hmap garp_rarp_data;
> +
> +    /* non_local_lports and local_lports are used in the incremental
> handlers
> +     * to trigger updates if such a port changes. */
> +    struct sset non_local_lports; /* lports that we did not consider
> because
> +                                     they where not local. */
> +    struct sset local_lports; /* lports where we did consider the
> addresses
> +                                 because they where local. */
> +};
> +
> +static void
> +en_garp_rarp_run(struct engine_node *node, void *data_)
> +{
> +    struct ed_type_garp_rarp *data = data_;
> +
> +    const struct ovsrec_open_vswitch_table *ovs_table =
> +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    ovs_assert(chassis_id);
> +
> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_chassis", node),
> +                "name");
> +    const struct sbrec_chassis *chassis
> +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> +    ovs_assert(chassis);
> +
> +    const struct ovsrec_bridge_table *bridge_table =
> +        EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> ovs_table);
> +
> +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_port_binding", node),
> +                "datapath");
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_port_binding", node),
> +                "name");
> +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_mac_binding", node),
> +                "lport_ip");
> +
> +    struct ovsdb_idl_txn *ovnsb_idl_txn =
> engine_get_context()->ovnsb_idl_txn;
> +
> +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
> +
> sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
> +
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    struct garp_rarp_ctx_in r_ctx_in = {
> +        .ovnsb_idl_txn = ovnsb_idl_txn,
> +        .sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath,
> +        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> +        .sbrec_mac_binding_by_lport_ip = sbrec_mac_binding_by_lport_ip,
> +        .ecmp_nh_table = ecmp_nh_table,
> +        .br_int = br_int,
> +        .chassis = chassis,
> +        .active_tunnels = &rt_data->active_tunnels,
> +        .local_datapaths = &rt_data->local_datapaths,
> +        .non_local_lports = &data->non_local_lports,
> +        .local_lports = &data->local_lports,
> +    };
> +
> +    struct garp_rarp_ctx_out r_ctx_out = {
> +        .garp_rarp_data = &data->garp_rarp_data,
> +    };
> +
> +    garp_rarp_run(&r_ctx_in, &r_ctx_out);
> +}
> +
> +
> +static void *
> +en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
> +                  struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
> +    hmap_init(&gr->garp_rarp_data);
> +    sset_init(&gr->non_local_lports);
> +    sset_init(&gr->local_lports);
> +    return gr;
> +}
> +
> +static void
> +en_garp_rarp_cleanup(void *data_)
> +{
> +    struct ed_type_garp_rarp *data = data_;
> +    struct garp_rarp_node *grn;
> +    HMAP_FOR_EACH_POP (grn, hmap_node, &data->garp_rarp_data) {
> +        garp_rarp_node_free(grn);
> +    }
> +    hmap_destroy(&data->garp_rarp_data);
> +    sset_destroy(&data->non_local_lports);
> +    sset_destroy(&data->local_lports);
> +}
> +
> +static bool
> +garp_rarp_sb_port_binding_handler(struct engine_node *node,
> +                                  void *data_)
> +{
> +    /* We need to handle a change if there was change on a datapath with
> +     * a localnet port.
> +     * Also the ha_chassis status of a port binding might change. */
> +    struct ed_type_garp_rarp *data = data_;
> +
> +    const struct ovsrec_open_vswitch_table *ovs_table =
> +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    ovs_assert(chassis_id);
> +
> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_chassis", node),
> +                "name");
> +    const struct sbrec_chassis *chassis
> +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> +    ovs_assert(chassis);
> +
> +    struct ed_type_runtime_data *rt_data =
> +            engine_get_input_data("runtime_data", node);
> +    const struct sbrec_port_binding_table *port_binding_table =
> +        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
> +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_port_binding", node),
> +                "name");
> +
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, port_binding_table) {
> +        struct local_datapath *ld = get_local_datapath(
> +            &rt_data->local_datapaths, pb->datapath->tunnel_key);
> +
> +        if (!ld || ld->localnet_port) {
> +            /* XXX: actually handle this incrementally. */
> +            return false;
> +        }
> +
> +        if (sset_contains(&data->non_local_lports, pb->logical_port) &&
> +            lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
> +                                      &rt_data->active_tunnels,
> +                                      pb->logical_port)) {
> +            /* XXX: actually handle this incrementally. */
> +            return false;
> +        }
> +
> +        if (sset_contains(&data->local_lports, pb->logical_port) &&
> +            !lport_is_chassis_resident(sbrec_port_binding_by_name,
> chassis,
> +                                       &rt_data->active_tunnels,
> +                                       pb->logical_port)) {
> +            /* XXX: actually handle this incrementally. */
> +            return false;
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +static bool
> +garp_rarp_runtime_data_handler(struct engine_node *node, void *data
> OVS_UNUSED)
> +{
> +    /* We use two elements from rt_data:
> +     * 1. active_tunnels: There is currently not incremental processing
> for
> +     *    this in runtime_data. So we just fall back to a recompute.
> +     * 2. local_datapaths: This has incremental processing on the
> runtime_data
> +     *    side. We are only interested in datapaths with a localnet port
> so
> +     *    we just recompute if there is one in there. Otherwise the
> change is
> +     *    irrelevant for us. */
> +
> +    struct ed_type_runtime_data *rt_data =
> +            engine_get_input_data("runtime_data", node);
> +
> +    /* There are no tracked data. Fall back to full recompute. */
> +    if (!rt_data->tracked) {
> +        return false;
> +    }
> +
> +    struct tracked_datapath *tdp;
> +    HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
> +        if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
> +            /* This is currently not handled incrementally in runtime_data
> +             * so it should never happen. Recompute just in case. */
> +            return false;
> +        }
> +
> +        struct local_datapath *ld = get_local_datapath(
> +            &rt_data->local_datapaths, tdp->dp->tunnel_key);
> +
> +        if (!ld || ld->localnet_port) {
> +            /* XXX: actually handle this incrementally. */
> +            return false;
> +        }
> +
> +        /* The localnet port might also have been removed. */
> +        struct tracked_lport *tlp;
> +        struct shash_node *sn;
> +        SHASH_FOR_EACH (sn, &tdp->lports) {
> +            tlp = sn->data;
> +            if (!strcmp(tlp->pb->type, "localnet")) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
>  /* Returns false if the northd internal version stored in SB_Global
>   * and ovn-controller internal version don't match.
>   */
> @@ -5614,6 +5833,7 @@ main(int argc, char *argv[])
>      ENGINE_NODE(route, "route");
>      ENGINE_NODE(route_table_notify, "route_table_notify");
>      ENGINE_NODE(route_exchange, "route_exchange");
> +    ENGINE_NODE(garp_rarp, "garp_rarp");
>
>  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>      SB_NODES
> @@ -5826,6 +6046,15 @@ main(int argc, char *argv[])
>      engine_add_input(&en_dns_cache, &en_sb_dns,
>                       dns_cache_sb_dns_handler);
>
> +    engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
> +    engine_add_input(&en_garp_rarp, &en_ovs_bridge, NULL);
> +    engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
> +    engine_add_input(&en_garp_rarp, &en_sb_port_binding,
> +                     garp_rarp_sb_port_binding_handler);
> +    engine_add_input(&en_garp_rarp, &en_sb_mac_binding, NULL);
>

This dependency leads to recompute every time we insert MB locally.
It seems that we need this only for index, so it should be noop handler.


> +    engine_add_input(&en_garp_rarp, &en_runtime_data,
> +                     garp_rarp_runtime_data_handler);
> +
>      engine_add_input(&en_controller_output, &en_dns_cache,
>                       NULL);
>      engine_add_input(&en_controller_output, &en_lflow_output,
> @@ -5838,6 +6067,8 @@ main(int argc, char *argv[])
>                       controller_output_bfd_chassis_handler);
>      engine_add_input(&en_controller_output, &en_route_exchange,
>                       controller_output_route_exchange_handler);
> +    engine_add_input(&en_controller_output, &en_garp_rarp,
> +                     controller_output_garp_rarp_handler);
>
>      engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
>      engine_add_input(&en_controller_output, &en_acl_id,
> @@ -5874,6 +6105,8 @@ main(int argc, char *argv[])
>
>  sbrec_chassis_template_var_index_by_chassis);
>      engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
>                                  sbrec_learned_route_index_by_datapath);
> +    engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
> +                                sbrec_mac_binding_by_lport_ip);
>      engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
>                                  ovsrec_flow_sample_collector_set_by_id);
>      engine_ovsdb_node_add_index(&en_ovs_port, "qos", ovsrec_port_by_qos);
> @@ -6290,9 +6523,12 @@ main(int argc, char *argv[])
>                          stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
>                                          time_msec());
>                          pinctrl_update(ovnsb_idl_loop.idl);
> +                        const struct ed_type_garp_rarp *grd =
> engine_get_data(
> +                            &en_garp_rarp);
> +                        const struct hmap *garp_rarp_data =
> +                            grd ? &grd->garp_rarp_data : NULL;
>                          pinctrl_run(ovnsb_idl_txn,
>                                      sbrec_datapath_binding_by_key,
> -                                    sbrec_port_binding_by_datapath,
>                                      sbrec_port_binding_by_key,
>                                      sbrec_port_binding_by_name,
>                                      sbrec_mac_binding_by_lport_ip,
> @@ -6308,13 +6544,14 @@ main(int argc, char *argv[])
>
>  sbrec_bfd_table_get(ovnsb_idl_loop.idl),
>                                      sbrec_ecmp_nexthop_table_get(
>                                          ovnsb_idl_loop.idl),
> -                                    br_int, chassis,
> +                                    chassis,
>                                      &runtime_data->local_datapaths,
>                                      &runtime_data->active_tunnels,
>
>  &runtime_data->local_active_ports_ipv6_pd,
>                                      &runtime_data->local_active_ports_ras,
>                                      ovsrec_open_vswitch_table_get(
> -                                            ovs_idl_loop.idl));
> +                                            ovs_idl_loop.idl),
> +                                    garp_rarp_data);
>                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
>                                         time_msec());
>                          mirror_run(ovs_idl_txn,
> diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> index 1481b2547..6b4a4a520 100644
> --- a/controller/pinctrl.c
> +++ b/controller/pinctrl.c
> @@ -64,6 +64,7 @@
>  #include "ip-mcast.h"
>  #include "ovn-sb-idl.h"
>  #include "ovn-dns.h"
> +#include "garp_rarp.h"
>
>  VLOG_DEFINE_THIS_MODULE(pinctrl);
>
> @@ -233,15 +234,9 @@ static void destroy_send_arps_nds(void);
>  static void send_garp_rarp_wait(long long int send_garp_rarp_time);
>  static void send_arp_nd_wait(long long int send_arp_nd_time);
>  static void send_garp_rarp_prepare(
> -    struct ovsdb_idl_txn *ovnsb_idl_txn,
> -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> -    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> +    const struct hmap *garp_rarp_data,
>      const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> -    const struct ovsrec_bridge *,
> -    const struct sbrec_chassis *,
> -    const struct hmap *local_datapaths,
> -    const struct sset *active_tunnels,
> +    const struct sbrec_chassis *chassis,
>      const struct ovsrec_open_vswitch_table *ovs_table)
>      OVS_REQUIRES(pinctrl_mutex);
>  static void send_garp_rarp_run(struct rconn *swconn,
> @@ -4066,7 +4061,6 @@ pinctrl_update(const struct ovsdb_idl *idl)
>  void
>  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
>              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> -            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
>              struct ovsdb_idl_index *sbrec_port_binding_by_key,
>              struct ovsdb_idl_index *sbrec_port_binding_by_name,
>              struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> @@ -4078,13 +4072,13 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
>              const struct sbrec_mac_binding_table *mac_binding_table,
>              const struct sbrec_bfd_table *bfd_table,
>              const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> -            const struct ovsrec_bridge *br_int,
>              const struct sbrec_chassis *chassis,
>              const struct hmap *local_datapaths,
>              const struct sset *active_tunnels,
>              const struct shash *local_active_ports_ipv6_pd,
>              const struct shash *local_active_ports_ras,
> -            const struct ovsrec_open_vswitch_table *ovs_table)
> +            const struct ovsrec_open_vswitch_table *ovs_table,
> +            const struct hmap *garp_rarp_data)
>  {
>      ovs_mutex_lock(&pinctrl_mutex);
>      run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
> @@ -4092,11 +4086,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                           sbrec_mac_binding_by_lport_ip);
>      run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
>                             sbrec_port_binding_by_key, chassis);
> -    send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath,
> -                           sbrec_port_binding_by_name,
> -                           sbrec_mac_binding_by_lport_ip, ecmp_nh_table,
> -                           br_int, chassis, local_datapaths,
> active_tunnels,
> -                           ovs_table);
> +    send_garp_rarp_prepare(garp_rarp_data, ecmp_nh_table,
> +                           chassis, ovs_table);
>      prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
>      prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
>                           local_active_ports_ipv6_pd, chassis,
> @@ -4785,47 +4776,6 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn
> *ovnsb_idl_txn,
>      }
>  }
>
> -/* Simulate the effect of a GARP on local datapaths, i.e., create
> MAC_Bindings
> - * on peer router datapaths.
> - */
> -static void
> -send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
> -                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> -                  const struct hmap *local_datapaths,
> -                  const struct sbrec_port_binding *in_pb,
> -                  struct eth_addr ea, ovs_be32 ip)
> -{
> -    if (!ovnsb_idl_txn) {
> -        return;
> -    }
> -
> -    const struct local_datapath *ldp =
> -        get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
> -
> -    ovs_assert(ldp);
> -    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> -        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
> -        const struct sbrec_port_binding *remote =
> ldp->peer_ports[i].remote;
> -
> -        /* Skip "ingress" port. */
> -        if (local == in_pb) {
> -            continue;
> -        }
> -
> -        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
> -                                          "always_learn_from_arp_request",
> -                                          true);
> -
> -        struct ds ip_s = DS_EMPTY_INITIALIZER;
> -
> -        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> -        mac_binding_add_to_sb(ovnsb_idl_txn,
> sbrec_mac_binding_by_lport_ip,
> -                              remote->logical_port, remote->datapath,
> -                              ea, ds_cstr(&ip_s), update_only);
> -        ds_destroy(&ip_s);
> -    }
> -}
> -
>  static void
>  run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                      struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> @@ -4977,173 +4927,25 @@ wait_put_mac_bindings(struct ovsdb_idl_txn
> *ovnsb_idl_txn)
>   * are needed for switches and routers on the broadcast segment to update
>   * their port-mac and ARP tables.
>   */
> -struct garp_rarp_data {
> -    struct eth_addr ea;          /* Ethernet address of port. */
> -    ovs_be32 ipv4;               /* Ipv4 address of port. */
> -    long long int announce_time; /* Next announcement in ms. */
> -    int backoff;                 /* Backoff timeout for the next
> -                                  * announcement (in msecs). */
> -    uint32_t dp_key;             /* Datapath used to output this GARP. */
> -    uint32_t port_key;           /* Port to inject the GARP into. */
> -};
>
> -/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
> -static struct shash send_garp_rarp_data;
> +/* Contains GARPs/RARPs to be sent in struct garp_rarp_node.
> + * Protected by pinctrl_mutex. */
> +static struct hmap send_garp_rarp_data;
>
>  static void
>  init_send_garps_rarps(void)
>  {
> -    shash_init(&send_garp_rarp_data);
> +    hmap_init(&send_garp_rarp_data);
>  }
>
>  static void
>  destroy_send_garps_rarps(void)
>  {
> -    shash_destroy_free_data(&send_garp_rarp_data);
> -}
> -
> -/* Runs with in the main ovn-controller thread context. */
> -static void
> -add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
> -              uint32_t dp_key, uint32_t port_key)
> -{
> -    struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
> -    garp_rarp->ea = ea;
> -    garp_rarp->ipv4 = ip;
> -    garp_rarp->announce_time = time_msec() + 1000;
> -    garp_rarp->backoff = 1000; /* msec. */
> -    garp_rarp->dp_key = dp_key;
> -    garp_rarp->port_key = port_key;
> -    shash_add(&send_garp_rarp_data, name, garp_rarp);
> -
> -    /* Notify pinctrl_handler so that it can wakeup and process
> -     * these GARP/RARP requests. */
> -    notify_pinctrl_handler();
> -}
> -
> -/* Add or update a vif for which GARPs need to be announced. */
> -static void
> -send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
> -                      struct ovsdb_idl_index
> *sbrec_mac_binding_by_lport_ip,
> -                      const struct hmap *local_datapaths,
> -                      const struct sbrec_port_binding *binding_rec,
> -                      struct shash *nat_addresses,
> -                      long long int garp_max_timeout,
> -                      bool garp_continuous)
> -{
> -    volatile struct garp_rarp_data *garp_rarp = NULL;
> -
> -    /* Skip localports as they don't need to be announced */
> -    if (!strcmp(binding_rec->type, "localport")) {
> -        return;
> -    }
> -
> -    /* Update GARP for NAT IP if it exists.  Consider port bindings with
> type
> -     * "l3gateway" for logical switch ports attached to gateway routers,
> and
> -     * port bindings with type "patch" for logical switch ports attached
> to
> -     * distributed gateway ports. */
> -    if (!strcmp(binding_rec->type, "l3gateway")
> -        || !strcmp(binding_rec->type, "patch")) {
> -        struct lport_addresses *laddrs = NULL;
> -        while ((laddrs = shash_find_and_delete(nat_addresses,
> -
>  binding_rec->logical_port))) {
> -            int i;
> -            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> -                char *name = xasprintf("%s-%s", binding_rec->logical_port,
> -
> laddrs->ipv4_addrs[i].addr_s);
> -                garp_rarp = shash_find_data(&send_garp_rarp_data, name);
> -                if (garp_rarp) {
> -                    garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> -                    garp_rarp->port_key = binding_rec->tunnel_key;
> -                    if (garp_max_timeout != garp_rarp_max_timeout ||
> -                        garp_continuous != garp_rarp_continuous) {
> -                        /* reset backoff */
> -                        garp_rarp->announce_time = time_msec() + 1000;
> -                        garp_rarp->backoff = 1000; /* msec. */
> -                    }
> -                } else if (ovnsb_idl_txn) {
> -                    add_garp_rarp(name, laddrs->ea,
> -                                  laddrs->ipv4_addrs[i].addr,
> -                                  binding_rec->datapath->tunnel_key,
> -                                  binding_rec->tunnel_key);
> -                    send_garp_locally(ovnsb_idl_txn,
> -                                      sbrec_mac_binding_by_lport_ip,
> -                                      local_datapaths, binding_rec,
> laddrs->ea,
> -                                      laddrs->ipv4_addrs[i].addr);
> -
> -                }
> -                free(name);
> -            }
> -            /*
> -             * Send RARPs even if we do not have a ipv4 address as it e.g.
> -             * happens on ipv6 only ports.
> -             */
> -            if (laddrs->n_ipv4_addrs == 0) {
> -                    char *name = xasprintf("%s-noip",
> -                                           binding_rec->logical_port);
> -                    garp_rarp = shash_find_data(&send_garp_rarp_data,
> name);
> -                    if (garp_rarp) {
> -                        garp_rarp->dp_key =
> binding_rec->datapath->tunnel_key;
> -                        garp_rarp->port_key = binding_rec->tunnel_key;
> -                        if (garp_max_timeout != garp_rarp_max_timeout ||
> -                            garp_continuous != garp_rarp_continuous) {
> -                            /* reset backoff */
> -                            garp_rarp->announce_time = time_msec() + 1000;
> -                            garp_rarp->backoff = 1000; /* msec. */
> -                        }
> -                    } else {
> -                        add_garp_rarp(name, laddrs->ea,
> -                                      0,
> binding_rec->datapath->tunnel_key,
> -                                      binding_rec->tunnel_key);
> -                    }
> -                    free(name);
> -            }
> -            destroy_lport_addresses(laddrs);
> -            free(laddrs);
> -        }
> -        return;
> -    }
> -
> -    /* Update GARP for vif if it exists. */
> -    garp_rarp = shash_find_data(&send_garp_rarp_data,
> -                                binding_rec->logical_port);
> -    if (garp_rarp) {
> -        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> -        garp_rarp->port_key = binding_rec->tunnel_key;
> -        if (garp_max_timeout != garp_rarp_max_timeout ||
> -            garp_continuous != garp_rarp_continuous) {
> -            /* reset backoff */
> -            garp_rarp->announce_time = time_msec() + 1000;
> -            garp_rarp->backoff = 1000; /* msec. */
> -        }
> -        return;
> -    }
> -
> -    /* Add GARP for new vif. */
> -    int i;
> -    for (i = 0; i < binding_rec->n_mac; i++) {
> -        struct lport_addresses laddrs;
> -        ovs_be32 ip = 0;
> -        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> -            continue;
> -        }
> -
> -        if (laddrs.n_ipv4_addrs) {
> -            ip = laddrs.ipv4_addrs[0].addr;
> -        }
> -
> -        add_garp_rarp(binding_rec->logical_port,
> -                      laddrs.ea, ip,
> -                      binding_rec->datapath->tunnel_key,
> -                      binding_rec->tunnel_key);
> -        if (ip) {
> -            send_garp_locally(ovnsb_idl_txn,
> sbrec_mac_binding_by_lport_ip,
> -                              local_datapaths, binding_rec, laddrs.ea,
> ip);
> -        }
> -
> -        destroy_lport_addresses(&laddrs);
> -        break;
> +    struct garp_rarp_node *garp_rarp;
> +    HMAP_FOR_EACH_POP (garp_rarp, hmap_node, &send_garp_rarp_data) {
> +        garp_rarp_node_free(garp_rarp);
>      }
> +    hmap_destroy(&send_garp_rarp_data);
>  }
>
>  struct arp_nd_data {
> @@ -5289,16 +5091,6 @@ send_arp_nd_update(const struct sbrec_port_binding
> *pb, const char *nexthop,
>      }
>  }
>
> -/* Remove a vif from GARP announcements. */
> -static void
> -send_garp_rarp_delete(const char *lport)
> -{
> -    struct garp_rarp_data *garp_rarp = shash_find_and_delete
> -                                       (&send_garp_rarp_data, lport);
> -    free(garp_rarp);
> -    notify_pinctrl_handler();
> -}
> -
>  void
>  send_self_originated_neigh_packet(struct rconn *swconn,
>                                    uint32_t dp_key, uint32_t port_key,
> @@ -5350,7 +5142,7 @@ send_self_originated_neigh_packet(struct rconn
> *swconn,
>
>  /* Called with in the pinctrl_handler thread context. */
>  static long long int
> -send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
> +send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
>                 long long int current_time)
>      OVS_REQUIRES(pinctrl_mutex)
>  {
> @@ -6419,242 +6211,12 @@ ip_mcast_querier_wait(long long int query_time)
>      }
>  }
>
> -/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> ports. */
> -static void
> -get_localnet_vifs_l3gwports(
> -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> -    const struct ovsrec_bridge *br_int,
> -    const struct sbrec_chassis *chassis,
> -    const struct hmap *local_datapaths,
> -    struct sset *localnet_vifs,
> -    struct sset *local_l3gw_ports)
> -{
> -    for (int i = 0; i < br_int->n_ports; i++) {
> -        const struct ovsrec_port *port_rec = br_int->ports[i];
> -        if (!strcmp(port_rec->name, br_int->name)) {
> -            continue;
> -        }
> -        const char *tunnel_id = smap_get(&port_rec->external_ids,
> -                                          "ovn-chassis-id");
> -        if (tunnel_id &&
> -                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> NULL)) {
> -            continue;
> -        }
> -        const char *localnet = smap_get(&port_rec->external_ids,
> -                                        "ovn-localnet-port");
> -        if (localnet) {
> -            continue;
> -        }
> -        for (int j = 0; j < port_rec->n_interfaces; j++) {
> -            const struct ovsrec_interface *iface_rec =
> port_rec->interfaces[j];
> -            if (!iface_rec->n_ofport) {
> -                continue;
> -            }
> -            /* Get localnet vif. */
> -            const char *iface_id = smap_get(&iface_rec->external_ids,
> -                                            "iface-id");
> -            if (!iface_id) {
> -                continue;
> -            }
> -            const struct sbrec_port_binding *pb
> -                = lport_lookup_by_name(sbrec_port_binding_by_name,
> iface_id);
> -            if (!pb || pb->chassis != chassis) {
> -                continue;
> -            }
> -            if (!iface_rec->link_state ||
> -                    strcmp(iface_rec->link_state, "up")) {
> -                continue;
> -            }
> -            struct local_datapath *ld
> -                = get_local_datapath(local_datapaths,
> -                                     pb->datapath->tunnel_key);
> -            if (ld && ld->localnet_port) {
> -                sset_add(localnet_vifs, iface_id);
> -            }
> -        }
> -    }
> -
> -    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
> -        sbrec_port_binding_by_datapath);
> -
> -    const struct local_datapath *ld;
> -    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> -        const struct sbrec_port_binding *pb;
> -
> -        if (!ld->localnet_port) {
> -            continue;
> -        }
> -
> -        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
> -         * that connect to gateway routers (if local), and consider port
> -         * bindings of type "patch" since they might connect to
> -         * distributed gateway ports with NAT addresses. */
> -
> -        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> -        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> -
>  sbrec_port_binding_by_datapath) {
> -            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
> -                || !strcmp(pb->type, "patch")) {
> -                sset_add(local_l3gw_ports, pb->logical_port);
> -            }
> -        }
> -    }
> -    sbrec_port_binding_index_destroy_row(target);
> -}
> -
> -
> -/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> - * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> - * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
> - * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
> - * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> - *
> - * Returns true if at least 'MAC' is found in 'address', false otherwise.
> - *
> - * The caller must call destroy_lport_addresses() and free(*lport). */
> -static bool
> -extract_addresses_with_port(const char *addresses,
> -                            struct lport_addresses *laddrs,
> -                            char **lport)
> -{
> -    int ofs;
> -    if (!extract_addresses(addresses, laddrs, &ofs)) {
> -        return false;
> -    } else if (!addresses[ofs]) {
> -        return true;
> -    }
> -
> -    struct lexer lexer;
> -    lexer_init(&lexer, addresses + ofs);
> -    lexer_get(&lexer);
> -
> -    if (lexer.error || lexer.token.type != LEX_T_ID
> -        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
> -        lexer_destroy(&lexer);
> -        return true;
> -    }
> -
> -    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> -                          "'is_chassis_resident' in address '%s'",
> addresses);
> -        lexer_destroy(&lexer);
> -        return false;
> -    }
> -
> -    if (lexer.token.type != LEX_T_STRING) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_INFO_RL(&rl,
> -                    "Syntax error: expecting quoted string after "
> -                    "'is_chassis_resident' in address '%s'", addresses);
> -        lexer_destroy(&lexer);
> -        return false;
> -    }
> -
> -    *lport = xstrdup(lexer.token.s);
> -
> -    lexer_get(&lexer);
> -    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> -        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> string in "
> -                          "'is_chassis_resident()' in address '%s'",
> -                          addresses);
> -        lexer_destroy(&lexer);
> -        return false;
> -    }
> -
> -    lexer_destroy(&lexer);
> -    return true;
> -}
> -
> -static void
> -consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
> -                     const char *nat_address,
> -                     const struct sbrec_port_binding *pb,
> -                     struct sset *nat_address_keys,
> -                     const struct sbrec_chassis *chassis,
> -                     const struct sset *active_tunnels,
> -                     struct shash *nat_addresses)
> -{
> -    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> -    char *lport = NULL;
> -    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> -        || (!lport && !strcmp(pb->type, "patch"))
> -        || (lport && !lport_is_chassis_resident(
> -                sbrec_port_binding_by_name, chassis,
> -                active_tunnels, lport))) {
> -        destroy_lport_addresses(laddrs);
> -        free(laddrs);
> -        free(lport);
> -        return;
> -    }
> -    free(lport);
> -
> -    int i;
> -    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> -        char *name = xasprintf("%s-%s", pb->logical_port,
> -                                        laddrs->ipv4_addrs[i].addr_s);
> -        sset_add(nat_address_keys, name);
> -        free(name);
> -    }
> -    if (laddrs->n_ipv4_addrs == 0) {
> -        char *name = xasprintf("%s-noip", pb->logical_port);
> -        sset_add(nat_address_keys, name);
> -        free(name);
> -    }
> -    shash_add(nat_addresses, pb->logical_port, laddrs);
> -}
> -
> -static void
> -get_nat_addresses_and_keys(struct ovsdb_idl_index
> *sbrec_port_binding_by_name,
> -                           struct sset *nat_address_keys,
> -                           struct sset *local_l3gw_ports,
> -                           const struct sbrec_chassis *chassis,
> -                           const struct sset *active_tunnels,
> -                           struct shash *nat_addresses)
> -{
> -    const char *gw_port;
> -    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
> -        const struct sbrec_port_binding *pb;
> -
> -        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> -        if (!pb) {
> -            continue;
> -        }
> -
> -        if (pb->n_nat_addresses) {
> -            for (int i = 0; i < pb->n_nat_addresses; i++) {
> -                consider_nat_address(sbrec_port_binding_by_name,
> -                                     pb->nat_addresses[i], pb,
> -                                     nat_address_keys, chassis,
> -                                     active_tunnels,
> -                                     nat_addresses);
> -            }
> -        } else {
> -            /* Continue to support options:nat-addresses for version
> -             * upgrade. */
> -            const char *nat_addresses_options = smap_get(&pb->options,
> -                                                         "nat-addresses");
> -            if (nat_addresses_options) {
> -                consider_nat_address(sbrec_port_binding_by_name,
> -                                     nat_addresses_options, pb,
> -                                     nat_address_keys, chassis,
> -                                     active_tunnels,
> -                                     nat_addresses);
> -            }
> -        }
> -    }
> -}
> -
>  static void
>  send_garp_rarp_wait(long long int send_garp_rarp_time)
>  {
>      /* Set the poll timer for next garp/rarp only if there is data to
>       * be sent. */
> -    if (!shash_is_empty(&send_garp_rarp_data)) {
> +    if (!hmap_is_empty(&send_garp_rarp_data)) {
>          poll_timer_wait_until(send_garp_rarp_time);
>      }
>  }
> @@ -6674,16 +6236,16 @@ static void
>  send_garp_rarp_run(struct rconn *swconn, long long int
> *send_garp_rarp_time)
>      OVS_REQUIRES(pinctrl_mutex)
>  {
> -    if (shash_is_empty(&send_garp_rarp_data)) {
> +    if (hmap_is_empty(&send_garp_rarp_data)) {
>          return;
>      }
>
>      /* Send GARPs, and update the next announcement. */
> -    struct shash_node *iter;
>      long long int current_time = time_msec();
>      *send_garp_rarp_time = LLONG_MAX;
> -    SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
> -        long long int next_announce = send_garp_rarp(swconn, iter->data,
> +    struct garp_rarp_node *garp;
> +    HMAP_FOR_EACH (garp, hmap_node, &send_garp_rarp_data) {
> +        long long int next_announce = send_garp_rarp(swconn, garp,
>                                                       current_time);
>          if (*send_garp_rarp_time > next_announce) {
>              *send_garp_rarp_time = next_announce;
> @@ -6743,22 +6305,12 @@ send_arp_nd_run(struct rconn *swconn, long long
> int *send_arp_nd_time)
>  /* Called by pinctrl_run(). Runs with in the main ovn-controller
>   * thread context. */
>  static void
> -send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
> -                       struct ovsdb_idl_index
> *sbrec_port_binding_by_datapath,
> -                       struct ovsdb_idl_index *sbrec_port_binding_by_name,
> -                       struct ovsdb_idl_index
> *sbrec_mac_binding_by_lport_ip,
> +send_garp_rarp_prepare(const struct hmap *garp_rarp_data,
>                         const struct sbrec_ecmp_nexthop_table
> *ecmp_nh_table,
> -                       const struct ovsrec_bridge *br_int,
>                         const struct sbrec_chassis *chassis,
> -                       const struct hmap *local_datapaths,
> -                       const struct sset *active_tunnels,
>                         const struct ovsrec_open_vswitch_table *ovs_table)
>      OVS_REQUIRES(pinctrl_mutex)
>  {
> -    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> -    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
> -    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> -    struct shash nat_addresses;
>      unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
>      unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
>      bool garp_continuous = false, continuous_arp_nd = true;
> @@ -6778,51 +6330,13 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> *ovnsb_idl_txn,
>          continuous_arp_nd = !!max_arp_nd_timeout;
>      }
>
> -    shash_init(&nat_addresses);
> +    bool reset_timers = (garp_max_timeout != garp_rarp_max_timeout ||
> +                         garp_continuous != garp_rarp_continuous);
>
> -    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
> -                                sbrec_port_binding_by_name,
> -                                br_int, chassis, local_datapaths,
> -                                &localnet_vifs, &local_l3gw_ports);
> -
> -    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
> -                               &nat_ip_keys, &local_l3gw_ports,
> -                               chassis, active_tunnels,
> -                               &nat_addresses);
> -
> -    /* For deleted ports and deleted nat ips, remove from
> -     * send_garp_rarp_data. */
> -    struct shash_node *iter;
> -    SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
> -        if (!sset_contains(&localnet_vifs, iter->name) &&
> -            !sset_contains(&nat_ip_keys, iter->name)) {
> -            send_garp_rarp_delete(iter->name);
> -        }
> -    }
> -
> -    /* Update send_garp_rarp_data. */
> -    const char *iface_id;
> -    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> -        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> -            sbrec_port_binding_by_name, iface_id);
> -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> false)) {
> -            send_garp_rarp_update(ovnsb_idl_txn,
> -                                  sbrec_mac_binding_by_lport_ip,
> -                                  local_datapaths, pb, &nat_addresses,
> -                                  garp_max_timeout, garp_continuous);
> -        }
> -    }
> -
> -    /* Update send_garp_rarp_data for nat-addresses. */
> -    const char *gw_port;
> -    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> -        const struct sbrec_port_binding *pb
> -            = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> false)) {
> -            send_garp_rarp_update(ovnsb_idl_txn,
> sbrec_mac_binding_by_lport_ip,
> -                                  local_datapaths, pb, &nat_addresses,
> -                                  garp_max_timeout, garp_continuous);
> -        }
> +    if (garp_rarp_sync(garp_rarp_data, &send_garp_rarp_data,
> reset_timers)) {
> +        /* Notify pinctrl_handler so that it can wakeup and process
> +         * these GARP/RARP requests. */
> +        notify_pinctrl_handler();
>      }
>
>      arp_nd_sync_data(ecmp_nh_table);
> @@ -6836,21 +6350,6 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> *ovnsb_idl_txn,
>          }
>      }
>
> -    /* pinctrl_handler thread will send the GARPs. */
> -
> -    sset_destroy(&localnet_vifs);
> -    sset_destroy(&local_l3gw_ports);
> -
> -    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> -        struct lport_addresses *laddrs = iter->data;
> -        destroy_lport_addresses(laddrs);
> -        shash_delete(&nat_addresses, iter);
> -        free(laddrs);
> -    }
> -    shash_destroy(&nat_addresses);
> -
> -    sset_destroy(&nat_ip_keys);
> -
>      garp_rarp_max_timeout = garp_max_timeout;
>      garp_rarp_continuous = garp_continuous;
>
> @@ -6862,7 +6361,7 @@ static bool
>  may_inject_pkts(void)
>  {
>      return (!shash_is_empty(&ipv6_ras) ||
> -            !shash_is_empty(&send_garp_rarp_data) ||
> +            !hmap_is_empty(&send_garp_rarp_data) ||
>              ipv6_prefixd_should_inject() ||
>              !ovs_list_is_empty(&mcast_query_list) ||
>              buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)
> ||
> diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> index e33d7cbf5..f57f5c306 100644
> --- a/controller/pinctrl.h
> +++ b/controller/pinctrl.h
> @@ -44,24 +44,24 @@ struct sbrec_mac_binding_table;
>  void pinctrl_init(void);
>  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
>                   struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> -                 struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
>                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
>                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
>                   struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
>                   struct ovsdb_idl_index *sbrec_igmp_groups,
>                   struct ovsdb_idl_index *sbrec_ip_multicast_opts,
>                   struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
> -                 const struct sbrec_controller_event_table *,
> -                 const struct sbrec_service_monitor_table *,
> -                 const struct sbrec_mac_binding_table *,
> -                 const struct sbrec_bfd_table *,
> -                 const struct sbrec_ecmp_nexthop_table *,
> -                 const struct ovsrec_bridge *, const struct sbrec_chassis
> *,
> +                 const struct sbrec_controller_event_table *ce_table,
> +                 const struct sbrec_service_monitor_table *svc_mon_table,
> +                 const struct sbrec_mac_binding_table *mac_binding_table,
> +                 const struct sbrec_bfd_table *bfd_table,
> +                 const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> +                 const struct sbrec_chassis *chassis,
>

nit: Unrelated change.

                  const struct hmap *local_datapaths,
>                   const struct sset *active_tunnels,
>                   const struct shash *local_active_ports_ipv6_pd,
>                   const struct shash *local_active_ports_ras,
> -                 const struct ovsrec_open_vswitch_table *ovs_table);
> +                 const struct ovsrec_open_vswitch_table *ovs_table,
> +                 const struct hmap *garp_rarp_data);
>  void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
>  void pinctrl_destroy(void);
>
> --
> 2.43.0
>
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Thanks,
Ales
Felix Huettner April 7, 2025, noon UTC | #2
On Thu, Apr 03, 2025 at 01:25:10PM +0200, Ales Musil wrote:
> On Thu, Mar 20, 2025 at 1:02 PM Felix Huettner via dev <
> ovs-dev@openvswitch.org> wrote:
> 
> > Previously on each call to pinctrl_run all addresses that need to be
> > announced via garps or rarps where computed newly. On larger external
> > networks that might take significant time.
> > However the content of the data can only change in a run of the I+P
> > engine. So we move the whole logic to an engine node and only handle the
> > minimal needed set in pinctrl_run.
> >
> > Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> > Co-Authored-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > Signed-off-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
> > ---
> >
> 
> Hi Felix,
> 
> thank you for the patch. I have a few comments down below.

Hi Ales,

> 
> v6->v7: added ack
> >
> >  controller/automake.mk      |   4 +-
> >  controller/garp_rarp.c      | 626 ++++++++++++++++++++++++++++++++++++
> >  controller/garp_rarp.h      |  61 ++++
> >  controller/ovn-controller.c | 243 +++++++++++++-
> >  controller/pinctrl.c        | 559 ++------------------------------
> >  controller/pinctrl.h        |  16 +-
> >  6 files changed, 967 insertions(+), 542 deletions(-)
> >  create mode 100644 controller/garp_rarp.c
> >  create mode 100644 controller/garp_rarp.h
> >
> > diff --git a/controller/automake.mk b/controller/automake.mk
> > index 067c2e6e4..f0638ea97 100644
> > --- a/controller/automake.mk
> > +++ b/controller/automake.mk
> > @@ -59,7 +59,9 @@ controller_ovn_controller_SOURCES = \
> >         controller/route-exchange.h \
> >         controller/route-table-notify.h \
> >         controller/route.h \
> > -       controller/route.c
> > +       controller/route.c \
> > +       controller/garp_rarp.h \
> > +       controller/garp_rarp.c
> >
> >  if HAVE_NETLINK
> >  controller_ovn_controller_SOURCES += \
> > diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
> > new file mode 100644
> > index 000000000..916ff896a
> > --- /dev/null
> > +++ b/controller/garp_rarp.c
> > @@ -0,0 +1,626 @@
> > +/*
> > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> > implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#include <config.h>
> > +
> > +#include <net/if.h>
> > +#include <stdbool.h>
> > +#include <stddef.h>
> > +
> > +#include "controller/encaps.h"
> > +#include "controller/local_data.h"
> > +#include "controller/lport.h"
> > +#include "hash.h"
> > +#include "mac-binding-index.h"
> > +#include "openvswitch/hmap.h"
> > +#include "openvswitch/vlog.h"
> > +#include "ovn/lex.h"
> > +#include "packets.h"
> > +#include "smap.h"
> > +#include "sset.h"
> > +#include "openvswitch/shash.h"
> > +#include "garp_rarp.h"
> > +#include "ovn-sb-idl.h"
> > +#include "vswitch-idl.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(garp_rarp);
> > +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> > +
> > +/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > ports. */
> > +static void
> > +get_localnet_vifs_l3gwports(
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > +    const struct ovsrec_bridge *br_int,
> > +    const struct sbrec_chassis *chassis,
> > +    const struct hmap *local_datapaths,
> > +    struct sset *localnet_vifs,
> > +    struct sset *local_l3gw_ports)
> > +{
> > +    for (int i = 0; i < br_int->n_ports; i++) {
> > +        const struct ovsrec_port *port_rec = br_int->ports[i];
> > +        if (!strcmp(port_rec->name, br_int->name)) {
> > +            continue;
> > +        }
> > +        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > +                                          "ovn-chassis-id");
> > +        if (tunnel_id &&
> > +                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > NULL)) {
> > +            continue;
> > +        }
> > +        const char *localnet = smap_get(&port_rec->external_ids,
> > +                                        "ovn-localnet-port");
> > +        if (localnet) {
> > +            continue;
> > +        }
> > +        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > +            const struct ovsrec_interface *iface_rec =
> > port_rec->interfaces[j];
> > +            if (!iface_rec->n_ofport) {
> > +                continue;
> > +            }
> > +            /* Get localnet vif. */
> > +            const char *iface_id = smap_get(&iface_rec->external_ids,
> > +                                            "iface-id");
> > +            if (!iface_id) {
> > +                continue;
> > +            }
> > +            const struct sbrec_port_binding *pb
> > +                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > iface_id);
> > +            if (!pb || pb->chassis != chassis) {
> > +                continue;
> > +            }
> > +            if (!iface_rec->link_state ||
> > +                    strcmp(iface_rec->link_state, "up")) {
> > +                continue;
> > +            }
> > +            struct local_datapath *ld
> > +                = get_local_datapath(local_datapaths,
> > +                                     pb->datapath->tunnel_key);
> > +            if (ld && ld->localnet_port) {
> > +                sset_add(localnet_vifs, iface_id);
> > +            }
> > +        }
> > +    }
> >
> 
> I know that this is old code but it makes me wonder if there's a better
> way to find local vifs that are connected to localnet port. I think
> it should be doable through bindings.

Actually it seems like we can get this easily in the local_datapaths
loop below. I will update this in the next version. It is so simple that
i am unsure if i missed something.

> 
> +
> > +    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
> > +        sbrec_port_binding_by_datapath);
> > +
> > +    const struct local_datapath *ld;
> > +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > +        const struct sbrec_port_binding *pb;
> > +
> > +        if (!ld->localnet_port) {
> > +            continue;
> > +        }
> > +
> > +        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
> > +         * that connect to gateway routers (if local), and consider port
> > +         * bindings of type "patch" since they might connect to
> > +         * distributed gateway ports with NAT addresses. */
> > +
> > +        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > +        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > +
> >  sbrec_port_binding_by_datapath) {
> > +            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
> > +                || !strcmp(pb->type, "patch")) {
> > +                sset_add(local_l3gw_ports, pb->logical_port);
> > +            }
> > +        }
> > +    }
> > +    sbrec_port_binding_index_destroy_row(target);
> > +}
> > +
> > +
> > +/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > + * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > + * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
> > + * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
> > + * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > + *
> > + * Returns true if at least 'MAC' is found in 'address', false otherwise.
> > + *
> > + * The caller must call destroy_lport_addresses() and free(*lport). */
> > +static bool
> > +extract_addresses_with_port(const char *addresses,
> > +                            struct lport_addresses *laddrs,
> > +                            char **lport)
> > +{
> > +    int ofs;
> > +    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > +        return false;
> > +    } else if (!addresses[ofs]) {
> > +        return true;
> > +    }
> > +
> > +    struct lexer lexer;
> > +    lexer_init(&lexer, addresses + ofs);
> > +    lexer_get(&lexer);
> > +
> > +    if (lexer.error || lexer.token.type != LEX_T_ID
> > +        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > +        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
> > +        lexer_destroy(&lexer);
> > +        return true;
> > +    }
> > +
> > +    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > +        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > +                          "'is_chassis_resident' in address '%s'",
> > addresses);
> > +        lexer_destroy(&lexer);
> > +        return false;
> > +    }
> > +
> > +    if (lexer.token.type != LEX_T_STRING) {
> > +        VLOG_INFO_RL(&rl,
> > +                    "Syntax error: expecting quoted string after "
> > +                    "'is_chassis_resident' in address '%s'", addresses);
> > +        lexer_destroy(&lexer);
> > +        return false;
> > +    }
> > +
> > +    *lport = xstrdup(lexer.token.s);
> > +
> > +    lexer_get(&lexer);
> > +    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > +        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > string in "
> > +                          "'is_chassis_resident()' in address '%s'",
> > +                          addresses);
> > +        lexer_destroy(&lexer);
> > +        return false;
> > +    }
> > +
> > +    lexer_destroy(&lexer);
> > +    return true;
> > +}
> > +
> > +static void
> > +consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > +                     const char *nat_address,
> > +                     const struct sbrec_port_binding *pb,
> > +                     struct sset *nat_address_keys,
> > +                     const struct sbrec_chassis *chassis,
> > +                     const struct sset *active_tunnels,
> > +                     struct shash *nat_addresses,
> > +                     struct sset *non_local_lports,
> > +                     struct sset *local_lports)
> > +{
> > +    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > +    char *lport = NULL;
> > +    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > +        || (!lport && !strcmp(pb->type, "patch"))) {
> > +        destroy_lport_addresses(laddrs);
> > +        free(laddrs);
> > +        free(lport);
> > +        return;
> > +    }
> > +    if (lport) {
> > +        if (!lport_is_chassis_resident(sbrec_port_binding_by_name,
> > +                chassis, active_tunnels, lport)) {
> > +            sset_add(non_local_lports, lport);
> > +            destroy_lport_addresses(laddrs);
> > +            free(laddrs);
> > +            free(lport);
> > +            return;
> > +        } else {
> > +            sset_add(local_lports, lport);
> > +        }
> > +    }
> > +    free(lport);
> > +
> > +    int i;
> > +    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > +        char *name = xasprintf("%s-%s", pb->logical_port,
> > +                                        laddrs->ipv4_addrs[i].addr_s);
> > +        sset_add(nat_address_keys, name);
> > +        free(name);
> > +    }
> > +    if (laddrs->n_ipv4_addrs == 0) {
> > +        char *name = xasprintf("%s-noip", pb->logical_port);
> > +        sset_add(nat_address_keys, name);
> > +        free(name);
> > +    }
> > +    shash_add(nat_addresses, pb->logical_port, laddrs);
> > +}
> > +
> > +static void
> > +get_nat_addresses_and_keys(struct ovsdb_idl_index
> > *sbrec_port_binding_by_name,
> > +                           struct sset *nat_address_keys,
> > +                           struct sset *local_l3gw_ports,
> > +                           const struct sbrec_chassis *chassis,
> > +                           const struct sset *active_tunnels,
> > +                           struct shash *nat_addresses,
> > +                           struct sset *non_local_lports,
> > +                           struct sset *local_lports)
> > +{
> > +    const char *gw_port;
> > +    SSET_FOR_EACH (gw_port, local_l3gw_ports) {
> > +        const struct sbrec_port_binding *pb;
> > +
> > +        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> > +        if (!pb) {
> > +            continue;
> > +        }
> > +
> > +        if (pb->n_nat_addresses) {
> > +            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > +                consider_nat_address(sbrec_port_binding_by_name,
> > +                                     pb->nat_addresses[i], pb,
> > +                                     nat_address_keys, chassis,
> > +                                     active_tunnels,
> > +                                     nat_addresses,
> > +                                     non_local_lports,
> > +                                     local_lports);
> > +            }
> > +        } else {
> > +            /* Continue to support options:nat-addresses for version
> > +             * upgrade. */
> > +            const char *nat_addresses_options = smap_get(&pb->options,
> > +                                                         "nat-addresses");
> > +            if (nat_addresses_options) {
> > +                consider_nat_address(sbrec_port_binding_by_name,
> > +                                     nat_addresses_options, pb,
> > +                                     nat_address_keys, chassis,
> > +                                     active_tunnels,
> > +                                     nat_addresses,
> > +                                     non_local_lports,
> > +                                     local_lports);
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +static uint32_t
> > +garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
> > +                    uint32_t port_key)
> > +{
> > +    return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
> > +}
> > +
> > +static uint32_t
> > +garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
> > +{
> > +    return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
> > +}
> > +
> > +static void
> > +garp_rarp_node_add(struct hmap *map, const struct eth_addr ea, ovs_be32
> > ip,
> > +                   uint32_t dp_key, uint32_t port_key)
> > +{
> > +    struct garp_rarp_node *grn = xmalloc(sizeof *grn);
> > +    grn->ea = ea;
> > +    grn->ipv4 = ip;
> > +    grn->announce_time = time_msec() + 1000;
> > +    grn->backoff = 1000; /* msec. */
> > +    grn->dp_key = dp_key;
> > +    grn->port_key = port_key;
> > +    hmap_insert(map, &grn->hmap_node, garp_rarp_node_hash_struct(grn));
> > +}
> > +
> > +/* Searches for a given garp_rarp_node in a hmap. Ignores the
> > announce_time
> > + * and backoff field since they might be different based on runtime. */
> > +static struct garp_rarp_node*
> > +garp_rarp_lookup(const struct hmap *map, const struct garp_rarp_node
> > *search)
> > +{
> > +    struct garp_rarp_node *n;
> > +    uint32_t hash = garp_rarp_node_hash_struct(search);
> > +    HMAP_FOR_EACH_WITH_HASH (n, hmap_node, hash, map) {
> > +        if (!eth_addr_equals(search->ea, n->ea)) {
> > +            continue;
> > +        }
> > +
> > +        if (search->ipv4 != n->ipv4) {
> > +            continue;
> > +        }
> > +
> > +        if (search->dp_key != n->dp_key) {
> > +            continue;
> > +        }
> > +
> > +        if (search->port_key != n->port_key) {
> > +            continue;
> > +        }
> > +
> > +        return n;
> > +    }
> > +    return NULL;
> > +}
> > +
> > +/* Update or add an IP-MAC binding for 'logical_port'.
> > + * Caller should make sure that 'ovnsb_idl_txn' is valid. */
> > +static void
> > +mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > +                      struct ovsdb_idl_index
> > *sbrec_mac_binding_by_lport_ip,
> > +                      const char *logical_port,
> > +                      const struct sbrec_datapath_binding *dp,
> > +                      struct eth_addr ea, const char *ip,
> > +                      bool update_only)
> > +{
> > +    /* Convert ethernet argument to string form for database. */
> > +    char mac_string[ETH_ADDR_STRLEN + 1];
> > +    snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT,
> > ETH_ADDR_ARGS(ea));
> > +
> > +    const struct sbrec_mac_binding *b =
> > +            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
> > +                               logical_port, ip);
> > +    if (!b) {
> > +        if (update_only) {
> > +            return;
> > +        }
> > +        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
> > +        sbrec_mac_binding_set_logical_port(b, logical_port);
> > +        sbrec_mac_binding_set_ip(b, ip);
> > +        sbrec_mac_binding_set_datapath(b, dp);
> > +    }
> > +
> > +    if (strcmp(b->mac, mac_string)) {
> > +        sbrec_mac_binding_set_mac(b, mac_string);
> > +
> > +        /* For backward compatibility check if timestamp column is
> > available
> > +         * in SB DB. */
> > +        if (sbrec_server_has_mac_binding_table_col_timestamp(
> > +                ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
> > +            VLOG_DBG("Setting MAC binding timestamp for "
> > +                     "ip:%s mac:%s port:%s to %lld",
> > +                     b->ip, b->mac, logical_port, time_wall_msec());
> > +            sbrec_mac_binding_set_timestamp(b, time_wall_msec());
> > +        }
> > +    }
> > +}
> >
> 
> Unless I've missed something this is duplicated from pinctrl.c, how about
> moving
> this function to a module where it could be used by both?

Sorry for missing that. Will be centralized in the next version.

> 
> 
> > +
> > +/* Simulate the effect of a GARP on local datapaths, i.e., create
> > MAC_Bindings
> > + * on peer router datapaths.
> > + */
> > +static void
> > +send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
> > +                  const struct sbrec_port_binding *in_pb,
> > +                  struct eth_addr ea, ovs_be32 ip)
> > +{
> > +    if (!r_ctx_in->ovnsb_idl_txn) {
> > +        return;
> > +    }
> > +
> > +    const struct local_datapath *ldp =
> > +        get_local_datapath(r_ctx_in->local_datapaths,
> > +                           in_pb->datapath->tunnel_key);
> > +
> > +    ovs_assert(ldp);
> > +    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > +        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
> > +        const struct sbrec_port_binding *remote =
> > ldp->peer_ports[i].remote;
> > +
> > +        /* Skip "ingress" port. */
> > +        if (local == in_pb) {
> > +            continue;
> > +        }
> > +
> > +        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
> > +                                          "always_learn_from_arp_request",
> > +                                          true);
> > +
> > +        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > +
> > +        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > +        mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
> > +                              r_ctx_in->sbrec_mac_binding_by_lport_ip,
> > +                              remote->logical_port, remote->datapath,
> > +                              ea, ds_cstr(&ip_s), update_only);
> > +        ds_destroy(&ip_s);
> > +    }
> > +}
> > +
> > +/* Add or update a vif for which GARPs need to be announced. */
> > +static void
> > +send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
> > +                      const struct sbrec_port_binding *binding_rec,
> > +                      struct shash *nat_addresses,
> > +                      struct hmap *garp_rarp_data)
> > +{
> > +    /* Skip localports as they don't need to be announced */
> > +    if (!strcmp(binding_rec->type, "localport")) {
> > +        return;
> > +    }
> > +
> > +    /* Update GARP for NAT IP if it exists.  Consider port bindings with
> > type
> > +     * "l3gateway" for logical switch ports attached to gateway routers,
> > and
> > +     * port bindings with type "patch" for logical switch ports attached
> > to
> > +     * distributed gateway ports. */
> > +    if (!strcmp(binding_rec->type, "l3gateway")
> > +        || !strcmp(binding_rec->type, "patch")) {
> > +        struct lport_addresses *laddrs = NULL;
> > +        while ((laddrs = shash_find_and_delete(nat_addresses,
> > +
> >  binding_rec->logical_port))) {
> > +            int i;
> > +            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > +                              laddrs->ipv4_addrs[i].addr,
> > +                              binding_rec->datapath->tunnel_key,
> > +                              binding_rec->tunnel_key);
> > +                send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
> > +                                  laddrs->ipv4_addrs[i].addr);
> > +            }
> > +            /*
> > +             * Send RARPs even if we do not have a ipv4 address as it e.g.
> > +             * happens on ipv6 only ports.
> > +             */
> > +            if (laddrs->n_ipv4_addrs == 0) {
> > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > +                              0, binding_rec->datapath->tunnel_key,
> > +                              binding_rec->tunnel_key);
> > +            }
> > +            destroy_lport_addresses(laddrs);
> > +            free(laddrs);
> > +        }
> > +        return;
> > +    }
> > +
> > +    /* Add GARP for new vif. */
> > +    int i;
> > +    for (i = 0; i < binding_rec->n_mac; i++) {
> > +        struct lport_addresses laddrs;
> > +        ovs_be32 ip = 0;
> > +        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > +            continue;
> > +        }
> > +
> > +        if (laddrs.n_ipv4_addrs) {
> > +            ip = laddrs.ipv4_addrs[0].addr;
> > +        }
> > +
> > +        garp_rarp_node_add(garp_rarp_data,
> > +                      laddrs.ea, ip,
> > +                      binding_rec->datapath->tunnel_key,
> > +                      binding_rec->tunnel_key);
> > +        if (ip) {
> > +            send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
> > +        }
> > +
> > +        destroy_lport_addresses(&laddrs);
> > +        break;
> > +    }
> > +}
> > +
> > +static struct garp_rarp_node*
> > +garp_rarp_node_clone(const struct garp_rarp_node *src)
> > +{
> > +    struct garp_rarp_node *dst = xzalloc(sizeof *dst);
> > +    dst->ea = src->ea;
> > +    dst->ipv4 = src->ipv4;
> > +    dst->announce_time = time_msec() + 1000;
> > +    dst->backoff = 1000;
> > +    dst->dp_key = src->dp_key;
> > +    dst->port_key = src->port_key;
> > +    return dst;
> > +}
> > +
> > +static void
> > +garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in,
> > +                struct garp_rarp_ctx_out *r_ctx_out)
> > +{
> > +    struct garp_rarp_node *grn;
> > +    HMAP_FOR_EACH_POP (grn, hmap_node, r_ctx_out->garp_rarp_data) {
> > +        garp_rarp_node_free(grn);
> > +    }
> > +    hmap_clear(r_ctx_out->garp_rarp_data);
> > +    sset_clear(r_ctx_in->non_local_lports);
> > +    sset_clear(r_ctx_in->local_lports);
> > +}
> > +
> > +void
> > +garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in,
> > +              struct garp_rarp_ctx_out *r_ctx_out)
> > +{
> > +    garp_rarp_clear(r_ctx_in, r_ctx_out);
> > +
> > +    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > +    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
> > +    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > +    struct shash nat_addresses;
> > +
> > +    shash_init(&nat_addresses);
> > +
> > +    get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
> > +                                r_ctx_in->sbrec_port_binding_by_name,
> > +                                r_ctx_in->br_int, r_ctx_in->chassis,
> > +                                r_ctx_in->local_datapaths,
> > +                                &localnet_vifs, &local_l3gw_ports);
> > +
> > +    get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
> > +                               &nat_ip_keys, &local_l3gw_ports,
> > +                               r_ctx_in->chassis,
> > r_ctx_in->active_tunnels,
> > +                               &nat_addresses, r_ctx_in->non_local_lports,
> > +                               r_ctx_in->local_lports);
> > +
> > +    /* Update send_garp_rarp_data. */
> > +    const char *iface_id;
> > +    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > +            r_ctx_in->sbrec_port_binding_by_name, iface_id);
> > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > false)) {
> > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > +                                  r_ctx_out->garp_rarp_data);
> > +        }
> > +    }
> > +
> > +    /* Update send_garp_rarp_data for nat-addresses. */
> > +    const char *gw_port;
> > +    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > +            r_ctx_in->sbrec_port_binding_by_name, gw_port);
> > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > false)) {
> > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > +                                  r_ctx_out->garp_rarp_data);
> > +        }
> > +    }
> > +
> > +    sset_destroy(&localnet_vifs);
> > +    sset_destroy(&local_l3gw_ports);
> > +
> > +    struct shash_node *iter;
> > +    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > +        struct lport_addresses *laddrs = iter->data;
> > +        destroy_lport_addresses(laddrs);
> > +        shash_delete(&nat_addresses, iter);
> > +        free(laddrs);
> > +    }
> > +    shash_destroy(&nat_addresses);
> > +
> > +    sset_destroy(&nat_ip_keys);
> > +}
> > +
> > +/* Syncs a hmap of garp_rarp_node to another hmap. The elements in the
> > + * destination hmap are initialized with valid announce times based on the
> > + * current timestamp.
> > + * If source is NULL then destination will at most change the timer if
> > + * reset_timers is set.
> > + * If reset_timers is true then the timers of all entries in the
> > destination
> > + * hmap will be reset to default values.
> > + * Returns true if the destination hmap was chnaged. */
> > +bool
> > +garp_rarp_sync(const struct hmap *source, struct hmap *dest, bool
> > reset_timers)
> > +{
> > +    struct garp_rarp_node *grn;
> > +    bool changed = false;
> > +
> > +    HMAP_FOR_EACH_SAFE (grn, hmap_node, dest) {
> > +        if (source && !garp_rarp_lookup(source, grn)) {
> > +            hmap_remove(dest, &grn->hmap_node);
> > +            garp_rarp_node_free(grn);
> > +            changed = true;
> > +        } else if (reset_timers) {
> > +            grn->announce_time = time_msec() + 1000;
> > +            grn->backoff = 1000;
> > +            changed = true;
> > +        }
> > +    }
> > +
> > +    if (source) {
> > +        HMAP_FOR_EACH (grn, hmap_node, source) {
> > +            if (!garp_rarp_lookup(dest, grn)) {
> > +                uint32_t hash = garp_rarp_node_hash_struct(grn);
> > +                hmap_insert(dest, &garp_rarp_node_clone(grn)->hmap_node,
> > hash);
> > +                changed = true;
> > +            }
> > +        }
> > +    }
> > +
> > +    return changed;
> > +}
> >
> 
> That's a lot of copying between the hmaps, why can't we indicate that the
> announcement was sent for the last time and use just the status instead
> of copying parts of the hmap?

The idea was that any state that needs to be persistently kept is part
of the pinctrl module. The engine node is then free to discard the hmap
and generate it again from scratch.

Also the data is needed in the pinctrl thread separately from the main
thread. So copying seemed like the best solution to avoid needing to
lock the data between pinctrl and the engine node.

> 
> 
> > +
> > +void
> > +garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
> > +{
> > +    free(garp_rarp);
> > +}
> > diff --git a/controller/garp_rarp.h b/controller/garp_rarp.h
> > new file mode 100644
> > index 000000000..b85e75a7c
> > --- /dev/null
> > +++ b/controller/garp_rarp.h
> > @@ -0,0 +1,61 @@
> > +/*
> > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > + *
> > + * 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 GARP_RARP_H
> > +#define GARP_RARP_H 1
> > +
> > +#include "openvswitch/hmap.h"
> > +#include "openvswitch/types.h"
> > +
> > +/* Contains a single mac and ip address that should be announced. */
> > +struct garp_rarp_node {
> > +    struct hmap_node hmap_node;
> > +    struct eth_addr ea;          /* Ethernet address of port. */
> > +    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > +    long long int announce_time; /* Next announcement in ms.
> > +                                  * If LLONG_MAX there should be no
> > +                                  * annoucement. */
> > +    int backoff;                 /* Backoff timeout for the next
> > +                                  * announcement (in msecs). */
> > +    uint32_t dp_key;             /* Datapath used to output this GARP. */
> > +    uint32_t port_key;           /* Port to inject the GARP into. */
> > +};
> > +
> > +struct garp_rarp_ctx_in {
> > +    struct ovsdb_idl_txn *ovnsb_idl_txn;
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name;
> > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
> > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
> > +    const struct ovsrec_bridge *br_int;
> > +    const struct sbrec_chassis *chassis;
> > +    const struct hmap *local_datapaths;
> > +    const struct sset *active_tunnels;
> > +    struct sset *non_local_lports;
> > +    struct sset *local_lports;
> > +};
> > +
> > +struct garp_rarp_ctx_out {
> > +    /* Contains struct garp_rarp_node. */
> > +    struct hmap *garp_rarp_data;
> > +};
> > +
> > +void garp_rarp_run(struct garp_rarp_ctx_in *, struct garp_rarp_ctx_out *);
> > +bool garp_rarp_sync(const struct hmap *source, struct hmap *dest,
> > +                    bool reset_timers);
> > +void garp_rarp_node_free(struct garp_rarp_node *);
> > +
> > +#endif /* GARP_RARP_H */
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index cfed5282c..6f1b27876 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -92,6 +92,7 @@
> >  #include "route.h"
> >  #include "route-exchange.h"
> >  #include "route-table-notify.h"
> > +#include "garp_rarp.h"
> >
> >  VLOG_DEFINE_THIS_MODULE(main);
> >
> > @@ -4952,6 +4953,14 @@ controller_output_route_exchange_handler(struct
> > engine_node *node,
> >      return true;
> >  }
> >
> > +static bool
> > +controller_output_garp_rarp_handler(struct engine_node *node,
> > +                                    void *data OVS_UNUSED)
> > +{
> > +    engine_set_node_state(node, EN_UPDATED);
> > +    return true;
> > +}
> > +
> >  /* Handles sbrec_chassis changes.
> >   * If a new chassis is added or removed return false, so that
> >   * flows are recomputed.  For any updates, there is no need for
> > @@ -5305,6 +5314,216 @@ en_route_table_notify_cleanup(void *data
> > OVS_UNUSED)
> >  {
> >  }
> >
> > +struct ed_type_garp_rarp {
> > +    struct hmap garp_rarp_data;
> > +
> > +    /* non_local_lports and local_lports are used in the incremental
> > handlers
> > +     * to trigger updates if such a port changes. */
> > +    struct sset non_local_lports; /* lports that we did not consider
> > because
> > +                                     they where not local. */
> > +    struct sset local_lports; /* lports where we did consider the
> > addresses
> > +                                 because they where local. */
> > +};
> > +
> > +static void
> > +en_garp_rarp_run(struct engine_node *node, void *data_)
> > +{
> > +    struct ed_type_garp_rarp *data = data_;
> > +
> > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > +    ovs_assert(chassis_id);
> > +
> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_chassis", node),
> > +                "name");
> > +    const struct sbrec_chassis *chassis
> > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > +    ovs_assert(chassis);
> > +
> > +    const struct ovsrec_bridge_table *bridge_table =
> > +        EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
> > +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> > ovs_table);
> > +
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_port_binding", node),
> > +                "datapath");
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_port_binding", node),
> > +                "name");
> > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_mac_binding", node),
> > +                "lport_ip");
> > +
> > +    struct ovsdb_idl_txn *ovnsb_idl_txn =
> > engine_get_context()->ovnsb_idl_txn;
> > +
> > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
> > +
> > sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
> > +
> > +    struct ed_type_runtime_data *rt_data =
> > +        engine_get_input_data("runtime_data", node);
> > +
> > +    struct garp_rarp_ctx_in r_ctx_in = {
> > +        .ovnsb_idl_txn = ovnsb_idl_txn,
> > +        .sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath,
> > +        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> > +        .sbrec_mac_binding_by_lport_ip = sbrec_mac_binding_by_lport_ip,
> > +        .ecmp_nh_table = ecmp_nh_table,
> > +        .br_int = br_int,
> > +        .chassis = chassis,
> > +        .active_tunnels = &rt_data->active_tunnels,
> > +        .local_datapaths = &rt_data->local_datapaths,
> > +        .non_local_lports = &data->non_local_lports,
> > +        .local_lports = &data->local_lports,
> > +    };
> > +
> > +    struct garp_rarp_ctx_out r_ctx_out = {
> > +        .garp_rarp_data = &data->garp_rarp_data,
> > +    };
> > +
> > +    garp_rarp_run(&r_ctx_in, &r_ctx_out);
> > +}
> > +
> > +
> > +static void *
> > +en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
> > +                  struct engine_arg *arg OVS_UNUSED)
> > +{
> > +    struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
> > +    hmap_init(&gr->garp_rarp_data);
> > +    sset_init(&gr->non_local_lports);
> > +    sset_init(&gr->local_lports);
> > +    return gr;
> > +}
> > +
> > +static void
> > +en_garp_rarp_cleanup(void *data_)
> > +{
> > +    struct ed_type_garp_rarp *data = data_;
> > +    struct garp_rarp_node *grn;
> > +    HMAP_FOR_EACH_POP (grn, hmap_node, &data->garp_rarp_data) {
> > +        garp_rarp_node_free(grn);
> > +    }
> > +    hmap_destroy(&data->garp_rarp_data);
> > +    sset_destroy(&data->non_local_lports);
> > +    sset_destroy(&data->local_lports);
> > +}
> > +
> > +static bool
> > +garp_rarp_sb_port_binding_handler(struct engine_node *node,
> > +                                  void *data_)
> > +{
> > +    /* We need to handle a change if there was change on a datapath with
> > +     * a localnet port.
> > +     * Also the ha_chassis status of a port binding might change. */
> > +    struct ed_type_garp_rarp *data = data_;
> > +
> > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > +    ovs_assert(chassis_id);
> > +
> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_chassis", node),
> > +                "name");
> > +    const struct sbrec_chassis *chassis
> > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > +    ovs_assert(chassis);
> > +
> > +    struct ed_type_runtime_data *rt_data =
> > +            engine_get_input_data("runtime_data", node);
> > +    const struct sbrec_port_binding_table *port_binding_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
> > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_port_binding", node),
> > +                "name");
> > +
> > +    const struct sbrec_port_binding *pb;
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, port_binding_table) {
> > +        struct local_datapath *ld = get_local_datapath(
> > +            &rt_data->local_datapaths, pb->datapath->tunnel_key);
> > +
> > +        if (!ld || ld->localnet_port) {
> > +            /* XXX: actually handle this incrementally. */
> > +            return false;
> > +        }
> > +
> > +        if (sset_contains(&data->non_local_lports, pb->logical_port) &&
> > +            lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
> > +                                      &rt_data->active_tunnels,
> > +                                      pb->logical_port)) {
> > +            /* XXX: actually handle this incrementally. */
> > +            return false;
> > +        }
> > +
> > +        if (sset_contains(&data->local_lports, pb->logical_port) &&
> > +            !lport_is_chassis_resident(sbrec_port_binding_by_name,
> > chassis,
> > +                                       &rt_data->active_tunnels,
> > +                                       pb->logical_port)) {
> > +            /* XXX: actually handle this incrementally. */
> > +            return false;
> > +        }
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +static bool
> > +garp_rarp_runtime_data_handler(struct engine_node *node, void *data
> > OVS_UNUSED)
> > +{
> > +    /* We use two elements from rt_data:
> > +     * 1. active_tunnels: There is currently not incremental processing
> > for
> > +     *    this in runtime_data. So we just fall back to a recompute.
> > +     * 2. local_datapaths: This has incremental processing on the
> > runtime_data
> > +     *    side. We are only interested in datapaths with a localnet port
> > so
> > +     *    we just recompute if there is one in there. Otherwise the
> > change is
> > +     *    irrelevant for us. */
> > +
> > +    struct ed_type_runtime_data *rt_data =
> > +            engine_get_input_data("runtime_data", node);
> > +
> > +    /* There are no tracked data. Fall back to full recompute. */
> > +    if (!rt_data->tracked) {
> > +        return false;
> > +    }
> > +
> > +    struct tracked_datapath *tdp;
> > +    HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
> > +        if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
> > +            /* This is currently not handled incrementally in runtime_data
> > +             * so it should never happen. Recompute just in case. */
> > +            return false;
> > +        }
> > +
> > +        struct local_datapath *ld = get_local_datapath(
> > +            &rt_data->local_datapaths, tdp->dp->tunnel_key);
> > +
> > +        if (!ld || ld->localnet_port) {
> > +            /* XXX: actually handle this incrementally. */
> > +            return false;
> > +        }
> > +
> > +        /* The localnet port might also have been removed. */
> > +        struct tracked_lport *tlp;
> > +        struct shash_node *sn;
> > +        SHASH_FOR_EACH (sn, &tdp->lports) {
> > +            tlp = sn->data;
> > +            if (!strcmp(tlp->pb->type, "localnet")) {
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +
> > +    return true;
> > +}
> > +
> >  /* Returns false if the northd internal version stored in SB_Global
> >   * and ovn-controller internal version don't match.
> >   */
> > @@ -5614,6 +5833,7 @@ main(int argc, char *argv[])
> >      ENGINE_NODE(route, "route");
> >      ENGINE_NODE(route_table_notify, "route_table_notify");
> >      ENGINE_NODE(route_exchange, "route_exchange");
> > +    ENGINE_NODE(garp_rarp, "garp_rarp");
> >
> >  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> >      SB_NODES
> > @@ -5826,6 +6046,15 @@ main(int argc, char *argv[])
> >      engine_add_input(&en_dns_cache, &en_sb_dns,
> >                       dns_cache_sb_dns_handler);
> >
> > +    engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
> > +    engine_add_input(&en_garp_rarp, &en_ovs_bridge, NULL);
> > +    engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
> > +    engine_add_input(&en_garp_rarp, &en_sb_port_binding,
> > +                     garp_rarp_sb_port_binding_handler);
> > +    engine_add_input(&en_garp_rarp, &en_sb_mac_binding, NULL);
> >
> 
> This dependency leads to recompute every time we insert MB locally.
> It seems that we need this only for index, so it should be noop handler.

Yes, thanks.

> 
> 
> > +    engine_add_input(&en_garp_rarp, &en_runtime_data,
> > +                     garp_rarp_runtime_data_handler);
> > +
> >      engine_add_input(&en_controller_output, &en_dns_cache,
> >                       NULL);
> >      engine_add_input(&en_controller_output, &en_lflow_output,
> > @@ -5838,6 +6067,8 @@ main(int argc, char *argv[])
> >                       controller_output_bfd_chassis_handler);
> >      engine_add_input(&en_controller_output, &en_route_exchange,
> >                       controller_output_route_exchange_handler);
> > +    engine_add_input(&en_controller_output, &en_garp_rarp,
> > +                     controller_output_garp_rarp_handler);
> >
> >      engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
> >      engine_add_input(&en_controller_output, &en_acl_id,
> > @@ -5874,6 +6105,8 @@ main(int argc, char *argv[])
> >
> >  sbrec_chassis_template_var_index_by_chassis);
> >      engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
> >                                  sbrec_learned_route_index_by_datapath);
> > +    engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
> > +                                sbrec_mac_binding_by_lport_ip);
> >      engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
> >                                  ovsrec_flow_sample_collector_set_by_id);
> >      engine_ovsdb_node_add_index(&en_ovs_port, "qos", ovsrec_port_by_qos);
> > @@ -6290,9 +6523,12 @@ main(int argc, char *argv[])
> >                          stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
> >                                          time_msec());
> >                          pinctrl_update(ovnsb_idl_loop.idl);
> > +                        const struct ed_type_garp_rarp *grd =
> > engine_get_data(
> > +                            &en_garp_rarp);
> > +                        const struct hmap *garp_rarp_data =
> > +                            grd ? &grd->garp_rarp_data : NULL;
> >                          pinctrl_run(ovnsb_idl_txn,
> >                                      sbrec_datapath_binding_by_key,
> > -                                    sbrec_port_binding_by_datapath,
> >                                      sbrec_port_binding_by_key,
> >                                      sbrec_port_binding_by_name,
> >                                      sbrec_mac_binding_by_lport_ip,
> > @@ -6308,13 +6544,14 @@ main(int argc, char *argv[])
> >
> >  sbrec_bfd_table_get(ovnsb_idl_loop.idl),
> >                                      sbrec_ecmp_nexthop_table_get(
> >                                          ovnsb_idl_loop.idl),
> > -                                    br_int, chassis,
> > +                                    chassis,
> >                                      &runtime_data->local_datapaths,
> >                                      &runtime_data->active_tunnels,
> >
> >  &runtime_data->local_active_ports_ipv6_pd,
> >                                      &runtime_data->local_active_ports_ras,
> >                                      ovsrec_open_vswitch_table_get(
> > -                                            ovs_idl_loop.idl));
> > +                                            ovs_idl_loop.idl),
> > +                                    garp_rarp_data);
> >                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
> >                                         time_msec());
> >                          mirror_run(ovs_idl_txn,
> > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > index 1481b2547..6b4a4a520 100644
> > --- a/controller/pinctrl.c
> > +++ b/controller/pinctrl.c
> > @@ -64,6 +64,7 @@
> >  #include "ip-mcast.h"
> >  #include "ovn-sb-idl.h"
> >  #include "ovn-dns.h"
> > +#include "garp_rarp.h"
> >
> >  VLOG_DEFINE_THIS_MODULE(pinctrl);
> >
> > @@ -233,15 +234,9 @@ static void destroy_send_arps_nds(void);
> >  static void send_garp_rarp_wait(long long int send_garp_rarp_time);
> >  static void send_arp_nd_wait(long long int send_arp_nd_time);
> >  static void send_garp_rarp_prepare(
> > -    struct ovsdb_idl_txn *ovnsb_idl_txn,
> > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > -    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > +    const struct hmap *garp_rarp_data,
> >      const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > -    const struct ovsrec_bridge *,
> > -    const struct sbrec_chassis *,
> > -    const struct hmap *local_datapaths,
> > -    const struct sset *active_tunnels,
> > +    const struct sbrec_chassis *chassis,
> >      const struct ovsrec_open_vswitch_table *ovs_table)
> >      OVS_REQUIRES(pinctrl_mutex);
> >  static void send_garp_rarp_run(struct rconn *swconn,
> > @@ -4066,7 +4061,6 @@ pinctrl_update(const struct ovsdb_idl *idl)
> >  void
> >  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> > -            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> >              struct ovsdb_idl_index *sbrec_port_binding_by_key,
> >              struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >              struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > @@ -4078,13 +4072,13 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >              const struct sbrec_mac_binding_table *mac_binding_table,
> >              const struct sbrec_bfd_table *bfd_table,
> >              const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > -            const struct ovsrec_bridge *br_int,
> >              const struct sbrec_chassis *chassis,
> >              const struct hmap *local_datapaths,
> >              const struct sset *active_tunnels,
> >              const struct shash *local_active_ports_ipv6_pd,
> >              const struct shash *local_active_ports_ras,
> > -            const struct ovsrec_open_vswitch_table *ovs_table)
> > +            const struct ovsrec_open_vswitch_table *ovs_table,
> > +            const struct hmap *garp_rarp_data)
> >  {
> >      ovs_mutex_lock(&pinctrl_mutex);
> >      run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
> > @@ -4092,11 +4086,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >                           sbrec_mac_binding_by_lport_ip);
> >      run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
> >                             sbrec_port_binding_by_key, chassis);
> > -    send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath,
> > -                           sbrec_port_binding_by_name,
> > -                           sbrec_mac_binding_by_lport_ip, ecmp_nh_table,
> > -                           br_int, chassis, local_datapaths,
> > active_tunnels,
> > -                           ovs_table);
> > +    send_garp_rarp_prepare(garp_rarp_data, ecmp_nh_table,
> > +                           chassis, ovs_table);
> >      prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
> >      prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
> >                           local_active_ports_ipv6_pd, chassis,
> > @@ -4785,47 +4776,6 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn
> > *ovnsb_idl_txn,
> >      }
> >  }
> >
> > -/* Simulate the effect of a GARP on local datapaths, i.e., create
> > MAC_Bindings
> > - * on peer router datapaths.
> > - */
> > -static void
> > -send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > -                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > -                  const struct hmap *local_datapaths,
> > -                  const struct sbrec_port_binding *in_pb,
> > -                  struct eth_addr ea, ovs_be32 ip)
> > -{
> > -    if (!ovnsb_idl_txn) {
> > -        return;
> > -    }
> > -
> > -    const struct local_datapath *ldp =
> > -        get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
> > -
> > -    ovs_assert(ldp);
> > -    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > -        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
> > -        const struct sbrec_port_binding *remote =
> > ldp->peer_ports[i].remote;
> > -
> > -        /* Skip "ingress" port. */
> > -        if (local == in_pb) {
> > -            continue;
> > -        }
> > -
> > -        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
> > -                                          "always_learn_from_arp_request",
> > -                                          true);
> > -
> > -        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > -
> > -        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > -        mac_binding_add_to_sb(ovnsb_idl_txn,
> > sbrec_mac_binding_by_lport_ip,
> > -                              remote->logical_port, remote->datapath,
> > -                              ea, ds_cstr(&ip_s), update_only);
> > -        ds_destroy(&ip_s);
> > -    }
> > -}
> > -
> >  static void
> >  run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >                      struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> > @@ -4977,173 +4927,25 @@ wait_put_mac_bindings(struct ovsdb_idl_txn
> > *ovnsb_idl_txn)
> >   * are needed for switches and routers on the broadcast segment to update
> >   * their port-mac and ARP tables.
> >   */
> > -struct garp_rarp_data {
> > -    struct eth_addr ea;          /* Ethernet address of port. */
> > -    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > -    long long int announce_time; /* Next announcement in ms. */
> > -    int backoff;                 /* Backoff timeout for the next
> > -                                  * announcement (in msecs). */
> > -    uint32_t dp_key;             /* Datapath used to output this GARP. */
> > -    uint32_t port_key;           /* Port to inject the GARP into. */
> > -};
> >
> > -/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
> > -static struct shash send_garp_rarp_data;
> > +/* Contains GARPs/RARPs to be sent in struct garp_rarp_node.
> > + * Protected by pinctrl_mutex. */
> > +static struct hmap send_garp_rarp_data;
> >
> >  static void
> >  init_send_garps_rarps(void)
> >  {
> > -    shash_init(&send_garp_rarp_data);
> > +    hmap_init(&send_garp_rarp_data);
> >  }
> >
> >  static void
> >  destroy_send_garps_rarps(void)
> >  {
> > -    shash_destroy_free_data(&send_garp_rarp_data);
> > -}
> > -
> > -/* Runs with in the main ovn-controller thread context. */
> > -static void
> > -add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
> > -              uint32_t dp_key, uint32_t port_key)
> > -{
> > -    struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
> > -    garp_rarp->ea = ea;
> > -    garp_rarp->ipv4 = ip;
> > -    garp_rarp->announce_time = time_msec() + 1000;
> > -    garp_rarp->backoff = 1000; /* msec. */
> > -    garp_rarp->dp_key = dp_key;
> > -    garp_rarp->port_key = port_key;
> > -    shash_add(&send_garp_rarp_data, name, garp_rarp);
> > -
> > -    /* Notify pinctrl_handler so that it can wakeup and process
> > -     * these GARP/RARP requests. */
> > -    notify_pinctrl_handler();
> > -}
> > -
> > -/* Add or update a vif for which GARPs need to be announced. */
> > -static void
> > -send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > -                      struct ovsdb_idl_index
> > *sbrec_mac_binding_by_lport_ip,
> > -                      const struct hmap *local_datapaths,
> > -                      const struct sbrec_port_binding *binding_rec,
> > -                      struct shash *nat_addresses,
> > -                      long long int garp_max_timeout,
> > -                      bool garp_continuous)
> > -{
> > -    volatile struct garp_rarp_data *garp_rarp = NULL;
> > -
> > -    /* Skip localports as they don't need to be announced */
> > -    if (!strcmp(binding_rec->type, "localport")) {
> > -        return;
> > -    }
> > -
> > -    /* Update GARP for NAT IP if it exists.  Consider port bindings with
> > type
> > -     * "l3gateway" for logical switch ports attached to gateway routers,
> > and
> > -     * port bindings with type "patch" for logical switch ports attached
> > to
> > -     * distributed gateway ports. */
> > -    if (!strcmp(binding_rec->type, "l3gateway")
> > -        || !strcmp(binding_rec->type, "patch")) {
> > -        struct lport_addresses *laddrs = NULL;
> > -        while ((laddrs = shash_find_and_delete(nat_addresses,
> > -
> >  binding_rec->logical_port))) {
> > -            int i;
> > -            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > -                char *name = xasprintf("%s-%s", binding_rec->logical_port,
> > -
> > laddrs->ipv4_addrs[i].addr_s);
> > -                garp_rarp = shash_find_data(&send_garp_rarp_data, name);
> > -                if (garp_rarp) {
> > -                    garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> > -                    garp_rarp->port_key = binding_rec->tunnel_key;
> > -                    if (garp_max_timeout != garp_rarp_max_timeout ||
> > -                        garp_continuous != garp_rarp_continuous) {
> > -                        /* reset backoff */
> > -                        garp_rarp->announce_time = time_msec() + 1000;
> > -                        garp_rarp->backoff = 1000; /* msec. */
> > -                    }
> > -                } else if (ovnsb_idl_txn) {
> > -                    add_garp_rarp(name, laddrs->ea,
> > -                                  laddrs->ipv4_addrs[i].addr,
> > -                                  binding_rec->datapath->tunnel_key,
> > -                                  binding_rec->tunnel_key);
> > -                    send_garp_locally(ovnsb_idl_txn,
> > -                                      sbrec_mac_binding_by_lport_ip,
> > -                                      local_datapaths, binding_rec,
> > laddrs->ea,
> > -                                      laddrs->ipv4_addrs[i].addr);
> > -
> > -                }
> > -                free(name);
> > -            }
> > -            /*
> > -             * Send RARPs even if we do not have a ipv4 address as it e.g.
> > -             * happens on ipv6 only ports.
> > -             */
> > -            if (laddrs->n_ipv4_addrs == 0) {
> > -                    char *name = xasprintf("%s-noip",
> > -                                           binding_rec->logical_port);
> > -                    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > name);
> > -                    if (garp_rarp) {
> > -                        garp_rarp->dp_key =
> > binding_rec->datapath->tunnel_key;
> > -                        garp_rarp->port_key = binding_rec->tunnel_key;
> > -                        if (garp_max_timeout != garp_rarp_max_timeout ||
> > -                            garp_continuous != garp_rarp_continuous) {
> > -                            /* reset backoff */
> > -                            garp_rarp->announce_time = time_msec() + 1000;
> > -                            garp_rarp->backoff = 1000; /* msec. */
> > -                        }
> > -                    } else {
> > -                        add_garp_rarp(name, laddrs->ea,
> > -                                      0,
> > binding_rec->datapath->tunnel_key,
> > -                                      binding_rec->tunnel_key);
> > -                    }
> > -                    free(name);
> > -            }
> > -            destroy_lport_addresses(laddrs);
> > -            free(laddrs);
> > -        }
> > -        return;
> > -    }
> > -
> > -    /* Update GARP for vif if it exists. */
> > -    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > -                                binding_rec->logical_port);
> > -    if (garp_rarp) {
> > -        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> > -        garp_rarp->port_key = binding_rec->tunnel_key;
> > -        if (garp_max_timeout != garp_rarp_max_timeout ||
> > -            garp_continuous != garp_rarp_continuous) {
> > -            /* reset backoff */
> > -            garp_rarp->announce_time = time_msec() + 1000;
> > -            garp_rarp->backoff = 1000; /* msec. */
> > -        }
> > -        return;
> > -    }
> > -
> > -    /* Add GARP for new vif. */
> > -    int i;
> > -    for (i = 0; i < binding_rec->n_mac; i++) {
> > -        struct lport_addresses laddrs;
> > -        ovs_be32 ip = 0;
> > -        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > -            continue;
> > -        }
> > -
> > -        if (laddrs.n_ipv4_addrs) {
> > -            ip = laddrs.ipv4_addrs[0].addr;
> > -        }
> > -
> > -        add_garp_rarp(binding_rec->logical_port,
> > -                      laddrs.ea, ip,
> > -                      binding_rec->datapath->tunnel_key,
> > -                      binding_rec->tunnel_key);
> > -        if (ip) {
> > -            send_garp_locally(ovnsb_idl_txn,
> > sbrec_mac_binding_by_lport_ip,
> > -                              local_datapaths, binding_rec, laddrs.ea,
> > ip);
> > -        }
> > -
> > -        destroy_lport_addresses(&laddrs);
> > -        break;
> > +    struct garp_rarp_node *garp_rarp;
> > +    HMAP_FOR_EACH_POP (garp_rarp, hmap_node, &send_garp_rarp_data) {
> > +        garp_rarp_node_free(garp_rarp);
> >      }
> > +    hmap_destroy(&send_garp_rarp_data);
> >  }
> >
> >  struct arp_nd_data {
> > @@ -5289,16 +5091,6 @@ send_arp_nd_update(const struct sbrec_port_binding
> > *pb, const char *nexthop,
> >      }
> >  }
> >
> > -/* Remove a vif from GARP announcements. */
> > -static void
> > -send_garp_rarp_delete(const char *lport)
> > -{
> > -    struct garp_rarp_data *garp_rarp = shash_find_and_delete
> > -                                       (&send_garp_rarp_data, lport);
> > -    free(garp_rarp);
> > -    notify_pinctrl_handler();
> > -}
> > -
> >  void
> >  send_self_originated_neigh_packet(struct rconn *swconn,
> >                                    uint32_t dp_key, uint32_t port_key,
> > @@ -5350,7 +5142,7 @@ send_self_originated_neigh_packet(struct rconn
> > *swconn,
> >
> >  /* Called with in the pinctrl_handler thread context. */
> >  static long long int
> > -send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
> > +send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
> >                 long long int current_time)
> >      OVS_REQUIRES(pinctrl_mutex)
> >  {
> > @@ -6419,242 +6211,12 @@ ip_mcast_querier_wait(long long int query_time)
> >      }
> >  }
> >
> > -/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > ports. */
> > -static void
> > -get_localnet_vifs_l3gwports(
> > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > -    const struct ovsrec_bridge *br_int,
> > -    const struct sbrec_chassis *chassis,
> > -    const struct hmap *local_datapaths,
> > -    struct sset *localnet_vifs,
> > -    struct sset *local_l3gw_ports)
> > -{
> > -    for (int i = 0; i < br_int->n_ports; i++) {
> > -        const struct ovsrec_port *port_rec = br_int->ports[i];
> > -        if (!strcmp(port_rec->name, br_int->name)) {
> > -            continue;
> > -        }
> > -        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > -                                          "ovn-chassis-id");
> > -        if (tunnel_id &&
> > -                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > NULL)) {
> > -            continue;
> > -        }
> > -        const char *localnet = smap_get(&port_rec->external_ids,
> > -                                        "ovn-localnet-port");
> > -        if (localnet) {
> > -            continue;
> > -        }
> > -        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > -            const struct ovsrec_interface *iface_rec =
> > port_rec->interfaces[j];
> > -            if (!iface_rec->n_ofport) {
> > -                continue;
> > -            }
> > -            /* Get localnet vif. */
> > -            const char *iface_id = smap_get(&iface_rec->external_ids,
> > -                                            "iface-id");
> > -            if (!iface_id) {
> > -                continue;
> > -            }
> > -            const struct sbrec_port_binding *pb
> > -                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > iface_id);
> > -            if (!pb || pb->chassis != chassis) {
> > -                continue;
> > -            }
> > -            if (!iface_rec->link_state ||
> > -                    strcmp(iface_rec->link_state, "up")) {
> > -                continue;
> > -            }
> > -            struct local_datapath *ld
> > -                = get_local_datapath(local_datapaths,
> > -                                     pb->datapath->tunnel_key);
> > -            if (ld && ld->localnet_port) {
> > -                sset_add(localnet_vifs, iface_id);
> > -            }
> > -        }
> > -    }
> > -
> > -    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
> > -        sbrec_port_binding_by_datapath);
> > -
> > -    const struct local_datapath *ld;
> > -    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > -        const struct sbrec_port_binding *pb;
> > -
> > -        if (!ld->localnet_port) {
> > -            continue;
> > -        }
> > -
> > -        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
> > -         * that connect to gateway routers (if local), and consider port
> > -         * bindings of type "patch" since they might connect to
> > -         * distributed gateway ports with NAT addresses. */
> > -
> > -        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > -        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > -
> >  sbrec_port_binding_by_datapath) {
> > -            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
> > -                || !strcmp(pb->type, "patch")) {
> > -                sset_add(local_l3gw_ports, pb->logical_port);
> > -            }
> > -        }
> > -    }
> > -    sbrec_port_binding_index_destroy_row(target);
> > -}
> > -
> > -
> > -/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > - * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > - * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
> > - * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
> > - * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > - *
> > - * Returns true if at least 'MAC' is found in 'address', false otherwise.
> > - *
> > - * The caller must call destroy_lport_addresses() and free(*lport). */
> > -static bool
> > -extract_addresses_with_port(const char *addresses,
> > -                            struct lport_addresses *laddrs,
> > -                            char **lport)
> > -{
> > -    int ofs;
> > -    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > -        return false;
> > -    } else if (!addresses[ofs]) {
> > -        return true;
> > -    }
> > -
> > -    struct lexer lexer;
> > -    lexer_init(&lexer, addresses + ofs);
> > -    lexer_get(&lexer);
> > -
> > -    if (lexer.error || lexer.token.type != LEX_T_ID
> > -        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > -        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
> > -        lexer_destroy(&lexer);
> > -        return true;
> > -    }
> > -
> > -    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > -        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > -                          "'is_chassis_resident' in address '%s'",
> > addresses);
> > -        lexer_destroy(&lexer);
> > -        return false;
> > -    }
> > -
> > -    if (lexer.token.type != LEX_T_STRING) {
> > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > -        VLOG_INFO_RL(&rl,
> > -                    "Syntax error: expecting quoted string after "
> > -                    "'is_chassis_resident' in address '%s'", addresses);
> > -        lexer_destroy(&lexer);
> > -        return false;
> > -    }
> > -
> > -    *lport = xstrdup(lexer.token.s);
> > -
> > -    lexer_get(&lexer);
> > -    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > -        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > string in "
> > -                          "'is_chassis_resident()' in address '%s'",
> > -                          addresses);
> > -        lexer_destroy(&lexer);
> > -        return false;
> > -    }
> > -
> > -    lexer_destroy(&lexer);
> > -    return true;
> > -}
> > -
> > -static void
> > -consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > -                     const char *nat_address,
> > -                     const struct sbrec_port_binding *pb,
> > -                     struct sset *nat_address_keys,
> > -                     const struct sbrec_chassis *chassis,
> > -                     const struct sset *active_tunnels,
> > -                     struct shash *nat_addresses)
> > -{
> > -    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > -    char *lport = NULL;
> > -    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > -        || (!lport && !strcmp(pb->type, "patch"))
> > -        || (lport && !lport_is_chassis_resident(
> > -                sbrec_port_binding_by_name, chassis,
> > -                active_tunnels, lport))) {
> > -        destroy_lport_addresses(laddrs);
> > -        free(laddrs);
> > -        free(lport);
> > -        return;
> > -    }
> > -    free(lport);
> > -
> > -    int i;
> > -    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > -        char *name = xasprintf("%s-%s", pb->logical_port,
> > -                                        laddrs->ipv4_addrs[i].addr_s);
> > -        sset_add(nat_address_keys, name);
> > -        free(name);
> > -    }
> > -    if (laddrs->n_ipv4_addrs == 0) {
> > -        char *name = xasprintf("%s-noip", pb->logical_port);
> > -        sset_add(nat_address_keys, name);
> > -        free(name);
> > -    }
> > -    shash_add(nat_addresses, pb->logical_port, laddrs);
> > -}
> > -
> > -static void
> > -get_nat_addresses_and_keys(struct ovsdb_idl_index
> > *sbrec_port_binding_by_name,
> > -                           struct sset *nat_address_keys,
> > -                           struct sset *local_l3gw_ports,
> > -                           const struct sbrec_chassis *chassis,
> > -                           const struct sset *active_tunnels,
> > -                           struct shash *nat_addresses)
> > -{
> > -    const char *gw_port;
> > -    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
> > -        const struct sbrec_port_binding *pb;
> > -
> > -        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> > -        if (!pb) {
> > -            continue;
> > -        }
> > -
> > -        if (pb->n_nat_addresses) {
> > -            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > -                consider_nat_address(sbrec_port_binding_by_name,
> > -                                     pb->nat_addresses[i], pb,
> > -                                     nat_address_keys, chassis,
> > -                                     active_tunnels,
> > -                                     nat_addresses);
> > -            }
> > -        } else {
> > -            /* Continue to support options:nat-addresses for version
> > -             * upgrade. */
> > -            const char *nat_addresses_options = smap_get(&pb->options,
> > -                                                         "nat-addresses");
> > -            if (nat_addresses_options) {
> > -                consider_nat_address(sbrec_port_binding_by_name,
> > -                                     nat_addresses_options, pb,
> > -                                     nat_address_keys, chassis,
> > -                                     active_tunnels,
> > -                                     nat_addresses);
> > -            }
> > -        }
> > -    }
> > -}
> > -
> >  static void
> >  send_garp_rarp_wait(long long int send_garp_rarp_time)
> >  {
> >      /* Set the poll timer for next garp/rarp only if there is data to
> >       * be sent. */
> > -    if (!shash_is_empty(&send_garp_rarp_data)) {
> > +    if (!hmap_is_empty(&send_garp_rarp_data)) {
> >          poll_timer_wait_until(send_garp_rarp_time);
> >      }
> >  }
> > @@ -6674,16 +6236,16 @@ static void
> >  send_garp_rarp_run(struct rconn *swconn, long long int
> > *send_garp_rarp_time)
> >      OVS_REQUIRES(pinctrl_mutex)
> >  {
> > -    if (shash_is_empty(&send_garp_rarp_data)) {
> > +    if (hmap_is_empty(&send_garp_rarp_data)) {
> >          return;
> >      }
> >
> >      /* Send GARPs, and update the next announcement. */
> > -    struct shash_node *iter;
> >      long long int current_time = time_msec();
> >      *send_garp_rarp_time = LLONG_MAX;
> > -    SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
> > -        long long int next_announce = send_garp_rarp(swconn, iter->data,
> > +    struct garp_rarp_node *garp;
> > +    HMAP_FOR_EACH (garp, hmap_node, &send_garp_rarp_data) {
> > +        long long int next_announce = send_garp_rarp(swconn, garp,
> >                                                       current_time);
> >          if (*send_garp_rarp_time > next_announce) {
> >              *send_garp_rarp_time = next_announce;
> > @@ -6743,22 +6305,12 @@ send_arp_nd_run(struct rconn *swconn, long long
> > int *send_arp_nd_time)
> >  /* Called by pinctrl_run(). Runs with in the main ovn-controller
> >   * thread context. */
> >  static void
> > -send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > -                       struct ovsdb_idl_index
> > *sbrec_port_binding_by_datapath,
> > -                       struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > -                       struct ovsdb_idl_index
> > *sbrec_mac_binding_by_lport_ip,
> > +send_garp_rarp_prepare(const struct hmap *garp_rarp_data,
> >                         const struct sbrec_ecmp_nexthop_table
> > *ecmp_nh_table,
> > -                       const struct ovsrec_bridge *br_int,
> >                         const struct sbrec_chassis *chassis,
> > -                       const struct hmap *local_datapaths,
> > -                       const struct sset *active_tunnels,
> >                         const struct ovsrec_open_vswitch_table *ovs_table)
> >      OVS_REQUIRES(pinctrl_mutex)
> >  {
> > -    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > -    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
> > -    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > -    struct shash nat_addresses;
> >      unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> >      unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> >      bool garp_continuous = false, continuous_arp_nd = true;
> > @@ -6778,51 +6330,13 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > *ovnsb_idl_txn,
> >          continuous_arp_nd = !!max_arp_nd_timeout;
> >      }
> >
> > -    shash_init(&nat_addresses);
> > +    bool reset_timers = (garp_max_timeout != garp_rarp_max_timeout ||
> > +                         garp_continuous != garp_rarp_continuous);
> >
> > -    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
> > -                                sbrec_port_binding_by_name,
> > -                                br_int, chassis, local_datapaths,
> > -                                &localnet_vifs, &local_l3gw_ports);
> > -
> > -    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
> > -                               &nat_ip_keys, &local_l3gw_ports,
> > -                               chassis, active_tunnels,
> > -                               &nat_addresses);
> > -
> > -    /* For deleted ports and deleted nat ips, remove from
> > -     * send_garp_rarp_data. */
> > -    struct shash_node *iter;
> > -    SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
> > -        if (!sset_contains(&localnet_vifs, iter->name) &&
> > -            !sset_contains(&nat_ip_keys, iter->name)) {
> > -            send_garp_rarp_delete(iter->name);
> > -        }
> > -    }
> > -
> > -    /* Update send_garp_rarp_data. */
> > -    const char *iface_id;
> > -    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > -        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > -            sbrec_port_binding_by_name, iface_id);
> > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > false)) {
> > -            send_garp_rarp_update(ovnsb_idl_txn,
> > -                                  sbrec_mac_binding_by_lport_ip,
> > -                                  local_datapaths, pb, &nat_addresses,
> > -                                  garp_max_timeout, garp_continuous);
> > -        }
> > -    }
> > -
> > -    /* Update send_garp_rarp_data for nat-addresses. */
> > -    const char *gw_port;
> > -    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > -        const struct sbrec_port_binding *pb
> > -            = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
> > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > false)) {
> > -            send_garp_rarp_update(ovnsb_idl_txn,
> > sbrec_mac_binding_by_lport_ip,
> > -                                  local_datapaths, pb, &nat_addresses,
> > -                                  garp_max_timeout, garp_continuous);
> > -        }
> > +    if (garp_rarp_sync(garp_rarp_data, &send_garp_rarp_data,
> > reset_timers)) {
> > +        /* Notify pinctrl_handler so that it can wakeup and process
> > +         * these GARP/RARP requests. */
> > +        notify_pinctrl_handler();
> >      }
> >
> >      arp_nd_sync_data(ecmp_nh_table);
> > @@ -6836,21 +6350,6 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > *ovnsb_idl_txn,
> >          }
> >      }
> >
> > -    /* pinctrl_handler thread will send the GARPs. */
> > -
> > -    sset_destroy(&localnet_vifs);
> > -    sset_destroy(&local_l3gw_ports);
> > -
> > -    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > -        struct lport_addresses *laddrs = iter->data;
> > -        destroy_lport_addresses(laddrs);
> > -        shash_delete(&nat_addresses, iter);
> > -        free(laddrs);
> > -    }
> > -    shash_destroy(&nat_addresses);
> > -
> > -    sset_destroy(&nat_ip_keys);
> > -
> >      garp_rarp_max_timeout = garp_max_timeout;
> >      garp_rarp_continuous = garp_continuous;
> >
> > @@ -6862,7 +6361,7 @@ static bool
> >  may_inject_pkts(void)
> >  {
> >      return (!shash_is_empty(&ipv6_ras) ||
> > -            !shash_is_empty(&send_garp_rarp_data) ||
> > +            !hmap_is_empty(&send_garp_rarp_data) ||
> >              ipv6_prefixd_should_inject() ||
> >              !ovs_list_is_empty(&mcast_query_list) ||
> >              buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)
> > ||
> > diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> > index e33d7cbf5..f57f5c306 100644
> > --- a/controller/pinctrl.h
> > +++ b/controller/pinctrl.h
> > @@ -44,24 +44,24 @@ struct sbrec_mac_binding_table;
> >  void pinctrl_init(void);
> >  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> >                   struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> > -                 struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> >                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
> >                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >                   struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> >                   struct ovsdb_idl_index *sbrec_igmp_groups,
> >                   struct ovsdb_idl_index *sbrec_ip_multicast_opts,
> >                   struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
> > -                 const struct sbrec_controller_event_table *,
> > -                 const struct sbrec_service_monitor_table *,
> > -                 const struct sbrec_mac_binding_table *,
> > -                 const struct sbrec_bfd_table *,
> > -                 const struct sbrec_ecmp_nexthop_table *,
> > -                 const struct ovsrec_bridge *, const struct sbrec_chassis
> > *,
> > +                 const struct sbrec_controller_event_table *ce_table,
> > +                 const struct sbrec_service_monitor_table *svc_mon_table,
> > +                 const struct sbrec_mac_binding_table *mac_binding_table,
> > +                 const struct sbrec_bfd_table *bfd_table,
> > +                 const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > +                 const struct sbrec_chassis *chassis,
> >
> 
> nit: Unrelated change.

will be cleaned up.

Thanks a lot,
Felix

> 
>                   const struct hmap *local_datapaths,
> >                   const struct sset *active_tunnels,
> >                   const struct shash *local_active_ports_ipv6_pd,
> >                   const struct shash *local_active_ports_ras,
> > -                 const struct ovsrec_open_vswitch_table *ovs_table);
> > +                 const struct ovsrec_open_vswitch_table *ovs_table,
> > +                 const struct hmap *garp_rarp_data);
> >  void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
> >  void pinctrl_destroy(void);
> >
> > --
> > 2.43.0
> >
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> >
> >
> Thanks,
> Ales
Ales Musil April 8, 2025, 5:58 a.m. UTC | #3
On Mon, Apr 7, 2025 at 2:00 PM Felix Huettner <felix.huettner@stackit.cloud>
wrote:

> On Thu, Apr 03, 2025 at 01:25:10PM +0200, Ales Musil wrote:
> > On Thu, Mar 20, 2025 at 1:02 PM Felix Huettner via dev <
> > ovs-dev@openvswitch.org> wrote:
> >
> > > Previously on each call to pinctrl_run all addresses that need to be
> > > announced via garps or rarps where computed newly. On larger external
> > > networks that might take significant time.
> > > However the content of the data can only change in a run of the I+P
> > > engine. So we move the whole logic to an engine node and only handle
> the
> > > minimal needed set in pinctrl_run.
> > >
> > > Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> > > Co-Authored-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > > Signed-off-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > > Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
> > > ---
> > >
> >
> > Hi Felix,
> >
> > thank you for the patch. I have a few comments down below.
>
> Hi Ales,
>

Hi Felix,

thank you for working on this.


> >
> > v6->v7: added ack
> > >
> > >  controller/automake.mk      |   4 +-
> > >  controller/garp_rarp.c      | 626 ++++++++++++++++++++++++++++++++++++
> > >  controller/garp_rarp.h      |  61 ++++
> > >  controller/ovn-controller.c | 243 +++++++++++++-
> > >  controller/pinctrl.c        | 559 ++------------------------------
> > >  controller/pinctrl.h        |  16 +-
> > >  6 files changed, 967 insertions(+), 542 deletions(-)
> > >  create mode 100644 controller/garp_rarp.c
> > >  create mode 100644 controller/garp_rarp.h
> > >
> > > diff --git a/controller/automake.mk b/controller/automake.mk
> > > index 067c2e6e4..f0638ea97 100644
> > > --- a/controller/automake.mk
> > > +++ b/controller/automake.mk
> > > @@ -59,7 +59,9 @@ controller_ovn_controller_SOURCES = \
> > >         controller/route-exchange.h \
> > >         controller/route-table-notify.h \
> > >         controller/route.h \
> > > -       controller/route.c
> > > +       controller/route.c \
> > > +       controller/garp_rarp.h \
> > > +       controller/garp_rarp.c
> > >
> > >  if HAVE_NETLINK
> > >  controller_ovn_controller_SOURCES += \
> > > diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
> > > new file mode 100644
> > > index 000000000..916ff896a
> > > --- /dev/null
> > > +++ b/controller/garp_rarp.c
> > > @@ -0,0 +1,626 @@
> > > +/*
> > > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > > + *
> > > + * Licensed under the Apache License, Version 2.0 (the "License");
> > > + * you may not use this file except in compliance with the License.
> > > + * You may obtain a copy of the License at:
> > > + *
> > > + *     http://www.apache.org/licenses/LICENSE-2.0
> > > + *
> > > + * Unless required by applicable law or agreed to in writing, software
> > > + * distributed under the License is distributed on an "AS IS" BASIS,
> > > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> > > implied.
> > > + * See the License for the specific language governing permissions and
> > > + * limitations under the License.
> > > + */
> > > +
> > > +#include <config.h>
> > > +
> > > +#include <net/if.h>
> > > +#include <stdbool.h>
> > > +#include <stddef.h>
> > > +
> > > +#include "controller/encaps.h"
> > > +#include "controller/local_data.h"
> > > +#include "controller/lport.h"
> > > +#include "hash.h"
> > > +#include "mac-binding-index.h"
> > > +#include "openvswitch/hmap.h"
> > > +#include "openvswitch/vlog.h"
> > > +#include "ovn/lex.h"
> > > +#include "packets.h"
> > > +#include "smap.h"
> > > +#include "sset.h"
> > > +#include "openvswitch/shash.h"
> > > +#include "garp_rarp.h"
> > > +#include "ovn-sb-idl.h"
> > > +#include "vswitch-idl.h"
> > > +
> > > +VLOG_DEFINE_THIS_MODULE(garp_rarp);
> > > +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> > > +
> > > +/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > > ports. */
> > > +static void
> > > +get_localnet_vifs_l3gwports(
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > +    const struct ovsrec_bridge *br_int,
> > > +    const struct sbrec_chassis *chassis,
> > > +    const struct hmap *local_datapaths,
> > > +    struct sset *localnet_vifs,
> > > +    struct sset *local_l3gw_ports)
> > > +{
> > > +    for (int i = 0; i < br_int->n_ports; i++) {
> > > +        const struct ovsrec_port *port_rec = br_int->ports[i];
> > > +        if (!strcmp(port_rec->name, br_int->name)) {
> > > +            continue;
> > > +        }
> > > +        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > > +                                          "ovn-chassis-id");
> > > +        if (tunnel_id &&
> > > +                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > > NULL)) {
> > > +            continue;
> > > +        }
> > > +        const char *localnet = smap_get(&port_rec->external_ids,
> > > +                                        "ovn-localnet-port");
> > > +        if (localnet) {
> > > +            continue;
> > > +        }
> > > +        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > > +            const struct ovsrec_interface *iface_rec =
> > > port_rec->interfaces[j];
> > > +            if (!iface_rec->n_ofport) {
> > > +                continue;
> > > +            }
> > > +            /* Get localnet vif. */
> > > +            const char *iface_id = smap_get(&iface_rec->external_ids,
> > > +                                            "iface-id");
> > > +            if (!iface_id) {
> > > +                continue;
> > > +            }
> > > +            const struct sbrec_port_binding *pb
> > > +                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > > iface_id);
> > > +            if (!pb || pb->chassis != chassis) {
> > > +                continue;
> > > +            }
> > > +            if (!iface_rec->link_state ||
> > > +                    strcmp(iface_rec->link_state, "up")) {
> > > +                continue;
> > > +            }
> > > +            struct local_datapath *ld
> > > +                = get_local_datapath(local_datapaths,
> > > +                                     pb->datapath->tunnel_key);
> > > +            if (ld && ld->localnet_port) {
> > > +                sset_add(localnet_vifs, iface_id);
> > > +            }
> > > +        }
> > > +    }
> > >
> >
> > I know that this is old code but it makes me wonder if there's a better
> > way to find local vifs that are connected to localnet port. I think
> > it should be doable through bindings.
>
> Actually it seems like we can get this easily in the local_datapaths
> loop below. I will update this in the next version. It is so simple that
> i am unsure if i missed something.


This part of pinctrl is really old at this point, back then there
probably wasn't anything like local_datapaths. And by using them
we can actually remove the dependency on "en_ovs_open_vswitch" and
"en_ovs_bridge" which is a good thing. Less dependencies that can
cause recomputes is better.


>
> >
> > +
> > > +    struct sbrec_port_binding *target =
> sbrec_port_binding_index_init_row(
> > > +        sbrec_port_binding_by_datapath);
> > > +
> > > +    const struct local_datapath *ld;
> > > +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > > +        const struct sbrec_port_binding *pb;
> > > +
> > > +        if (!ld->localnet_port) {
> > > +            continue;
> > > +        }
> > > +
> > > +        /* Get l3gw ports.  Consider port bindings with type
> "l3gateway"
> > > +         * that connect to gateway routers (if local), and consider
> port
> > > +         * bindings of type "patch" since they might connect to
> > > +         * distributed gateway ports with NAT addresses. */
> > > +
> > > +        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > > +        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > > +
> > >  sbrec_port_binding_by_datapath) {
> > > +            if ((!strcmp(pb->type, "l3gateway") && pb->chassis ==
> chassis)
> > > +                || !strcmp(pb->type, "patch")) {
> > > +                sset_add(local_l3gw_ports, pb->logical_port);
> > > +            }
> > > +        }
> > > +    }
> > > +    sbrec_port_binding_index_destroy_row(target);
> > > +}
> > > +
> > > +
> > > +/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > > + * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > > + * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid
> IPv4
> > > + * or IPv6 address, and stores them in the 'ipv4_addrs' and
> 'ipv6_addrs'
> > > + * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > > + *
> > > + * Returns true if at least 'MAC' is found in 'address', false
> otherwise.
> > > + *
> > > + * The caller must call destroy_lport_addresses() and free(*lport). */
> > > +static bool
> > > +extract_addresses_with_port(const char *addresses,
> > > +                            struct lport_addresses *laddrs,
> > > +                            char **lport)
> > > +{
> > > +    int ofs;
> > > +    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > > +        return false;
> > > +    } else if (!addresses[ofs]) {
> > > +        return true;
> > > +    }
> > > +
> > > +    struct lexer lexer;
> > > +    lexer_init(&lexer, addresses + ofs);
> > > +    lexer_get(&lexer);
> > > +
> > > +    if (lexer.error || lexer.token.type != LEX_T_ID
> > > +        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > > +        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address",
> addresses);
> > > +        lexer_destroy(&lexer);
> > > +        return true;
> > > +    }
> > > +
> > > +    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > > +        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > > +                          "'is_chassis_resident' in address '%s'",
> > > addresses);
> > > +        lexer_destroy(&lexer);
> > > +        return false;
> > > +    }
> > > +
> > > +    if (lexer.token.type != LEX_T_STRING) {
> > > +        VLOG_INFO_RL(&rl,
> > > +                    "Syntax error: expecting quoted string after "
> > > +                    "'is_chassis_resident' in address '%s'",
> addresses);
> > > +        lexer_destroy(&lexer);
> > > +        return false;
> > > +    }
> > > +
> > > +    *lport = xstrdup(lexer.token.s);
> > > +
> > > +    lexer_get(&lexer);
> > > +    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > > +        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > > string in "
> > > +                          "'is_chassis_resident()' in address '%s'",
> > > +                          addresses);
> > > +        lexer_destroy(&lexer);
> > > +        return false;
> > > +    }
> > > +
> > > +    lexer_destroy(&lexer);
> > > +    return true;
> > > +}
> > > +
> > > +static void
> > > +consider_nat_address(struct ovsdb_idl_index
> *sbrec_port_binding_by_name,
> > > +                     const char *nat_address,
> > > +                     const struct sbrec_port_binding *pb,
> > > +                     struct sset *nat_address_keys,
> > > +                     const struct sbrec_chassis *chassis,
> > > +                     const struct sset *active_tunnels,
> > > +                     struct shash *nat_addresses,
> > > +                     struct sset *non_local_lports,
> > > +                     struct sset *local_lports)
> > > +{
> > > +    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > > +    char *lport = NULL;
> > > +    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > > +        || (!lport && !strcmp(pb->type, "patch"))) {
> > > +        destroy_lport_addresses(laddrs);
> > > +        free(laddrs);
> > > +        free(lport);
> > > +        return;
> > > +    }
> > > +    if (lport) {
> > > +        if (!lport_is_chassis_resident(sbrec_port_binding_by_name,
> > > +                chassis, active_tunnels, lport)) {
> > > +            sset_add(non_local_lports, lport);
> > > +            destroy_lport_addresses(laddrs);
> > > +            free(laddrs);
> > > +            free(lport);
> > > +            return;
> > > +        } else {
> > > +            sset_add(local_lports, lport);
> > > +        }
> > > +    }
> > > +    free(lport);
> > > +
> > > +    int i;
> > > +    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > +        char *name = xasprintf("%s-%s", pb->logical_port,
> > > +                                        laddrs->ipv4_addrs[i].addr_s);
> > > +        sset_add(nat_address_keys, name);
> > > +        free(name);
> > > +    }
> > > +    if (laddrs->n_ipv4_addrs == 0) {
> > > +        char *name = xasprintf("%s-noip", pb->logical_port);
> > > +        sset_add(nat_address_keys, name);
> > > +        free(name);
> > > +    }
> > > +    shash_add(nat_addresses, pb->logical_port, laddrs);
> > > +}
> > > +
> > > +static void
> > > +get_nat_addresses_and_keys(struct ovsdb_idl_index
> > > *sbrec_port_binding_by_name,
> > > +                           struct sset *nat_address_keys,
> > > +                           struct sset *local_l3gw_ports,
> > > +                           const struct sbrec_chassis *chassis,
> > > +                           const struct sset *active_tunnels,
> > > +                           struct shash *nat_addresses,
> > > +                           struct sset *non_local_lports,
> > > +                           struct sset *local_lports)
> > > +{
> > > +    const char *gw_port;
> > > +    SSET_FOR_EACH (gw_port, local_l3gw_ports) {
> > > +        const struct sbrec_port_binding *pb;
> > > +
> > > +        pb = lport_lookup_by_name(sbrec_port_binding_by_name,
> gw_port);
> > > +        if (!pb) {
> > > +            continue;
> > > +        }
> > > +
> > > +        if (pb->n_nat_addresses) {
> > > +            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > > +                consider_nat_address(sbrec_port_binding_by_name,
> > > +                                     pb->nat_addresses[i], pb,
> > > +                                     nat_address_keys, chassis,
> > > +                                     active_tunnels,
> > > +                                     nat_addresses,
> > > +                                     non_local_lports,
> > > +                                     local_lports);
> > > +            }
> > > +        } else {
> > > +            /* Continue to support options:nat-addresses for version
> > > +             * upgrade. */
> > > +            const char *nat_addresses_options = smap_get(&pb->options,
> > > +
>  "nat-addresses");
> > > +            if (nat_addresses_options) {
> > > +                consider_nat_address(sbrec_port_binding_by_name,
> > > +                                     nat_addresses_options, pb,
> > > +                                     nat_address_keys, chassis,
> > > +                                     active_tunnels,
> > > +                                     nat_addresses,
> > > +                                     non_local_lports,
> > > +                                     local_lports);
> > > +            }
> > > +        }
> > > +    }
> > > +}
> > > +
> > > +static uint32_t
> > > +garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
> > > +                    uint32_t port_key)
> > > +{
> > > +    return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
> > > +}
> > > +
> > > +static uint32_t
> > > +garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
> > > +{
> > > +    return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
> > > +}
> > > +
> > > +static void
> > > +garp_rarp_node_add(struct hmap *map, const struct eth_addr ea,
> ovs_be32
> > > ip,
> > > +                   uint32_t dp_key, uint32_t port_key)
> > > +{
> > > +    struct garp_rarp_node *grn = xmalloc(sizeof *grn);
> > > +    grn->ea = ea;
> > > +    grn->ipv4 = ip;
> > > +    grn->announce_time = time_msec() + 1000;
> > > +    grn->backoff = 1000; /* msec. */
> > > +    grn->dp_key = dp_key;
> > > +    grn->port_key = port_key;
> > > +    hmap_insert(map, &grn->hmap_node,
> garp_rarp_node_hash_struct(grn));
> > > +}
> > > +
> > > +/* Searches for a given garp_rarp_node in a hmap. Ignores the
> > > announce_time
> > > + * and backoff field since they might be different based on runtime.
> */
> > > +static struct garp_rarp_node*
> > > +garp_rarp_lookup(const struct hmap *map, const struct garp_rarp_node
> > > *search)
> > > +{
> > > +    struct garp_rarp_node *n;
> > > +    uint32_t hash = garp_rarp_node_hash_struct(search);
> > > +    HMAP_FOR_EACH_WITH_HASH (n, hmap_node, hash, map) {
> > > +        if (!eth_addr_equals(search->ea, n->ea)) {
> > > +            continue;
> > > +        }
> > > +
> > > +        if (search->ipv4 != n->ipv4) {
> > > +            continue;
> > > +        }
> > > +
> > > +        if (search->dp_key != n->dp_key) {
> > > +            continue;
> > > +        }
> > > +
> > > +        if (search->port_key != n->port_key) {
> > > +            continue;
> > > +        }
> > > +
> > > +        return n;
> > > +    }
> > > +    return NULL;
> > > +}
> > > +
> > > +/* Update or add an IP-MAC binding for 'logical_port'.
> > > + * Caller should make sure that 'ovnsb_idl_txn' is valid. */
> > > +static void
> > > +mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > +                      struct ovsdb_idl_index
> > > *sbrec_mac_binding_by_lport_ip,
> > > +                      const char *logical_port,
> > > +                      const struct sbrec_datapath_binding *dp,
> > > +                      struct eth_addr ea, const char *ip,
> > > +                      bool update_only)
> > > +{
> > > +    /* Convert ethernet argument to string form for database. */
> > > +    char mac_string[ETH_ADDR_STRLEN + 1];
> > > +    snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT,
> > > ETH_ADDR_ARGS(ea));
> > > +
> > > +    const struct sbrec_mac_binding *b =
> > > +            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
> > > +                               logical_port, ip);
> > > +    if (!b) {
> > > +        if (update_only) {
> > > +            return;
> > > +        }
> > > +        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
> > > +        sbrec_mac_binding_set_logical_port(b, logical_port);
> > > +        sbrec_mac_binding_set_ip(b, ip);
> > > +        sbrec_mac_binding_set_datapath(b, dp);
> > > +    }
> > > +
> > > +    if (strcmp(b->mac, mac_string)) {
> > > +        sbrec_mac_binding_set_mac(b, mac_string);
> > > +
> > > +        /* For backward compatibility check if timestamp column is
> > > available
> > > +         * in SB DB. */
> > > +        if (sbrec_server_has_mac_binding_table_col_timestamp(
> > > +                ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
> > > +            VLOG_DBG("Setting MAC binding timestamp for "
> > > +                     "ip:%s mac:%s port:%s to %lld",
> > > +                     b->ip, b->mac, logical_port, time_wall_msec());
> > > +            sbrec_mac_binding_set_timestamp(b, time_wall_msec());
> > > +        }
> > > +    }
> > > +}
> > >
> >
> > Unless I've missed something this is duplicated from pinctrl.c, how about
> > moving
> > this function to a module where it could be used by both?
>
> Sorry for missing that. Will be centralized in the next version.
>
> >
> >
> > > +
> > > +/* Simulate the effect of a GARP on local datapaths, i.e., create
> > > MAC_Bindings
> > > + * on peer router datapaths.
> > > + */
> > > +static void
> > > +send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
> > > +                  const struct sbrec_port_binding *in_pb,
> > > +                  struct eth_addr ea, ovs_be32 ip)
> > > +{
> > > +    if (!r_ctx_in->ovnsb_idl_txn) {
> > > +        return;
> > > +    }
> > > +
> > > +    const struct local_datapath *ldp =
> > > +        get_local_datapath(r_ctx_in->local_datapaths,
> > > +                           in_pb->datapath->tunnel_key);
> > > +
> > > +    ovs_assert(ldp);
> > > +    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > > +        const struct sbrec_port_binding *local =
> ldp->peer_ports[i].local;
> > > +        const struct sbrec_port_binding *remote =
> > > ldp->peer_ports[i].remote;
> > > +
> > > +        /* Skip "ingress" port. */
> > > +        if (local == in_pb) {
> > > +            continue;
> > > +        }
> > > +
> > > +        bool update_only =
> !smap_get_bool(&remote->datapath->external_ids,
> > > +
> "always_learn_from_arp_request",
> > > +                                          true);
> > > +
> > > +        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > > +
> > > +        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > > +        mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
> > > +                              r_ctx_in->sbrec_mac_binding_by_lport_ip,
> > > +                              remote->logical_port, remote->datapath,
> > > +                              ea, ds_cstr(&ip_s), update_only);
> > > +        ds_destroy(&ip_s);
> > > +    }
> > > +}
> > > +
> > > +/* Add or update a vif for which GARPs need to be announced. */
> > > +static void
> > > +send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
> > > +                      const struct sbrec_port_binding *binding_rec,
> > > +                      struct shash *nat_addresses,
> > > +                      struct hmap *garp_rarp_data)
> > > +{
> > > +    /* Skip localports as they don't need to be announced */
> > > +    if (!strcmp(binding_rec->type, "localport")) {
> > > +        return;
> > > +    }
> > > +
> > > +    /* Update GARP for NAT IP if it exists.  Consider port bindings
> with
> > > type
> > > +     * "l3gateway" for logical switch ports attached to gateway
> routers,
> > > and
> > > +     * port bindings with type "patch" for logical switch ports
> attached
> > > to
> > > +     * distributed gateway ports. */
> > > +    if (!strcmp(binding_rec->type, "l3gateway")
> > > +        || !strcmp(binding_rec->type, "patch")) {
> > > +        struct lport_addresses *laddrs = NULL;
> > > +        while ((laddrs = shash_find_and_delete(nat_addresses,
> > > +
> > >  binding_rec->logical_port))) {
> > > +            int i;
> > > +            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > > +                              laddrs->ipv4_addrs[i].addr,
> > > +                              binding_rec->datapath->tunnel_key,
> > > +                              binding_rec->tunnel_key);
> > > +                send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
> > > +                                  laddrs->ipv4_addrs[i].addr);
> > > +            }
> > > +            /*
> > > +             * Send RARPs even if we do not have a ipv4 address as it
> e.g.
> > > +             * happens on ipv6 only ports.
> > > +             */
> > > +            if (laddrs->n_ipv4_addrs == 0) {
> > > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > > +                              0, binding_rec->datapath->tunnel_key,
> > > +                              binding_rec->tunnel_key);
> > > +            }
> > > +            destroy_lport_addresses(laddrs);
> > > +            free(laddrs);
> > > +        }
> > > +        return;
> > > +    }
> > > +
> > > +    /* Add GARP for new vif. */
> > > +    int i;
> > > +    for (i = 0; i < binding_rec->n_mac; i++) {
> > > +        struct lport_addresses laddrs;
> > > +        ovs_be32 ip = 0;
> > > +        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > > +            continue;
> > > +        }
> > > +
> > > +        if (laddrs.n_ipv4_addrs) {
> > > +            ip = laddrs.ipv4_addrs[0].addr;
> > > +        }
> > > +
> > > +        garp_rarp_node_add(garp_rarp_data,
> > > +                      laddrs.ea, ip,
> > > +                      binding_rec->datapath->tunnel_key,
> > > +                      binding_rec->tunnel_key);
> > > +        if (ip) {
> > > +            send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
> > > +        }
> > > +
> > > +        destroy_lport_addresses(&laddrs);
> > > +        break;
> > > +    }
> > > +}
> > > +
> > > +static struct garp_rarp_node*
> > > +garp_rarp_node_clone(const struct garp_rarp_node *src)
> > > +{
> > > +    struct garp_rarp_node *dst = xzalloc(sizeof *dst);
> > > +    dst->ea = src->ea;
> > > +    dst->ipv4 = src->ipv4;
> > > +    dst->announce_time = time_msec() + 1000;
> > > +    dst->backoff = 1000;
> > > +    dst->dp_key = src->dp_key;
> > > +    dst->port_key = src->port_key;
> > > +    return dst;
> > > +}
> > > +
> > > +static void
> > > +garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in,
> > > +                struct garp_rarp_ctx_out *r_ctx_out)
> > > +{
> > > +    struct garp_rarp_node *grn;
> > > +    HMAP_FOR_EACH_POP (grn, hmap_node, r_ctx_out->garp_rarp_data) {
> > > +        garp_rarp_node_free(grn);
> > > +    }
> > > +    hmap_clear(r_ctx_out->garp_rarp_data);
> > > +    sset_clear(r_ctx_in->non_local_lports);
> > > +    sset_clear(r_ctx_in->local_lports);
> > > +}
> > > +
> > > +void
> > > +garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in,
> > > +              struct garp_rarp_ctx_out *r_ctx_out)
> > > +{
> > > +    garp_rarp_clear(r_ctx_in, r_ctx_out);
> > > +
> > > +    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > > +    struct sset local_l3gw_ports =
> SSET_INITIALIZER(&local_l3gw_ports);
> > > +    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > > +    struct shash nat_addresses;
> > > +
> > > +    shash_init(&nat_addresses);
> > > +
> > > +
> get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
> > > +                                r_ctx_in->sbrec_port_binding_by_name,
> > > +                                r_ctx_in->br_int, r_ctx_in->chassis,
> > > +                                r_ctx_in->local_datapaths,
> > > +                                &localnet_vifs, &local_l3gw_ports);
> > > +
> > > +    get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
> > > +                               &nat_ip_keys, &local_l3gw_ports,
> > > +                               r_ctx_in->chassis,
> > > r_ctx_in->active_tunnels,
> > > +                               &nat_addresses,
> r_ctx_in->non_local_lports,
> > > +                               r_ctx_in->local_lports);
> > > +
> > > +    /* Update send_garp_rarp_data. */
> > > +    const char *iface_id;
> > > +    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > +            r_ctx_in->sbrec_port_binding_by_name, iface_id);
> > > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > false)) {
> > > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > > +                                  r_ctx_out->garp_rarp_data);
> > > +        }
> > > +    }
> > > +
> > > +    /* Update send_garp_rarp_data for nat-addresses. */
> > > +    const char *gw_port;
> > > +    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > +            r_ctx_in->sbrec_port_binding_by_name, gw_port);
> > > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > false)) {
> > > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > > +                                  r_ctx_out->garp_rarp_data);
> > > +        }
> > > +    }
> > > +
> > > +    sset_destroy(&localnet_vifs);
> > > +    sset_destroy(&local_l3gw_ports);
> > > +
> > > +    struct shash_node *iter;
> > > +    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > > +        struct lport_addresses *laddrs = iter->data;
> > > +        destroy_lport_addresses(laddrs);
> > > +        shash_delete(&nat_addresses, iter);
> > > +        free(laddrs);
> > > +    }
> > > +    shash_destroy(&nat_addresses);
> > > +
> > > +    sset_destroy(&nat_ip_keys);
> > > +}
> > > +
> > > +/* Syncs a hmap of garp_rarp_node to another hmap. The elements in the
> > > + * destination hmap are initialized with valid announce times based
> on the
> > > + * current timestamp.
> > > + * If source is NULL then destination will at most change the timer if
> > > + * reset_timers is set.
> > > + * If reset_timers is true then the timers of all entries in the
> > > destination
> > > + * hmap will be reset to default values.
> > > + * Returns true if the destination hmap was chnaged. */
> > > +bool
> > > +garp_rarp_sync(const struct hmap *source, struct hmap *dest, bool
> > > reset_timers)
> > > +{
> > > +    struct garp_rarp_node *grn;
> > > +    bool changed = false;
> > > +
> > > +    HMAP_FOR_EACH_SAFE (grn, hmap_node, dest) {
> > > +        if (source && !garp_rarp_lookup(source, grn)) {
> > > +            hmap_remove(dest, &grn->hmap_node);
> > > +            garp_rarp_node_free(grn);
> > > +            changed = true;
> > > +        } else if (reset_timers) {
> > > +            grn->announce_time = time_msec() + 1000;
> > > +            grn->backoff = 1000;
> > > +            changed = true;
> > > +        }
> > > +    }
> > > +
> > > +    if (source) {
> > > +        HMAP_FOR_EACH (grn, hmap_node, source) {
> > > +            if (!garp_rarp_lookup(dest, grn)) {
> > > +                uint32_t hash = garp_rarp_node_hash_struct(grn);
> > > +                hmap_insert(dest,
> &garp_rarp_node_clone(grn)->hmap_node,
> > > hash);
> > > +                changed = true;
> > > +            }
> > > +        }
> > > +    }
> > > +
> > > +    return changed;
> > > +}
> > >
> >
> > That's a lot of copying between the hmaps, why can't we indicate that the
> > announcement was sent for the last time and use just the status instead
> > of copying parts of the hmap?
>
> The idea was that any state that needs to be persistently kept is part
> of the pinctrl module. The engine node is then free to discard the hmap
> and generate it again from scratch.
>
> Also the data is needed in the pinctrl thread separately from the main
> thread. So copying seemed like the best solution to avoid needing to
> lock the data between pinctrl and the engine node.
>

Yes the locking might be a problem, how about using CMAP instead?
That should get rid of the locking problem and we could avoid the
copies. I'll leave it up to you if you would like to do it as part
of this patch or as follow up.


>
> >
> >
> > > +
> > > +void
> > > +garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
> > > +{
> > > +    free(garp_rarp);
> > > +}
> > > diff --git a/controller/garp_rarp.h b/controller/garp_rarp.h
> > > new file mode 100644
> > > index 000000000..b85e75a7c
> > > --- /dev/null
> > > +++ b/controller/garp_rarp.h
> > > @@ -0,0 +1,61 @@
> > > +/*
> > > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > > + *
> > > + * 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 GARP_RARP_H
> > > +#define GARP_RARP_H 1
> > > +
> > > +#include "openvswitch/hmap.h"
> > > +#include "openvswitch/types.h"
> > > +
> > > +/* Contains a single mac and ip address that should be announced. */
> > > +struct garp_rarp_node {
> > > +    struct hmap_node hmap_node;
> > > +    struct eth_addr ea;          /* Ethernet address of port. */
> > > +    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > > +    long long int announce_time; /* Next announcement in ms.
> > > +                                  * If LLONG_MAX there should be no
> > > +                                  * annoucement. */
> > > +    int backoff;                 /* Backoff timeout for the next
> > > +                                  * announcement (in msecs). */
> > > +    uint32_t dp_key;             /* Datapath used to output this
> GARP. */
> > > +    uint32_t port_key;           /* Port to inject the GARP into. */
> > > +};
> > > +
> > > +struct garp_rarp_ctx_in {
> > > +    struct ovsdb_idl_txn *ovnsb_idl_txn;
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name;
> > > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
> > > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
> > > +    const struct ovsrec_bridge *br_int;
> > > +    const struct sbrec_chassis *chassis;
> > > +    const struct hmap *local_datapaths;
> > > +    const struct sset *active_tunnels;
> > > +    struct sset *non_local_lports;
> > > +    struct sset *local_lports;
> > > +};
> > > +
> > > +struct garp_rarp_ctx_out {
> > > +    /* Contains struct garp_rarp_node. */
> > > +    struct hmap *garp_rarp_data;
> > > +};
> > > +
> > > +void garp_rarp_run(struct garp_rarp_ctx_in *, struct
> garp_rarp_ctx_out *);
> > > +bool garp_rarp_sync(const struct hmap *source, struct hmap *dest,
> > > +                    bool reset_timers);
> > > +void garp_rarp_node_free(struct garp_rarp_node *);
> > > +
> > > +#endif /* GARP_RARP_H */
> > > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > > index cfed5282c..6f1b27876 100644
> > > --- a/controller/ovn-controller.c
> > > +++ b/controller/ovn-controller.c
> > > @@ -92,6 +92,7 @@
> > >  #include "route.h"
> > >  #include "route-exchange.h"
> > >  #include "route-table-notify.h"
> > > +#include "garp_rarp.h"
> > >
> > >  VLOG_DEFINE_THIS_MODULE(main);
> > >
> > > @@ -4952,6 +4953,14 @@ controller_output_route_exchange_handler(struct
> > > engine_node *node,
> > >      return true;
> > >  }
> > >
> > > +static bool
> > > +controller_output_garp_rarp_handler(struct engine_node *node,
> > > +                                    void *data OVS_UNUSED)
> > > +{
> > > +    engine_set_node_state(node, EN_UPDATED);
> > > +    return true;
> > > +}
> > > +
> > >  /* Handles sbrec_chassis changes.
> > >   * If a new chassis is added or removed return false, so that
> > >   * flows are recomputed.  For any updates, there is no need for
> > > @@ -5305,6 +5314,216 @@ en_route_table_notify_cleanup(void *data
> > > OVS_UNUSED)
> > >  {
> > >  }
> > >
> > > +struct ed_type_garp_rarp {
> > > +    struct hmap garp_rarp_data;
> > > +
> > > +    /* non_local_lports and local_lports are used in the incremental
> > > handlers
> > > +     * to trigger updates if such a port changes. */
> > > +    struct sset non_local_lports; /* lports that we did not consider
> > > because
> > > +                                     they where not local. */
> > > +    struct sset local_lports; /* lports where we did consider the
> > > addresses
> > > +                                 because they where local. */
> > > +};
> > > +
> > > +static void
> > > +en_garp_rarp_run(struct engine_node *node, void *data_)
> > > +{
> > > +    struct ed_type_garp_rarp *data = data_;
> > > +
> > > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > > +    ovs_assert(chassis_id);
> > > +
> > > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_chassis", node),
> > > +                "name");
> > > +    const struct sbrec_chassis *chassis
> > > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > > +    ovs_assert(chassis);
> > > +
> > > +    const struct ovsrec_bridge_table *bridge_table =
> > > +        EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
> > > +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> > > ovs_table);
> > > +
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_port_binding", node),
> > > +                "datapath");
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_port_binding", node),
> > > +                "name");
> > > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_mac_binding", node),
> > > +                "lport_ip");
> > > +
> > > +    struct ovsdb_idl_txn *ovnsb_idl_txn =
> > > engine_get_context()->ovnsb_idl_txn;
> > > +
> > > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
> > > +
> > > sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
> > > +
> > > +    struct ed_type_runtime_data *rt_data =
> > > +        engine_get_input_data("runtime_data", node);
> > > +
> > > +    struct garp_rarp_ctx_in r_ctx_in = {
> > > +        .ovnsb_idl_txn = ovnsb_idl_txn,
> > > +        .sbrec_port_binding_by_datapath =
> sbrec_port_binding_by_datapath,
> > > +        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> > > +        .sbrec_mac_binding_by_lport_ip =
> sbrec_mac_binding_by_lport_ip,
> > > +        .ecmp_nh_table = ecmp_nh_table,
> > > +        .br_int = br_int,
> > > +        .chassis = chassis,
> > > +        .active_tunnels = &rt_data->active_tunnels,
> > > +        .local_datapaths = &rt_data->local_datapaths,
> > > +        .non_local_lports = &data->non_local_lports,
> > > +        .local_lports = &data->local_lports,
> > > +    };
> > > +
> > > +    struct garp_rarp_ctx_out r_ctx_out = {
> > > +        .garp_rarp_data = &data->garp_rarp_data,
> > > +    };
> > > +
> > > +    garp_rarp_run(&r_ctx_in, &r_ctx_out);
> > > +}
> > > +
> > > +
> > > +static void *
> > > +en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
> > > +                  struct engine_arg *arg OVS_UNUSED)
> > > +{
> > > +    struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
> > > +    hmap_init(&gr->garp_rarp_data);
> > > +    sset_init(&gr->non_local_lports);
> > > +    sset_init(&gr->local_lports);
> > > +    return gr;
> > > +}
> > > +
> > > +static void
> > > +en_garp_rarp_cleanup(void *data_)
> > > +{
> > > +    struct ed_type_garp_rarp *data = data_;
> > > +    struct garp_rarp_node *grn;
> > > +    HMAP_FOR_EACH_POP (grn, hmap_node, &data->garp_rarp_data) {
> > > +        garp_rarp_node_free(grn);
> > > +    }
> > > +    hmap_destroy(&data->garp_rarp_data);
> > > +    sset_destroy(&data->non_local_lports);
> > > +    sset_destroy(&data->local_lports);
> > > +}
> > > +
> > > +static bool
> > > +garp_rarp_sb_port_binding_handler(struct engine_node *node,
> > > +                                  void *data_)
> > > +{
> > > +    /* We need to handle a change if there was change on a datapath
> with
> > > +     * a localnet port.
> > > +     * Also the ha_chassis status of a port binding might change. */
> > > +    struct ed_type_garp_rarp *data = data_;
> > > +
> > > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > > +    ovs_assert(chassis_id);
> > > +
> > > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_chassis", node),
> > > +                "name");
> > > +    const struct sbrec_chassis *chassis
> > > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > > +    ovs_assert(chassis);
> > > +
> > > +    struct ed_type_runtime_data *rt_data =
> > > +            engine_get_input_data("runtime_data", node);
> > > +    const struct sbrec_port_binding_table *port_binding_table =
> > > +        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
> > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > > +        engine_ovsdb_node_get_index(
> > > +                engine_get_input("SB_port_binding", node),
> > > +                "name");
> > > +
> > > +    const struct sbrec_port_binding *pb;
> > > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> port_binding_table) {
> > > +        struct local_datapath *ld = get_local_datapath(
> > > +            &rt_data->local_datapaths, pb->datapath->tunnel_key);
> > > +
> > > +        if (!ld || ld->localnet_port) {
> > > +            /* XXX: actually handle this incrementally. */
> > > +            return false;
> > > +        }
> > > +
> > > +        if (sset_contains(&data->non_local_lports, pb->logical_port)
> &&
> > > +            lport_is_chassis_resident(sbrec_port_binding_by_name,
> chassis,
> > > +                                      &rt_data->active_tunnels,
> > > +                                      pb->logical_port)) {
> > > +            /* XXX: actually handle this incrementally. */
> > > +            return false;
> > > +        }
> > > +
> > > +        if (sset_contains(&data->local_lports, pb->logical_port) &&
> > > +            !lport_is_chassis_resident(sbrec_port_binding_by_name,
> > > chassis,
> > > +                                       &rt_data->active_tunnels,
> > > +                                       pb->logical_port)) {
> > > +            /* XXX: actually handle this incrementally. */
> > > +            return false;
> > > +        }
> > > +    }
> > > +
> > > +    return true;
> > > +}
> > > +
> > > +static bool
> > > +garp_rarp_runtime_data_handler(struct engine_node *node, void *data
> > > OVS_UNUSED)
> > > +{
> > > +    /* We use two elements from rt_data:
> > > +     * 1. active_tunnels: There is currently not incremental
> processing
> > > for
> > > +     *    this in runtime_data. So we just fall back to a recompute.
> > > +     * 2. local_datapaths: This has incremental processing on the
> > > runtime_data
> > > +     *    side. We are only interested in datapaths with a localnet
> port
> > > so
> > > +     *    we just recompute if there is one in there. Otherwise the
> > > change is
> > > +     *    irrelevant for us. */
> > > +
> > > +    struct ed_type_runtime_data *rt_data =
> > > +            engine_get_input_data("runtime_data", node);
> > > +
> > > +    /* There are no tracked data. Fall back to full recompute. */
> > > +    if (!rt_data->tracked) {
> > > +        return false;
> > > +    }
> > > +
> > > +    struct tracked_datapath *tdp;
> > > +    HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
> > > +        if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
> > > +            /* This is currently not handled incrementally in
> runtime_data
> > > +             * so it should never happen. Recompute just in case. */
> > > +            return false;
> > > +        }
> > > +
> > > +        struct local_datapath *ld = get_local_datapath(
> > > +            &rt_data->local_datapaths, tdp->dp->tunnel_key);
> > > +
> > > +        if (!ld || ld->localnet_port) {
> > > +            /* XXX: actually handle this incrementally. */
> > > +            return false;
> > > +        }
> > > +
> > > +        /* The localnet port might also have been removed. */
> > > +        struct tracked_lport *tlp;
> > > +        struct shash_node *sn;
> > > +        SHASH_FOR_EACH (sn, &tdp->lports) {
> > > +            tlp = sn->data;
> > > +            if (!strcmp(tlp->pb->type, "localnet")) {
> > > +                return false;
> > > +            }
> > > +        }
> > > +    }
> > > +
> > > +    return true;
> > > +}
> > > +
> > >  /* Returns false if the northd internal version stored in SB_Global
> > >   * and ovn-controller internal version don't match.
> > >   */
> > > @@ -5614,6 +5833,7 @@ main(int argc, char *argv[])
> > >      ENGINE_NODE(route, "route");
> > >      ENGINE_NODE(route_table_notify, "route_table_notify");
> > >      ENGINE_NODE(route_exchange, "route_exchange");
> > > +    ENGINE_NODE(garp_rarp, "garp_rarp");
> > >
> > >  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> > >      SB_NODES
> > > @@ -5826,6 +6046,15 @@ main(int argc, char *argv[])
> > >      engine_add_input(&en_dns_cache, &en_sb_dns,
> > >                       dns_cache_sb_dns_handler);
> > >
> > > +    engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
> > > +    engine_add_input(&en_garp_rarp, &en_ovs_bridge, NULL);
> > > +    engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
> > > +    engine_add_input(&en_garp_rarp, &en_sb_port_binding,
> > > +                     garp_rarp_sb_port_binding_handler);
> > > +    engine_add_input(&en_garp_rarp, &en_sb_mac_binding, NULL);
> > >
> >
> > This dependency leads to recompute every time we insert MB locally.
> > It seems that we need this only for index, so it should be noop handler.
>
> Yes, thanks.
>
> >
> >
> > > +    engine_add_input(&en_garp_rarp, &en_runtime_data,
> > > +                     garp_rarp_runtime_data_handler);
> > > +
> > >      engine_add_input(&en_controller_output, &en_dns_cache,
> > >                       NULL);
> > >      engine_add_input(&en_controller_output, &en_lflow_output,
> > > @@ -5838,6 +6067,8 @@ main(int argc, char *argv[])
> > >                       controller_output_bfd_chassis_handler);
> > >      engine_add_input(&en_controller_output, &en_route_exchange,
> > >                       controller_output_route_exchange_handler);
> > > +    engine_add_input(&en_controller_output, &en_garp_rarp,
> > > +                     controller_output_garp_rarp_handler);
> > >
> > >      engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
> > >      engine_add_input(&en_controller_output, &en_acl_id,
> > > @@ -5874,6 +6105,8 @@ main(int argc, char *argv[])
> > >
> > >  sbrec_chassis_template_var_index_by_chassis);
> > >      engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
> > >
> sbrec_learned_route_index_by_datapath);
> > > +    engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
> > > +                                sbrec_mac_binding_by_lport_ip);
> > >      engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set,
> "id",
> > >
> ovsrec_flow_sample_collector_set_by_id);
> > >      engine_ovsdb_node_add_index(&en_ovs_port, "qos",
> ovsrec_port_by_qos);
> > > @@ -6290,9 +6523,12 @@ main(int argc, char *argv[])
> > >                          stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
> > >                                          time_msec());
> > >                          pinctrl_update(ovnsb_idl_loop.idl);
> > > +                        const struct ed_type_garp_rarp *grd =
> > > engine_get_data(
> > > +                            &en_garp_rarp);
> > > +                        const struct hmap *garp_rarp_data =
> > > +                            grd ? &grd->garp_rarp_data : NULL;
> > >                          pinctrl_run(ovnsb_idl_txn,
> > >                                      sbrec_datapath_binding_by_key,
> > > -                                    sbrec_port_binding_by_datapath,
> > >                                      sbrec_port_binding_by_key,
> > >                                      sbrec_port_binding_by_name,
> > >                                      sbrec_mac_binding_by_lport_ip,
> > > @@ -6308,13 +6544,14 @@ main(int argc, char *argv[])
> > >
> > >  sbrec_bfd_table_get(ovnsb_idl_loop.idl),
> > >                                      sbrec_ecmp_nexthop_table_get(
> > >                                          ovnsb_idl_loop.idl),
> > > -                                    br_int, chassis,
> > > +                                    chassis,
> > >                                      &runtime_data->local_datapaths,
> > >                                      &runtime_data->active_tunnels,
> > >
> > >  &runtime_data->local_active_ports_ipv6_pd,
> > >
> &runtime_data->local_active_ports_ras,
> > >                                      ovsrec_open_vswitch_table_get(
> > > -                                            ovs_idl_loop.idl));
> > > +                                            ovs_idl_loop.idl),
> > > +                                    garp_rarp_data);
> > >                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
> > >                                         time_msec());
> > >                          mirror_run(ovs_idl_txn,
> > > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > > index 1481b2547..6b4a4a520 100644
> > > --- a/controller/pinctrl.c
> > > +++ b/controller/pinctrl.c
> > > @@ -64,6 +64,7 @@
> > >  #include "ip-mcast.h"
> > >  #include "ovn-sb-idl.h"
> > >  #include "ovn-dns.h"
> > > +#include "garp_rarp.h"
> > >
> > >  VLOG_DEFINE_THIS_MODULE(pinctrl);
> > >
> > > @@ -233,15 +234,9 @@ static void destroy_send_arps_nds(void);
> > >  static void send_garp_rarp_wait(long long int send_garp_rarp_time);
> > >  static void send_arp_nd_wait(long long int send_arp_nd_time);
> > >  static void send_garp_rarp_prepare(
> > > -    struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > -    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > > +    const struct hmap *garp_rarp_data,
> > >      const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > -    const struct ovsrec_bridge *,
> > > -    const struct sbrec_chassis *,
> > > -    const struct hmap *local_datapaths,
> > > -    const struct sset *active_tunnels,
> > > +    const struct sbrec_chassis *chassis,
> > >      const struct ovsrec_open_vswitch_table *ovs_table)
> > >      OVS_REQUIRES(pinctrl_mutex);
> > >  static void send_garp_rarp_run(struct rconn *swconn,
> > > @@ -4066,7 +4061,6 @@ pinctrl_update(const struct ovsdb_idl *idl)
> > >  void
> > >  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > >              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> > > -            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > >              struct ovsdb_idl_index *sbrec_port_binding_by_key,
> > >              struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > >              struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > > @@ -4078,13 +4072,13 @@ pinctrl_run(struct ovsdb_idl_txn
> *ovnsb_idl_txn,
> > >              const struct sbrec_mac_binding_table *mac_binding_table,
> > >              const struct sbrec_bfd_table *bfd_table,
> > >              const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > -            const struct ovsrec_bridge *br_int,
> > >              const struct sbrec_chassis *chassis,
> > >              const struct hmap *local_datapaths,
> > >              const struct sset *active_tunnels,
> > >              const struct shash *local_active_ports_ipv6_pd,
> > >              const struct shash *local_active_ports_ras,
> > > -            const struct ovsrec_open_vswitch_table *ovs_table)
> > > +            const struct ovsrec_open_vswitch_table *ovs_table,
> > > +            const struct hmap *garp_rarp_data)
> > >  {
> > >      ovs_mutex_lock(&pinctrl_mutex);
> > >      run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
> > > @@ -4092,11 +4086,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > >                           sbrec_mac_binding_by_lport_ip);
> > >      run_put_vport_bindings(ovnsb_idl_txn,
> sbrec_datapath_binding_by_key,
> > >                             sbrec_port_binding_by_key, chassis);
> > > -    send_garp_rarp_prepare(ovnsb_idl_txn,
> sbrec_port_binding_by_datapath,
> > > -                           sbrec_port_binding_by_name,
> > > -                           sbrec_mac_binding_by_lport_ip,
> ecmp_nh_table,
> > > -                           br_int, chassis, local_datapaths,
> > > active_tunnels,
> > > -                           ovs_table);
> > > +    send_garp_rarp_prepare(garp_rarp_data, ecmp_nh_table,
> > > +                           chassis, ovs_table);
> > >      prepare_ipv6_ras(local_active_ports_ras,
> sbrec_port_binding_by_name);
> > >      prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
> > >                           local_active_ports_ipv6_pd, chassis,
> > > @@ -4785,47 +4776,6 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn
> > > *ovnsb_idl_txn,
> > >      }
> > >  }
> > >
> > > -/* Simulate the effect of a GARP on local datapaths, i.e., create
> > > MAC_Bindings
> > > - * on peer router datapaths.
> > > - */
> > > -static void
> > > -send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > -                  struct ovsdb_idl_index
> *sbrec_mac_binding_by_lport_ip,
> > > -                  const struct hmap *local_datapaths,
> > > -                  const struct sbrec_port_binding *in_pb,
> > > -                  struct eth_addr ea, ovs_be32 ip)
> > > -{
> > > -    if (!ovnsb_idl_txn) {
> > > -        return;
> > > -    }
> > > -
> > > -    const struct local_datapath *ldp =
> > > -        get_local_datapath(local_datapaths,
> in_pb->datapath->tunnel_key);
> > > -
> > > -    ovs_assert(ldp);
> > > -    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > > -        const struct sbrec_port_binding *local =
> ldp->peer_ports[i].local;
> > > -        const struct sbrec_port_binding *remote =
> > > ldp->peer_ports[i].remote;
> > > -
> > > -        /* Skip "ingress" port. */
> > > -        if (local == in_pb) {
> > > -            continue;
> > > -        }
> > > -
> > > -        bool update_only =
> !smap_get_bool(&remote->datapath->external_ids,
> > > -
> "always_learn_from_arp_request",
> > > -                                          true);
> > > -
> > > -        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > > -
> > > -        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > > -        mac_binding_add_to_sb(ovnsb_idl_txn,
> > > sbrec_mac_binding_by_lport_ip,
> > > -                              remote->logical_port, remote->datapath,
> > > -                              ea, ds_cstr(&ip_s), update_only);
> > > -        ds_destroy(&ip_s);
> > > -    }
> > > -}
> > > -
> > >  static void
> > >  run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > >                      struct ovsdb_idl_index
> *sbrec_datapath_binding_by_key,
> > > @@ -4977,173 +4927,25 @@ wait_put_mac_bindings(struct ovsdb_idl_txn
> > > *ovnsb_idl_txn)
> > >   * are needed for switches and routers on the broadcast segment to
> update
> > >   * their port-mac and ARP tables.
> > >   */
> > > -struct garp_rarp_data {
> > > -    struct eth_addr ea;          /* Ethernet address of port. */
> > > -    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > > -    long long int announce_time; /* Next announcement in ms. */
> > > -    int backoff;                 /* Backoff timeout for the next
> > > -                                  * announcement (in msecs). */
> > > -    uint32_t dp_key;             /* Datapath used to output this
> GARP. */
> > > -    uint32_t port_key;           /* Port to inject the GARP into. */
> > > -};
> > >
> > > -/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
> > > -static struct shash send_garp_rarp_data;
> > > +/* Contains GARPs/RARPs to be sent in struct garp_rarp_node.
> > > + * Protected by pinctrl_mutex. */
> > > +static struct hmap send_garp_rarp_data;
> > >
> > >  static void
> > >  init_send_garps_rarps(void)
> > >  {
> > > -    shash_init(&send_garp_rarp_data);
> > > +    hmap_init(&send_garp_rarp_data);
> > >  }
> > >
> > >  static void
> > >  destroy_send_garps_rarps(void)
> > >  {
> > > -    shash_destroy_free_data(&send_garp_rarp_data);
> > > -}
> > > -
> > > -/* Runs with in the main ovn-controller thread context. */
> > > -static void
> > > -add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
> > > -              uint32_t dp_key, uint32_t port_key)
> > > -{
> > > -    struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
> > > -    garp_rarp->ea = ea;
> > > -    garp_rarp->ipv4 = ip;
> > > -    garp_rarp->announce_time = time_msec() + 1000;
> > > -    garp_rarp->backoff = 1000; /* msec. */
> > > -    garp_rarp->dp_key = dp_key;
> > > -    garp_rarp->port_key = port_key;
> > > -    shash_add(&send_garp_rarp_data, name, garp_rarp);
> > > -
> > > -    /* Notify pinctrl_handler so that it can wakeup and process
> > > -     * these GARP/RARP requests. */
> > > -    notify_pinctrl_handler();
> > > -}
> > > -
> > > -/* Add or update a vif for which GARPs need to be announced. */
> > > -static void
> > > -send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > -                      struct ovsdb_idl_index
> > > *sbrec_mac_binding_by_lport_ip,
> > > -                      const struct hmap *local_datapaths,
> > > -                      const struct sbrec_port_binding *binding_rec,
> > > -                      struct shash *nat_addresses,
> > > -                      long long int garp_max_timeout,
> > > -                      bool garp_continuous)
> > > -{
> > > -    volatile struct garp_rarp_data *garp_rarp = NULL;
> > > -
> > > -    /* Skip localports as they don't need to be announced */
> > > -    if (!strcmp(binding_rec->type, "localport")) {
> > > -        return;
> > > -    }
> > > -
> > > -    /* Update GARP for NAT IP if it exists.  Consider port bindings
> with
> > > type
> > > -     * "l3gateway" for logical switch ports attached to gateway
> routers,
> > > and
> > > -     * port bindings with type "patch" for logical switch ports
> attached
> > > to
> > > -     * distributed gateway ports. */
> > > -    if (!strcmp(binding_rec->type, "l3gateway")
> > > -        || !strcmp(binding_rec->type, "patch")) {
> > > -        struct lport_addresses *laddrs = NULL;
> > > -        while ((laddrs = shash_find_and_delete(nat_addresses,
> > > -
> > >  binding_rec->logical_port))) {
> > > -            int i;
> > > -            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > -                char *name = xasprintf("%s-%s",
> binding_rec->logical_port,
> > > -
> > > laddrs->ipv4_addrs[i].addr_s);
> > > -                garp_rarp = shash_find_data(&send_garp_rarp_data,
> name);
> > > -                if (garp_rarp) {
> > > -                    garp_rarp->dp_key =
> binding_rec->datapath->tunnel_key;
> > > -                    garp_rarp->port_key = binding_rec->tunnel_key;
> > > -                    if (garp_max_timeout != garp_rarp_max_timeout ||
> > > -                        garp_continuous != garp_rarp_continuous) {
> > > -                        /* reset backoff */
> > > -                        garp_rarp->announce_time = time_msec() + 1000;
> > > -                        garp_rarp->backoff = 1000; /* msec. */
> > > -                    }
> > > -                } else if (ovnsb_idl_txn) {
> > > -                    add_garp_rarp(name, laddrs->ea,
> > > -                                  laddrs->ipv4_addrs[i].addr,
> > > -                                  binding_rec->datapath->tunnel_key,
> > > -                                  binding_rec->tunnel_key);
> > > -                    send_garp_locally(ovnsb_idl_txn,
> > > -                                      sbrec_mac_binding_by_lport_ip,
> > > -                                      local_datapaths, binding_rec,
> > > laddrs->ea,
> > > -                                      laddrs->ipv4_addrs[i].addr);
> > > -
> > > -                }
> > > -                free(name);
> > > -            }
> > > -            /*
> > > -             * Send RARPs even if we do not have a ipv4 address as it
> e.g.
> > > -             * happens on ipv6 only ports.
> > > -             */
> > > -            if (laddrs->n_ipv4_addrs == 0) {
> > > -                    char *name = xasprintf("%s-noip",
> > > -                                           binding_rec->logical_port);
> > > -                    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > > name);
> > > -                    if (garp_rarp) {
> > > -                        garp_rarp->dp_key =
> > > binding_rec->datapath->tunnel_key;
> > > -                        garp_rarp->port_key = binding_rec->tunnel_key;
> > > -                        if (garp_max_timeout != garp_rarp_max_timeout
> ||
> > > -                            garp_continuous != garp_rarp_continuous) {
> > > -                            /* reset backoff */
> > > -                            garp_rarp->announce_time = time_msec() +
> 1000;
> > > -                            garp_rarp->backoff = 1000; /* msec. */
> > > -                        }
> > > -                    } else {
> > > -                        add_garp_rarp(name, laddrs->ea,
> > > -                                      0,
> > > binding_rec->datapath->tunnel_key,
> > > -                                      binding_rec->tunnel_key);
> > > -                    }
> > > -                    free(name);
> > > -            }
> > > -            destroy_lport_addresses(laddrs);
> > > -            free(laddrs);
> > > -        }
> > > -        return;
> > > -    }
> > > -
> > > -    /* Update GARP for vif if it exists. */
> > > -    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > > -                                binding_rec->logical_port);
> > > -    if (garp_rarp) {
> > > -        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> > > -        garp_rarp->port_key = binding_rec->tunnel_key;
> > > -        if (garp_max_timeout != garp_rarp_max_timeout ||
> > > -            garp_continuous != garp_rarp_continuous) {
> > > -            /* reset backoff */
> > > -            garp_rarp->announce_time = time_msec() + 1000;
> > > -            garp_rarp->backoff = 1000; /* msec. */
> > > -        }
> > > -        return;
> > > -    }
> > > -
> > > -    /* Add GARP for new vif. */
> > > -    int i;
> > > -    for (i = 0; i < binding_rec->n_mac; i++) {
> > > -        struct lport_addresses laddrs;
> > > -        ovs_be32 ip = 0;
> > > -        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > > -            continue;
> > > -        }
> > > -
> > > -        if (laddrs.n_ipv4_addrs) {
> > > -            ip = laddrs.ipv4_addrs[0].addr;
> > > -        }
> > > -
> > > -        add_garp_rarp(binding_rec->logical_port,
> > > -                      laddrs.ea, ip,
> > > -                      binding_rec->datapath->tunnel_key,
> > > -                      binding_rec->tunnel_key);
> > > -        if (ip) {
> > > -            send_garp_locally(ovnsb_idl_txn,
> > > sbrec_mac_binding_by_lport_ip,
> > > -                              local_datapaths, binding_rec, laddrs.ea,
> > > ip);
> > > -        }
> > > -
> > > -        destroy_lport_addresses(&laddrs);
> > > -        break;
> > > +    struct garp_rarp_node *garp_rarp;
> > > +    HMAP_FOR_EACH_POP (garp_rarp, hmap_node, &send_garp_rarp_data) {
> > > +        garp_rarp_node_free(garp_rarp);
> > >      }
> > > +    hmap_destroy(&send_garp_rarp_data);
> > >  }
> > >
> > >  struct arp_nd_data {
> > > @@ -5289,16 +5091,6 @@ send_arp_nd_update(const struct
> sbrec_port_binding
> > > *pb, const char *nexthop,
> > >      }
> > >  }
> > >
> > > -/* Remove a vif from GARP announcements. */
> > > -static void
> > > -send_garp_rarp_delete(const char *lport)
> > > -{
> > > -    struct garp_rarp_data *garp_rarp = shash_find_and_delete
> > > -                                       (&send_garp_rarp_data, lport);
> > > -    free(garp_rarp);
> > > -    notify_pinctrl_handler();
> > > -}
> > > -
> > >  void
> > >  send_self_originated_neigh_packet(struct rconn *swconn,
> > >                                    uint32_t dp_key, uint32_t port_key,
> > > @@ -5350,7 +5142,7 @@ send_self_originated_neigh_packet(struct rconn
> > > *swconn,
> > >
> > >  /* Called with in the pinctrl_handler thread context. */
> > >  static long long int
> > > -send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
> > > +send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
> > >                 long long int current_time)
> > >      OVS_REQUIRES(pinctrl_mutex)
> > >  {
> > > @@ -6419,242 +6211,12 @@ ip_mcast_querier_wait(long long int
> query_time)
> > >      }
> > >  }
> > >
> > > -/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > > ports. */
> > > -static void
> > > -get_localnet_vifs_l3gwports(
> > > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > -    const struct ovsrec_bridge *br_int,
> > > -    const struct sbrec_chassis *chassis,
> > > -    const struct hmap *local_datapaths,
> > > -    struct sset *localnet_vifs,
> > > -    struct sset *local_l3gw_ports)
> > > -{
> > > -    for (int i = 0; i < br_int->n_ports; i++) {
> > > -        const struct ovsrec_port *port_rec = br_int->ports[i];
> > > -        if (!strcmp(port_rec->name, br_int->name)) {
> > > -            continue;
> > > -        }
> > > -        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > > -                                          "ovn-chassis-id");
> > > -        if (tunnel_id &&
> > > -                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > > NULL)) {
> > > -            continue;
> > > -        }
> > > -        const char *localnet = smap_get(&port_rec->external_ids,
> > > -                                        "ovn-localnet-port");
> > > -        if (localnet) {
> > > -            continue;
> > > -        }
> > > -        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > > -            const struct ovsrec_interface *iface_rec =
> > > port_rec->interfaces[j];
> > > -            if (!iface_rec->n_ofport) {
> > > -                continue;
> > > -            }
> > > -            /* Get localnet vif. */
> > > -            const char *iface_id = smap_get(&iface_rec->external_ids,
> > > -                                            "iface-id");
> > > -            if (!iface_id) {
> > > -                continue;
> > > -            }
> > > -            const struct sbrec_port_binding *pb
> > > -                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > > iface_id);
> > > -            if (!pb || pb->chassis != chassis) {
> > > -                continue;
> > > -            }
> > > -            if (!iface_rec->link_state ||
> > > -                    strcmp(iface_rec->link_state, "up")) {
> > > -                continue;
> > > -            }
> > > -            struct local_datapath *ld
> > > -                = get_local_datapath(local_datapaths,
> > > -                                     pb->datapath->tunnel_key);
> > > -            if (ld && ld->localnet_port) {
> > > -                sset_add(localnet_vifs, iface_id);
> > > -            }
> > > -        }
> > > -    }
> > > -
> > > -    struct sbrec_port_binding *target =
> sbrec_port_binding_index_init_row(
> > > -        sbrec_port_binding_by_datapath);
> > > -
> > > -    const struct local_datapath *ld;
> > > -    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > > -        const struct sbrec_port_binding *pb;
> > > -
> > > -        if (!ld->localnet_port) {
> > > -            continue;
> > > -        }
> > > -
> > > -        /* Get l3gw ports.  Consider port bindings with type
> "l3gateway"
> > > -         * that connect to gateway routers (if local), and consider
> port
> > > -         * bindings of type "patch" since they might connect to
> > > -         * distributed gateway ports with NAT addresses. */
> > > -
> > > -        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > > -        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > > -
> > >  sbrec_port_binding_by_datapath) {
> > > -            if ((!strcmp(pb->type, "l3gateway") && pb->chassis ==
> chassis)
> > > -                || !strcmp(pb->type, "patch")) {
> > > -                sset_add(local_l3gw_ports, pb->logical_port);
> > > -            }
> > > -        }
> > > -    }
> > > -    sbrec_port_binding_index_destroy_row(target);
> > > -}
> > > -
> > > -
> > > -/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > > - * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > > - * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid
> IPv4
> > > - * or IPv6 address, and stores them in the 'ipv4_addrs' and
> 'ipv6_addrs'
> > > - * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > > - *
> > > - * Returns true if at least 'MAC' is found in 'address', false
> otherwise.
> > > - *
> > > - * The caller must call destroy_lport_addresses() and free(*lport). */
> > > -static bool
> > > -extract_addresses_with_port(const char *addresses,
> > > -                            struct lport_addresses *laddrs,
> > > -                            char **lport)
> > > -{
> > > -    int ofs;
> > > -    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > > -        return false;
> > > -    } else if (!addresses[ofs]) {
> > > -        return true;
> > > -    }
> > > -
> > > -    struct lexer lexer;
> > > -    lexer_init(&lexer, addresses + ofs);
> > > -    lexer_get(&lexer);
> > > -
> > > -    if (lexer.error || lexer.token.type != LEX_T_ID
> > > -        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > -        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address",
> addresses);
> > > -        lexer_destroy(&lexer);
> > > -        return true;
> > > -    }
> > > -
> > > -    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > -        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > > -                          "'is_chassis_resident' in address '%s'",
> > > addresses);
> > > -        lexer_destroy(&lexer);
> > > -        return false;
> > > -    }
> > > -
> > > -    if (lexer.token.type != LEX_T_STRING) {
> > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > -        VLOG_INFO_RL(&rl,
> > > -                    "Syntax error: expecting quoted string after "
> > > -                    "'is_chassis_resident' in address '%s'",
> addresses);
> > > -        lexer_destroy(&lexer);
> > > -        return false;
> > > -    }
> > > -
> > > -    *lport = xstrdup(lexer.token.s);
> > > -
> > > -    lexer_get(&lexer);
> > > -    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > -        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > > string in "
> > > -                          "'is_chassis_resident()' in address '%s'",
> > > -                          addresses);
> > > -        lexer_destroy(&lexer);
> > > -        return false;
> > > -    }
> > > -
> > > -    lexer_destroy(&lexer);
> > > -    return true;
> > > -}
> > > -
> > > -static void
> > > -consider_nat_address(struct ovsdb_idl_index
> *sbrec_port_binding_by_name,
> > > -                     const char *nat_address,
> > > -                     const struct sbrec_port_binding *pb,
> > > -                     struct sset *nat_address_keys,
> > > -                     const struct sbrec_chassis *chassis,
> > > -                     const struct sset *active_tunnels,
> > > -                     struct shash *nat_addresses)
> > > -{
> > > -    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > > -    char *lport = NULL;
> > > -    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > > -        || (!lport && !strcmp(pb->type, "patch"))
> > > -        || (lport && !lport_is_chassis_resident(
> > > -                sbrec_port_binding_by_name, chassis,
> > > -                active_tunnels, lport))) {
> > > -        destroy_lport_addresses(laddrs);
> > > -        free(laddrs);
> > > -        free(lport);
> > > -        return;
> > > -    }
> > > -    free(lport);
> > > -
> > > -    int i;
> > > -    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > -        char *name = xasprintf("%s-%s", pb->logical_port,
> > > -                                        laddrs->ipv4_addrs[i].addr_s);
> > > -        sset_add(nat_address_keys, name);
> > > -        free(name);
> > > -    }
> > > -    if (laddrs->n_ipv4_addrs == 0) {
> > > -        char *name = xasprintf("%s-noip", pb->logical_port);
> > > -        sset_add(nat_address_keys, name);
> > > -        free(name);
> > > -    }
> > > -    shash_add(nat_addresses, pb->logical_port, laddrs);
> > > -}
> > > -
> > > -static void
> > > -get_nat_addresses_and_keys(struct ovsdb_idl_index
> > > *sbrec_port_binding_by_name,
> > > -                           struct sset *nat_address_keys,
> > > -                           struct sset *local_l3gw_ports,
> > > -                           const struct sbrec_chassis *chassis,
> > > -                           const struct sset *active_tunnels,
> > > -                           struct shash *nat_addresses)
> > > -{
> > > -    const char *gw_port;
> > > -    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
> > > -        const struct sbrec_port_binding *pb;
> > > -
> > > -        pb = lport_lookup_by_name(sbrec_port_binding_by_name,
> gw_port);
> > > -        if (!pb) {
> > > -            continue;
> > > -        }
> > > -
> > > -        if (pb->n_nat_addresses) {
> > > -            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > > -                consider_nat_address(sbrec_port_binding_by_name,
> > > -                                     pb->nat_addresses[i], pb,
> > > -                                     nat_address_keys, chassis,
> > > -                                     active_tunnels,
> > > -                                     nat_addresses);
> > > -            }
> > > -        } else {
> > > -            /* Continue to support options:nat-addresses for version
> > > -             * upgrade. */
> > > -            const char *nat_addresses_options = smap_get(&pb->options,
> > > -
>  "nat-addresses");
> > > -            if (nat_addresses_options) {
> > > -                consider_nat_address(sbrec_port_binding_by_name,
> > > -                                     nat_addresses_options, pb,
> > > -                                     nat_address_keys, chassis,
> > > -                                     active_tunnels,
> > > -                                     nat_addresses);
> > > -            }
> > > -        }
> > > -    }
> > > -}
> > > -
> > >  static void
> > >  send_garp_rarp_wait(long long int send_garp_rarp_time)
> > >  {
> > >      /* Set the poll timer for next garp/rarp only if there is data to
> > >       * be sent. */
> > > -    if (!shash_is_empty(&send_garp_rarp_data)) {
> > > +    if (!hmap_is_empty(&send_garp_rarp_data)) {
> > >          poll_timer_wait_until(send_garp_rarp_time);
> > >      }
> > >  }
> > > @@ -6674,16 +6236,16 @@ static void
> > >  send_garp_rarp_run(struct rconn *swconn, long long int
> > > *send_garp_rarp_time)
> > >      OVS_REQUIRES(pinctrl_mutex)
> > >  {
> > > -    if (shash_is_empty(&send_garp_rarp_data)) {
> > > +    if (hmap_is_empty(&send_garp_rarp_data)) {
> > >          return;
> > >      }
> > >
> > >      /* Send GARPs, and update the next announcement. */
> > > -    struct shash_node *iter;
> > >      long long int current_time = time_msec();
> > >      *send_garp_rarp_time = LLONG_MAX;
> > > -    SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
> > > -        long long int next_announce = send_garp_rarp(swconn,
> iter->data,
> > > +    struct garp_rarp_node *garp;
> > > +    HMAP_FOR_EACH (garp, hmap_node, &send_garp_rarp_data) {
> > > +        long long int next_announce = send_garp_rarp(swconn, garp,
> > >                                                       current_time);
> > >          if (*send_garp_rarp_time > next_announce) {
> > >              *send_garp_rarp_time = next_announce;
> > > @@ -6743,22 +6305,12 @@ send_arp_nd_run(struct rconn *swconn, long long
> > > int *send_arp_nd_time)
> > >  /* Called by pinctrl_run(). Runs with in the main ovn-controller
> > >   * thread context. */
> > >  static void
> > > -send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > -                       struct ovsdb_idl_index
> > > *sbrec_port_binding_by_datapath,
> > > -                       struct ovsdb_idl_index
> *sbrec_port_binding_by_name,
> > > -                       struct ovsdb_idl_index
> > > *sbrec_mac_binding_by_lport_ip,
> > > +send_garp_rarp_prepare(const struct hmap *garp_rarp_data,
> > >                         const struct sbrec_ecmp_nexthop_table
> > > *ecmp_nh_table,
> > > -                       const struct ovsrec_bridge *br_int,
> > >                         const struct sbrec_chassis *chassis,
> > > -                       const struct hmap *local_datapaths,
> > > -                       const struct sset *active_tunnels,
> > >                         const struct ovsrec_open_vswitch_table
> *ovs_table)
> > >      OVS_REQUIRES(pinctrl_mutex)
> > >  {
> > > -    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > > -    struct sset local_l3gw_ports =
> SSET_INITIALIZER(&local_l3gw_ports);
> > > -    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > > -    struct shash nat_addresses;
> > >      unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> > >      unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> > >      bool garp_continuous = false, continuous_arp_nd = true;
> > > @@ -6778,51 +6330,13 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > > *ovnsb_idl_txn,
> > >          continuous_arp_nd = !!max_arp_nd_timeout;
> > >      }
> > >
> > > -    shash_init(&nat_addresses);
> > > +    bool reset_timers = (garp_max_timeout != garp_rarp_max_timeout ||
> > > +                         garp_continuous != garp_rarp_continuous);
> > >
> > > -    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
> > > -                                sbrec_port_binding_by_name,
> > > -                                br_int, chassis, local_datapaths,
> > > -                                &localnet_vifs, &local_l3gw_ports);
> > > -
> > > -    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
> > > -                               &nat_ip_keys, &local_l3gw_ports,
> > > -                               chassis, active_tunnels,
> > > -                               &nat_addresses);
> > > -
> > > -    /* For deleted ports and deleted nat ips, remove from
> > > -     * send_garp_rarp_data. */
> > > -    struct shash_node *iter;
> > > -    SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
> > > -        if (!sset_contains(&localnet_vifs, iter->name) &&
> > > -            !sset_contains(&nat_ip_keys, iter->name)) {
> > > -            send_garp_rarp_delete(iter->name);
> > > -        }
> > > -    }
> > > -
> > > -    /* Update send_garp_rarp_data. */
> > > -    const char *iface_id;
> > > -    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > > -        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > -            sbrec_port_binding_by_name, iface_id);
> > > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > false)) {
> > > -            send_garp_rarp_update(ovnsb_idl_txn,
> > > -                                  sbrec_mac_binding_by_lport_ip,
> > > -                                  local_datapaths, pb, &nat_addresses,
> > > -                                  garp_max_timeout, garp_continuous);
> > > -        }
> > > -    }
> > > -
> > > -    /* Update send_garp_rarp_data for nat-addresses. */
> > > -    const char *gw_port;
> > > -    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > > -        const struct sbrec_port_binding *pb
> > > -            = lport_lookup_by_name(sbrec_port_binding_by_name,
> gw_port);
> > > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > false)) {
> > > -            send_garp_rarp_update(ovnsb_idl_txn,
> > > sbrec_mac_binding_by_lport_ip,
> > > -                                  local_datapaths, pb, &nat_addresses,
> > > -                                  garp_max_timeout, garp_continuous);
> > > -        }
> > > +    if (garp_rarp_sync(garp_rarp_data, &send_garp_rarp_data,
> > > reset_timers)) {
> > > +        /* Notify pinctrl_handler so that it can wakeup and process
> > > +         * these GARP/RARP requests. */
> > > +        notify_pinctrl_handler();
> > >      }
> > >
> > >      arp_nd_sync_data(ecmp_nh_table);
> > > @@ -6836,21 +6350,6 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > > *ovnsb_idl_txn,
> > >          }
> > >      }
> > >
> > > -    /* pinctrl_handler thread will send the GARPs. */
> > > -
> > > -    sset_destroy(&localnet_vifs);
> > > -    sset_destroy(&local_l3gw_ports);
> > > -
> > > -    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > > -        struct lport_addresses *laddrs = iter->data;
> > > -        destroy_lport_addresses(laddrs);
> > > -        shash_delete(&nat_addresses, iter);
> > > -        free(laddrs);
> > > -    }
> > > -    shash_destroy(&nat_addresses);
> > > -
> > > -    sset_destroy(&nat_ip_keys);
> > > -
> > >      garp_rarp_max_timeout = garp_max_timeout;
> > >      garp_rarp_continuous = garp_continuous;
> > >
> > > @@ -6862,7 +6361,7 @@ static bool
> > >  may_inject_pkts(void)
> > >  {
> > >      return (!shash_is_empty(&ipv6_ras) ||
> > > -            !shash_is_empty(&send_garp_rarp_data) ||
> > > +            !hmap_is_empty(&send_garp_rarp_data) ||
> > >              ipv6_prefixd_should_inject() ||
> > >              !ovs_list_is_empty(&mcast_query_list) ||
> > >
> buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)
> > > ||
> > > diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> > > index e33d7cbf5..f57f5c306 100644
> > > --- a/controller/pinctrl.h
> > > +++ b/controller/pinctrl.h
> > > @@ -44,24 +44,24 @@ struct sbrec_mac_binding_table;
> > >  void pinctrl_init(void);
> > >  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > >                   struct ovsdb_idl_index
> *sbrec_datapath_binding_by_key,
> > > -                 struct ovsdb_idl_index
> *sbrec_port_binding_by_datapath,
> > >                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
> > >                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > >                   struct ovsdb_idl_index
> *sbrec_mac_binding_by_lport_ip,
> > >                   struct ovsdb_idl_index *sbrec_igmp_groups,
> > >                   struct ovsdb_idl_index *sbrec_ip_multicast_opts,
> > >                   struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
> > > -                 const struct sbrec_controller_event_table *,
> > > -                 const struct sbrec_service_monitor_table *,
> > > -                 const struct sbrec_mac_binding_table *,
> > > -                 const struct sbrec_bfd_table *,
> > > -                 const struct sbrec_ecmp_nexthop_table *,
> > > -                 const struct ovsrec_bridge *, const struct
> sbrec_chassis
> > > *,
> > > +                 const struct sbrec_controller_event_table *ce_table,
> > > +                 const struct sbrec_service_monitor_table
> *svc_mon_table,
> > > +                 const struct sbrec_mac_binding_table
> *mac_binding_table,
> > > +                 const struct sbrec_bfd_table *bfd_table,
> > > +                 const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > +                 const struct sbrec_chassis *chassis,
> > >
> >
> > nit: Unrelated change.
>
> will be cleaned up.
>
> Thanks a lot,
> Felix
>
> >
> >                   const struct hmap *local_datapaths,
> > >                   const struct sset *active_tunnels,
> > >                   const struct shash *local_active_ports_ipv6_pd,
> > >                   const struct shash *local_active_ports_ras,
> > > -                 const struct ovsrec_open_vswitch_table *ovs_table);
> > > +                 const struct ovsrec_open_vswitch_table *ovs_table,
> > > +                 const struct hmap *garp_rarp_data);
> > >  void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
> > >  void pinctrl_destroy(void);
> > >
> > > --
> > > 2.43.0
> > >
> > >
> > > _______________________________________________
> > > dev mailing list
> > > dev@openvswitch.org
> > > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> > >
> > >
> > Thanks,
> > Ales
>
>
Thanks,
Ales
Felix Huettner April 8, 2025, noon UTC | #4
On Tue, Apr 08, 2025 at 07:58:40AM +0200, Ales Musil via dev wrote:
> On Mon, Apr 7, 2025 at 2:00 PM Felix Huettner <felix.huettner@stackit.cloud>
> wrote:
> 
> > On Thu, Apr 03, 2025 at 01:25:10PM +0200, Ales Musil wrote:
> > > On Thu, Mar 20, 2025 at 1:02 PM Felix Huettner via dev <
> > > ovs-dev@openvswitch.org> wrote:
> > >
> > > > Previously on each call to pinctrl_run all addresses that need to be
> > > > announced via garps or rarps where computed newly. On larger external
> > > > networks that might take significant time.
> > > > However the content of the data can only change in a run of the I+P
> > > > engine. So we move the whole logic to an engine node and only handle
> > the
> > > > minimal needed set in pinctrl_run.
> > > >
> > > > Acked-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
> > > > Co-Authored-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > > > Signed-off-by: Max Lamprecht <max.lamprecht@mail.schwarz>
> > > > Signed-off-by: Felix Huettner <felix.huettner@stackit.cloud>
> > > > ---
> > > >
> > >
> > > Hi Felix,
> > >
> > > thank you for the patch. I have a few comments down below.
> >
> > Hi Ales,
> >
> 
> Hi Felix,
> 
> thank you for working on this.

No problem, it eats a lot of performance for us.

> 
> 
> > >
> > > v6->v7: added ack
> > > >
> > > >  controller/automake.mk      |   4 +-
> > > >  controller/garp_rarp.c      | 626 ++++++++++++++++++++++++++++++++++++
> > > >  controller/garp_rarp.h      |  61 ++++
> > > >  controller/ovn-controller.c | 243 +++++++++++++-
> > > >  controller/pinctrl.c        | 559 ++------------------------------
> > > >  controller/pinctrl.h        |  16 +-
> > > >  6 files changed, 967 insertions(+), 542 deletions(-)
> > > >  create mode 100644 controller/garp_rarp.c
> > > >  create mode 100644 controller/garp_rarp.h
> > > >
> > > > diff --git a/controller/automake.mk b/controller/automake.mk
> > > > index 067c2e6e4..f0638ea97 100644
> > > > --- a/controller/automake.mk
> > > > +++ b/controller/automake.mk
> > > > @@ -59,7 +59,9 @@ controller_ovn_controller_SOURCES = \
> > > >         controller/route-exchange.h \
> > > >         controller/route-table-notify.h \
> > > >         controller/route.h \
> > > > -       controller/route.c
> > > > +       controller/route.c \
> > > > +       controller/garp_rarp.h \
> > > > +       controller/garp_rarp.c
> > > >
> > > >  if HAVE_NETLINK
> > > >  controller_ovn_controller_SOURCES += \
> > > > diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
> > > > new file mode 100644
> > > > index 000000000..916ff896a
> > > > --- /dev/null
> > > > +++ b/controller/garp_rarp.c
> > > > @@ -0,0 +1,626 @@
> > > > +/*
> > > > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > > > + *
> > > > + * Licensed under the Apache License, Version 2.0 (the "License");
> > > > + * you may not use this file except in compliance with the License.
> > > > + * You may obtain a copy of the License at:
> > > > + *
> > > > + *     http://www.apache.org/licenses/LICENSE-2.0
> > > > + *
> > > > + * Unless required by applicable law or agreed to in writing, software
> > > > + * distributed under the License is distributed on an "AS IS" BASIS,
> > > > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> > > > implied.
> > > > + * See the License for the specific language governing permissions and
> > > > + * limitations under the License.
> > > > + */
> > > > +
> > > > +#include <config.h>
> > > > +
> > > > +#include <net/if.h>
> > > > +#include <stdbool.h>
> > > > +#include <stddef.h>
> > > > +
> > > > +#include "controller/encaps.h"
> > > > +#include "controller/local_data.h"
> > > > +#include "controller/lport.h"
> > > > +#include "hash.h"
> > > > +#include "mac-binding-index.h"
> > > > +#include "openvswitch/hmap.h"
> > > > +#include "openvswitch/vlog.h"
> > > > +#include "ovn/lex.h"
> > > > +#include "packets.h"
> > > > +#include "smap.h"
> > > > +#include "sset.h"
> > > > +#include "openvswitch/shash.h"
> > > > +#include "garp_rarp.h"
> > > > +#include "ovn-sb-idl.h"
> > > > +#include "vswitch-idl.h"
> > > > +
> > > > +VLOG_DEFINE_THIS_MODULE(garp_rarp);
> > > > +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> > > > +
> > > > +/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > > > ports. */
> > > > +static void
> > > > +get_localnet_vifs_l3gwports(
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > > +    const struct ovsrec_bridge *br_int,
> > > > +    const struct sbrec_chassis *chassis,
> > > > +    const struct hmap *local_datapaths,
> > > > +    struct sset *localnet_vifs,
> > > > +    struct sset *local_l3gw_ports)
> > > > +{
> > > > +    for (int i = 0; i < br_int->n_ports; i++) {
> > > > +        const struct ovsrec_port *port_rec = br_int->ports[i];
> > > > +        if (!strcmp(port_rec->name, br_int->name)) {
> > > > +            continue;
> > > > +        }
> > > > +        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > > > +                                          "ovn-chassis-id");
> > > > +        if (tunnel_id &&
> > > > +                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > > > NULL)) {
> > > > +            continue;
> > > > +        }
> > > > +        const char *localnet = smap_get(&port_rec->external_ids,
> > > > +                                        "ovn-localnet-port");
> > > > +        if (localnet) {
> > > > +            continue;
> > > > +        }
> > > > +        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > > > +            const struct ovsrec_interface *iface_rec =
> > > > port_rec->interfaces[j];
> > > > +            if (!iface_rec->n_ofport) {
> > > > +                continue;
> > > > +            }
> > > > +            /* Get localnet vif. */
> > > > +            const char *iface_id = smap_get(&iface_rec->external_ids,
> > > > +                                            "iface-id");
> > > > +            if (!iface_id) {
> > > > +                continue;
> > > > +            }
> > > > +            const struct sbrec_port_binding *pb
> > > > +                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > > > iface_id);
> > > > +            if (!pb || pb->chassis != chassis) {
> > > > +                continue;
> > > > +            }
> > > > +            if (!iface_rec->link_state ||
> > > > +                    strcmp(iface_rec->link_state, "up")) {
> > > > +                continue;
> > > > +            }
> > > > +            struct local_datapath *ld
> > > > +                = get_local_datapath(local_datapaths,
> > > > +                                     pb->datapath->tunnel_key);
> > > > +            if (ld && ld->localnet_port) {
> > > > +                sset_add(localnet_vifs, iface_id);
> > > > +            }
> > > > +        }
> > > > +    }
> > > >
> > >
> > > I know that this is old code but it makes me wonder if there's a better
> > > way to find local vifs that are connected to localnet port. I think
> > > it should be doable through bindings.
> >
> > Actually it seems like we can get this easily in the local_datapaths
> > loop below. I will update this in the next version. It is so simple that
> > i am unsure if i missed something.
> 
> 
> This part of pinctrl is really old at this point, back then there
> probably wasn't anything like local_datapaths. And by using them
> we can actually remove the dependency on "en_ovs_open_vswitch" and
> "en_ovs_bridge" which is a good thing. Less dependencies that can
> cause recomputes is better.

Yep "en_ovs_bridge" can be removed.
We still need "en_ovs_open_vswitch" to get the chassis record. Maybe we
should rework this as well, it seems strange to have nearly all engine
nodes get it.

> 
> 
> >
> > >
> > > +
> > > > +    struct sbrec_port_binding *target =
> > sbrec_port_binding_index_init_row(
> > > > +        sbrec_port_binding_by_datapath);
> > > > +
> > > > +    const struct local_datapath *ld;
> > > > +    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > > > +        const struct sbrec_port_binding *pb;
> > > > +
> > > > +        if (!ld->localnet_port) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        /* Get l3gw ports.  Consider port bindings with type
> > "l3gateway"
> > > > +         * that connect to gateway routers (if local), and consider
> > port
> > > > +         * bindings of type "patch" since they might connect to
> > > > +         * distributed gateway ports with NAT addresses. */
> > > > +
> > > > +        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > > > +        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > > > +
> > > >  sbrec_port_binding_by_datapath) {
> > > > +            if ((!strcmp(pb->type, "l3gateway") && pb->chassis ==
> > chassis)
> > > > +                || !strcmp(pb->type, "patch")) {
> > > > +                sset_add(local_l3gw_ports, pb->logical_port);
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +    sbrec_port_binding_index_destroy_row(target);
> > > > +}
> > > > +
> > > > +
> > > > +/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > > > + * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > > > + * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid
> > IPv4
> > > > + * or IPv6 address, and stores them in the 'ipv4_addrs' and
> > 'ipv6_addrs'
> > > > + * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > > > + *
> > > > + * Returns true if at least 'MAC' is found in 'address', false
> > otherwise.
> > > > + *
> > > > + * The caller must call destroy_lport_addresses() and free(*lport). */
> > > > +static bool
> > > > +extract_addresses_with_port(const char *addresses,
> > > > +                            struct lport_addresses *laddrs,
> > > > +                            char **lport)
> > > > +{
> > > > +    int ofs;
> > > > +    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > > > +        return false;
> > > > +    } else if (!addresses[ofs]) {
> > > > +        return true;
> > > > +    }
> > > > +
> > > > +    struct lexer lexer;
> > > > +    lexer_init(&lexer, addresses + ofs);
> > > > +    lexer_get(&lexer);
> > > > +
> > > > +    if (lexer.error || lexer.token.type != LEX_T_ID
> > > > +        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > > > +        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address",
> > addresses);
> > > > +        lexer_destroy(&lexer);
> > > > +        return true;
> > > > +    }
> > > > +
> > > > +    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > > > +        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > > > +                          "'is_chassis_resident' in address '%s'",
> > > > addresses);
> > > > +        lexer_destroy(&lexer);
> > > > +        return false;
> > > > +    }
> > > > +
> > > > +    if (lexer.token.type != LEX_T_STRING) {
> > > > +        VLOG_INFO_RL(&rl,
> > > > +                    "Syntax error: expecting quoted string after "
> > > > +                    "'is_chassis_resident' in address '%s'",
> > addresses);
> > > > +        lexer_destroy(&lexer);
> > > > +        return false;
> > > > +    }
> > > > +
> > > > +    *lport = xstrdup(lexer.token.s);
> > > > +
> > > > +    lexer_get(&lexer);
> > > > +    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > > > +        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > > > string in "
> > > > +                          "'is_chassis_resident()' in address '%s'",
> > > > +                          addresses);
> > > > +        lexer_destroy(&lexer);
> > > > +        return false;
> > > > +    }
> > > > +
> > > > +    lexer_destroy(&lexer);
> > > > +    return true;
> > > > +}
> > > > +
> > > > +static void
> > > > +consider_nat_address(struct ovsdb_idl_index
> > *sbrec_port_binding_by_name,
> > > > +                     const char *nat_address,
> > > > +                     const struct sbrec_port_binding *pb,
> > > > +                     struct sset *nat_address_keys,
> > > > +                     const struct sbrec_chassis *chassis,
> > > > +                     const struct sset *active_tunnels,
> > > > +                     struct shash *nat_addresses,
> > > > +                     struct sset *non_local_lports,
> > > > +                     struct sset *local_lports)
> > > > +{
> > > > +    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > > > +    char *lport = NULL;
> > > > +    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > > > +        || (!lport && !strcmp(pb->type, "patch"))) {
> > > > +        destroy_lport_addresses(laddrs);
> > > > +        free(laddrs);
> > > > +        free(lport);
> > > > +        return;
> > > > +    }
> > > > +    if (lport) {
> > > > +        if (!lport_is_chassis_resident(sbrec_port_binding_by_name,
> > > > +                chassis, active_tunnels, lport)) {
> > > > +            sset_add(non_local_lports, lport);
> > > > +            destroy_lport_addresses(laddrs);
> > > > +            free(laddrs);
> > > > +            free(lport);
> > > > +            return;
> > > > +        } else {
> > > > +            sset_add(local_lports, lport);
> > > > +        }
> > > > +    }
> > > > +    free(lport);
> > > > +
> > > > +    int i;
> > > > +    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > > +        char *name = xasprintf("%s-%s", pb->logical_port,
> > > > +                                        laddrs->ipv4_addrs[i].addr_s);
> > > > +        sset_add(nat_address_keys, name);
> > > > +        free(name);
> > > > +    }
> > > > +    if (laddrs->n_ipv4_addrs == 0) {
> > > > +        char *name = xasprintf("%s-noip", pb->logical_port);
> > > > +        sset_add(nat_address_keys, name);
> > > > +        free(name);
> > > > +    }
> > > > +    shash_add(nat_addresses, pb->logical_port, laddrs);
> > > > +}
> > > > +
> > > > +static void
> > > > +get_nat_addresses_and_keys(struct ovsdb_idl_index
> > > > *sbrec_port_binding_by_name,
> > > > +                           struct sset *nat_address_keys,
> > > > +                           struct sset *local_l3gw_ports,
> > > > +                           const struct sbrec_chassis *chassis,
> > > > +                           const struct sset *active_tunnels,
> > > > +                           struct shash *nat_addresses,
> > > > +                           struct sset *non_local_lports,
> > > > +                           struct sset *local_lports)
> > > > +{
> > > > +    const char *gw_port;
> > > > +    SSET_FOR_EACH (gw_port, local_l3gw_ports) {
> > > > +        const struct sbrec_port_binding *pb;
> > > > +
> > > > +        pb = lport_lookup_by_name(sbrec_port_binding_by_name,
> > gw_port);
> > > > +        if (!pb) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        if (pb->n_nat_addresses) {
> > > > +            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > > > +                consider_nat_address(sbrec_port_binding_by_name,
> > > > +                                     pb->nat_addresses[i], pb,
> > > > +                                     nat_address_keys, chassis,
> > > > +                                     active_tunnels,
> > > > +                                     nat_addresses,
> > > > +                                     non_local_lports,
> > > > +                                     local_lports);
> > > > +            }
> > > > +        } else {
> > > > +            /* Continue to support options:nat-addresses for version
> > > > +             * upgrade. */
> > > > +            const char *nat_addresses_options = smap_get(&pb->options,
> > > > +
> >  "nat-addresses");
> > > > +            if (nat_addresses_options) {
> > > > +                consider_nat_address(sbrec_port_binding_by_name,
> > > > +                                     nat_addresses_options, pb,
> > > > +                                     nat_address_keys, chassis,
> > > > +                                     active_tunnels,
> > > > +                                     nat_addresses,
> > > > +                                     non_local_lports,
> > > > +                                     local_lports);
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +}
> > > > +
> > > > +static uint32_t
> > > > +garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
> > > > +                    uint32_t port_key)
> > > > +{
> > > > +    return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
> > > > +}
> > > > +
> > > > +static uint32_t
> > > > +garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
> > > > +{
> > > > +    return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
> > > > +}
> > > > +
> > > > +static void
> > > > +garp_rarp_node_add(struct hmap *map, const struct eth_addr ea,
> > ovs_be32
> > > > ip,
> > > > +                   uint32_t dp_key, uint32_t port_key)
> > > > +{
> > > > +    struct garp_rarp_node *grn = xmalloc(sizeof *grn);
> > > > +    grn->ea = ea;
> > > > +    grn->ipv4 = ip;
> > > > +    grn->announce_time = time_msec() + 1000;
> > > > +    grn->backoff = 1000; /* msec. */
> > > > +    grn->dp_key = dp_key;
> > > > +    grn->port_key = port_key;
> > > > +    hmap_insert(map, &grn->hmap_node,
> > garp_rarp_node_hash_struct(grn));
> > > > +}
> > > > +
> > > > +/* Searches for a given garp_rarp_node in a hmap. Ignores the
> > > > announce_time
> > > > + * and backoff field since they might be different based on runtime.
> > */
> > > > +static struct garp_rarp_node*
> > > > +garp_rarp_lookup(const struct hmap *map, const struct garp_rarp_node
> > > > *search)
> > > > +{
> > > > +    struct garp_rarp_node *n;
> > > > +    uint32_t hash = garp_rarp_node_hash_struct(search);
> > > > +    HMAP_FOR_EACH_WITH_HASH (n, hmap_node, hash, map) {
> > > > +        if (!eth_addr_equals(search->ea, n->ea)) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        if (search->ipv4 != n->ipv4) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        if (search->dp_key != n->dp_key) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        if (search->port_key != n->port_key) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        return n;
> > > > +    }
> > > > +    return NULL;
> > > > +}
> > > > +
> > > > +/* Update or add an IP-MAC binding for 'logical_port'.
> > > > + * Caller should make sure that 'ovnsb_idl_txn' is valid. */
> > > > +static void
> > > > +mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > > +                      struct ovsdb_idl_index
> > > > *sbrec_mac_binding_by_lport_ip,
> > > > +                      const char *logical_port,
> > > > +                      const struct sbrec_datapath_binding *dp,
> > > > +                      struct eth_addr ea, const char *ip,
> > > > +                      bool update_only)
> > > > +{
> > > > +    /* Convert ethernet argument to string form for database. */
> > > > +    char mac_string[ETH_ADDR_STRLEN + 1];
> > > > +    snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT,
> > > > ETH_ADDR_ARGS(ea));
> > > > +
> > > > +    const struct sbrec_mac_binding *b =
> > > > +            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
> > > > +                               logical_port, ip);
> > > > +    if (!b) {
> > > > +        if (update_only) {
> > > > +            return;
> > > > +        }
> > > > +        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
> > > > +        sbrec_mac_binding_set_logical_port(b, logical_port);
> > > > +        sbrec_mac_binding_set_ip(b, ip);
> > > > +        sbrec_mac_binding_set_datapath(b, dp);
> > > > +    }
> > > > +
> > > > +    if (strcmp(b->mac, mac_string)) {
> > > > +        sbrec_mac_binding_set_mac(b, mac_string);
> > > > +
> > > > +        /* For backward compatibility check if timestamp column is
> > > > available
> > > > +         * in SB DB. */
> > > > +        if (sbrec_server_has_mac_binding_table_col_timestamp(
> > > > +                ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
> > > > +            VLOG_DBG("Setting MAC binding timestamp for "
> > > > +                     "ip:%s mac:%s port:%s to %lld",
> > > > +                     b->ip, b->mac, logical_port, time_wall_msec());
> > > > +            sbrec_mac_binding_set_timestamp(b, time_wall_msec());
> > > > +        }
> > > > +    }
> > > > +}
> > > >
> > >
> > > Unless I've missed something this is duplicated from pinctrl.c, how about
> > > moving
> > > this function to a module where it could be used by both?
> >
> > Sorry for missing that. Will be centralized in the next version.
> >
> > >
> > >
> > > > +
> > > > +/* Simulate the effect of a GARP on local datapaths, i.e., create
> > > > MAC_Bindings
> > > > + * on peer router datapaths.
> > > > + */
> > > > +static void
> > > > +send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
> > > > +                  const struct sbrec_port_binding *in_pb,
> > > > +                  struct eth_addr ea, ovs_be32 ip)
> > > > +{
> > > > +    if (!r_ctx_in->ovnsb_idl_txn) {
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    const struct local_datapath *ldp =
> > > > +        get_local_datapath(r_ctx_in->local_datapaths,
> > > > +                           in_pb->datapath->tunnel_key);
> > > > +
> > > > +    ovs_assert(ldp);
> > > > +    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > > > +        const struct sbrec_port_binding *local =
> > ldp->peer_ports[i].local;
> > > > +        const struct sbrec_port_binding *remote =
> > > > ldp->peer_ports[i].remote;
> > > > +
> > > > +        /* Skip "ingress" port. */
> > > > +        if (local == in_pb) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        bool update_only =
> > !smap_get_bool(&remote->datapath->external_ids,
> > > > +
> > "always_learn_from_arp_request",
> > > > +                                          true);
> > > > +
> > > > +        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > > > +
> > > > +        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > > > +        mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
> > > > +                              r_ctx_in->sbrec_mac_binding_by_lport_ip,
> > > > +                              remote->logical_port, remote->datapath,
> > > > +                              ea, ds_cstr(&ip_s), update_only);
> > > > +        ds_destroy(&ip_s);
> > > > +    }
> > > > +}
> > > > +
> > > > +/* Add or update a vif for which GARPs need to be announced. */
> > > > +static void
> > > > +send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
> > > > +                      const struct sbrec_port_binding *binding_rec,
> > > > +                      struct shash *nat_addresses,
> > > > +                      struct hmap *garp_rarp_data)
> > > > +{
> > > > +    /* Skip localports as they don't need to be announced */
> > > > +    if (!strcmp(binding_rec->type, "localport")) {
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    /* Update GARP for NAT IP if it exists.  Consider port bindings
> > with
> > > > type
> > > > +     * "l3gateway" for logical switch ports attached to gateway
> > routers,
> > > > and
> > > > +     * port bindings with type "patch" for logical switch ports
> > attached
> > > > to
> > > > +     * distributed gateway ports. */
> > > > +    if (!strcmp(binding_rec->type, "l3gateway")
> > > > +        || !strcmp(binding_rec->type, "patch")) {
> > > > +        struct lport_addresses *laddrs = NULL;
> > > > +        while ((laddrs = shash_find_and_delete(nat_addresses,
> > > > +
> > > >  binding_rec->logical_port))) {
> > > > +            int i;
> > > > +            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > > > +                              laddrs->ipv4_addrs[i].addr,
> > > > +                              binding_rec->datapath->tunnel_key,
> > > > +                              binding_rec->tunnel_key);
> > > > +                send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
> > > > +                                  laddrs->ipv4_addrs[i].addr);
> > > > +            }
> > > > +            /*
> > > > +             * Send RARPs even if we do not have a ipv4 address as it
> > e.g.
> > > > +             * happens on ipv6 only ports.
> > > > +             */
> > > > +            if (laddrs->n_ipv4_addrs == 0) {
> > > > +                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
> > > > +                              0, binding_rec->datapath->tunnel_key,
> > > > +                              binding_rec->tunnel_key);
> > > > +            }
> > > > +            destroy_lport_addresses(laddrs);
> > > > +            free(laddrs);
> > > > +        }
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    /* Add GARP for new vif. */
> > > > +    int i;
> > > > +    for (i = 0; i < binding_rec->n_mac; i++) {
> > > > +        struct lport_addresses laddrs;
> > > > +        ovs_be32 ip = 0;
> > > > +        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > > > +            continue;
> > > > +        }
> > > > +
> > > > +        if (laddrs.n_ipv4_addrs) {
> > > > +            ip = laddrs.ipv4_addrs[0].addr;
> > > > +        }
> > > > +
> > > > +        garp_rarp_node_add(garp_rarp_data,
> > > > +                      laddrs.ea, ip,
> > > > +                      binding_rec->datapath->tunnel_key,
> > > > +                      binding_rec->tunnel_key);
> > > > +        if (ip) {
> > > > +            send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
> > > > +        }
> > > > +
> > > > +        destroy_lport_addresses(&laddrs);
> > > > +        break;
> > > > +    }
> > > > +}
> > > > +
> > > > +static struct garp_rarp_node*
> > > > +garp_rarp_node_clone(const struct garp_rarp_node *src)
> > > > +{
> > > > +    struct garp_rarp_node *dst = xzalloc(sizeof *dst);
> > > > +    dst->ea = src->ea;
> > > > +    dst->ipv4 = src->ipv4;
> > > > +    dst->announce_time = time_msec() + 1000;
> > > > +    dst->backoff = 1000;
> > > > +    dst->dp_key = src->dp_key;
> > > > +    dst->port_key = src->port_key;
> > > > +    return dst;
> > > > +}
> > > > +
> > > > +static void
> > > > +garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in,
> > > > +                struct garp_rarp_ctx_out *r_ctx_out)
> > > > +{
> > > > +    struct garp_rarp_node *grn;
> > > > +    HMAP_FOR_EACH_POP (grn, hmap_node, r_ctx_out->garp_rarp_data) {
> > > > +        garp_rarp_node_free(grn);
> > > > +    }
> > > > +    hmap_clear(r_ctx_out->garp_rarp_data);
> > > > +    sset_clear(r_ctx_in->non_local_lports);
> > > > +    sset_clear(r_ctx_in->local_lports);
> > > > +}
> > > > +
> > > > +void
> > > > +garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in,
> > > > +              struct garp_rarp_ctx_out *r_ctx_out)
> > > > +{
> > > > +    garp_rarp_clear(r_ctx_in, r_ctx_out);
> > > > +
> > > > +    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > > > +    struct sset local_l3gw_ports =
> > SSET_INITIALIZER(&local_l3gw_ports);
> > > > +    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > > > +    struct shash nat_addresses;
> > > > +
> > > > +    shash_init(&nat_addresses);
> > > > +
> > > > +
> > get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
> > > > +                                r_ctx_in->sbrec_port_binding_by_name,
> > > > +                                r_ctx_in->br_int, r_ctx_in->chassis,
> > > > +                                r_ctx_in->local_datapaths,
> > > > +                                &localnet_vifs, &local_l3gw_ports);
> > > > +
> > > > +    get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
> > > > +                               &nat_ip_keys, &local_l3gw_ports,
> > > > +                               r_ctx_in->chassis,
> > > > r_ctx_in->active_tunnels,
> > > > +                               &nat_addresses,
> > r_ctx_in->non_local_lports,
> > > > +                               r_ctx_in->local_lports);
> > > > +
> > > > +    /* Update send_garp_rarp_data. */
> > > > +    const char *iface_id;
> > > > +    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > > > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > > +            r_ctx_in->sbrec_port_binding_by_name, iface_id);
> > > > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > > false)) {
> > > > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > > > +                                  r_ctx_out->garp_rarp_data);
> > > > +        }
> > > > +    }
> > > > +
> > > > +    /* Update send_garp_rarp_data for nat-addresses. */
> > > > +    const char *gw_port;
> > > > +    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > > > +        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > > +            r_ctx_in->sbrec_port_binding_by_name, gw_port);
> > > > +        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > > false)) {
> > > > +            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
> > > > +                                  r_ctx_out->garp_rarp_data);
> > > > +        }
> > > > +    }
> > > > +
> > > > +    sset_destroy(&localnet_vifs);
> > > > +    sset_destroy(&local_l3gw_ports);
> > > > +
> > > > +    struct shash_node *iter;
> > > > +    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > > > +        struct lport_addresses *laddrs = iter->data;
> > > > +        destroy_lport_addresses(laddrs);
> > > > +        shash_delete(&nat_addresses, iter);
> > > > +        free(laddrs);
> > > > +    }
> > > > +    shash_destroy(&nat_addresses);
> > > > +
> > > > +    sset_destroy(&nat_ip_keys);
> > > > +}
> > > > +
> > > > +/* Syncs a hmap of garp_rarp_node to another hmap. The elements in the
> > > > + * destination hmap are initialized with valid announce times based
> > on the
> > > > + * current timestamp.
> > > > + * If source is NULL then destination will at most change the timer if
> > > > + * reset_timers is set.
> > > > + * If reset_timers is true then the timers of all entries in the
> > > > destination
> > > > + * hmap will be reset to default values.
> > > > + * Returns true if the destination hmap was chnaged. */
> > > > +bool
> > > > +garp_rarp_sync(const struct hmap *source, struct hmap *dest, bool
> > > > reset_timers)
> > > > +{
> > > > +    struct garp_rarp_node *grn;
> > > > +    bool changed = false;
> > > > +
> > > > +    HMAP_FOR_EACH_SAFE (grn, hmap_node, dest) {
> > > > +        if (source && !garp_rarp_lookup(source, grn)) {
> > > > +            hmap_remove(dest, &grn->hmap_node);
> > > > +            garp_rarp_node_free(grn);
> > > > +            changed = true;
> > > > +        } else if (reset_timers) {
> > > > +            grn->announce_time = time_msec() + 1000;
> > > > +            grn->backoff = 1000;
> > > > +            changed = true;
> > > > +        }
> > > > +    }
> > > > +
> > > > +    if (source) {
> > > > +        HMAP_FOR_EACH (grn, hmap_node, source) {
> > > > +            if (!garp_rarp_lookup(dest, grn)) {
> > > > +                uint32_t hash = garp_rarp_node_hash_struct(grn);
> > > > +                hmap_insert(dest,
> > &garp_rarp_node_clone(grn)->hmap_node,
> > > > hash);
> > > > +                changed = true;
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +
> > > > +    return changed;
> > > > +}
> > > >
> > >
> > > That's a lot of copying between the hmaps, why can't we indicate that the
> > > announcement was sent for the last time and use just the status instead
> > > of copying parts of the hmap?
> >
> > The idea was that any state that needs to be persistently kept is part
> > of the pinctrl module. The engine node is then free to discard the hmap
> > and generate it again from scratch.
> >
> > Also the data is needed in the pinctrl thread separately from the main
> > thread. So copying seemed like the best solution to avoid needing to
> > lock the data between pinctrl and the engine node.
> >
> 
> Yes the locking might be a problem, how about using CMAP instead?
> That should get rid of the locking problem and we could avoid the
> copies. I'll leave it up to you if you would like to do it as part
> of this patch or as follow up.

I'll include it in these changes. It seems to be cleaner when i rework
it in one go.

Thanks a lot,
Felix

> 
> 
> >
> > >
> > >
> > > > +
> > > > +void
> > > > +garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
> > > > +{
> > > > +    free(garp_rarp);
> > > > +}
> > > > diff --git a/controller/garp_rarp.h b/controller/garp_rarp.h
> > > > new file mode 100644
> > > > index 000000000..b85e75a7c
> > > > --- /dev/null
> > > > +++ b/controller/garp_rarp.h
> > > > @@ -0,0 +1,61 @@
> > > > +/*
> > > > + * Copyright (c) 2025, STACKIT GmbH & Co. KG
> > > > + *
> > > > + * 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 GARP_RARP_H
> > > > +#define GARP_RARP_H 1
> > > > +
> > > > +#include "openvswitch/hmap.h"
> > > > +#include "openvswitch/types.h"
> > > > +
> > > > +/* Contains a single mac and ip address that should be announced. */
> > > > +struct garp_rarp_node {
> > > > +    struct hmap_node hmap_node;
> > > > +    struct eth_addr ea;          /* Ethernet address of port. */
> > > > +    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > > > +    long long int announce_time; /* Next announcement in ms.
> > > > +                                  * If LLONG_MAX there should be no
> > > > +                                  * annoucement. */
> > > > +    int backoff;                 /* Backoff timeout for the next
> > > > +                                  * announcement (in msecs). */
> > > > +    uint32_t dp_key;             /* Datapath used to output this
> > GARP. */
> > > > +    uint32_t port_key;           /* Port to inject the GARP into. */
> > > > +};
> > > > +
> > > > +struct garp_rarp_ctx_in {
> > > > +    struct ovsdb_idl_txn *ovnsb_idl_txn;
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name;
> > > > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
> > > > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
> > > > +    const struct ovsrec_bridge *br_int;
> > > > +    const struct sbrec_chassis *chassis;
> > > > +    const struct hmap *local_datapaths;
> > > > +    const struct sset *active_tunnels;
> > > > +    struct sset *non_local_lports;
> > > > +    struct sset *local_lports;
> > > > +};
> > > > +
> > > > +struct garp_rarp_ctx_out {
> > > > +    /* Contains struct garp_rarp_node. */
> > > > +    struct hmap *garp_rarp_data;
> > > > +};
> > > > +
> > > > +void garp_rarp_run(struct garp_rarp_ctx_in *, struct
> > garp_rarp_ctx_out *);
> > > > +bool garp_rarp_sync(const struct hmap *source, struct hmap *dest,
> > > > +                    bool reset_timers);
> > > > +void garp_rarp_node_free(struct garp_rarp_node *);
> > > > +
> > > > +#endif /* GARP_RARP_H */
> > > > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > > > index cfed5282c..6f1b27876 100644
> > > > --- a/controller/ovn-controller.c
> > > > +++ b/controller/ovn-controller.c
> > > > @@ -92,6 +92,7 @@
> > > >  #include "route.h"
> > > >  #include "route-exchange.h"
> > > >  #include "route-table-notify.h"
> > > > +#include "garp_rarp.h"
> > > >
> > > >  VLOG_DEFINE_THIS_MODULE(main);
> > > >
> > > > @@ -4952,6 +4953,14 @@ controller_output_route_exchange_handler(struct
> > > > engine_node *node,
> > > >      return true;
> > > >  }
> > > >
> > > > +static bool
> > > > +controller_output_garp_rarp_handler(struct engine_node *node,
> > > > +                                    void *data OVS_UNUSED)
> > > > +{
> > > > +    engine_set_node_state(node, EN_UPDATED);
> > > > +    return true;
> > > > +}
> > > > +
> > > >  /* Handles sbrec_chassis changes.
> > > >   * If a new chassis is added or removed return false, so that
> > > >   * flows are recomputed.  For any updates, there is no need for
> > > > @@ -5305,6 +5314,216 @@ en_route_table_notify_cleanup(void *data
> > > > OVS_UNUSED)
> > > >  {
> > > >  }
> > > >
> > > > +struct ed_type_garp_rarp {
> > > > +    struct hmap garp_rarp_data;
> > > > +
> > > > +    /* non_local_lports and local_lports are used in the incremental
> > > > handlers
> > > > +     * to trigger updates if such a port changes. */
> > > > +    struct sset non_local_lports; /* lports that we did not consider
> > > > because
> > > > +                                     they where not local. */
> > > > +    struct sset local_lports; /* lports where we did consider the
> > > > addresses
> > > > +                                 because they where local. */
> > > > +};
> > > > +
> > > > +static void
> > > > +en_garp_rarp_run(struct engine_node *node, void *data_)
> > > > +{
> > > > +    struct ed_type_garp_rarp *data = data_;
> > > > +
> > > > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > > > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > > > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > > > +    ovs_assert(chassis_id);
> > > > +
> > > > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_chassis", node),
> > > > +                "name");
> > > > +    const struct sbrec_chassis *chassis
> > > > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > > > +    ovs_assert(chassis);
> > > > +
> > > > +    const struct ovsrec_bridge_table *bridge_table =
> > > > +        EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
> > > > +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> > > > ovs_table);
> > > > +
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_port_binding", node),
> > > > +                "datapath");
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_port_binding", node),
> > > > +                "name");
> > > > +    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_mac_binding", node),
> > > > +                "lport_ip");
> > > > +
> > > > +    struct ovsdb_idl_txn *ovnsb_idl_txn =
> > > > engine_get_context()->ovnsb_idl_txn;
> > > > +
> > > > +    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
> > > > +
> > > > sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
> > > > +
> > > > +    struct ed_type_runtime_data *rt_data =
> > > > +        engine_get_input_data("runtime_data", node);
> > > > +
> > > > +    struct garp_rarp_ctx_in r_ctx_in = {
> > > > +        .ovnsb_idl_txn = ovnsb_idl_txn,
> > > > +        .sbrec_port_binding_by_datapath =
> > sbrec_port_binding_by_datapath,
> > > > +        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> > > > +        .sbrec_mac_binding_by_lport_ip =
> > sbrec_mac_binding_by_lport_ip,
> > > > +        .ecmp_nh_table = ecmp_nh_table,
> > > > +        .br_int = br_int,
> > > > +        .chassis = chassis,
> > > > +        .active_tunnels = &rt_data->active_tunnels,
> > > > +        .local_datapaths = &rt_data->local_datapaths,
> > > > +        .non_local_lports = &data->non_local_lports,
> > > > +        .local_lports = &data->local_lports,
> > > > +    };
> > > > +
> > > > +    struct garp_rarp_ctx_out r_ctx_out = {
> > > > +        .garp_rarp_data = &data->garp_rarp_data,
> > > > +    };
> > > > +
> > > > +    garp_rarp_run(&r_ctx_in, &r_ctx_out);
> > > > +}
> > > > +
> > > > +
> > > > +static void *
> > > > +en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
> > > > +                  struct engine_arg *arg OVS_UNUSED)
> > > > +{
> > > > +    struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
> > > > +    hmap_init(&gr->garp_rarp_data);
> > > > +    sset_init(&gr->non_local_lports);
> > > > +    sset_init(&gr->local_lports);
> > > > +    return gr;
> > > > +}
> > > > +
> > > > +static void
> > > > +en_garp_rarp_cleanup(void *data_)
> > > > +{
> > > > +    struct ed_type_garp_rarp *data = data_;
> > > > +    struct garp_rarp_node *grn;
> > > > +    HMAP_FOR_EACH_POP (grn, hmap_node, &data->garp_rarp_data) {
> > > > +        garp_rarp_node_free(grn);
> > > > +    }
> > > > +    hmap_destroy(&data->garp_rarp_data);
> > > > +    sset_destroy(&data->non_local_lports);
> > > > +    sset_destroy(&data->local_lports);
> > > > +}
> > > > +
> > > > +static bool
> > > > +garp_rarp_sb_port_binding_handler(struct engine_node *node,
> > > > +                                  void *data_)
> > > > +{
> > > > +    /* We need to handle a change if there was change on a datapath
> > with
> > > > +     * a localnet port.
> > > > +     * Also the ha_chassis status of a port binding might change. */
> > > > +    struct ed_type_garp_rarp *data = data_;
> > > > +
> > > > +    const struct ovsrec_open_vswitch_table *ovs_table =
> > > > +        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
> > > > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > > > +    ovs_assert(chassis_id);
> > > > +
> > > > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_chassis", node),
> > > > +                "name");
> > > > +    const struct sbrec_chassis *chassis
> > > > +        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> > > > +    ovs_assert(chassis);
> > > > +
> > > > +    struct ed_type_runtime_data *rt_data =
> > > > +            engine_get_input_data("runtime_data", node);
> > > > +    const struct sbrec_port_binding_table *port_binding_table =
> > > > +        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
> > > > +    struct ovsdb_idl_index *sbrec_port_binding_by_name =
> > > > +        engine_ovsdb_node_get_index(
> > > > +                engine_get_input("SB_port_binding", node),
> > > > +                "name");
> > > > +
> > > > +    const struct sbrec_port_binding *pb;
> > > > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > port_binding_table) {
> > > > +        struct local_datapath *ld = get_local_datapath(
> > > > +            &rt_data->local_datapaths, pb->datapath->tunnel_key);
> > > > +
> > > > +        if (!ld || ld->localnet_port) {
> > > > +            /* XXX: actually handle this incrementally. */
> > > > +            return false;
> > > > +        }
> > > > +
> > > > +        if (sset_contains(&data->non_local_lports, pb->logical_port)
> > &&
> > > > +            lport_is_chassis_resident(sbrec_port_binding_by_name,
> > chassis,
> > > > +                                      &rt_data->active_tunnels,
> > > > +                                      pb->logical_port)) {
> > > > +            /* XXX: actually handle this incrementally. */
> > > > +            return false;
> > > > +        }
> > > > +
> > > > +        if (sset_contains(&data->local_lports, pb->logical_port) &&
> > > > +            !lport_is_chassis_resident(sbrec_port_binding_by_name,
> > > > chassis,
> > > > +                                       &rt_data->active_tunnels,
> > > > +                                       pb->logical_port)) {
> > > > +            /* XXX: actually handle this incrementally. */
> > > > +            return false;
> > > > +        }
> > > > +    }
> > > > +
> > > > +    return true;
> > > > +}
> > > > +
> > > > +static bool
> > > > +garp_rarp_runtime_data_handler(struct engine_node *node, void *data
> > > > OVS_UNUSED)
> > > > +{
> > > > +    /* We use two elements from rt_data:
> > > > +     * 1. active_tunnels: There is currently not incremental
> > processing
> > > > for
> > > > +     *    this in runtime_data. So we just fall back to a recompute.
> > > > +     * 2. local_datapaths: This has incremental processing on the
> > > > runtime_data
> > > > +     *    side. We are only interested in datapaths with a localnet
> > port
> > > > so
> > > > +     *    we just recompute if there is one in there. Otherwise the
> > > > change is
> > > > +     *    irrelevant for us. */
> > > > +
> > > > +    struct ed_type_runtime_data *rt_data =
> > > > +            engine_get_input_data("runtime_data", node);
> > > > +
> > > > +    /* There are no tracked data. Fall back to full recompute. */
> > > > +    if (!rt_data->tracked) {
> > > > +        return false;
> > > > +    }
> > > > +
> > > > +    struct tracked_datapath *tdp;
> > > > +    HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
> > > > +        if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
> > > > +            /* This is currently not handled incrementally in
> > runtime_data
> > > > +             * so it should never happen. Recompute just in case. */
> > > > +            return false;
> > > > +        }
> > > > +
> > > > +        struct local_datapath *ld = get_local_datapath(
> > > > +            &rt_data->local_datapaths, tdp->dp->tunnel_key);
> > > > +
> > > > +        if (!ld || ld->localnet_port) {
> > > > +            /* XXX: actually handle this incrementally. */
> > > > +            return false;
> > > > +        }
> > > > +
> > > > +        /* The localnet port might also have been removed. */
> > > > +        struct tracked_lport *tlp;
> > > > +        struct shash_node *sn;
> > > > +        SHASH_FOR_EACH (sn, &tdp->lports) {
> > > > +            tlp = sn->data;
> > > > +            if (!strcmp(tlp->pb->type, "localnet")) {
> > > > +                return false;
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +
> > > > +    return true;
> > > > +}
> > > > +
> > > >  /* Returns false if the northd internal version stored in SB_Global
> > > >   * and ovn-controller internal version don't match.
> > > >   */
> > > > @@ -5614,6 +5833,7 @@ main(int argc, char *argv[])
> > > >      ENGINE_NODE(route, "route");
> > > >      ENGINE_NODE(route_table_notify, "route_table_notify");
> > > >      ENGINE_NODE(route_exchange, "route_exchange");
> > > > +    ENGINE_NODE(garp_rarp, "garp_rarp");
> > > >
> > > >  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> > > >      SB_NODES
> > > > @@ -5826,6 +6046,15 @@ main(int argc, char *argv[])
> > > >      engine_add_input(&en_dns_cache, &en_sb_dns,
> > > >                       dns_cache_sb_dns_handler);
> > > >
> > > > +    engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
> > > > +    engine_add_input(&en_garp_rarp, &en_ovs_bridge, NULL);
> > > > +    engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
> > > > +    engine_add_input(&en_garp_rarp, &en_sb_port_binding,
> > > > +                     garp_rarp_sb_port_binding_handler);
> > > > +    engine_add_input(&en_garp_rarp, &en_sb_mac_binding, NULL);
> > > >
> > >
> > > This dependency leads to recompute every time we insert MB locally.
> > > It seems that we need this only for index, so it should be noop handler.
> >
> > Yes, thanks.
> >
> > >
> > >
> > > > +    engine_add_input(&en_garp_rarp, &en_runtime_data,
> > > > +                     garp_rarp_runtime_data_handler);
> > > > +
> > > >      engine_add_input(&en_controller_output, &en_dns_cache,
> > > >                       NULL);
> > > >      engine_add_input(&en_controller_output, &en_lflow_output,
> > > > @@ -5838,6 +6067,8 @@ main(int argc, char *argv[])
> > > >                       controller_output_bfd_chassis_handler);
> > > >      engine_add_input(&en_controller_output, &en_route_exchange,
> > > >                       controller_output_route_exchange_handler);
> > > > +    engine_add_input(&en_controller_output, &en_garp_rarp,
> > > > +                     controller_output_garp_rarp_handler);
> > > >
> > > >      engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
> > > >      engine_add_input(&en_controller_output, &en_acl_id,
> > > > @@ -5874,6 +6105,8 @@ main(int argc, char *argv[])
> > > >
> > > >  sbrec_chassis_template_var_index_by_chassis);
> > > >      engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
> > > >
> > sbrec_learned_route_index_by_datapath);
> > > > +    engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
> > > > +                                sbrec_mac_binding_by_lport_ip);
> > > >      engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set,
> > "id",
> > > >
> > ovsrec_flow_sample_collector_set_by_id);
> > > >      engine_ovsdb_node_add_index(&en_ovs_port, "qos",
> > ovsrec_port_by_qos);
> > > > @@ -6290,9 +6523,12 @@ main(int argc, char *argv[])
> > > >                          stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
> > > >                                          time_msec());
> > > >                          pinctrl_update(ovnsb_idl_loop.idl);
> > > > +                        const struct ed_type_garp_rarp *grd =
> > > > engine_get_data(
> > > > +                            &en_garp_rarp);
> > > > +                        const struct hmap *garp_rarp_data =
> > > > +                            grd ? &grd->garp_rarp_data : NULL;
> > > >                          pinctrl_run(ovnsb_idl_txn,
> > > >                                      sbrec_datapath_binding_by_key,
> > > > -                                    sbrec_port_binding_by_datapath,
> > > >                                      sbrec_port_binding_by_key,
> > > >                                      sbrec_port_binding_by_name,
> > > >                                      sbrec_mac_binding_by_lport_ip,
> > > > @@ -6308,13 +6544,14 @@ main(int argc, char *argv[])
> > > >
> > > >  sbrec_bfd_table_get(ovnsb_idl_loop.idl),
> > > >                                      sbrec_ecmp_nexthop_table_get(
> > > >                                          ovnsb_idl_loop.idl),
> > > > -                                    br_int, chassis,
> > > > +                                    chassis,
> > > >                                      &runtime_data->local_datapaths,
> > > >                                      &runtime_data->active_tunnels,
> > > >
> > > >  &runtime_data->local_active_ports_ipv6_pd,
> > > >
> > &runtime_data->local_active_ports_ras,
> > > >                                      ovsrec_open_vswitch_table_get(
> > > > -                                            ovs_idl_loop.idl));
> > > > +                                            ovs_idl_loop.idl),
> > > > +                                    garp_rarp_data);
> > > >                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
> > > >                                         time_msec());
> > > >                          mirror_run(ovs_idl_txn,
> > > > diff --git a/controller/pinctrl.c b/controller/pinctrl.c
> > > > index 1481b2547..6b4a4a520 100644
> > > > --- a/controller/pinctrl.c
> > > > +++ b/controller/pinctrl.c
> > > > @@ -64,6 +64,7 @@
> > > >  #include "ip-mcast.h"
> > > >  #include "ovn-sb-idl.h"
> > > >  #include "ovn-dns.h"
> > > > +#include "garp_rarp.h"
> > > >
> > > >  VLOG_DEFINE_THIS_MODULE(pinctrl);
> > > >
> > > > @@ -233,15 +234,9 @@ static void destroy_send_arps_nds(void);
> > > >  static void send_garp_rarp_wait(long long int send_garp_rarp_time);
> > > >  static void send_arp_nd_wait(long long int send_arp_nd_time);
> > > >  static void send_garp_rarp_prepare(
> > > > -    struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > > -    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > > > +    const struct hmap *garp_rarp_data,
> > > >      const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > > -    const struct ovsrec_bridge *,
> > > > -    const struct sbrec_chassis *,
> > > > -    const struct hmap *local_datapaths,
> > > > -    const struct sset *active_tunnels,
> > > > +    const struct sbrec_chassis *chassis,
> > > >      const struct ovsrec_open_vswitch_table *ovs_table)
> > > >      OVS_REQUIRES(pinctrl_mutex);
> > > >  static void send_garp_rarp_run(struct rconn *swconn,
> > > > @@ -4066,7 +4061,6 @@ pinctrl_update(const struct ovsdb_idl *idl)
> > > >  void
> > > >  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > >              struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> > > > -            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > >              struct ovsdb_idl_index *sbrec_port_binding_by_key,
> > > >              struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > >              struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
> > > > @@ -4078,13 +4072,13 @@ pinctrl_run(struct ovsdb_idl_txn
> > *ovnsb_idl_txn,
> > > >              const struct sbrec_mac_binding_table *mac_binding_table,
> > > >              const struct sbrec_bfd_table *bfd_table,
> > > >              const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > > -            const struct ovsrec_bridge *br_int,
> > > >              const struct sbrec_chassis *chassis,
> > > >              const struct hmap *local_datapaths,
> > > >              const struct sset *active_tunnels,
> > > >              const struct shash *local_active_ports_ipv6_pd,
> > > >              const struct shash *local_active_ports_ras,
> > > > -            const struct ovsrec_open_vswitch_table *ovs_table)
> > > > +            const struct ovsrec_open_vswitch_table *ovs_table,
> > > > +            const struct hmap *garp_rarp_data)
> > > >  {
> > > >      ovs_mutex_lock(&pinctrl_mutex);
> > > >      run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
> > > > @@ -4092,11 +4086,8 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > >                           sbrec_mac_binding_by_lport_ip);
> > > >      run_put_vport_bindings(ovnsb_idl_txn,
> > sbrec_datapath_binding_by_key,
> > > >                             sbrec_port_binding_by_key, chassis);
> > > > -    send_garp_rarp_prepare(ovnsb_idl_txn,
> > sbrec_port_binding_by_datapath,
> > > > -                           sbrec_port_binding_by_name,
> > > > -                           sbrec_mac_binding_by_lport_ip,
> > ecmp_nh_table,
> > > > -                           br_int, chassis, local_datapaths,
> > > > active_tunnels,
> > > > -                           ovs_table);
> > > > +    send_garp_rarp_prepare(garp_rarp_data, ecmp_nh_table,
> > > > +                           chassis, ovs_table);
> > > >      prepare_ipv6_ras(local_active_ports_ras,
> > sbrec_port_binding_by_name);
> > > >      prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
> > > >                           local_active_ports_ipv6_pd, chassis,
> > > > @@ -4785,47 +4776,6 @@ mac_binding_add_to_sb(struct ovsdb_idl_txn
> > > > *ovnsb_idl_txn,
> > > >      }
> > > >  }
> > > >
> > > > -/* Simulate the effect of a GARP on local datapaths, i.e., create
> > > > MAC_Bindings
> > > > - * on peer router datapaths.
> > > > - */
> > > > -static void
> > > > -send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > > -                  struct ovsdb_idl_index
> > *sbrec_mac_binding_by_lport_ip,
> > > > -                  const struct hmap *local_datapaths,
> > > > -                  const struct sbrec_port_binding *in_pb,
> > > > -                  struct eth_addr ea, ovs_be32 ip)
> > > > -{
> > > > -    if (!ovnsb_idl_txn) {
> > > > -        return;
> > > > -    }
> > > > -
> > > > -    const struct local_datapath *ldp =
> > > > -        get_local_datapath(local_datapaths,
> > in_pb->datapath->tunnel_key);
> > > > -
> > > > -    ovs_assert(ldp);
> > > > -    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
> > > > -        const struct sbrec_port_binding *local =
> > ldp->peer_ports[i].local;
> > > > -        const struct sbrec_port_binding *remote =
> > > > ldp->peer_ports[i].remote;
> > > > -
> > > > -        /* Skip "ingress" port. */
> > > > -        if (local == in_pb) {
> > > > -            continue;
> > > > -        }
> > > > -
> > > > -        bool update_only =
> > !smap_get_bool(&remote->datapath->external_ids,
> > > > -
> > "always_learn_from_arp_request",
> > > > -                                          true);
> > > > -
> > > > -        struct ds ip_s = DS_EMPTY_INITIALIZER;
> > > > -
> > > > -        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
> > > > -        mac_binding_add_to_sb(ovnsb_idl_txn,
> > > > sbrec_mac_binding_by_lport_ip,
> > > > -                              remote->logical_port, remote->datapath,
> > > > -                              ea, ds_cstr(&ip_s), update_only);
> > > > -        ds_destroy(&ip_s);
> > > > -    }
> > > > -}
> > > > -
> > > >  static void
> > > >  run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > >                      struct ovsdb_idl_index
> > *sbrec_datapath_binding_by_key,
> > > > @@ -4977,173 +4927,25 @@ wait_put_mac_bindings(struct ovsdb_idl_txn
> > > > *ovnsb_idl_txn)
> > > >   * are needed for switches and routers on the broadcast segment to
> > update
> > > >   * their port-mac and ARP tables.
> > > >   */
> > > > -struct garp_rarp_data {
> > > > -    struct eth_addr ea;          /* Ethernet address of port. */
> > > > -    ovs_be32 ipv4;               /* Ipv4 address of port. */
> > > > -    long long int announce_time; /* Next announcement in ms. */
> > > > -    int backoff;                 /* Backoff timeout for the next
> > > > -                                  * announcement (in msecs). */
> > > > -    uint32_t dp_key;             /* Datapath used to output this
> > GARP. */
> > > > -    uint32_t port_key;           /* Port to inject the GARP into. */
> > > > -};
> > > >
> > > > -/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
> > > > -static struct shash send_garp_rarp_data;
> > > > +/* Contains GARPs/RARPs to be sent in struct garp_rarp_node.
> > > > + * Protected by pinctrl_mutex. */
> > > > +static struct hmap send_garp_rarp_data;
> > > >
> > > >  static void
> > > >  init_send_garps_rarps(void)
> > > >  {
> > > > -    shash_init(&send_garp_rarp_data);
> > > > +    hmap_init(&send_garp_rarp_data);
> > > >  }
> > > >
> > > >  static void
> > > >  destroy_send_garps_rarps(void)
> > > >  {
> > > > -    shash_destroy_free_data(&send_garp_rarp_data);
> > > > -}
> > > > -
> > > > -/* Runs with in the main ovn-controller thread context. */
> > > > -static void
> > > > -add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
> > > > -              uint32_t dp_key, uint32_t port_key)
> > > > -{
> > > > -    struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
> > > > -    garp_rarp->ea = ea;
> > > > -    garp_rarp->ipv4 = ip;
> > > > -    garp_rarp->announce_time = time_msec() + 1000;
> > > > -    garp_rarp->backoff = 1000; /* msec. */
> > > > -    garp_rarp->dp_key = dp_key;
> > > > -    garp_rarp->port_key = port_key;
> > > > -    shash_add(&send_garp_rarp_data, name, garp_rarp);
> > > > -
> > > > -    /* Notify pinctrl_handler so that it can wakeup and process
> > > > -     * these GARP/RARP requests. */
> > > > -    notify_pinctrl_handler();
> > > > -}
> > > > -
> > > > -/* Add or update a vif for which GARPs need to be announced. */
> > > > -static void
> > > > -send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > > -                      struct ovsdb_idl_index
> > > > *sbrec_mac_binding_by_lport_ip,
> > > > -                      const struct hmap *local_datapaths,
> > > > -                      const struct sbrec_port_binding *binding_rec,
> > > > -                      struct shash *nat_addresses,
> > > > -                      long long int garp_max_timeout,
> > > > -                      bool garp_continuous)
> > > > -{
> > > > -    volatile struct garp_rarp_data *garp_rarp = NULL;
> > > > -
> > > > -    /* Skip localports as they don't need to be announced */
> > > > -    if (!strcmp(binding_rec->type, "localport")) {
> > > > -        return;
> > > > -    }
> > > > -
> > > > -    /* Update GARP for NAT IP if it exists.  Consider port bindings
> > with
> > > > type
> > > > -     * "l3gateway" for logical switch ports attached to gateway
> > routers,
> > > > and
> > > > -     * port bindings with type "patch" for logical switch ports
> > attached
> > > > to
> > > > -     * distributed gateway ports. */
> > > > -    if (!strcmp(binding_rec->type, "l3gateway")
> > > > -        || !strcmp(binding_rec->type, "patch")) {
> > > > -        struct lport_addresses *laddrs = NULL;
> > > > -        while ((laddrs = shash_find_and_delete(nat_addresses,
> > > > -
> > > >  binding_rec->logical_port))) {
> > > > -            int i;
> > > > -            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > > -                char *name = xasprintf("%s-%s",
> > binding_rec->logical_port,
> > > > -
> > > > laddrs->ipv4_addrs[i].addr_s);
> > > > -                garp_rarp = shash_find_data(&send_garp_rarp_data,
> > name);
> > > > -                if (garp_rarp) {
> > > > -                    garp_rarp->dp_key =
> > binding_rec->datapath->tunnel_key;
> > > > -                    garp_rarp->port_key = binding_rec->tunnel_key;
> > > > -                    if (garp_max_timeout != garp_rarp_max_timeout ||
> > > > -                        garp_continuous != garp_rarp_continuous) {
> > > > -                        /* reset backoff */
> > > > -                        garp_rarp->announce_time = time_msec() + 1000;
> > > > -                        garp_rarp->backoff = 1000; /* msec. */
> > > > -                    }
> > > > -                } else if (ovnsb_idl_txn) {
> > > > -                    add_garp_rarp(name, laddrs->ea,
> > > > -                                  laddrs->ipv4_addrs[i].addr,
> > > > -                                  binding_rec->datapath->tunnel_key,
> > > > -                                  binding_rec->tunnel_key);
> > > > -                    send_garp_locally(ovnsb_idl_txn,
> > > > -                                      sbrec_mac_binding_by_lport_ip,
> > > > -                                      local_datapaths, binding_rec,
> > > > laddrs->ea,
> > > > -                                      laddrs->ipv4_addrs[i].addr);
> > > > -
> > > > -                }
> > > > -                free(name);
> > > > -            }
> > > > -            /*
> > > > -             * Send RARPs even if we do not have a ipv4 address as it
> > e.g.
> > > > -             * happens on ipv6 only ports.
> > > > -             */
> > > > -            if (laddrs->n_ipv4_addrs == 0) {
> > > > -                    char *name = xasprintf("%s-noip",
> > > > -                                           binding_rec->logical_port);
> > > > -                    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > > > name);
> > > > -                    if (garp_rarp) {
> > > > -                        garp_rarp->dp_key =
> > > > binding_rec->datapath->tunnel_key;
> > > > -                        garp_rarp->port_key = binding_rec->tunnel_key;
> > > > -                        if (garp_max_timeout != garp_rarp_max_timeout
> > ||
> > > > -                            garp_continuous != garp_rarp_continuous) {
> > > > -                            /* reset backoff */
> > > > -                            garp_rarp->announce_time = time_msec() +
> > 1000;
> > > > -                            garp_rarp->backoff = 1000; /* msec. */
> > > > -                        }
> > > > -                    } else {
> > > > -                        add_garp_rarp(name, laddrs->ea,
> > > > -                                      0,
> > > > binding_rec->datapath->tunnel_key,
> > > > -                                      binding_rec->tunnel_key);
> > > > -                    }
> > > > -                    free(name);
> > > > -            }
> > > > -            destroy_lport_addresses(laddrs);
> > > > -            free(laddrs);
> > > > -        }
> > > > -        return;
> > > > -    }
> > > > -
> > > > -    /* Update GARP for vif if it exists. */
> > > > -    garp_rarp = shash_find_data(&send_garp_rarp_data,
> > > > -                                binding_rec->logical_port);
> > > > -    if (garp_rarp) {
> > > > -        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
> > > > -        garp_rarp->port_key = binding_rec->tunnel_key;
> > > > -        if (garp_max_timeout != garp_rarp_max_timeout ||
> > > > -            garp_continuous != garp_rarp_continuous) {
> > > > -            /* reset backoff */
> > > > -            garp_rarp->announce_time = time_msec() + 1000;
> > > > -            garp_rarp->backoff = 1000; /* msec. */
> > > > -        }
> > > > -        return;
> > > > -    }
> > > > -
> > > > -    /* Add GARP for new vif. */
> > > > -    int i;
> > > > -    for (i = 0; i < binding_rec->n_mac; i++) {
> > > > -        struct lport_addresses laddrs;
> > > > -        ovs_be32 ip = 0;
> > > > -        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
> > > > -            continue;
> > > > -        }
> > > > -
> > > > -        if (laddrs.n_ipv4_addrs) {
> > > > -            ip = laddrs.ipv4_addrs[0].addr;
> > > > -        }
> > > > -
> > > > -        add_garp_rarp(binding_rec->logical_port,
> > > > -                      laddrs.ea, ip,
> > > > -                      binding_rec->datapath->tunnel_key,
> > > > -                      binding_rec->tunnel_key);
> > > > -        if (ip) {
> > > > -            send_garp_locally(ovnsb_idl_txn,
> > > > sbrec_mac_binding_by_lport_ip,
> > > > -                              local_datapaths, binding_rec, laddrs.ea,
> > > > ip);
> > > > -        }
> > > > -
> > > > -        destroy_lport_addresses(&laddrs);
> > > > -        break;
> > > > +    struct garp_rarp_node *garp_rarp;
> > > > +    HMAP_FOR_EACH_POP (garp_rarp, hmap_node, &send_garp_rarp_data) {
> > > > +        garp_rarp_node_free(garp_rarp);
> > > >      }
> > > > +    hmap_destroy(&send_garp_rarp_data);
> > > >  }
> > > >
> > > >  struct arp_nd_data {
> > > > @@ -5289,16 +5091,6 @@ send_arp_nd_update(const struct
> > sbrec_port_binding
> > > > *pb, const char *nexthop,
> > > >      }
> > > >  }
> > > >
> > > > -/* Remove a vif from GARP announcements. */
> > > > -static void
> > > > -send_garp_rarp_delete(const char *lport)
> > > > -{
> > > > -    struct garp_rarp_data *garp_rarp = shash_find_and_delete
> > > > -                                       (&send_garp_rarp_data, lport);
> > > > -    free(garp_rarp);
> > > > -    notify_pinctrl_handler();
> > > > -}
> > > > -
> > > >  void
> > > >  send_self_originated_neigh_packet(struct rconn *swconn,
> > > >                                    uint32_t dp_key, uint32_t port_key,
> > > > @@ -5350,7 +5142,7 @@ send_self_originated_neigh_packet(struct rconn
> > > > *swconn,
> > > >
> > > >  /* Called with in the pinctrl_handler thread context. */
> > > >  static long long int
> > > > -send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
> > > > +send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
> > > >                 long long int current_time)
> > > >      OVS_REQUIRES(pinctrl_mutex)
> > > >  {
> > > > @@ -6419,242 +6211,12 @@ ip_mcast_querier_wait(long long int
> > query_time)
> > > >      }
> > > >  }
> > > >
> > > > -/* Get localnet vifs, local l3gw ports and ofport for localnet patch
> > > > ports. */
> > > > -static void
> > > > -get_localnet_vifs_l3gwports(
> > > > -    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
> > > > -    struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > > -    const struct ovsrec_bridge *br_int,
> > > > -    const struct sbrec_chassis *chassis,
> > > > -    const struct hmap *local_datapaths,
> > > > -    struct sset *localnet_vifs,
> > > > -    struct sset *local_l3gw_ports)
> > > > -{
> > > > -    for (int i = 0; i < br_int->n_ports; i++) {
> > > > -        const struct ovsrec_port *port_rec = br_int->ports[i];
> > > > -        if (!strcmp(port_rec->name, br_int->name)) {
> > > > -            continue;
> > > > -        }
> > > > -        const char *tunnel_id = smap_get(&port_rec->external_ids,
> > > > -                                          "ovn-chassis-id");
> > > > -        if (tunnel_id &&
> > > > -                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL,
> > > > NULL)) {
> > > > -            continue;
> > > > -        }
> > > > -        const char *localnet = smap_get(&port_rec->external_ids,
> > > > -                                        "ovn-localnet-port");
> > > > -        if (localnet) {
> > > > -            continue;
> > > > -        }
> > > > -        for (int j = 0; j < port_rec->n_interfaces; j++) {
> > > > -            const struct ovsrec_interface *iface_rec =
> > > > port_rec->interfaces[j];
> > > > -            if (!iface_rec->n_ofport) {
> > > > -                continue;
> > > > -            }
> > > > -            /* Get localnet vif. */
> > > > -            const char *iface_id = smap_get(&iface_rec->external_ids,
> > > > -                                            "iface-id");
> > > > -            if (!iface_id) {
> > > > -                continue;
> > > > -            }
> > > > -            const struct sbrec_port_binding *pb
> > > > -                = lport_lookup_by_name(sbrec_port_binding_by_name,
> > > > iface_id);
> > > > -            if (!pb || pb->chassis != chassis) {
> > > > -                continue;
> > > > -            }
> > > > -            if (!iface_rec->link_state ||
> > > > -                    strcmp(iface_rec->link_state, "up")) {
> > > > -                continue;
> > > > -            }
> > > > -            struct local_datapath *ld
> > > > -                = get_local_datapath(local_datapaths,
> > > > -                                     pb->datapath->tunnel_key);
> > > > -            if (ld && ld->localnet_port) {
> > > > -                sset_add(localnet_vifs, iface_id);
> > > > -            }
> > > > -        }
> > > > -    }
> > > > -
> > > > -    struct sbrec_port_binding *target =
> > sbrec_port_binding_index_init_row(
> > > > -        sbrec_port_binding_by_datapath);
> > > > -
> > > > -    const struct local_datapath *ld;
> > > > -    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
> > > > -        const struct sbrec_port_binding *pb;
> > > > -
> > > > -        if (!ld->localnet_port) {
> > > > -            continue;
> > > > -        }
> > > > -
> > > > -        /* Get l3gw ports.  Consider port bindings with type
> > "l3gateway"
> > > > -         * that connect to gateway routers (if local), and consider
> > port
> > > > -         * bindings of type "patch" since they might connect to
> > > > -         * distributed gateway ports with NAT addresses. */
> > > > -
> > > > -        sbrec_port_binding_index_set_datapath(target, ld->datapath);
> > > > -        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
> > > > -
> > > >  sbrec_port_binding_by_datapath) {
> > > > -            if ((!strcmp(pb->type, "l3gateway") && pb->chassis ==
> > chassis)
> > > > -                || !strcmp(pb->type, "patch")) {
> > > > -                sset_add(local_l3gw_ports, pb->logical_port);
> > > > -            }
> > > > -        }
> > > > -    }
> > > > -    sbrec_port_binding_index_destroy_row(target);
> > > > -}
> > > > -
> > > > -
> > > > -/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
> > > > - * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
> > > > - * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid
> > IPv4
> > > > - * or IPv6 address, and stores them in the 'ipv4_addrs' and
> > 'ipv6_addrs'
> > > > - * fields of 'laddrs'.  The logical port name is stored in 'lport'.
> > > > - *
> > > > - * Returns true if at least 'MAC' is found in 'address', false
> > otherwise.
> > > > - *
> > > > - * The caller must call destroy_lport_addresses() and free(*lport). */
> > > > -static bool
> > > > -extract_addresses_with_port(const char *addresses,
> > > > -                            struct lport_addresses *laddrs,
> > > > -                            char **lport)
> > > > -{
> > > > -    int ofs;
> > > > -    if (!extract_addresses(addresses, laddrs, &ofs)) {
> > > > -        return false;
> > > > -    } else if (!addresses[ofs]) {
> > > > -        return true;
> > > > -    }
> > > > -
> > > > -    struct lexer lexer;
> > > > -    lexer_init(&lexer, addresses + ofs);
> > > > -    lexer_get(&lexer);
> > > > -
> > > > -    if (lexer.error || lexer.token.type != LEX_T_ID
> > > > -        || !lexer_match_id(&lexer, "is_chassis_resident")) {
> > > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > > -        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address",
> > addresses);
> > > > -        lexer_destroy(&lexer);
> > > > -        return true;
> > > > -    }
> > > > -
> > > > -    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
> > > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > > -        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
> > > > -                          "'is_chassis_resident' in address '%s'",
> > > > addresses);
> > > > -        lexer_destroy(&lexer);
> > > > -        return false;
> > > > -    }
> > > > -
> > > > -    if (lexer.token.type != LEX_T_STRING) {
> > > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > > -        VLOG_INFO_RL(&rl,
> > > > -                    "Syntax error: expecting quoted string after "
> > > > -                    "'is_chassis_resident' in address '%s'",
> > addresses);
> > > > -        lexer_destroy(&lexer);
> > > > -        return false;
> > > > -    }
> > > > -
> > > > -    *lport = xstrdup(lexer.token.s);
> > > > -
> > > > -    lexer_get(&lexer);
> > > > -    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
> > > > -        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> > > > -        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted
> > > > string in "
> > > > -                          "'is_chassis_resident()' in address '%s'",
> > > > -                          addresses);
> > > > -        lexer_destroy(&lexer);
> > > > -        return false;
> > > > -    }
> > > > -
> > > > -    lexer_destroy(&lexer);
> > > > -    return true;
> > > > -}
> > > > -
> > > > -static void
> > > > -consider_nat_address(struct ovsdb_idl_index
> > *sbrec_port_binding_by_name,
> > > > -                     const char *nat_address,
> > > > -                     const struct sbrec_port_binding *pb,
> > > > -                     struct sset *nat_address_keys,
> > > > -                     const struct sbrec_chassis *chassis,
> > > > -                     const struct sset *active_tunnels,
> > > > -                     struct shash *nat_addresses)
> > > > -{
> > > > -    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
> > > > -    char *lport = NULL;
> > > > -    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
> > > > -        || (!lport && !strcmp(pb->type, "patch"))
> > > > -        || (lport && !lport_is_chassis_resident(
> > > > -                sbrec_port_binding_by_name, chassis,
> > > > -                active_tunnels, lport))) {
> > > > -        destroy_lport_addresses(laddrs);
> > > > -        free(laddrs);
> > > > -        free(lport);
> > > > -        return;
> > > > -    }
> > > > -    free(lport);
> > > > -
> > > > -    int i;
> > > > -    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
> > > > -        char *name = xasprintf("%s-%s", pb->logical_port,
> > > > -                                        laddrs->ipv4_addrs[i].addr_s);
> > > > -        sset_add(nat_address_keys, name);
> > > > -        free(name);
> > > > -    }
> > > > -    if (laddrs->n_ipv4_addrs == 0) {
> > > > -        char *name = xasprintf("%s-noip", pb->logical_port);
> > > > -        sset_add(nat_address_keys, name);
> > > > -        free(name);
> > > > -    }
> > > > -    shash_add(nat_addresses, pb->logical_port, laddrs);
> > > > -}
> > > > -
> > > > -static void
> > > > -get_nat_addresses_and_keys(struct ovsdb_idl_index
> > > > *sbrec_port_binding_by_name,
> > > > -                           struct sset *nat_address_keys,
> > > > -                           struct sset *local_l3gw_ports,
> > > > -                           const struct sbrec_chassis *chassis,
> > > > -                           const struct sset *active_tunnels,
> > > > -                           struct shash *nat_addresses)
> > > > -{
> > > > -    const char *gw_port;
> > > > -    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
> > > > -        const struct sbrec_port_binding *pb;
> > > > -
> > > > -        pb = lport_lookup_by_name(sbrec_port_binding_by_name,
> > gw_port);
> > > > -        if (!pb) {
> > > > -            continue;
> > > > -        }
> > > > -
> > > > -        if (pb->n_nat_addresses) {
> > > > -            for (int i = 0; i < pb->n_nat_addresses; i++) {
> > > > -                consider_nat_address(sbrec_port_binding_by_name,
> > > > -                                     pb->nat_addresses[i], pb,
> > > > -                                     nat_address_keys, chassis,
> > > > -                                     active_tunnels,
> > > > -                                     nat_addresses);
> > > > -            }
> > > > -        } else {
> > > > -            /* Continue to support options:nat-addresses for version
> > > > -             * upgrade. */
> > > > -            const char *nat_addresses_options = smap_get(&pb->options,
> > > > -
> >  "nat-addresses");
> > > > -            if (nat_addresses_options) {
> > > > -                consider_nat_address(sbrec_port_binding_by_name,
> > > > -                                     nat_addresses_options, pb,
> > > > -                                     nat_address_keys, chassis,
> > > > -                                     active_tunnels,
> > > > -                                     nat_addresses);
> > > > -            }
> > > > -        }
> > > > -    }
> > > > -}
> > > > -
> > > >  static void
> > > >  send_garp_rarp_wait(long long int send_garp_rarp_time)
> > > >  {
> > > >      /* Set the poll timer for next garp/rarp only if there is data to
> > > >       * be sent. */
> > > > -    if (!shash_is_empty(&send_garp_rarp_data)) {
> > > > +    if (!hmap_is_empty(&send_garp_rarp_data)) {
> > > >          poll_timer_wait_until(send_garp_rarp_time);
> > > >      }
> > > >  }
> > > > @@ -6674,16 +6236,16 @@ static void
> > > >  send_garp_rarp_run(struct rconn *swconn, long long int
> > > > *send_garp_rarp_time)
> > > >      OVS_REQUIRES(pinctrl_mutex)
> > > >  {
> > > > -    if (shash_is_empty(&send_garp_rarp_data)) {
> > > > +    if (hmap_is_empty(&send_garp_rarp_data)) {
> > > >          return;
> > > >      }
> > > >
> > > >      /* Send GARPs, and update the next announcement. */
> > > > -    struct shash_node *iter;
> > > >      long long int current_time = time_msec();
> > > >      *send_garp_rarp_time = LLONG_MAX;
> > > > -    SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
> > > > -        long long int next_announce = send_garp_rarp(swconn,
> > iter->data,
> > > > +    struct garp_rarp_node *garp;
> > > > +    HMAP_FOR_EACH (garp, hmap_node, &send_garp_rarp_data) {
> > > > +        long long int next_announce = send_garp_rarp(swconn, garp,
> > > >                                                       current_time);
> > > >          if (*send_garp_rarp_time > next_announce) {
> > > >              *send_garp_rarp_time = next_announce;
> > > > @@ -6743,22 +6305,12 @@ send_arp_nd_run(struct rconn *swconn, long long
> > > > int *send_arp_nd_time)
> > > >  /* Called by pinctrl_run(). Runs with in the main ovn-controller
> > > >   * thread context. */
> > > >  static void
> > > > -send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > > -                       struct ovsdb_idl_index
> > > > *sbrec_port_binding_by_datapath,
> > > > -                       struct ovsdb_idl_index
> > *sbrec_port_binding_by_name,
> > > > -                       struct ovsdb_idl_index
> > > > *sbrec_mac_binding_by_lport_ip,
> > > > +send_garp_rarp_prepare(const struct hmap *garp_rarp_data,
> > > >                         const struct sbrec_ecmp_nexthop_table
> > > > *ecmp_nh_table,
> > > > -                       const struct ovsrec_bridge *br_int,
> > > >                         const struct sbrec_chassis *chassis,
> > > > -                       const struct hmap *local_datapaths,
> > > > -                       const struct sset *active_tunnels,
> > > >                         const struct ovsrec_open_vswitch_table
> > *ovs_table)
> > > >      OVS_REQUIRES(pinctrl_mutex)
> > > >  {
> > > > -    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
> > > > -    struct sset local_l3gw_ports =
> > SSET_INITIALIZER(&local_l3gw_ports);
> > > > -    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
> > > > -    struct shash nat_addresses;
> > > >      unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> > > >      unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
> > > >      bool garp_continuous = false, continuous_arp_nd = true;
> > > > @@ -6778,51 +6330,13 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > > > *ovnsb_idl_txn,
> > > >          continuous_arp_nd = !!max_arp_nd_timeout;
> > > >      }
> > > >
> > > > -    shash_init(&nat_addresses);
> > > > +    bool reset_timers = (garp_max_timeout != garp_rarp_max_timeout ||
> > > > +                         garp_continuous != garp_rarp_continuous);
> > > >
> > > > -    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
> > > > -                                sbrec_port_binding_by_name,
> > > > -                                br_int, chassis, local_datapaths,
> > > > -                                &localnet_vifs, &local_l3gw_ports);
> > > > -
> > > > -    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
> > > > -                               &nat_ip_keys, &local_l3gw_ports,
> > > > -                               chassis, active_tunnels,
> > > > -                               &nat_addresses);
> > > > -
> > > > -    /* For deleted ports and deleted nat ips, remove from
> > > > -     * send_garp_rarp_data. */
> > > > -    struct shash_node *iter;
> > > > -    SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
> > > > -        if (!sset_contains(&localnet_vifs, iter->name) &&
> > > > -            !sset_contains(&nat_ip_keys, iter->name)) {
> > > > -            send_garp_rarp_delete(iter->name);
> > > > -        }
> > > > -    }
> > > > -
> > > > -    /* Update send_garp_rarp_data. */
> > > > -    const char *iface_id;
> > > > -    SSET_FOR_EACH (iface_id, &localnet_vifs) {
> > > > -        const struct sbrec_port_binding *pb = lport_lookup_by_name(
> > > > -            sbrec_port_binding_by_name, iface_id);
> > > > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > > false)) {
> > > > -            send_garp_rarp_update(ovnsb_idl_txn,
> > > > -                                  sbrec_mac_binding_by_lport_ip,
> > > > -                                  local_datapaths, pb, &nat_addresses,
> > > > -                                  garp_max_timeout, garp_continuous);
> > > > -        }
> > > > -    }
> > > > -
> > > > -    /* Update send_garp_rarp_data for nat-addresses. */
> > > > -    const char *gw_port;
> > > > -    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
> > > > -        const struct sbrec_port_binding *pb
> > > > -            = lport_lookup_by_name(sbrec_port_binding_by_name,
> > gw_port);
> > > > -        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp",
> > > > false)) {
> > > > -            send_garp_rarp_update(ovnsb_idl_txn,
> > > > sbrec_mac_binding_by_lport_ip,
> > > > -                                  local_datapaths, pb, &nat_addresses,
> > > > -                                  garp_max_timeout, garp_continuous);
> > > > -        }
> > > > +    if (garp_rarp_sync(garp_rarp_data, &send_garp_rarp_data,
> > > > reset_timers)) {
> > > > +        /* Notify pinctrl_handler so that it can wakeup and process
> > > > +         * these GARP/RARP requests. */
> > > > +        notify_pinctrl_handler();
> > > >      }
> > > >
> > > >      arp_nd_sync_data(ecmp_nh_table);
> > > > @@ -6836,21 +6350,6 @@ send_garp_rarp_prepare(struct ovsdb_idl_txn
> > > > *ovnsb_idl_txn,
> > > >          }
> > > >      }
> > > >
> > > > -    /* pinctrl_handler thread will send the GARPs. */
> > > > -
> > > > -    sset_destroy(&localnet_vifs);
> > > > -    sset_destroy(&local_l3gw_ports);
> > > > -
> > > > -    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
> > > > -        struct lport_addresses *laddrs = iter->data;
> > > > -        destroy_lport_addresses(laddrs);
> > > > -        shash_delete(&nat_addresses, iter);
> > > > -        free(laddrs);
> > > > -    }
> > > > -    shash_destroy(&nat_addresses);
> > > > -
> > > > -    sset_destroy(&nat_ip_keys);
> > > > -
> > > >      garp_rarp_max_timeout = garp_max_timeout;
> > > >      garp_rarp_continuous = garp_continuous;
> > > >
> > > > @@ -6862,7 +6361,7 @@ static bool
> > > >  may_inject_pkts(void)
> > > >  {
> > > >      return (!shash_is_empty(&ipv6_ras) ||
> > > > -            !shash_is_empty(&send_garp_rarp_data) ||
> > > > +            !hmap_is_empty(&send_garp_rarp_data) ||
> > > >              ipv6_prefixd_should_inject() ||
> > > >              !ovs_list_is_empty(&mcast_query_list) ||
> > > >
> > buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx)
> > > > ||
> > > > diff --git a/controller/pinctrl.h b/controller/pinctrl.h
> > > > index e33d7cbf5..f57f5c306 100644
> > > > --- a/controller/pinctrl.h
> > > > +++ b/controller/pinctrl.h
> > > > @@ -44,24 +44,24 @@ struct sbrec_mac_binding_table;
> > > >  void pinctrl_init(void);
> > > >  void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
> > > >                   struct ovsdb_idl_index
> > *sbrec_datapath_binding_by_key,
> > > > -                 struct ovsdb_idl_index
> > *sbrec_port_binding_by_datapath,
> > > >                   struct ovsdb_idl_index *sbrec_port_binding_by_key,
> > > >                   struct ovsdb_idl_index *sbrec_port_binding_by_name,
> > > >                   struct ovsdb_idl_index
> > *sbrec_mac_binding_by_lport_ip,
> > > >                   struct ovsdb_idl_index *sbrec_igmp_groups,
> > > >                   struct ovsdb_idl_index *sbrec_ip_multicast_opts,
> > > >                   struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
> > > > -                 const struct sbrec_controller_event_table *,
> > > > -                 const struct sbrec_service_monitor_table *,
> > > > -                 const struct sbrec_mac_binding_table *,
> > > > -                 const struct sbrec_bfd_table *,
> > > > -                 const struct sbrec_ecmp_nexthop_table *,
> > > > -                 const struct ovsrec_bridge *, const struct
> > sbrec_chassis
> > > > *,
> > > > +                 const struct sbrec_controller_event_table *ce_table,
> > > > +                 const struct sbrec_service_monitor_table
> > *svc_mon_table,
> > > > +                 const struct sbrec_mac_binding_table
> > *mac_binding_table,
> > > > +                 const struct sbrec_bfd_table *bfd_table,
> > > > +                 const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
> > > > +                 const struct sbrec_chassis *chassis,
> > > >
> > >
> > > nit: Unrelated change.
> >
> > will be cleaned up.
> >
> > Thanks a lot,
> > Felix
> >
> > >
> > >                   const struct hmap *local_datapaths,
> > > >                   const struct sset *active_tunnels,
> > > >                   const struct shash *local_active_ports_ipv6_pd,
> > > >                   const struct shash *local_active_ports_ras,
> > > > -                 const struct ovsrec_open_vswitch_table *ovs_table);
> > > > +                 const struct ovsrec_open_vswitch_table *ovs_table,
> > > > +                 const struct hmap *garp_rarp_data);
> > > >  void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
> > > >  void pinctrl_destroy(void);
> > > >
> > > > --
> > > > 2.43.0
> > > >
> > > >
> > > > _______________________________________________
> > > > dev mailing list
> > > > dev@openvswitch.org
> > > > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> > > >
> > > >
> > > Thanks,
> > > Ales
> >
> >
> Thanks,
> Ales
> _______________________________________________
> 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 067c2e6e4..f0638ea97 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -59,7 +59,9 @@  controller_ovn_controller_SOURCES = \
 	controller/route-exchange.h \
 	controller/route-table-notify.h \
 	controller/route.h \
-	controller/route.c
+	controller/route.c \
+	controller/garp_rarp.h \
+	controller/garp_rarp.c
 
 if HAVE_NETLINK
 controller_ovn_controller_SOURCES += \
diff --git a/controller/garp_rarp.c b/controller/garp_rarp.c
new file mode 100644
index 000000000..916ff896a
--- /dev/null
+++ b/controller/garp_rarp.c
@@ -0,0 +1,626 @@ 
+/*
+ * Copyright (c) 2025, STACKIT GmbH & Co. KG
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <net/if.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "controller/encaps.h"
+#include "controller/local_data.h"
+#include "controller/lport.h"
+#include "hash.h"
+#include "mac-binding-index.h"
+#include "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+#include "ovn/lex.h"
+#include "packets.h"
+#include "smap.h"
+#include "sset.h"
+#include "openvswitch/shash.h"
+#include "garp_rarp.h"
+#include "ovn-sb-idl.h"
+#include "vswitch-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(garp_rarp);
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
+static void
+get_localnet_vifs_l3gwports(
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
+    struct ovsdb_idl_index *sbrec_port_binding_by_name,
+    const struct ovsrec_bridge *br_int,
+    const struct sbrec_chassis *chassis,
+    const struct hmap *local_datapaths,
+    struct sset *localnet_vifs,
+    struct sset *local_l3gw_ports)
+{
+    for (int i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *port_rec = br_int->ports[i];
+        if (!strcmp(port_rec->name, br_int->name)) {
+            continue;
+        }
+        const char *tunnel_id = smap_get(&port_rec->external_ids,
+                                          "ovn-chassis-id");
+        if (tunnel_id &&
+                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL, NULL)) {
+            continue;
+        }
+        const char *localnet = smap_get(&port_rec->external_ids,
+                                        "ovn-localnet-port");
+        if (localnet) {
+            continue;
+        }
+        for (int j = 0; j < port_rec->n_interfaces; j++) {
+            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
+            if (!iface_rec->n_ofport) {
+                continue;
+            }
+            /* Get localnet vif. */
+            const char *iface_id = smap_get(&iface_rec->external_ids,
+                                            "iface-id");
+            if (!iface_id) {
+                continue;
+            }
+            const struct sbrec_port_binding *pb
+                = lport_lookup_by_name(sbrec_port_binding_by_name, iface_id);
+            if (!pb || pb->chassis != chassis) {
+                continue;
+            }
+            if (!iface_rec->link_state ||
+                    strcmp(iface_rec->link_state, "up")) {
+                continue;
+            }
+            struct local_datapath *ld
+                = get_local_datapath(local_datapaths,
+                                     pb->datapath->tunnel_key);
+            if (ld && ld->localnet_port) {
+                sset_add(localnet_vifs, iface_id);
+            }
+        }
+    }
+
+    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
+        sbrec_port_binding_by_datapath);
+
+    const struct local_datapath *ld;
+    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
+        const struct sbrec_port_binding *pb;
+
+        if (!ld->localnet_port) {
+            continue;
+        }
+
+        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
+         * that connect to gateway routers (if local), and consider port
+         * bindings of type "patch" since they might connect to
+         * distributed gateway ports with NAT addresses. */
+
+        sbrec_port_binding_index_set_datapath(target, ld->datapath);
+        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
+                                           sbrec_port_binding_by_datapath) {
+            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
+                || !strcmp(pb->type, "patch")) {
+                sset_add(local_l3gw_ports, pb->logical_port);
+            }
+        }
+    }
+    sbrec_port_binding_index_destroy_row(target);
+}
+
+
+/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
+ * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
+ * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
+ * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
+ * fields of 'laddrs'.  The logical port name is stored in 'lport'.
+ *
+ * Returns true if at least 'MAC' is found in 'address', false otherwise.
+ *
+ * The caller must call destroy_lport_addresses() and free(*lport). */
+static bool
+extract_addresses_with_port(const char *addresses,
+                            struct lport_addresses *laddrs,
+                            char **lport)
+{
+    int ofs;
+    if (!extract_addresses(addresses, laddrs, &ofs)) {
+        return false;
+    } else if (!addresses[ofs]) {
+        return true;
+    }
+
+    struct lexer lexer;
+    lexer_init(&lexer, addresses + ofs);
+    lexer_get(&lexer);
+
+    if (lexer.error || lexer.token.type != LEX_T_ID
+        || !lexer_match_id(&lexer, "is_chassis_resident")) {
+        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
+        lexer_destroy(&lexer);
+        return true;
+    }
+
+    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
+        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
+                          "'is_chassis_resident' in address '%s'", addresses);
+        lexer_destroy(&lexer);
+        return false;
+    }
+
+    if (lexer.token.type != LEX_T_STRING) {
+        VLOG_INFO_RL(&rl,
+                    "Syntax error: expecting quoted string after "
+                    "'is_chassis_resident' in address '%s'", addresses);
+        lexer_destroy(&lexer);
+        return false;
+    }
+
+    *lport = xstrdup(lexer.token.s);
+
+    lexer_get(&lexer);
+    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
+        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in "
+                          "'is_chassis_resident()' in address '%s'",
+                          addresses);
+        lexer_destroy(&lexer);
+        return false;
+    }
+
+    lexer_destroy(&lexer);
+    return true;
+}
+
+static void
+consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                     const char *nat_address,
+                     const struct sbrec_port_binding *pb,
+                     struct sset *nat_address_keys,
+                     const struct sbrec_chassis *chassis,
+                     const struct sset *active_tunnels,
+                     struct shash *nat_addresses,
+                     struct sset *non_local_lports,
+                     struct sset *local_lports)
+{
+    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
+    char *lport = NULL;
+    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
+        || (!lport && !strcmp(pb->type, "patch"))) {
+        destroy_lport_addresses(laddrs);
+        free(laddrs);
+        free(lport);
+        return;
+    }
+    if (lport) {
+        if (!lport_is_chassis_resident(sbrec_port_binding_by_name,
+                chassis, active_tunnels, lport)) {
+            sset_add(non_local_lports, lport);
+            destroy_lport_addresses(laddrs);
+            free(laddrs);
+            free(lport);
+            return;
+        } else {
+            sset_add(local_lports, lport);
+        }
+    }
+    free(lport);
+
+    int i;
+    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
+        char *name = xasprintf("%s-%s", pb->logical_port,
+                                        laddrs->ipv4_addrs[i].addr_s);
+        sset_add(nat_address_keys, name);
+        free(name);
+    }
+    if (laddrs->n_ipv4_addrs == 0) {
+        char *name = xasprintf("%s-noip", pb->logical_port);
+        sset_add(nat_address_keys, name);
+        free(name);
+    }
+    shash_add(nat_addresses, pb->logical_port, laddrs);
+}
+
+static void
+get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+                           struct sset *nat_address_keys,
+                           struct sset *local_l3gw_ports,
+                           const struct sbrec_chassis *chassis,
+                           const struct sset *active_tunnels,
+                           struct shash *nat_addresses,
+                           struct sset *non_local_lports,
+                           struct sset *local_lports)
+{
+    const char *gw_port;
+    SSET_FOR_EACH (gw_port, local_l3gw_ports) {
+        const struct sbrec_port_binding *pb;
+
+        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
+        if (!pb) {
+            continue;
+        }
+
+        if (pb->n_nat_addresses) {
+            for (int i = 0; i < pb->n_nat_addresses; i++) {
+                consider_nat_address(sbrec_port_binding_by_name,
+                                     pb->nat_addresses[i], pb,
+                                     nat_address_keys, chassis,
+                                     active_tunnels,
+                                     nat_addresses,
+                                     non_local_lports,
+                                     local_lports);
+            }
+        } else {
+            /* Continue to support options:nat-addresses for version
+             * upgrade. */
+            const char *nat_addresses_options = smap_get(&pb->options,
+                                                         "nat-addresses");
+            if (nat_addresses_options) {
+                consider_nat_address(sbrec_port_binding_by_name,
+                                     nat_addresses_options, pb,
+                                     nat_address_keys, chassis,
+                                     active_tunnels,
+                                     nat_addresses,
+                                     non_local_lports,
+                                     local_lports);
+            }
+        }
+    }
+}
+
+static uint32_t
+garp_rarp_node_hash(const struct eth_addr *ea, uint32_t dp_key,
+                    uint32_t port_key)
+{
+    return hash_bytes(ea, sizeof *ea, hash_2words(dp_key, port_key));
+}
+
+static uint32_t
+garp_rarp_node_hash_struct(const struct garp_rarp_node *n)
+{
+    return garp_rarp_node_hash(&n->ea, n->dp_key, n->port_key);
+}
+
+static void
+garp_rarp_node_add(struct hmap *map, const struct eth_addr ea, ovs_be32 ip,
+                   uint32_t dp_key, uint32_t port_key)
+{
+    struct garp_rarp_node *grn = xmalloc(sizeof *grn);
+    grn->ea = ea;
+    grn->ipv4 = ip;
+    grn->announce_time = time_msec() + 1000;
+    grn->backoff = 1000; /* msec. */
+    grn->dp_key = dp_key;
+    grn->port_key = port_key;
+    hmap_insert(map, &grn->hmap_node, garp_rarp_node_hash_struct(grn));
+}
+
+/* Searches for a given garp_rarp_node in a hmap. Ignores the announce_time
+ * and backoff field since they might be different based on runtime. */
+static struct garp_rarp_node*
+garp_rarp_lookup(const struct hmap *map, const struct garp_rarp_node *search)
+{
+    struct garp_rarp_node *n;
+    uint32_t hash = garp_rarp_node_hash_struct(search);
+    HMAP_FOR_EACH_WITH_HASH (n, hmap_node, hash, map) {
+        if (!eth_addr_equals(search->ea, n->ea)) {
+            continue;
+        }
+
+        if (search->ipv4 != n->ipv4) {
+            continue;
+        }
+
+        if (search->dp_key != n->dp_key) {
+            continue;
+        }
+
+        if (search->port_key != n->port_key) {
+            continue;
+        }
+
+        return n;
+    }
+    return NULL;
+}
+
+/* Update or add an IP-MAC binding for 'logical_port'.
+ * Caller should make sure that 'ovnsb_idl_txn' is valid. */
+static void
+mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
+                      struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+                      const char *logical_port,
+                      const struct sbrec_datapath_binding *dp,
+                      struct eth_addr ea, const char *ip,
+                      bool update_only)
+{
+    /* Convert ethernet argument to string form for database. */
+    char mac_string[ETH_ADDR_STRLEN + 1];
+    snprintf(mac_string, sizeof mac_string, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea));
+
+    const struct sbrec_mac_binding *b =
+            mac_binding_lookup(sbrec_mac_binding_by_lport_ip,
+                               logical_port, ip);
+    if (!b) {
+        if (update_only) {
+            return;
+        }
+        b = sbrec_mac_binding_insert(ovnsb_idl_txn);
+        sbrec_mac_binding_set_logical_port(b, logical_port);
+        sbrec_mac_binding_set_ip(b, ip);
+        sbrec_mac_binding_set_datapath(b, dp);
+    }
+
+    if (strcmp(b->mac, mac_string)) {
+        sbrec_mac_binding_set_mac(b, mac_string);
+
+        /* For backward compatibility check if timestamp column is available
+         * in SB DB. */
+        if (sbrec_server_has_mac_binding_table_col_timestamp(
+                ovsdb_idl_txn_get_idl(ovnsb_idl_txn))) {
+            VLOG_DBG("Setting MAC binding timestamp for "
+                     "ip:%s mac:%s port:%s to %lld",
+                     b->ip, b->mac, logical_port, time_wall_msec());
+            sbrec_mac_binding_set_timestamp(b, time_wall_msec());
+        }
+    }
+}
+
+/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
+ * on peer router datapaths.
+ */
+static void
+send_garp_locally(const struct garp_rarp_ctx_in *r_ctx_in,
+                  const struct sbrec_port_binding *in_pb,
+                  struct eth_addr ea, ovs_be32 ip)
+{
+    if (!r_ctx_in->ovnsb_idl_txn) {
+        return;
+    }
+
+    const struct local_datapath *ldp =
+        get_local_datapath(r_ctx_in->local_datapaths,
+                           in_pb->datapath->tunnel_key);
+
+    ovs_assert(ldp);
+    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
+        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
+        const struct sbrec_port_binding *remote = ldp->peer_ports[i].remote;
+
+        /* Skip "ingress" port. */
+        if (local == in_pb) {
+            continue;
+        }
+
+        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
+                                          "always_learn_from_arp_request",
+                                          true);
+
+        struct ds ip_s = DS_EMPTY_INITIALIZER;
+
+        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
+        mac_binding_add_to_sb(r_ctx_in->ovnsb_idl_txn,
+                              r_ctx_in->sbrec_mac_binding_by_lport_ip,
+                              remote->logical_port, remote->datapath,
+                              ea, ds_cstr(&ip_s), update_only);
+        ds_destroy(&ip_s);
+    }
+}
+
+/* Add or update a vif for which GARPs need to be announced. */
+static void
+send_garp_rarp_update(const struct garp_rarp_ctx_in *r_ctx_in,
+                      const struct sbrec_port_binding *binding_rec,
+                      struct shash *nat_addresses,
+                      struct hmap *garp_rarp_data)
+{
+    /* Skip localports as they don't need to be announced */
+    if (!strcmp(binding_rec->type, "localport")) {
+        return;
+    }
+
+    /* Update GARP for NAT IP if it exists.  Consider port bindings with type
+     * "l3gateway" for logical switch ports attached to gateway routers, and
+     * port bindings with type "patch" for logical switch ports attached to
+     * distributed gateway ports. */
+    if (!strcmp(binding_rec->type, "l3gateway")
+        || !strcmp(binding_rec->type, "patch")) {
+        struct lport_addresses *laddrs = NULL;
+        while ((laddrs = shash_find_and_delete(nat_addresses,
+                                               binding_rec->logical_port))) {
+            int i;
+            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
+                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
+                              laddrs->ipv4_addrs[i].addr,
+                              binding_rec->datapath->tunnel_key,
+                              binding_rec->tunnel_key);
+                send_garp_locally(r_ctx_in, binding_rec, laddrs->ea,
+                                  laddrs->ipv4_addrs[i].addr);
+            }
+            /*
+             * Send RARPs even if we do not have a ipv4 address as it e.g.
+             * happens on ipv6 only ports.
+             */
+            if (laddrs->n_ipv4_addrs == 0) {
+                garp_rarp_node_add(garp_rarp_data, laddrs->ea,
+                              0, binding_rec->datapath->tunnel_key,
+                              binding_rec->tunnel_key);
+            }
+            destroy_lport_addresses(laddrs);
+            free(laddrs);
+        }
+        return;
+    }
+
+    /* Add GARP for new vif. */
+    int i;
+    for (i = 0; i < binding_rec->n_mac; i++) {
+        struct lport_addresses laddrs;
+        ovs_be32 ip = 0;
+        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
+            continue;
+        }
+
+        if (laddrs.n_ipv4_addrs) {
+            ip = laddrs.ipv4_addrs[0].addr;
+        }
+
+        garp_rarp_node_add(garp_rarp_data,
+                      laddrs.ea, ip,
+                      binding_rec->datapath->tunnel_key,
+                      binding_rec->tunnel_key);
+        if (ip) {
+            send_garp_locally(r_ctx_in, binding_rec, laddrs.ea, ip);
+        }
+
+        destroy_lport_addresses(&laddrs);
+        break;
+    }
+}
+
+static struct garp_rarp_node*
+garp_rarp_node_clone(const struct garp_rarp_node *src)
+{
+    struct garp_rarp_node *dst = xzalloc(sizeof *dst);
+    dst->ea = src->ea;
+    dst->ipv4 = src->ipv4;
+    dst->announce_time = time_msec() + 1000;
+    dst->backoff = 1000;
+    dst->dp_key = src->dp_key;
+    dst->port_key = src->port_key;
+    return dst;
+}
+
+static void
+garp_rarp_clear(struct garp_rarp_ctx_in *r_ctx_in,
+                struct garp_rarp_ctx_out *r_ctx_out)
+{
+    struct garp_rarp_node *grn;
+    HMAP_FOR_EACH_POP (grn, hmap_node, r_ctx_out->garp_rarp_data) {
+        garp_rarp_node_free(grn);
+    }
+    hmap_clear(r_ctx_out->garp_rarp_data);
+    sset_clear(r_ctx_in->non_local_lports);
+    sset_clear(r_ctx_in->local_lports);
+}
+
+void
+garp_rarp_run(struct garp_rarp_ctx_in *r_ctx_in,
+              struct garp_rarp_ctx_out *r_ctx_out)
+{
+    garp_rarp_clear(r_ctx_in, r_ctx_out);
+
+    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
+    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
+    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
+    struct shash nat_addresses;
+
+    shash_init(&nat_addresses);
+
+    get_localnet_vifs_l3gwports(r_ctx_in->sbrec_port_binding_by_datapath,
+                                r_ctx_in->sbrec_port_binding_by_name,
+                                r_ctx_in->br_int, r_ctx_in->chassis,
+                                r_ctx_in->local_datapaths,
+                                &localnet_vifs, &local_l3gw_ports);
+
+    get_nat_addresses_and_keys(r_ctx_in->sbrec_port_binding_by_name,
+                               &nat_ip_keys, &local_l3gw_ports,
+                               r_ctx_in->chassis, r_ctx_in->active_tunnels,
+                               &nat_addresses, r_ctx_in->non_local_lports,
+                               r_ctx_in->local_lports);
+
+    /* Update send_garp_rarp_data. */
+    const char *iface_id;
+    SSET_FOR_EACH (iface_id, &localnet_vifs) {
+        const struct sbrec_port_binding *pb = lport_lookup_by_name(
+            r_ctx_in->sbrec_port_binding_by_name, iface_id);
+        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
+            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
+                                  r_ctx_out->garp_rarp_data);
+        }
+    }
+
+    /* Update send_garp_rarp_data for nat-addresses. */
+    const char *gw_port;
+    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
+        const struct sbrec_port_binding *pb = lport_lookup_by_name(
+            r_ctx_in->sbrec_port_binding_by_name, gw_port);
+        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
+            send_garp_rarp_update(r_ctx_in, pb, &nat_addresses,
+                                  r_ctx_out->garp_rarp_data);
+        }
+    }
+
+    sset_destroy(&localnet_vifs);
+    sset_destroy(&local_l3gw_ports);
+
+    struct shash_node *iter;
+    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
+        struct lport_addresses *laddrs = iter->data;
+        destroy_lport_addresses(laddrs);
+        shash_delete(&nat_addresses, iter);
+        free(laddrs);
+    }
+    shash_destroy(&nat_addresses);
+
+    sset_destroy(&nat_ip_keys);
+}
+
+/* Syncs a hmap of garp_rarp_node to another hmap. The elements in the
+ * destination hmap are initialized with valid announce times based on the
+ * current timestamp.
+ * If source is NULL then destination will at most change the timer if
+ * reset_timers is set.
+ * If reset_timers is true then the timers of all entries in the destination
+ * hmap will be reset to default values.
+ * Returns true if the destination hmap was chnaged. */
+bool
+garp_rarp_sync(const struct hmap *source, struct hmap *dest, bool reset_timers)
+{
+    struct garp_rarp_node *grn;
+    bool changed = false;
+
+    HMAP_FOR_EACH_SAFE (grn, hmap_node, dest) {
+        if (source && !garp_rarp_lookup(source, grn)) {
+            hmap_remove(dest, &grn->hmap_node);
+            garp_rarp_node_free(grn);
+            changed = true;
+        } else if (reset_timers) {
+            grn->announce_time = time_msec() + 1000;
+            grn->backoff = 1000;
+            changed = true;
+        }
+    }
+
+    if (source) {
+        HMAP_FOR_EACH (grn, hmap_node, source) {
+            if (!garp_rarp_lookup(dest, grn)) {
+                uint32_t hash = garp_rarp_node_hash_struct(grn);
+                hmap_insert(dest, &garp_rarp_node_clone(grn)->hmap_node, hash);
+                changed = true;
+            }
+        }
+    }
+
+    return changed;
+}
+
+void
+garp_rarp_node_free(struct garp_rarp_node *garp_rarp)
+{
+    free(garp_rarp);
+}
diff --git a/controller/garp_rarp.h b/controller/garp_rarp.h
new file mode 100644
index 000000000..b85e75a7c
--- /dev/null
+++ b/controller/garp_rarp.h
@@ -0,0 +1,61 @@ 
+/*
+ * Copyright (c) 2025, STACKIT GmbH & Co. KG
+ *
+ * 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 GARP_RARP_H
+#define GARP_RARP_H 1
+
+#include "openvswitch/hmap.h"
+#include "openvswitch/types.h"
+
+/* Contains a single mac and ip address that should be announced. */
+struct garp_rarp_node {
+    struct hmap_node hmap_node;
+    struct eth_addr ea;          /* Ethernet address of port. */
+    ovs_be32 ipv4;               /* Ipv4 address of port. */
+    long long int announce_time; /* Next announcement in ms.
+                                  * If LLONG_MAX there should be no
+                                  * annoucement. */
+    int backoff;                 /* Backoff timeout for the next
+                                  * announcement (in msecs). */
+    uint32_t dp_key;             /* Datapath used to output this GARP. */
+    uint32_t port_key;           /* Port to inject the GARP into. */
+};
+
+struct garp_rarp_ctx_in {
+    struct ovsdb_idl_txn *ovnsb_idl_txn;
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath;
+    struct ovsdb_idl_index *sbrec_port_binding_by_name;
+    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip;
+    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table;
+    const struct ovsrec_bridge *br_int;
+    const struct sbrec_chassis *chassis;
+    const struct hmap *local_datapaths;
+    const struct sset *active_tunnels;
+    struct sset *non_local_lports;
+    struct sset *local_lports;
+};
+
+struct garp_rarp_ctx_out {
+    /* Contains struct garp_rarp_node. */
+    struct hmap *garp_rarp_data;
+};
+
+void garp_rarp_run(struct garp_rarp_ctx_in *, struct garp_rarp_ctx_out *);
+bool garp_rarp_sync(const struct hmap *source, struct hmap *dest,
+                    bool reset_timers);
+void garp_rarp_node_free(struct garp_rarp_node *);
+
+#endif /* GARP_RARP_H */
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index cfed5282c..6f1b27876 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -92,6 +92,7 @@ 
 #include "route.h"
 #include "route-exchange.h"
 #include "route-table-notify.h"
+#include "garp_rarp.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -4952,6 +4953,14 @@  controller_output_route_exchange_handler(struct engine_node *node,
     return true;
 }
 
+static bool
+controller_output_garp_rarp_handler(struct engine_node *node,
+                                    void *data OVS_UNUSED)
+{
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
 /* Handles sbrec_chassis changes.
  * If a new chassis is added or removed return false, so that
  * flows are recomputed.  For any updates, there is no need for
@@ -5305,6 +5314,216 @@  en_route_table_notify_cleanup(void *data OVS_UNUSED)
 {
 }
 
+struct ed_type_garp_rarp {
+    struct hmap garp_rarp_data;
+
+    /* non_local_lports and local_lports are used in the incremental handlers
+     * to trigger updates if such a port changes. */
+    struct sset non_local_lports; /* lports that we did not consider because
+                                     they where not local. */
+    struct sset local_lports; /* lports where we did consider the addresses
+                                 because they where local. */
+};
+
+static void
+en_garp_rarp_run(struct engine_node *node, void *data_)
+{
+    struct ed_type_garp_rarp *data = data_;
+
+    const struct ovsrec_open_vswitch_table *ovs_table =
+        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    ovs_assert(chassis_id);
+
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+    const struct sbrec_chassis *chassis
+        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    ovs_assert(chassis);
+
+    const struct ovsrec_bridge_table *bridge_table =
+        EN_OVSDB_GET(engine_get_input("OVS_bridge", node));
+    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
+
+    struct ovsdb_idl_index *sbrec_port_binding_by_datapath =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "datapath");
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "name");
+    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_mac_binding", node),
+                "lport_ip");
+
+    struct ovsdb_idl_txn *ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn;
+
+    const struct sbrec_ecmp_nexthop_table *ecmp_nh_table =
+        sbrec_ecmp_nexthop_table_get(ovsdb_idl_txn_get_idl(ovnsb_idl_txn));
+
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    struct garp_rarp_ctx_in r_ctx_in = {
+        .ovnsb_idl_txn = ovnsb_idl_txn,
+        .sbrec_port_binding_by_datapath = sbrec_port_binding_by_datapath,
+        .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
+        .sbrec_mac_binding_by_lport_ip = sbrec_mac_binding_by_lport_ip,
+        .ecmp_nh_table = ecmp_nh_table,
+        .br_int = br_int,
+        .chassis = chassis,
+        .active_tunnels = &rt_data->active_tunnels,
+        .local_datapaths = &rt_data->local_datapaths,
+        .non_local_lports = &data->non_local_lports,
+        .local_lports = &data->local_lports,
+    };
+
+    struct garp_rarp_ctx_out r_ctx_out = {
+        .garp_rarp_data = &data->garp_rarp_data,
+    };
+
+    garp_rarp_run(&r_ctx_in, &r_ctx_out);
+}
+
+
+static void *
+en_garp_rarp_init(struct engine_node *node OVS_UNUSED,
+                  struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_garp_rarp *gr = xmalloc(sizeof *gr);
+    hmap_init(&gr->garp_rarp_data);
+    sset_init(&gr->non_local_lports);
+    sset_init(&gr->local_lports);
+    return gr;
+}
+
+static void
+en_garp_rarp_cleanup(void *data_)
+{
+    struct ed_type_garp_rarp *data = data_;
+    struct garp_rarp_node *grn;
+    HMAP_FOR_EACH_POP (grn, hmap_node, &data->garp_rarp_data) {
+        garp_rarp_node_free(grn);
+    }
+    hmap_destroy(&data->garp_rarp_data);
+    sset_destroy(&data->non_local_lports);
+    sset_destroy(&data->local_lports);
+}
+
+static bool
+garp_rarp_sb_port_binding_handler(struct engine_node *node,
+                                  void *data_)
+{
+    /* We need to handle a change if there was change on a datapath with
+     * a localnet port.
+     * Also the ha_chassis status of a port binding might change. */
+    struct ed_type_garp_rarp *data = data_;
+
+    const struct ovsrec_open_vswitch_table *ovs_table =
+        EN_OVSDB_GET(engine_get_input("OVS_open_vswitch", node));
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    ovs_assert(chassis_id);
+
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+    const struct sbrec_chassis *chassis
+        = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    ovs_assert(chassis);
+
+    struct ed_type_runtime_data *rt_data =
+            engine_get_input_data("runtime_data", node);
+    const struct sbrec_port_binding_table *port_binding_table =
+        EN_OVSDB_GET(engine_get_input("SB_port_binding", node));
+    struct ovsdb_idl_index *sbrec_port_binding_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_port_binding", node),
+                "name");
+
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, port_binding_table) {
+        struct local_datapath *ld = get_local_datapath(
+            &rt_data->local_datapaths, pb->datapath->tunnel_key);
+
+        if (!ld || ld->localnet_port) {
+            /* XXX: actually handle this incrementally. */
+            return false;
+        }
+
+        if (sset_contains(&data->non_local_lports, pb->logical_port) &&
+            lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
+                                      &rt_data->active_tunnels,
+                                      pb->logical_port)) {
+            /* XXX: actually handle this incrementally. */
+            return false;
+        }
+
+        if (sset_contains(&data->local_lports, pb->logical_port) &&
+            !lport_is_chassis_resident(sbrec_port_binding_by_name, chassis,
+                                       &rt_data->active_tunnels,
+                                       pb->logical_port)) {
+            /* XXX: actually handle this incrementally. */
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+garp_rarp_runtime_data_handler(struct engine_node *node, void *data OVS_UNUSED)
+{
+    /* We use two elements from rt_data:
+     * 1. active_tunnels: There is currently not incremental processing for
+     *    this in runtime_data. So we just fall back to a recompute.
+     * 2. local_datapaths: This has incremental processing on the runtime_data
+     *    side. We are only interested in datapaths with a localnet port so
+     *    we just recompute if there is one in there. Otherwise the change is
+     *    irrelevant for us. */
+
+    struct ed_type_runtime_data *rt_data =
+            engine_get_input_data("runtime_data", node);
+
+    /* There are no tracked data. Fall back to full recompute. */
+    if (!rt_data->tracked) {
+        return false;
+    }
+
+    struct tracked_datapath *tdp;
+    HMAP_FOR_EACH (tdp, node, &rt_data->tracked_dp_bindings) {
+        if (tdp->tracked_type == TRACKED_RESOURCE_REMOVED) {
+            /* This is currently not handled incrementally in runtime_data
+             * so it should never happen. Recompute just in case. */
+            return false;
+        }
+
+        struct local_datapath *ld = get_local_datapath(
+            &rt_data->local_datapaths, tdp->dp->tunnel_key);
+
+        if (!ld || ld->localnet_port) {
+            /* XXX: actually handle this incrementally. */
+            return false;
+        }
+
+        /* The localnet port might also have been removed. */
+        struct tracked_lport *tlp;
+        struct shash_node *sn;
+        SHASH_FOR_EACH (sn, &tdp->lports) {
+            tlp = sn->data;
+            if (!strcmp(tlp->pb->type, "localnet")) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 /* Returns false if the northd internal version stored in SB_Global
  * and ovn-controller internal version don't match.
  */
@@ -5614,6 +5833,7 @@  main(int argc, char *argv[])
     ENGINE_NODE(route, "route");
     ENGINE_NODE(route_table_notify, "route_table_notify");
     ENGINE_NODE(route_exchange, "route_exchange");
+    ENGINE_NODE(garp_rarp, "garp_rarp");
 
 #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
     SB_NODES
@@ -5826,6 +6046,15 @@  main(int argc, char *argv[])
     engine_add_input(&en_dns_cache, &en_sb_dns,
                      dns_cache_sb_dns_handler);
 
+    engine_add_input(&en_garp_rarp, &en_ovs_open_vswitch, NULL);
+    engine_add_input(&en_garp_rarp, &en_ovs_bridge, NULL);
+    engine_add_input(&en_garp_rarp, &en_sb_chassis, NULL);
+    engine_add_input(&en_garp_rarp, &en_sb_port_binding,
+                     garp_rarp_sb_port_binding_handler);
+    engine_add_input(&en_garp_rarp, &en_sb_mac_binding, NULL);
+    engine_add_input(&en_garp_rarp, &en_runtime_data,
+                     garp_rarp_runtime_data_handler);
+
     engine_add_input(&en_controller_output, &en_dns_cache,
                      NULL);
     engine_add_input(&en_controller_output, &en_lflow_output,
@@ -5838,6 +6067,8 @@  main(int argc, char *argv[])
                      controller_output_bfd_chassis_handler);
     engine_add_input(&en_controller_output, &en_route_exchange,
                      controller_output_route_exchange_handler);
+    engine_add_input(&en_controller_output, &en_garp_rarp,
+                     controller_output_garp_rarp_handler);
 
     engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
     engine_add_input(&en_controller_output, &en_acl_id,
@@ -5874,6 +6105,8 @@  main(int argc, char *argv[])
                                 sbrec_chassis_template_var_index_by_chassis);
     engine_ovsdb_node_add_index(&en_sb_learned_route, "datapath",
                                 sbrec_learned_route_index_by_datapath);
+    engine_ovsdb_node_add_index(&en_sb_mac_binding, "lport_ip",
+                                sbrec_mac_binding_by_lport_ip);
     engine_ovsdb_node_add_index(&en_ovs_flow_sample_collector_set, "id",
                                 ovsrec_flow_sample_collector_set_by_id);
     engine_ovsdb_node_add_index(&en_ovs_port, "qos", ovsrec_port_by_qos);
@@ -6290,9 +6523,12 @@  main(int argc, char *argv[])
                         stopwatch_start(PINCTRL_RUN_STOPWATCH_NAME,
                                         time_msec());
                         pinctrl_update(ovnsb_idl_loop.idl);
+                        const struct ed_type_garp_rarp *grd = engine_get_data(
+                            &en_garp_rarp);
+                        const struct hmap *garp_rarp_data =
+                            grd ? &grd->garp_rarp_data : NULL;
                         pinctrl_run(ovnsb_idl_txn,
                                     sbrec_datapath_binding_by_key,
-                                    sbrec_port_binding_by_datapath,
                                     sbrec_port_binding_by_key,
                                     sbrec_port_binding_by_name,
                                     sbrec_mac_binding_by_lport_ip,
@@ -6308,13 +6544,14 @@  main(int argc, char *argv[])
                                     sbrec_bfd_table_get(ovnsb_idl_loop.idl),
                                     sbrec_ecmp_nexthop_table_get(
                                         ovnsb_idl_loop.idl),
-                                    br_int, chassis,
+                                    chassis,
                                     &runtime_data->local_datapaths,
                                     &runtime_data->active_tunnels,
                                     &runtime_data->local_active_ports_ipv6_pd,
                                     &runtime_data->local_active_ports_ras,
                                     ovsrec_open_vswitch_table_get(
-                                            ovs_idl_loop.idl));
+                                            ovs_idl_loop.idl),
+                                    garp_rarp_data);
                         stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
                                        time_msec());
                         mirror_run(ovs_idl_txn,
diff --git a/controller/pinctrl.c b/controller/pinctrl.c
index 1481b2547..6b4a4a520 100644
--- a/controller/pinctrl.c
+++ b/controller/pinctrl.c
@@ -64,6 +64,7 @@ 
 #include "ip-mcast.h"
 #include "ovn-sb-idl.h"
 #include "ovn-dns.h"
+#include "garp_rarp.h"
 
 VLOG_DEFINE_THIS_MODULE(pinctrl);
 
@@ -233,15 +234,9 @@  static void destroy_send_arps_nds(void);
 static void send_garp_rarp_wait(long long int send_garp_rarp_time);
 static void send_arp_nd_wait(long long int send_arp_nd_time);
 static void send_garp_rarp_prepare(
-    struct ovsdb_idl_txn *ovnsb_idl_txn,
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+    const struct hmap *garp_rarp_data,
     const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
-    const struct ovsrec_bridge *,
-    const struct sbrec_chassis *,
-    const struct hmap *local_datapaths,
-    const struct sset *active_tunnels,
+    const struct sbrec_chassis *chassis,
     const struct ovsrec_open_vswitch_table *ovs_table)
     OVS_REQUIRES(pinctrl_mutex);
 static void send_garp_rarp_run(struct rconn *swconn,
@@ -4066,7 +4061,6 @@  pinctrl_update(const struct ovsdb_idl *idl)
 void
 pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-            struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
             struct ovsdb_idl_index *sbrec_port_binding_by_key,
             struct ovsdb_idl_index *sbrec_port_binding_by_name,
             struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
@@ -4078,13 +4072,13 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
             const struct sbrec_mac_binding_table *mac_binding_table,
             const struct sbrec_bfd_table *bfd_table,
             const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
-            const struct ovsrec_bridge *br_int,
             const struct sbrec_chassis *chassis,
             const struct hmap *local_datapaths,
             const struct sset *active_tunnels,
             const struct shash *local_active_ports_ipv6_pd,
             const struct shash *local_active_ports_ras,
-            const struct ovsrec_open_vswitch_table *ovs_table)
+            const struct ovsrec_open_vswitch_table *ovs_table,
+            const struct hmap *garp_rarp_data)
 {
     ovs_mutex_lock(&pinctrl_mutex);
     run_put_mac_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
@@ -4092,11 +4086,8 @@  pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                          sbrec_mac_binding_by_lport_ip);
     run_put_vport_bindings(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
                            sbrec_port_binding_by_key, chassis);
-    send_garp_rarp_prepare(ovnsb_idl_txn, sbrec_port_binding_by_datapath,
-                           sbrec_port_binding_by_name,
-                           sbrec_mac_binding_by_lport_ip, ecmp_nh_table,
-                           br_int, chassis, local_datapaths, active_tunnels,
-                           ovs_table);
+    send_garp_rarp_prepare(garp_rarp_data, ecmp_nh_table,
+                           chassis, ovs_table);
     prepare_ipv6_ras(local_active_ports_ras, sbrec_port_binding_by_name);
     prepare_ipv6_prefixd(ovnsb_idl_txn, sbrec_port_binding_by_name,
                          local_active_ports_ipv6_pd, chassis,
@@ -4785,47 +4776,6 @@  mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
     }
 }
 
-/* Simulate the effect of a GARP on local datapaths, i.e., create MAC_Bindings
- * on peer router datapaths.
- */
-static void
-send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                  const struct hmap *local_datapaths,
-                  const struct sbrec_port_binding *in_pb,
-                  struct eth_addr ea, ovs_be32 ip)
-{
-    if (!ovnsb_idl_txn) {
-        return;
-    }
-
-    const struct local_datapath *ldp =
-        get_local_datapath(local_datapaths, in_pb->datapath->tunnel_key);
-
-    ovs_assert(ldp);
-    for (size_t i = 0; i < ldp->n_peer_ports; i++) {
-        const struct sbrec_port_binding *local = ldp->peer_ports[i].local;
-        const struct sbrec_port_binding *remote = ldp->peer_ports[i].remote;
-
-        /* Skip "ingress" port. */
-        if (local == in_pb) {
-            continue;
-        }
-
-        bool update_only = !smap_get_bool(&remote->datapath->external_ids,
-                                          "always_learn_from_arp_request",
-                                          true);
-
-        struct ds ip_s = DS_EMPTY_INITIALIZER;
-
-        ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
-        mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                              remote->logical_port, remote->datapath,
-                              ea, ds_cstr(&ip_s), update_only);
-        ds_destroy(&ip_s);
-    }
-}
-
 static void
 run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
                     struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
@@ -4977,173 +4927,25 @@  wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
  * are needed for switches and routers on the broadcast segment to update
  * their port-mac and ARP tables.
  */
-struct garp_rarp_data {
-    struct eth_addr ea;          /* Ethernet address of port. */
-    ovs_be32 ipv4;               /* Ipv4 address of port. */
-    long long int announce_time; /* Next announcement in ms. */
-    int backoff;                 /* Backoff timeout for the next
-                                  * announcement (in msecs). */
-    uint32_t dp_key;             /* Datapath used to output this GARP. */
-    uint32_t port_key;           /* Port to inject the GARP into. */
-};
 
-/* Contains GARPs/RARPs to be sent. Protected by pinctrl_mutex*/
-static struct shash send_garp_rarp_data;
+/* Contains GARPs/RARPs to be sent in struct garp_rarp_node.
+ * Protected by pinctrl_mutex. */
+static struct hmap send_garp_rarp_data;
 
 static void
 init_send_garps_rarps(void)
 {
-    shash_init(&send_garp_rarp_data);
+    hmap_init(&send_garp_rarp_data);
 }
 
 static void
 destroy_send_garps_rarps(void)
 {
-    shash_destroy_free_data(&send_garp_rarp_data);
-}
-
-/* Runs with in the main ovn-controller thread context. */
-static void
-add_garp_rarp(const char *name, const struct eth_addr ea, ovs_be32 ip,
-              uint32_t dp_key, uint32_t port_key)
-{
-    struct garp_rarp_data *garp_rarp = xmalloc(sizeof *garp_rarp);
-    garp_rarp->ea = ea;
-    garp_rarp->ipv4 = ip;
-    garp_rarp->announce_time = time_msec() + 1000;
-    garp_rarp->backoff = 1000; /* msec. */
-    garp_rarp->dp_key = dp_key;
-    garp_rarp->port_key = port_key;
-    shash_add(&send_garp_rarp_data, name, garp_rarp);
-
-    /* Notify pinctrl_handler so that it can wakeup and process
-     * these GARP/RARP requests. */
-    notify_pinctrl_handler();
-}
-
-/* Add or update a vif for which GARPs need to be announced. */
-static void
-send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                      struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
-                      const struct hmap *local_datapaths,
-                      const struct sbrec_port_binding *binding_rec,
-                      struct shash *nat_addresses,
-                      long long int garp_max_timeout,
-                      bool garp_continuous)
-{
-    volatile struct garp_rarp_data *garp_rarp = NULL;
-
-    /* Skip localports as they don't need to be announced */
-    if (!strcmp(binding_rec->type, "localport")) {
-        return;
-    }
-
-    /* Update GARP for NAT IP if it exists.  Consider port bindings with type
-     * "l3gateway" for logical switch ports attached to gateway routers, and
-     * port bindings with type "patch" for logical switch ports attached to
-     * distributed gateway ports. */
-    if (!strcmp(binding_rec->type, "l3gateway")
-        || !strcmp(binding_rec->type, "patch")) {
-        struct lport_addresses *laddrs = NULL;
-        while ((laddrs = shash_find_and_delete(nat_addresses,
-                                               binding_rec->logical_port))) {
-            int i;
-            for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
-                char *name = xasprintf("%s-%s", binding_rec->logical_port,
-                                                laddrs->ipv4_addrs[i].addr_s);
-                garp_rarp = shash_find_data(&send_garp_rarp_data, name);
-                if (garp_rarp) {
-                    garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
-                    garp_rarp->port_key = binding_rec->tunnel_key;
-                    if (garp_max_timeout != garp_rarp_max_timeout ||
-                        garp_continuous != garp_rarp_continuous) {
-                        /* reset backoff */
-                        garp_rarp->announce_time = time_msec() + 1000;
-                        garp_rarp->backoff = 1000; /* msec. */
-                    }
-                } else if (ovnsb_idl_txn) {
-                    add_garp_rarp(name, laddrs->ea,
-                                  laddrs->ipv4_addrs[i].addr,
-                                  binding_rec->datapath->tunnel_key,
-                                  binding_rec->tunnel_key);
-                    send_garp_locally(ovnsb_idl_txn,
-                                      sbrec_mac_binding_by_lport_ip,
-                                      local_datapaths, binding_rec, laddrs->ea,
-                                      laddrs->ipv4_addrs[i].addr);
-
-                }
-                free(name);
-            }
-            /*
-             * Send RARPs even if we do not have a ipv4 address as it e.g.
-             * happens on ipv6 only ports.
-             */
-            if (laddrs->n_ipv4_addrs == 0) {
-                    char *name = xasprintf("%s-noip",
-                                           binding_rec->logical_port);
-                    garp_rarp = shash_find_data(&send_garp_rarp_data, name);
-                    if (garp_rarp) {
-                        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
-                        garp_rarp->port_key = binding_rec->tunnel_key;
-                        if (garp_max_timeout != garp_rarp_max_timeout ||
-                            garp_continuous != garp_rarp_continuous) {
-                            /* reset backoff */
-                            garp_rarp->announce_time = time_msec() + 1000;
-                            garp_rarp->backoff = 1000; /* msec. */
-                        }
-                    } else {
-                        add_garp_rarp(name, laddrs->ea,
-                                      0, binding_rec->datapath->tunnel_key,
-                                      binding_rec->tunnel_key);
-                    }
-                    free(name);
-            }
-            destroy_lport_addresses(laddrs);
-            free(laddrs);
-        }
-        return;
-    }
-
-    /* Update GARP for vif if it exists. */
-    garp_rarp = shash_find_data(&send_garp_rarp_data,
-                                binding_rec->logical_port);
-    if (garp_rarp) {
-        garp_rarp->dp_key = binding_rec->datapath->tunnel_key;
-        garp_rarp->port_key = binding_rec->tunnel_key;
-        if (garp_max_timeout != garp_rarp_max_timeout ||
-            garp_continuous != garp_rarp_continuous) {
-            /* reset backoff */
-            garp_rarp->announce_time = time_msec() + 1000;
-            garp_rarp->backoff = 1000; /* msec. */
-        }
-        return;
-    }
-
-    /* Add GARP for new vif. */
-    int i;
-    for (i = 0; i < binding_rec->n_mac; i++) {
-        struct lport_addresses laddrs;
-        ovs_be32 ip = 0;
-        if (!extract_lsp_addresses(binding_rec->mac[i], &laddrs)) {
-            continue;
-        }
-
-        if (laddrs.n_ipv4_addrs) {
-            ip = laddrs.ipv4_addrs[0].addr;
-        }
-
-        add_garp_rarp(binding_rec->logical_port,
-                      laddrs.ea, ip,
-                      binding_rec->datapath->tunnel_key,
-                      binding_rec->tunnel_key);
-        if (ip) {
-            send_garp_locally(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                              local_datapaths, binding_rec, laddrs.ea, ip);
-        }
-
-        destroy_lport_addresses(&laddrs);
-        break;
+    struct garp_rarp_node *garp_rarp;
+    HMAP_FOR_EACH_POP (garp_rarp, hmap_node, &send_garp_rarp_data) {
+        garp_rarp_node_free(garp_rarp);
     }
+    hmap_destroy(&send_garp_rarp_data);
 }
 
 struct arp_nd_data {
@@ -5289,16 +5091,6 @@  send_arp_nd_update(const struct sbrec_port_binding *pb, const char *nexthop,
     }
 }
 
-/* Remove a vif from GARP announcements. */
-static void
-send_garp_rarp_delete(const char *lport)
-{
-    struct garp_rarp_data *garp_rarp = shash_find_and_delete
-                                       (&send_garp_rarp_data, lport);
-    free(garp_rarp);
-    notify_pinctrl_handler();
-}
-
 void
 send_self_originated_neigh_packet(struct rconn *swconn,
                                   uint32_t dp_key, uint32_t port_key,
@@ -5350,7 +5142,7 @@  send_self_originated_neigh_packet(struct rconn *swconn,
 
 /* Called with in the pinctrl_handler thread context. */
 static long long int
-send_garp_rarp(struct rconn *swconn, struct garp_rarp_data *garp_rarp,
+send_garp_rarp(struct rconn *swconn, struct garp_rarp_node *garp_rarp,
                long long int current_time)
     OVS_REQUIRES(pinctrl_mutex)
 {
@@ -6419,242 +6211,12 @@  ip_mcast_querier_wait(long long int query_time)
     }
 }
 
-/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
-static void
-get_localnet_vifs_l3gwports(
-    struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-    struct ovsdb_idl_index *sbrec_port_binding_by_name,
-    const struct ovsrec_bridge *br_int,
-    const struct sbrec_chassis *chassis,
-    const struct hmap *local_datapaths,
-    struct sset *localnet_vifs,
-    struct sset *local_l3gw_ports)
-{
-    for (int i = 0; i < br_int->n_ports; i++) {
-        const struct ovsrec_port *port_rec = br_int->ports[i];
-        if (!strcmp(port_rec->name, br_int->name)) {
-            continue;
-        }
-        const char *tunnel_id = smap_get(&port_rec->external_ids,
-                                          "ovn-chassis-id");
-        if (tunnel_id &&
-                encaps_tunnel_id_match(tunnel_id, chassis->name, NULL, NULL)) {
-            continue;
-        }
-        const char *localnet = smap_get(&port_rec->external_ids,
-                                        "ovn-localnet-port");
-        if (localnet) {
-            continue;
-        }
-        for (int j = 0; j < port_rec->n_interfaces; j++) {
-            const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
-            if (!iface_rec->n_ofport) {
-                continue;
-            }
-            /* Get localnet vif. */
-            const char *iface_id = smap_get(&iface_rec->external_ids,
-                                            "iface-id");
-            if (!iface_id) {
-                continue;
-            }
-            const struct sbrec_port_binding *pb
-                = lport_lookup_by_name(sbrec_port_binding_by_name, iface_id);
-            if (!pb || pb->chassis != chassis) {
-                continue;
-            }
-            if (!iface_rec->link_state ||
-                    strcmp(iface_rec->link_state, "up")) {
-                continue;
-            }
-            struct local_datapath *ld
-                = get_local_datapath(local_datapaths,
-                                     pb->datapath->tunnel_key);
-            if (ld && ld->localnet_port) {
-                sset_add(localnet_vifs, iface_id);
-            }
-        }
-    }
-
-    struct sbrec_port_binding *target = sbrec_port_binding_index_init_row(
-        sbrec_port_binding_by_datapath);
-
-    const struct local_datapath *ld;
-    HMAP_FOR_EACH (ld, hmap_node, local_datapaths) {
-        const struct sbrec_port_binding *pb;
-
-        if (!ld->localnet_port) {
-            continue;
-        }
-
-        /* Get l3gw ports.  Consider port bindings with type "l3gateway"
-         * that connect to gateway routers (if local), and consider port
-         * bindings of type "patch" since they might connect to
-         * distributed gateway ports with NAT addresses. */
-
-        sbrec_port_binding_index_set_datapath(target, ld->datapath);
-        SBREC_PORT_BINDING_FOR_EACH_EQUAL (pb, target,
-                                           sbrec_port_binding_by_datapath) {
-            if ((!strcmp(pb->type, "l3gateway") && pb->chassis == chassis)
-                || !strcmp(pb->type, "patch")) {
-                sset_add(local_l3gw_ports, pb->logical_port);
-            }
-        }
-    }
-    sbrec_port_binding_index_destroy_row(target);
-}
-
-
-/* Extracts the mac, IPv4 and IPv6 addresses, and logical port from
- * 'addresses' which should be of the format 'MAC [IP1 IP2 ..]
- * [is_chassis_resident("LPORT_NAME")]', where IPn should be a valid IPv4
- * or IPv6 address, and stores them in the 'ipv4_addrs' and 'ipv6_addrs'
- * fields of 'laddrs'.  The logical port name is stored in 'lport'.
- *
- * Returns true if at least 'MAC' is found in 'address', false otherwise.
- *
- * The caller must call destroy_lport_addresses() and free(*lport). */
-static bool
-extract_addresses_with_port(const char *addresses,
-                            struct lport_addresses *laddrs,
-                            char **lport)
-{
-    int ofs;
-    if (!extract_addresses(addresses, laddrs, &ofs)) {
-        return false;
-    } else if (!addresses[ofs]) {
-        return true;
-    }
-
-    struct lexer lexer;
-    lexer_init(&lexer, addresses + ofs);
-    lexer_get(&lexer);
-
-    if (lexer.error || lexer.token.type != LEX_T_ID
-        || !lexer_match_id(&lexer, "is_chassis_resident")) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", addresses);
-        lexer_destroy(&lexer);
-        return true;
-    }
-
-    if (!lexer_match(&lexer, LEX_T_LPAREN)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "Syntax error: expecting '(' after "
-                          "'is_chassis_resident' in address '%s'", addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    if (lexer.token.type != LEX_T_STRING) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl,
-                    "Syntax error: expecting quoted string after "
-                    "'is_chassis_resident' in address '%s'", addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    *lport = xstrdup(lexer.token.s);
-
-    lexer_get(&lexer);
-    if (!lexer_match(&lexer, LEX_T_RPAREN)) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-        VLOG_INFO_RL(&rl, "Syntax error: expecting ')' after quoted string in "
-                          "'is_chassis_resident()' in address '%s'",
-                          addresses);
-        lexer_destroy(&lexer);
-        return false;
-    }
-
-    lexer_destroy(&lexer);
-    return true;
-}
-
-static void
-consider_nat_address(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                     const char *nat_address,
-                     const struct sbrec_port_binding *pb,
-                     struct sset *nat_address_keys,
-                     const struct sbrec_chassis *chassis,
-                     const struct sset *active_tunnels,
-                     struct shash *nat_addresses)
-{
-    struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
-    char *lport = NULL;
-    if (!extract_addresses_with_port(nat_address, laddrs, &lport)
-        || (!lport && !strcmp(pb->type, "patch"))
-        || (lport && !lport_is_chassis_resident(
-                sbrec_port_binding_by_name, chassis,
-                active_tunnels, lport))) {
-        destroy_lport_addresses(laddrs);
-        free(laddrs);
-        free(lport);
-        return;
-    }
-    free(lport);
-
-    int i;
-    for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
-        char *name = xasprintf("%s-%s", pb->logical_port,
-                                        laddrs->ipv4_addrs[i].addr_s);
-        sset_add(nat_address_keys, name);
-        free(name);
-    }
-    if (laddrs->n_ipv4_addrs == 0) {
-        char *name = xasprintf("%s-noip", pb->logical_port);
-        sset_add(nat_address_keys, name);
-        free(name);
-    }
-    shash_add(nat_addresses, pb->logical_port, laddrs);
-}
-
-static void
-get_nat_addresses_and_keys(struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                           struct sset *nat_address_keys,
-                           struct sset *local_l3gw_ports,
-                           const struct sbrec_chassis *chassis,
-                           const struct sset *active_tunnels,
-                           struct shash *nat_addresses)
-{
-    const char *gw_port;
-    SSET_FOR_EACH(gw_port, local_l3gw_ports) {
-        const struct sbrec_port_binding *pb;
-
-        pb = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
-        if (!pb) {
-            continue;
-        }
-
-        if (pb->n_nat_addresses) {
-            for (int i = 0; i < pb->n_nat_addresses; i++) {
-                consider_nat_address(sbrec_port_binding_by_name,
-                                     pb->nat_addresses[i], pb,
-                                     nat_address_keys, chassis,
-                                     active_tunnels,
-                                     nat_addresses);
-            }
-        } else {
-            /* Continue to support options:nat-addresses for version
-             * upgrade. */
-            const char *nat_addresses_options = smap_get(&pb->options,
-                                                         "nat-addresses");
-            if (nat_addresses_options) {
-                consider_nat_address(sbrec_port_binding_by_name,
-                                     nat_addresses_options, pb,
-                                     nat_address_keys, chassis,
-                                     active_tunnels,
-                                     nat_addresses);
-            }
-        }
-    }
-}
-
 static void
 send_garp_rarp_wait(long long int send_garp_rarp_time)
 {
     /* Set the poll timer for next garp/rarp only if there is data to
      * be sent. */
-    if (!shash_is_empty(&send_garp_rarp_data)) {
+    if (!hmap_is_empty(&send_garp_rarp_data)) {
         poll_timer_wait_until(send_garp_rarp_time);
     }
 }
@@ -6674,16 +6236,16 @@  static void
 send_garp_rarp_run(struct rconn *swconn, long long int *send_garp_rarp_time)
     OVS_REQUIRES(pinctrl_mutex)
 {
-    if (shash_is_empty(&send_garp_rarp_data)) {
+    if (hmap_is_empty(&send_garp_rarp_data)) {
         return;
     }
 
     /* Send GARPs, and update the next announcement. */
-    struct shash_node *iter;
     long long int current_time = time_msec();
     *send_garp_rarp_time = LLONG_MAX;
-    SHASH_FOR_EACH (iter, &send_garp_rarp_data) {
-        long long int next_announce = send_garp_rarp(swconn, iter->data,
+    struct garp_rarp_node *garp;
+    HMAP_FOR_EACH (garp, hmap_node, &send_garp_rarp_data) {
+        long long int next_announce = send_garp_rarp(swconn, garp,
                                                      current_time);
         if (*send_garp_rarp_time > next_announce) {
             *send_garp_rarp_time = next_announce;
@@ -6743,22 +6305,12 @@  send_arp_nd_run(struct rconn *swconn, long long int *send_arp_nd_time)
 /* Called by pinctrl_run(). Runs with in the main ovn-controller
  * thread context. */
 static void
-send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                       struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
-                       struct ovsdb_idl_index *sbrec_port_binding_by_name,
-                       struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+send_garp_rarp_prepare(const struct hmap *garp_rarp_data,
                        const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
-                       const struct ovsrec_bridge *br_int,
                        const struct sbrec_chassis *chassis,
-                       const struct hmap *local_datapaths,
-                       const struct sset *active_tunnels,
                        const struct ovsrec_open_vswitch_table *ovs_table)
     OVS_REQUIRES(pinctrl_mutex)
 {
-    struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
-    struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
-    struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
-    struct shash nat_addresses;
     unsigned long long garp_max_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
     unsigned long long max_arp_nd_timeout = GARP_RARP_DEF_MAX_TIMEOUT;
     bool garp_continuous = false, continuous_arp_nd = true;
@@ -6778,51 +6330,13 @@  send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
         continuous_arp_nd = !!max_arp_nd_timeout;
     }
 
-    shash_init(&nat_addresses);
+    bool reset_timers = (garp_max_timeout != garp_rarp_max_timeout ||
+                         garp_continuous != garp_rarp_continuous);
 
-    get_localnet_vifs_l3gwports(sbrec_port_binding_by_datapath,
-                                sbrec_port_binding_by_name,
-                                br_int, chassis, local_datapaths,
-                                &localnet_vifs, &local_l3gw_ports);
-
-    get_nat_addresses_and_keys(sbrec_port_binding_by_name,
-                               &nat_ip_keys, &local_l3gw_ports,
-                               chassis, active_tunnels,
-                               &nat_addresses);
-
-    /* For deleted ports and deleted nat ips, remove from
-     * send_garp_rarp_data. */
-    struct shash_node *iter;
-    SHASH_FOR_EACH_SAFE (iter, &send_garp_rarp_data) {
-        if (!sset_contains(&localnet_vifs, iter->name) &&
-            !sset_contains(&nat_ip_keys, iter->name)) {
-            send_garp_rarp_delete(iter->name);
-        }
-    }
-
-    /* Update send_garp_rarp_data. */
-    const char *iface_id;
-    SSET_FOR_EACH (iface_id, &localnet_vifs) {
-        const struct sbrec_port_binding *pb = lport_lookup_by_name(
-            sbrec_port_binding_by_name, iface_id);
-        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
-            send_garp_rarp_update(ovnsb_idl_txn,
-                                  sbrec_mac_binding_by_lport_ip,
-                                  local_datapaths, pb, &nat_addresses,
-                                  garp_max_timeout, garp_continuous);
-        }
-    }
-
-    /* Update send_garp_rarp_data for nat-addresses. */
-    const char *gw_port;
-    SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
-        const struct sbrec_port_binding *pb
-            = lport_lookup_by_name(sbrec_port_binding_by_name, gw_port);
-        if (pb && !smap_get_bool(&pb->options, "disable_garp_rarp", false)) {
-            send_garp_rarp_update(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
-                                  local_datapaths, pb, &nat_addresses,
-                                  garp_max_timeout, garp_continuous);
-        }
+    if (garp_rarp_sync(garp_rarp_data, &send_garp_rarp_data, reset_timers)) {
+        /* Notify pinctrl_handler so that it can wakeup and process
+         * these GARP/RARP requests. */
+        notify_pinctrl_handler();
     }
 
     arp_nd_sync_data(ecmp_nh_table);
@@ -6836,21 +6350,6 @@  send_garp_rarp_prepare(struct ovsdb_idl_txn *ovnsb_idl_txn,
         }
     }
 
-    /* pinctrl_handler thread will send the GARPs. */
-
-    sset_destroy(&localnet_vifs);
-    sset_destroy(&local_l3gw_ports);
-
-    SHASH_FOR_EACH_SAFE (iter, &nat_addresses) {
-        struct lport_addresses *laddrs = iter->data;
-        destroy_lport_addresses(laddrs);
-        shash_delete(&nat_addresses, iter);
-        free(laddrs);
-    }
-    shash_destroy(&nat_addresses);
-
-    sset_destroy(&nat_ip_keys);
-
     garp_rarp_max_timeout = garp_max_timeout;
     garp_rarp_continuous = garp_continuous;
 
@@ -6862,7 +6361,7 @@  static bool
 may_inject_pkts(void)
 {
     return (!shash_is_empty(&ipv6_ras) ||
-            !shash_is_empty(&send_garp_rarp_data) ||
+            !hmap_is_empty(&send_garp_rarp_data) ||
             ipv6_prefixd_should_inject() ||
             !ovs_list_is_empty(&mcast_query_list) ||
             buffered_packets_ctx_is_ready_to_send(&buffered_packets_ctx) ||
diff --git a/controller/pinctrl.h b/controller/pinctrl.h
index e33d7cbf5..f57f5c306 100644
--- a/controller/pinctrl.h
+++ b/controller/pinctrl.h
@@ -44,24 +44,24 @@  struct sbrec_mac_binding_table;
 void pinctrl_init(void);
 void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
                  struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
-                 struct ovsdb_idl_index *sbrec_port_binding_by_datapath,
                  struct ovsdb_idl_index *sbrec_port_binding_by_key,
                  struct ovsdb_idl_index *sbrec_port_binding_by_name,
                  struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
                  struct ovsdb_idl_index *sbrec_igmp_groups,
                  struct ovsdb_idl_index *sbrec_ip_multicast_opts,
                  struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
-                 const struct sbrec_controller_event_table *,
-                 const struct sbrec_service_monitor_table *,
-                 const struct sbrec_mac_binding_table *,
-                 const struct sbrec_bfd_table *,
-                 const struct sbrec_ecmp_nexthop_table *,
-                 const struct ovsrec_bridge *, const struct sbrec_chassis *,
+                 const struct sbrec_controller_event_table *ce_table,
+                 const struct sbrec_service_monitor_table *svc_mon_table,
+                 const struct sbrec_mac_binding_table *mac_binding_table,
+                 const struct sbrec_bfd_table *bfd_table,
+                 const struct sbrec_ecmp_nexthop_table *ecmp_nh_table,
+                 const struct sbrec_chassis *chassis,
                  const struct hmap *local_datapaths,
                  const struct sset *active_tunnels,
                  const struct shash *local_active_ports_ipv6_pd,
                  const struct shash *local_active_ports_ras,
-                 const struct ovsrec_open_vswitch_table *ovs_table);
+                 const struct ovsrec_open_vswitch_table *ovs_table,
+                 const struct hmap *garp_rarp_data);
 void pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn);
 void pinctrl_destroy(void);