From: Dumitru Ceara <[email protected]>
For each (Linux) interface that's interesting for the OVN control plane,
watch for neighbor (fdb/arp) changes. This allows us to reconcile on
changed neighbor entries from outside routing agents (e.g., FRR).
This is the neighbor equivalent of the support added for route tables in
673d90f1173f ("controller: Watch for route changes.").
Signed-off-by: Dumitru Ceara <[email protected]>
---
Changes in V2:
- fixed up log messages
- changed code to make OVN only manage neighbor entries with VLAN 0
- added advertise_neigh_find() helper
---
controller/automake.mk | 5 +-
controller/neighbor-exchange-netlink.c | 27 +--
controller/neighbor-table-notify-stub.c | 57 ++++++
controller/neighbor-table-notify.c | 244 ++++++++++++++++++++++++
controller/neighbor-table-notify.h | 45 +++++
controller/neighbor.c | 19 +-
controller/neighbor.h | 5 +-
7 files changed, 386 insertions(+), 16 deletions(-)
create mode 100644 controller/neighbor-table-notify-stub.c
create mode 100644 controller/neighbor-table-notify.c
create mode 100644 controller/neighbor-table-notify.h
diff --git a/controller/automake.mk b/controller/automake.mk
index 3eb45475c..6af6ee2a9 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -63,18 +63,21 @@ controller_ovn_controller_SOURCES = \
controller/route.h \
controller/route.c \
controller/garp_rarp.h \
- controller/garp_rarp.c
+ controller/garp_rarp.c \
+ controller/neighbor-table-notify.h
if HAVE_NETLINK
controller_ovn_controller_SOURCES += \
controller/neighbor-exchange-netlink.h \
controller/neighbor-exchange-netlink.c \
+ controller/neighbor-table-notify.c \
controller/route-exchange-netlink.h \
controller/route-exchange-netlink.c \
controller/route-exchange.c \
controller/route-table-notify.c
else
controller_ovn_controller_SOURCES += \
+ controller/neighbor-table-notify-stub.c \
controller/route-exchange-stub.c \
controller/route-table-notify-stub.c
endif
diff --git a/controller/neighbor-exchange-netlink.c
b/controller/neighbor-exchange-netlink.c
index 3b9642da0..95c7e0d7d 100644
--- a/controller/neighbor-exchange-netlink.c
+++ b/controller/neighbor-exchange-netlink.c
@@ -287,6 +287,11 @@ 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;
+ /* OVN only manages VLAN 0 entries. */
+ if (nd->vlan) {
+ return;
+ }
+
if (!ne_is_ovn_owned(nd)) {
if (!handle_data->learned_neighbors) {
return;
@@ -298,17 +303,15 @@ handle_ne_msg(const struct ne_table_msg *msg, void *data)
}
/* 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;
- }
+ * OVN only adds neighbors with port set to 0, all others can be
+ * removed. */
+ if (!nd->port && handle_data->neighbors_to_advertise) {
+ struct advertise_neighbor_entry *an =
+ advertise_neigh_find(handle_data->neighbors, nd->lladdr,
+ &nd->addr);
+ if (an) {
+ hmapx_find_and_delete(handle_data->neighbors_to_advertise, an);
+ return;
}
}
@@ -341,7 +344,7 @@ ne_nl_add_neigh(int32_t if_index, uint8_t family,
uint16_t port, uint16_t vlan)
{
uint32_t nl_flags = NLM_F_REQUEST | NLM_F_ACK |
- NLM_F_CREATE | NLM_F_EXCL;
+ NLM_F_CREATE | NLM_F_REPLACE;
bool dst_set = !ipv6_is_zero(addr);
struct ofpbuf request;
uint8_t request_stub[NETNL_REQ_BUFFER_SIZE];
diff --git a/controller/neighbor-table-notify-stub.c
b/controller/neighbor-table-notify-stub.c
new file mode 100644
index 000000000..bb4fe5991
--- /dev/null
+++ b/controller/neighbor-table-notify-stub.c
@@ -0,0 +1,57 @@
+/* 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 "openvswitch/compiler.h"
+#include "neighbor-table-notify.h"
+
+bool
+neighbor_table_notify_run(void)
+{
+ return false;
+}
+
+void
+neighbor_table_notify_wait(void)
+{
+}
+
+void
+neighbor_table_add_watch_request(
+ struct hmap *neighbor_table_watches OVS_UNUSED,
+ int32_t if_index OVS_UNUSED,
+ const char *if_name OVS_UNUSED)
+{
+}
+
+void
+neighbor_table_watch_request_cleanup(
+ struct hmap *neighbor_table_watches OVS_UNUSED)
+{
+}
+
+void
+neighbor_table_notify_update_watches(
+ const struct hmap *neighbor_table_watches OVS_UNUSED)
+{
+}
+
+void
+neighbor_table_notify_destroy(void)
+{
+}
diff --git a/controller/neighbor-table-notify.c
b/controller/neighbor-table-notify.c
new file mode 100644
index 000000000..19b975e39
--- /dev/null
+++ b/controller/neighbor-table-notify.c
@@ -0,0 +1,244 @@
+/* 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 <linux/rtnetlink.h>
+#include <net/if.h>
+
+#include "hash.h"
+#include "hmapx.h"
+#include "lib/util.h"
+#include "netlink-notifier.h"
+#include "openvswitch/vlog.h"
+
+#include "neighbor-exchange-netlink.h"
+#include "neighbor-table-notify.h"
+
+VLOG_DEFINE_THIS_MODULE(neighbor_table_notify);
+
+struct neighbor_table_watch_request {
+ struct hmap_node node;
+ int32_t if_index;
+ char if_name[IFNAMSIZ + 1];
+};
+
+struct neighbor_table_watch_entry {
+ struct hmap_node node;
+ int32_t if_index;
+ char if_name[IFNAMSIZ + 1];
+};
+
+static struct hmap watches = HMAP_INITIALIZER(&watches);
+static bool any_neighbor_table_changed;
+static struct ne_table_msg nln_nmsg_change;
+
+static struct nln *nl_neighbor_handle;
+static struct nln_notifier *nl_neighbor_notifier;
+
+static void fdb_table_change(const void *change_, void *aux);
+
+static void
+neighbor_table_register_notifiers(void)
+{
+ VLOG_INFO("Adding neighbor table watchers.");
+ ovs_assert(!nl_neighbor_handle);
+
+ nl_neighbor_handle = nln_create(NETLINK_ROUTE, ne_table_parse,
+ &nln_nmsg_change);
+ ovs_assert(nl_neighbor_handle);
+
+ nl_neighbor_notifier =
+ nln_notifier_create(nl_neighbor_handle, RTNLGRP_NEIGH,
+ fdb_table_change, NULL);
+ if (!nl_neighbor_notifier) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "Failed to create neighbor table watcher.");
+ }
+}
+
+static void
+neighbor_table_deregister_notifiers(void)
+{
+ VLOG_INFO("Removing neighbor table watchers.");
+ ovs_assert(nl_neighbor_handle);
+
+ nln_notifier_destroy(nl_neighbor_notifier);
+ nln_destroy(nl_neighbor_handle);
+ nl_neighbor_notifier = NULL;
+ nl_neighbor_handle = NULL;
+}
+
+static uint32_t
+neighbor_table_notify_hash_watch(int32_t if_index)
+{
+ /* To allow lookups triggered by netlink messages, don't include the
+ * if_name in the hash. The netlink updates only include if_index. */
+ return hash_int(if_index, 0);
+}
+
+static void
+add_watch_entry(int32_t if_index, const char *if_name)
+{
+ VLOG_INFO("Registering new neighbor table watcher "
+ "for if_index %s (%"PRId32").",
+ if_name, if_index);
+
+ struct neighbor_table_watch_entry *we;
+ uint32_t hash = neighbor_table_notify_hash_watch(if_index);
+ we = xzalloc(sizeof *we);
+ we->if_index = if_index;
+ ovs_strzcpy(we->if_name, if_name, IFNAMSIZ + 1);
+ hmap_insert(&watches, &we->node, hash);
+
+ if (!nl_neighbor_handle) {
+ neighbor_table_register_notifiers();
+ }
+}
+
+static void
+remove_watch_entry(struct neighbor_table_watch_entry *we)
+{
+ VLOG_INFO("Removing neighbor table watcher for table %s (%"PRId32").",
+ we->if_name, we->if_index);
+ hmap_remove(&watches, &we->node);
+ free(we);
+
+ if (hmap_is_empty(&watches)) {
+ neighbor_table_deregister_notifiers();
+ }
+}
+
+bool
+neighbor_table_notify_run(void)
+{
+ any_neighbor_table_changed = false;
+
+ if (nl_neighbor_handle) {
+ nln_run(nl_neighbor_handle);
+ }
+
+ return any_neighbor_table_changed;
+}
+
+void
+neighbor_table_notify_wait(void)
+{
+ if (nl_neighbor_handle) {
+ nln_wait(nl_neighbor_handle);
+ }
+}
+
+void
+neighbor_table_add_watch_request(struct hmap *neighbor_table_watches,
+ int32_t if_index, const char *if_name)
+{
+ struct neighbor_table_watch_request *wr = xzalloc(sizeof *wr);
+
+ wr->if_index = if_index;
+ ovs_strzcpy(wr->if_name, if_name, IFNAMSIZ + 1);
+ hmap_insert(neighbor_table_watches, &wr->node,
+ neighbor_table_notify_hash_watch(wr->if_index));
+}
+
+void
+neighbor_table_watch_request_cleanup(struct hmap *neighbor_table_watches)
+{
+ struct neighbor_table_watch_request *wr;
+ HMAP_FOR_EACH_POP (wr, node, neighbor_table_watches) {
+ free(wr);
+ }
+}
+
+static struct neighbor_table_watch_entry *
+find_watch_entry(int32_t if_index, const char *if_name)
+{
+ struct neighbor_table_watch_entry *we;
+ uint32_t hash = neighbor_table_notify_hash_watch(if_index);
+ HMAP_FOR_EACH_WITH_HASH (we, node, hash, &watches) {
+ if (if_index == we->if_index && !strcmp(if_name, we->if_name)) {
+ return we;
+ }
+ }
+ return NULL;
+}
+
+static struct neighbor_table_watch_entry *
+find_watch_entry_by_if_index(int32_t if_index)
+{
+ struct neighbor_table_watch_entry *we;
+ uint32_t hash = neighbor_table_notify_hash_watch(if_index);
+ HMAP_FOR_EACH_WITH_HASH (we, node, hash, &watches) {
+ if (if_index == we->if_index) {
+ return we;
+ }
+ }
+ return NULL;
+}
+
+void
+neighbor_table_notify_update_watches(const struct hmap *neighbor_table_watches)
+{
+ struct hmapx sync_watches = HMAPX_INITIALIZER(&sync_watches);
+ struct neighbor_table_watch_entry *we;
+ HMAP_FOR_EACH (we, node, &watches) {
+ hmapx_add(&sync_watches, we);
+ }
+
+ struct neighbor_table_watch_request *wr;
+ HMAP_FOR_EACH (wr, node, neighbor_table_watches) {
+ we = find_watch_entry(wr->if_index, wr->if_name);
+ if (we) {
+ hmapx_find_and_delete(&sync_watches, we);
+ } else {
+ add_watch_entry(wr->if_index, wr->if_name);
+ }
+ }
+
+ struct hmapx_node *node;
+ HMAPX_FOR_EACH (node, &sync_watches) {
+ remove_watch_entry(node->data);
+ }
+
+ hmapx_destroy(&sync_watches);
+}
+
+void
+neighbor_table_notify_destroy(void)
+{
+ struct neighbor_table_watch_entry *we;
+ HMAP_FOR_EACH_SAFE (we, node, &watches) {
+ remove_watch_entry(we);
+ }
+}
+
+static void
+fdb_table_change(const void *change_, void *aux OVS_UNUSED)
+{
+ /* We currently track whether at least one recent neighbor table change
+ * was detected. If that's the case already there's no need to
+ * continue. */
+ if (any_neighbor_table_changed) {
+ return;
+ }
+
+ const struct ne_table_msg *change = change_;
+
+ if (change && !ne_is_ovn_owned(&change->nd)) {
+ if (find_watch_entry_by_if_index(change->nd.if_index)) {
+ any_neighbor_table_changed = true;
+ }
+ }
+}
diff --git a/controller/neighbor-table-notify.h
b/controller/neighbor-table-notify.h
new file mode 100644
index 000000000..9f21271cc
--- /dev/null
+++ b/controller/neighbor-table-notify.h
@@ -0,0 +1,45 @@
+/* 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_TABLE_NOTIFY_H
+#define NEIGHBOR_TABLE_NOTIFY_H 1
+
+#include <stdbool.h>
+#include "openvswitch/hmap.h"
+
+/* Returns true if any neighbor table has changed enough that we need
+ * to learn new neighbor entries. */
+bool neighbor_table_notify_run(void);
+void neighbor_table_notify_wait(void);
+
+/* Add a watch request to the hmap. The hmap should later be passed to
+ * neighbor_table_notify_update_watches*/
+void neighbor_table_add_watch_request(struct hmap *neighbor_table_watches,
+ int32_t if_index, const char *if_name);
+
+/* Cleanup all watch request in the provided hmap that where added using
+ * neighbor_table_add_watch_request. */
+void neighbor_table_watch_request_cleanup(
+ struct hmap *neighbor_table_watches);
+
+/* Updates the list of neighbor table watches that are currently active.
+ * hmap should contain struct neighbor_table_watch_request */
+void neighbor_table_notify_update_watches(
+ const struct hmap *neighbor_table_watches);
+
+/* Cleans up all neighbor table watches. */
+void neighbor_table_notify_destroy(void);
+
+#endif /* NEIGHBOR_TABLE_NOTIFY_H */
diff --git a/controller/neighbor.c b/controller/neighbor.c
index 8bfc2cf0d..150372fb2 100644
--- a/controller/neighbor.c
+++ b/controller/neighbor.c
@@ -24,12 +24,29 @@
static void neighbor_interface_monitor_destroy(
struct neighbor_interface_monitor *);
-uint32_t
+static 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));
}
+struct advertise_neighbor_entry *
+advertise_neigh_find(const struct hmap *neighbors, struct eth_addr mac,
+ const struct in6_addr *ip)
+{
+ uint32_t hash = advertise_neigh_hash(&mac, ip);
+
+ struct advertise_neighbor_entry *ne;
+ HMAP_FOR_EACH_WITH_HASH (ne, node, hash, neighbors) {
+ if (eth_addr_equals(ne->lladdr, mac) &&
+ ipv6_addr_equals(&ne->addr, ip)) {
+ return ne;
+ }
+ }
+
+ return NULL;
+}
+
void
neighbor_run(struct neighbor_ctx_in *n_ctx_in OVS_UNUSED,
struct neighbor_ctx_out *n_ctx_out OVS_UNUSED)
diff --git a/controller/neighbor.h b/controller/neighbor.h
index 3abc6e923..4cfb19c2b 100644
--- a/controller/neighbor.h
+++ b/controller/neighbor.h
@@ -64,8 +64,9 @@ struct advertise_neighbor_entry {
* all zero otherwise. */
};
-uint32_t advertise_neigh_hash(const struct eth_addr *,
- const struct in6_addr *);
+struct advertise_neighbor_entry *advertise_neigh_find(
+ const struct hmap *neighbors, struct eth_addr mac,
+ const struct in6_addr *ip);
void neighbor_run(struct neighbor_ctx_in *, struct neighbor_ctx_out *);
void neighbor_cleanup(struct vector *monitored_interfaces);
--
2.50.1
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev