On 8/7/25 3:28 PM, Xavier Simonart wrote:
> Hi Ales, Dumitru
> 

Hi Xavier,

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

Thanks for the review!

I'll take care of the nit before sending out v3.

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

Regards,
Dumitru

> On Wed, Aug 6, 2025 at 8:27 PM Ales Musil via dev <ovs-
> d...@openvswitch.org <mailto:ovs-dev@openvswitch.org>> wrote:
> 
>     From: Dumitru Ceara <dce...@redhat.com <mailto: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
>     <mailto:amu...@redhat.com>>
>     Signed-off-by: Ales Musil <amu...@redhat.com <mailto:amu...@redhat.com>>
>     Signed-off-by: Dumitru Ceara <dce...@redhat.com
>     <mailto:dce...@redhat.com>>
>     ---
>     Changes in V2:
>     - removed the ne_nl_neigh_filter
>     - fixed memory leak of announced_neighbors hmap.
>     ---
>      controller/automake.mk <http://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 <http://automake.mk> b/
>     controller/automake.mk <http://automake.mk>
>     index f0638ea97..3eb45475c 100644
>     --- a/controller/automake.mk <http://automake.mk>
>     +++ b/controller/automake.mk <http://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 <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 <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 <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 <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 <mailto:d...@openvswitch.org>
>     https://mail.openvswitch.org/mailman/listinfo/ovs-dev <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