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