From: Dumitru Ceara <dce...@redhat.com>

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 <dce...@redhat.com>
---
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
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to