Hi Ales, Dumitru

Thanks for the patch and the whole serie!.
I only have a small nit below

Acked-by: Xavier Simonart <xsimo...@redhat.com>
Thanks
Xavier

On Wed, Aug 6, 2025 at 8:27 PM Ales Musil via dev <ovs-dev@openvswitch.org>
wrote:

> From: Dumitru Ceara <dce...@redhat.com>
>
> This implements an interface for maintaining (Linux) neighbor entries
> through Netlink.  Netlink was chosen for the same reason it was selected
> for maintaining (Linux) route tables for control planes managed by OVN
> in 0ed52a4d5aaf ("controller: Introduce route-exchange-netlink.").
>
> Co-Authored-by: Ales Musil <amu...@redhat.com>
> Signed-off-by: Ales Musil <amu...@redhat.com>
> Signed-off-by: Dumitru Ceara <dce...@redhat.com>
> ---
> Changes in V2:
> - removed the ne_nl_neigh_filter
> - fixed memory leak of announced_neighbors hmap.
> ---
>  controller/automake.mk                 |   4 +
>  controller/neighbor-exchange-netlink.c | 467 +++++++++++++++++++++++++
>  controller/neighbor-exchange-netlink.h |  60 ++++
>  controller/neighbor.c                  |  60 ++++
>  controller/neighbor.h                  |  72 ++++
>  5 files changed, 663 insertions(+)
>  create mode 100644 controller/neighbor-exchange-netlink.c
>  create mode 100644 controller/neighbor-exchange-netlink.h
>  create mode 100644 controller/neighbor.c
>  create mode 100644 controller/neighbor.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index f0638ea97..3eb45475c 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -30,6 +30,8 @@ controller_ovn_controller_SOURCES = \
>         controller/ofctrl.h \
>         controller/ofctrl-seqno.c \
>         controller/ofctrl-seqno.h \
> +       controller/neighbor.c \
> +       controller/neighbor.h \
>         controller/pinctrl.c \
>         controller/pinctrl.h \
>         controller/patch.c \
> @@ -65,6 +67,8 @@ controller_ovn_controller_SOURCES = \
>
>  if HAVE_NETLINK
>  controller_ovn_controller_SOURCES += \
> +       controller/neighbor-exchange-netlink.h \
> +       controller/neighbor-exchange-netlink.c \
>         controller/route-exchange-netlink.h \
>         controller/route-exchange-netlink.c \
>         controller/route-exchange.c \
> diff --git a/controller/neighbor-exchange-netlink.c
> b/controller/neighbor-exchange-netlink.c
> new file mode 100644
> index 000000000..3b9642da0
> --- /dev/null
> +++ b/controller/neighbor-exchange-netlink.c
> @@ -0,0 +1,467 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <stdbool.h>
> +#include <linux/if_ether.h>
> +#include <linux/rtnetlink.h>
> +
> +#include "hmapx.h"
> +#include "lib/netlink.h"
> +#include "lib/netlink-socket.h"
> +#include "lib/packets.h"
> +#include "openvswitch/vlog.h"
> +
> +#include "neighbor-exchange-netlink.h"
> +#include "neighbor.h"
> +
> +VLOG_DEFINE_THIS_MODULE(neighbor_exchange_netlink);
> +
> +#define NETNL_REQ_BUFFER_SIZE 128
> +
> +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
> +
> +/* Inspired from route_table_dump_one_table() in OVS. */
> +typedef void ne_table_handle_msg_callback(const struct ne_table_msg *,
> +                                          void *aux);
> +static bool ne_table_dump_one_ifindex(unsigned char address_family,
> +                                      int32_t if_index,
> +                                      ne_table_handle_msg_callback *,
> +                                      void *aux);
> +struct ne_msg_handle_data {
> +    /* Stores 'struct advertise_neighbor_entry'. */
> +    struct hmapx *neighbors_to_advertise;
> +
> +    /* Stores 'struct ne_nl_received_neigh'. */
> +    struct vector *learned_neighbors;
> +
> +    /* Stores 'struct advertise_neighbor_entry'. */
> +    const struct hmap *neighbors;
> +
> +    /* Non-zero error code if any netlink operation failed. */
> +    int ret;
> +};
> +
> +static void handle_ne_msg(const struct ne_table_msg *, void *data);
> +
> +static int ne_table_parse__(struct ofpbuf *, size_t ofs,
> +                            const struct nlmsghdr *,
> +                            const struct ndmsg *,
> +                            struct ne_table_msg *);
> +static int ne_nl_add_neigh(int32_t if_index, uint8_t family,
> +                           uint16_t state, uint8_t flags,
> +                           const struct eth_addr *,
> +                           const struct in6_addr *,
> +                           uint16_t port, uint16_t vlan);
> +static int ne_nl_del_neigh(int32_t if_index, uint8_t family,
> +                           const struct eth_addr *,
> +                           const struct in6_addr *,
> +                           uint16_t port, uint16_t vlan);
> +
> +/* Inserts all neigh entries listed in 'neighbors' (of type
> + * 'struct advertise_neighbor_entry') in the table associated to
> + * 'if_index'.  Populates 'learned_neighbors' with all neigh entries
> + * (struct ne_nl_received_neigh) that exist in the table associated to
> + * 'if_index'.
> + *
> + *
> + * Returns 0 on success, errno on failure. */
> +int
> +ne_nl_sync_neigh(uint8_t family, int32_t if_index,
> +                 const struct hmap *neighbors,
> +                 struct vector *learned_neighbors)
> +{
> +    struct hmapx neighbors_to_advertise =
> +        HMAPX_INITIALIZER(&neighbors_to_advertise);
> +    struct advertise_neighbor_entry *an;
> +    int ret;
> +
> +    HMAP_FOR_EACH (an, node, neighbors) {
> +        hmapx_add(&neighbors_to_advertise, an);
> +    }
> +
> +    struct ne_msg_handle_data data = {
> +        .neighbors = neighbors,
> +        .neighbors_to_advertise = &neighbors_to_advertise,
> +        .learned_neighbors = learned_neighbors,
> +    };
> +    ne_table_dump_one_ifindex(family, if_index, handle_ne_msg, &data);
> +    ret = data.ret;
> +
> +    /* Add any remaining routes in the routes_to_advertise hmapx to the
> +     * system routing table. */
>
nit: s/routes/neighbors/

> +    struct hmapx_node *hn;
> +    HMAPX_FOR_EACH (hn, &neighbors_to_advertise) {
> +        an = hn->data;
> +        int err = ne_nl_add_neigh(if_index, family,
> +                                  NUD_NOARP,        /* state = static */
> +                                  0,                /* flags */
> +                                  &an->lladdr, &an->addr,
> +                                  0,                /* port */
> +                                  0);               /* vlan */
> +        if (err) {
> +            char addr_s[INET6_ADDRSTRLEN + 1];
> +            VLOG_WARN_RL(&rl, "Add neigh ifindex=%"PRId32
> +                              " eth=" ETH_ADDR_FMT " dst=%s"
> +                              " failed: %s",
> +                         if_index, ETH_ADDR_ARGS(an->lladdr),
> +                         ipv6_string_mapped(
> +                             addr_s, &an->addr) ? addr_s : "(invalid)",
> +                         ovs_strerror(err));
> +            if (!ret) {
> +                /* Report the first error value to the caller. */
> +                ret = err;
> +            }
> +        }
> +    }
> +    hmapx_destroy(&neighbors_to_advertise);
> +    return ret;
> +}
> +
> +/* OVN expects all static entries added on this ifindex to be OVN-owned.
> + * Everything else must be learnt. */
> +bool
> +ne_is_ovn_owned(const struct ne_nl_received_neigh *nd)
> +{
> +    return !(nd->state & NUD_PERMANENT) && (nd->state & NUD_NOARP)
> +           && !(nd->flags & NTF_EXT_LEARNED);
> +}
> +
> +static bool
> +ne_table_dump_one_ifindex(unsigned char address_family, int32_t if_index,
> +                          ne_table_handle_msg_callback *handle_msg_cb,
> +                          void *aux)
> +{
> +    uint64_t reply_stub[NL_DUMP_BUFSIZE / 8];
> +    struct ofpbuf request, reply, buf;
> +    struct ndmsg *rq_msg;
> +    bool filtered = true;
> +    struct nl_dump dump;
> +
> +    uint8_t request_stub[NETNL_REQ_BUFFER_SIZE];
> +    ofpbuf_use_stub(&request, request_stub, sizeof(request_stub));
> +
> +    nl_msg_put_nlmsghdr(&request, sizeof *rq_msg, RTM_GETNEIGH,
> NLM_F_REQUEST);
> +    rq_msg = ofpbuf_put_zeros(&request, sizeof *rq_msg);
> +    rq_msg->ndm_family = address_family;
> +    if (if_index) {
> +        nl_msg_put_u32(&request, NDA_IFINDEX, if_index);
> +    }
> +
> +    nl_dump_start(&dump, NETLINK_ROUTE, &request);
> +    ofpbuf_uninit(&request);
> +
> +    ofpbuf_use_stub(&buf, reply_stub, sizeof reply_stub);
> +    while (nl_dump_next(&dump, &reply, &buf)) {
> +        struct ne_table_msg msg;
> +
> +        if (ne_table_parse(&reply, &msg)) {
> +            struct nlmsghdr *nlmsghdr = nl_msg_nlmsghdr(&reply);
> +
> +            /* Older kernels do not support filtering. */
> +            if (!(nlmsghdr->nlmsg_flags & NLM_F_DUMP_FILTERED)) {
> +                filtered = false;
> +            }
> +            handle_msg_cb(&msg, aux);
> +        }
> +    }
> +    ofpbuf_uninit(&buf);
> +    nl_dump_done(&dump);
> +
> +    return filtered;
> +}
> +
> +static int
> +ne_table_parse__(struct ofpbuf *buf, size_t ofs, const struct nlmsghdr
> *nlmsg,
> +                 const struct ndmsg *nd, struct ne_table_msg *change)
> +{
> +    bool parsed;
> +
> +    static const struct nl_policy policy[] = {
> +        [NDA_DST] = { .type = NL_A_U32, .optional = true  },
> +        [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
> +        [NDA_PORT] = { .type = NL_A_U16, .optional = true },
> +    };
> +
> +    static const struct nl_policy policy6[] = {
> +        [NDA_DST] = { .type = NL_A_IPV6, .optional = true  },
> +        [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
> +        [NDA_PORT] = { .type = NL_A_U16, .optional = true },
> +    };
> +
> +    static const struct nl_policy policy_bridge[] = {
> +        [NDA_DST] = { .type = NL_A_UNSPEC, .optional = true,
> +                      .min_len = sizeof(struct in_addr),
> +                      .max_len = sizeof(struct in6_addr)},
> +        [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
> +        [NDA_PORT] = { .type = NL_A_U16, .optional = true },
> +        [NDA_VLAN] = { .type = NL_A_U16, .optional = true },
> +    };
> +
> +    BUILD_ASSERT(ARRAY_SIZE(policy) == ARRAY_SIZE(policy6));
> +    BUILD_ASSERT(ARRAY_SIZE(policy) == ARRAY_SIZE(policy_bridge));
> +    struct nlattr *attrs[ARRAY_SIZE(policy)];
> +
> +    if (nd->ndm_family == AF_INET) {
> +        parsed = nl_policy_parse(buf, ofs, policy, attrs,
> +                                 ARRAY_SIZE(policy));
> +    } else if (nd->ndm_family == AF_INET6) {
> +        parsed = nl_policy_parse(buf, ofs, policy6, attrs,
> +                                 ARRAY_SIZE(policy6));
> +    } else if (nd->ndm_family == AF_BRIDGE) {
> +        parsed = nl_policy_parse(buf, ofs, policy_bridge, attrs,
> +                                 ARRAY_SIZE(policy_bridge));
> +    } else {
> +        VLOG_WARN_RL(&rl, "received non AF_INET/AF_INET6/AF_BRIDGE
> rtnetlink "
> +                          "neigh message");
> +        return 0;
> +    }
> +
> +    if (parsed) {
> +        *change = (struct ne_table_msg) {
> +            .nlmsg_type = nlmsg->nlmsg_type,
> +            .nd.if_index = nd->ndm_ifindex,
> +            .nd.family = nd->ndm_family,
> +            .nd.state = nd->ndm_state,
> +            .nd.flags = nd->ndm_flags,
> +            .nd.type = nd->ndm_type,
> +        };
> +
> +        if (attrs[NDA_DST]) {
> +            size_t nda_dst_size = nl_attr_get_size(attrs[NDA_DST]);
> +
> +            switch (nda_dst_size) {
> +            case sizeof(uint32_t):
> +                in6_addr_set_mapped_ipv4(&change->nd.addr,
> +
>  nl_attr_get_be32(attrs[NDA_DST]));
> +                break;
> +            case sizeof(struct in6_addr):
> +                change->nd.addr = nl_attr_get_in6_addr(attrs[NDA_DST]);
> +                break;
> +            default:
> +                VLOG_DBG_RL(&rl,
> +                            "neigh message contains non-IPv4/IPv6
> NDA_DST");
> +                return 0;
> +            }
> +        }
> +
> +        if (attrs[NDA_LLADDR]) {
> +            if (nl_attr_get_size(attrs[NDA_LLADDR]) != ETH_ALEN) {
> +                VLOG_DBG_RL(&rl, "neigh message contains non-ETH
> NDA_LLADDR");
> +                return 0;
> +            }
> +            change->nd.lladdr = nl_attr_get_eth_addr(attrs[NDA_LLADDR]);
> +        }
> +
> +        if (attrs[NDA_PORT]) {
> +            change->nd.port = nl_attr_get_u16(attrs[NDA_PORT]);
> +        }
> +
> +        if (attrs[NDA_VLAN]) {
> +            change->nd.vlan = nl_attr_get_u16(attrs[NDA_VLAN]);
> +        }
> +    } else {
> +        VLOG_DBG_RL(&rl, "received unparseable rtnetlink neigh message");
> +        return 0;
> +    }
> +
> +    /* Success. */
> +    return RTNLGRP_NEIGH;
> +}
> +
> +static void
> +handle_ne_msg(const struct ne_table_msg *msg, void *data)
> +{
> +    struct ne_msg_handle_data *handle_data = data;
> +    const struct ne_nl_received_neigh *nd = &msg->nd;
> +
> +    if (!ne_is_ovn_owned(nd)) {
> +        if (!handle_data->learned_neighbors) {
> +            return;
> +        }
> +
> +        /* Learn the non-OVN entry. */
> +        vector_push(handle_data->learned_neighbors, nd);
> +        return;
> +    }
> +
> +    /* This neighbor was presumably added by OVN, see if it's still valid.
> +     * OVN only adds neighbors with vlan and port set to 0, all others
> +     * can be removed. */
> +    if (!nd->vlan && !nd->port && handle_data->neighbors_to_advertise) {
> +        uint32_t hash = advertise_neigh_hash(&nd->lladdr, &nd->addr);
> +        struct advertise_neighbor_entry *an;
> +        HMAP_FOR_EACH_WITH_HASH (an, node, hash, handle_data->neighbors) {
> +            if (eth_addr_equals(an->lladdr, nd->lladdr)
> +                && ipv6_addr_equals(&an->addr, &nd->addr)) {
> +
> hmapx_find_and_delete(handle_data->neighbors_to_advertise, an);
> +                return;
> +            }
> +        }
> +    }
> +
> +    int err = ne_nl_del_neigh(nd->if_index, nd->family,
> +                              &nd->lladdr, &nd->addr,
> +                              nd->port, nd->vlan);
> +    if (err) {
> +        char addr_s[INET6_ADDRSTRLEN + 1];
> +        VLOG_WARN_RL(&rl, "Delete neigh ifindex=%"PRId32" vlan=%"PRIu16
> +                          " eth=" ETH_ADDR_FMT " dst=%s port=%"PRIu16
> +                          " failed: %s",
> +                     nd->if_index, nd->vlan, ETH_ADDR_ARGS(nd->lladdr),
> +                     ipv6_string_mapped(addr_s, &nd->addr)
> +                     ? addr_s : "(invalid)",
> +                     nd->port,
> +                     ovs_strerror(err));
> +
> +        if (!handle_data->ret) {
> +            /* Report the first error value to the caller. */
> +            handle_data->ret = err;
> +        }
> +    }
> +}
> +
> +static int
> +ne_nl_add_neigh(int32_t if_index, uint8_t family,
> +                uint16_t state, uint8_t flags,
> +                const struct eth_addr *lladdr,
> +                const struct in6_addr *addr,
> +                uint16_t port, uint16_t vlan)
> +{
> +    uint32_t nl_flags = NLM_F_REQUEST | NLM_F_ACK |
> +                        NLM_F_CREATE | NLM_F_EXCL;
> +    bool dst_set = !ipv6_is_zero(addr);
> +    struct ofpbuf request;
> +    uint8_t request_stub[NETNL_REQ_BUFFER_SIZE];
> +    ofpbuf_use_stub(&request, request_stub, sizeof(request_stub));
> +
> +    nl_msg_put_nlmsghdr(&request, 0, RTM_NEWNEIGH, nl_flags);
> +
> +    struct ndmsg *nd = ofpbuf_put_zeros(&request, sizeof *nd);
> +    *nd = (struct ndmsg) {
> +        .ndm_family = family,
> +        .ndm_ifindex = if_index,
> +        .ndm_state = state,
> +        .ndm_flags = flags,
> +    };
> +
> +    nl_msg_put_unspec(&request, NDA_LLADDR, lladdr, sizeof *lladdr);
> +    if (dst_set) {
> +        if (IN6_IS_ADDR_V4MAPPED(addr)) {
> +            nl_msg_put_be32(&request, NDA_DST,
> in6_addr_get_mapped_ipv4(addr));
> +        } else {
> +            nl_msg_put_in6_addr(&request, NDA_DST, addr);
> +        }
> +    }
> +    if (port) {
> +        nl_msg_put_u16(&request, NDA_PORT, port);
> +    }
> +    if (vlan) {
> +        nl_msg_put_u16(&request, NDA_VLAN, vlan);
> +    }
> +
> +    if (VLOG_IS_DBG_ENABLED()) {
> +        struct ds msg = DS_EMPTY_INITIALIZER;
> +
> +        ds_put_format(&msg, "Adding neighbor ifindex %"PRId32 " for eth "
> +                            ETH_ADDR_FMT " port %"PRIu16" vlan %"PRIu16,
> +                            if_index, ETH_ADDR_ARGS(*lladdr),
> +                            port, vlan);
> +        if (dst_set) {
> +            ipv6_format_mapped(addr, &msg);
> +        }
> +        VLOG_DBG("%s", ds_cstr(&msg));
> +        ds_destroy(&msg);
> +    }
> +
> +    int err = nl_transact(NETLINK_ROUTE, &request, NULL);
> +
> +    ofpbuf_uninit(&request);
> +    return err;
> +}
> +
> +static int
> +ne_nl_del_neigh(int32_t if_index, uint8_t family,
> +                const struct eth_addr *lladdr,
> +                const struct in6_addr *addr,
> +                uint16_t port, uint16_t vlan)
> +{
> +    uint32_t flags = NLM_F_REQUEST | NLM_F_ACK;
> +    bool dst_set = !ipv6_is_zero(addr);
> +    struct ofpbuf request;
> +    uint8_t request_stub[NETNL_REQ_BUFFER_SIZE];
> +    ofpbuf_use_stub(&request, request_stub, sizeof(request_stub));
> +
> +    nl_msg_put_nlmsghdr(&request, 0, RTM_DELNEIGH, flags);
> +
> +    struct ndmsg *nd = ofpbuf_put_zeros(&request, sizeof *nd);
> +    *nd = (struct ndmsg) {
> +        .ndm_family = family,
> +        .ndm_ifindex = if_index,
> +    };
> +
> +    nl_msg_put_unspec(&request, NDA_LLADDR, lladdr, sizeof *lladdr);
> +    if (dst_set) {
> +        if (IN6_IS_ADDR_V4MAPPED(addr)) {
> +            nl_msg_put_be32(&request, NDA_DST,
> in6_addr_get_mapped_ipv4(addr));
> +        } else {
> +            nl_msg_put_in6_addr(&request, NDA_DST, addr);
> +        }
> +    }
> +    if (port) {
> +        nl_msg_put_u16(&request, NDA_PORT, port);
> +    }
> +    if (vlan) {
> +        nl_msg_put_u16(&request, NDA_VLAN, vlan);
> +    }
> +
> +    if (VLOG_IS_DBG_ENABLED()) {
> +        struct ds msg = DS_EMPTY_INITIALIZER;
> +
> +        ds_put_format(&msg, "Removing neighbor ifindex %"PRId32 " for eth
> "
> +                            ETH_ADDR_FMT " port %"PRIu16" vlan %"PRIu16,
> +                            if_index, ETH_ADDR_ARGS(*lladdr),
> +                            port, vlan);
> +        if (dst_set) {
> +            ds_put_char(&msg, ' ');
> +            ipv6_format_mapped(addr, &msg);
> +        }
> +        VLOG_DBG("%s", ds_cstr(&msg));
> +        ds_destroy(&msg);
> +    }
> +
> +    int err = nl_transact(NETLINK_ROUTE, &request, NULL);
> +
> +    ofpbuf_uninit(&request);
> +    return err;
> +}
> +
> +/* Parse Netlink message in buf, which is expected to contain a UAPI ndmsg
> + * header and associated neighbor attributes.
> + *
> + * Return RTNLGRP_NEIGH on success, and 0 on a parse error. */
> +int
> +ne_table_parse(struct ofpbuf *buf, void *change)
> +{
> +    struct nlmsghdr *nlmsg = ofpbuf_at(buf, 0, NLMSG_HDRLEN);
> +    struct ndmsg *nd = ofpbuf_at(buf, NLMSG_HDRLEN, sizeof *nd);
> +
> +    if (!nlmsg || !nd) {
> +        return 0;
> +    }
> +
> +    return ne_table_parse__(buf, NLMSG_HDRLEN + sizeof *nd,
> +                            nlmsg, nd, change);
> +}
> diff --git a/controller/neighbor-exchange-netlink.h
> b/controller/neighbor-exchange-netlink.h
> new file mode 100644
> index 000000000..e154d1b6e
> --- /dev/null
> +++ b/controller/neighbor-exchange-netlink.h
> @@ -0,0 +1,60 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef NEIGHBOR_EXCHANGE_NETLINK_H
> +#define NEIGHBOR_EXCHANGE_NETLINK_H 1
> +
> +#include <netinet/in.h>
> +#include <stdint.h>
> +
> +#include "openvswitch/hmap.h"
> +#include "openvswitch/ofpbuf.h"
> +
> +#include "vec.h"
> +
> +struct ne_nl_received_neigh {
> +    int32_t if_index;
> +    uint8_t  family;        /* AF_INET/AF_INET6/AF_BRIDGE. */
> +
> +    struct eth_addr lladdr; /* Interface index where the neigh is learnt
> on. */
> +    struct in6_addr addr;   /* In case of 'dst' entries non-zero;
> +                             * all zero otherwise. */
> +    uint16_t vlan;          /* Parsed from NDA_VLAN. */
> +    uint16_t port;          /* UDP port, e.g., for VXLAN,
> +                             * parsed from NDA_PORT. */
> +    uint16_t state;         /* A value out of NUD_*,
> +                             * from linux/neighbour.h. */
> +    uint8_t  flags;         /* A combination of NTF_* flags,
> +                             * from linux/neighbour.h. */
> +    uint8_t  type;          /* A value out of 'rtm_type' from
> linux/rtnetlink.h
> +                             * e.g., RTN_UNICAST, RTN_MULTICAST. */
> +};
> +
> +/* A digested version of a neigh message sent down by the kernel to
> indicate
> + * that a neigh entry has changed. */
> +struct ne_table_msg {
> +    uint16_t nlmsg_type;            /* E.g. RTM_NEWNEIGH, RTM_DELNEIGH. */
> +    struct ne_nl_received_neigh nd; /* Data parsed from this message. */
> +};
> +
> +int ne_nl_sync_neigh(uint8_t family, int32_t if_index,
> +                     const struct hmap *neighbors,
> +                     struct vector *learned_neighbors);
> +
> +bool ne_is_ovn_owned(const struct ne_nl_received_neigh *nd);
> +
> +int ne_table_parse(struct ofpbuf *, void *change);
> +
> +#endif /* NEIGHBOR_EXCHANGE_NETLINK_H */
> diff --git a/controller/neighbor.c b/controller/neighbor.c
> new file mode 100644
> index 000000000..8bfc2cf0d
> --- /dev/null
> +++ b/controller/neighbor.c
> @@ -0,0 +1,60 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +
> +#include "lib/hash.h"
> +#include "lib/packets.h"
> +#include "lib/sset.h"
> +
> +#include "neighbor.h"
> +
> +static void neighbor_interface_monitor_destroy(
> +    struct neighbor_interface_monitor *);
> +
> +uint32_t
> +advertise_neigh_hash(const struct eth_addr *eth, const struct in6_addr
> *ip)
> +{
> +    return hash_bytes(ip, sizeof *ip, hash_bytes(eth, sizeof *eth, 0));
> +}
> +
> +void
> +neighbor_run(struct neighbor_ctx_in *n_ctx_in OVS_UNUSED,
> +             struct neighbor_ctx_out *n_ctx_out OVS_UNUSED)
> +{
> +    /* XXX: Not implemented yet. */
> +}
> +
> +void
> +neighbor_cleanup(struct vector *monitored_interfaces)
> +{
> +    struct neighbor_interface_monitor *nim;
> +    VECTOR_FOR_EACH (monitored_interfaces, nim) {
> +        neighbor_interface_monitor_destroy(nim);
> +    }
> +    vector_clear(monitored_interfaces);
> +}
> +
> +static void
> +neighbor_interface_monitor_destroy(struct neighbor_interface_monitor *nim)
> +{
> +    struct advertise_neighbor_entry *an;
> +
> +    HMAP_FOR_EACH_POP (an, node, &nim->announced_neighbors) {
> +        free(an);
> +    }
> +    hmap_destroy(&nim->announced_neighbors);
> +    free(nim);
> +}
> diff --git a/controller/neighbor.h b/controller/neighbor.h
> new file mode 100644
> index 000000000..3abc6e923
> --- /dev/null
> +++ b/controller/neighbor.h
> @@ -0,0 +1,72 @@
> +/* Copyright (c) 2025, Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef NEIGHBOR_H
> +#define NEIGHBOR_H 1
> +
> +#include <sys/types.h>
> +#include <netinet/in.h>
> +#include <net/if.h>
> +#include <stdint.h>
> +
> +#include "lib/sset.h"
> +#include "openvswitch/hmap.h"
> +
> +#include "vec.h"
> +
> +/* XXX: AF_BRIDGE doesn't seem to be defined on OSX. */
> +#ifdef __APPLE__
> +#ifndef AF_BRIDGE
> +#define AF_BRIDGE AF_UNSPEC
> +#endif
> +#endif /* __APPLE__ */
> +
> +enum neighbor_family {
> +    NEIGH_AF_INET = AF_INET,
> +    NEIGH_AF_INET6 = AF_INET6,
> +    NEIGH_AF_BRIDGE = AF_BRIDGE,
> +};
> +
> +struct neighbor_ctx_in {
> +};
> +
> +struct neighbor_ctx_out {
> +    /* Contains struct neighbor_interface_monitor pointers. */
> +    struct vector *monitored_interfaces;
> +};
> +
> +struct neighbor_interface_monitor {
> +    enum neighbor_family family;
> +    char if_name[IFNAMSIZ + 1];
> +
> +    /* Contains struct advertise_neighbor_entry - the entries that OVN
> +     * advertises on this interface. */
> +    struct hmap announced_neighbors;
> +};
> +
> +struct advertise_neighbor_entry {
> +    struct hmap_node node;
> +
> +    struct eth_addr lladdr;
> +    struct in6_addr addr;   /* In case of 'dst' entries non-zero;
> +                             * all zero otherwise. */
> +};
> +
> +uint32_t advertise_neigh_hash(const struct eth_addr *,
> +                              const struct in6_addr *);
> +void neighbor_run(struct neighbor_ctx_in *, struct neighbor_ctx_out *);
> +void neighbor_cleanup(struct vector *monitored_interfaces);
> +
> +#endif /* NEIGHBOR_H */
> --
> 2.50.1
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to