From: Dumitru Ceara <dce...@redhat.com> When monitoring and updating (Linux) neighbor tables through Netlink, the interfaces in question are identified by if-index. It's more CMS-friendly to allow OVN to be configured to track interfaces by name.
In order to achieve that, we introduce a new host-if-monitor module which tracks the if-index <-> if-name mapping for relevant interfaces. NOTE: OVS has the lib/if-notifier.[ch] module already. However, that is not really useful for the OVN use case because it doesn't really pass the struct rtnetlink_change message to its registered callbacks. NOTE2: Interface name changes are not supported as RTM_DELLINK is not sent in that case. In order to change the host interface name users should first bring the interface down and then change its name. Signed-off-by: Dumitru Ceara <dce...@redhat.com> --- V3: - Add note about interface name change. - Remove not-relevant-anymore mappings in host_if_monitor_update_watches(). - Added tests. --- controller/automake.mk | 5 +- controller/host-if-monitor-stub.c | 43 ++++++++ controller/host-if-monitor.c | 161 ++++++++++++++++++++++++++++++ controller/host-if-monitor.h | 30 ++++++ controller/test-ovn-netlink.c | 36 +++++++ tests/automake.mk | 1 + tests/system-ovn-netlink.at | 24 +++++ 7 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 controller/host-if-monitor-stub.c create mode 100644 controller/host-if-monitor.c create mode 100644 controller/host-if-monitor.h diff --git a/controller/automake.mk b/controller/automake.mk index 6af6ee2a9..6eca5e8ed 100644 --- a/controller/automake.mk +++ b/controller/automake.mk @@ -64,10 +64,12 @@ controller_ovn_controller_SOURCES = \ controller/route.c \ controller/garp_rarp.h \ controller/garp_rarp.c \ - controller/neighbor-table-notify.h + controller/neighbor-table-notify.h \ + controller/host-if-monitor.h if HAVE_NETLINK controller_ovn_controller_SOURCES += \ + controller/host-if-monitor.c \ controller/neighbor-exchange-netlink.h \ controller/neighbor-exchange-netlink.c \ controller/neighbor-table-notify.c \ @@ -77,6 +79,7 @@ controller_ovn_controller_SOURCES += \ controller/route-table-notify.c else controller_ovn_controller_SOURCES += \ + controller/host-if-monitor-stub.c \ controller/neighbor-table-notify-stub.c \ controller/route-exchange-stub.c \ controller/route-table-notify-stub.c diff --git a/controller/host-if-monitor-stub.c b/controller/host-if-monitor-stub.c new file mode 100644 index 000000000..b174cf996 --- /dev/null +++ b/controller/host-if-monitor-stub.c @@ -0,0 +1,43 @@ +/* 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 "host-if-monitor.h" + +void +host_if_monitor_wait(void) +{ +} + +bool +host_if_monitor_run(void) +{ + return false; +} + +void +host_if_monitor_update_watches(const struct sset *if_names OVS_UNUSED) +{ +} + +int32_t +host_if_monitor_ifname_toindex(const char *if_name OVS_UNUSED) +{ + return 0; +} diff --git a/controller/host-if-monitor.c b/controller/host-if-monitor.c new file mode 100644 index 000000000..5a3c920b0 --- /dev/null +++ b/controller/host-if-monitor.c @@ -0,0 +1,161 @@ +/* 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 "lib/rtnetlink.h" +#include "lib/simap.h" +#include "openvswitch/vlog.h" + +#include "host-if-monitor.h" + +VLOG_DEFINE_THIS_MODULE(host_if_monitor); + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + +struct host_if_monitor { + struct nln_notifier *link_notifier; + + struct sset watched_interfaces; + struct simap ifname_to_ifindex; + + bool changes_detected; +}; + +static struct host_if_monitor monitor = (struct host_if_monitor) { + .link_notifier = NULL, + .watched_interfaces = SSET_INITIALIZER(&monitor.watched_interfaces), + .ifname_to_ifindex = SIMAP_INITIALIZER(&monitor.ifname_to_ifindex), + .changes_detected = false, +}; + +static void if_notifier_cb(const struct rtnetlink_change *, void *aux); + +void +host_if_monitor_wait(void) +{ + rtnetlink_wait(); +} + +bool +host_if_monitor_run(void) +{ + monitor.changes_detected = false; + + /* If any relevant interface if-index <-> if-name mapping changes are + * dected, monitor.changes_detected will be updated accordingly by the + * if_notifier_cb(). */ + rtnetlink_run(); + + return monitor.changes_detected; +} + +void +host_if_monitor_update_watches(const struct sset *if_names) +{ + struct sset new_if_names = SSET_INITIALIZER(&new_if_names); + const char *if_name; + + /* The notifier only triggers the callback on interface updates. + * For newly added ones we need to fetch the initial if_index ourselves. + */ + SSET_FOR_EACH (if_name, if_names) { + if (!sset_contains(&monitor.watched_interfaces, if_name)) { + sset_add(&new_if_names, if_name); + } + } + + if (!sset_equals(&monitor.watched_interfaces, if_names)) { + sset_destroy(&monitor.watched_interfaces); + sset_clone(&monitor.watched_interfaces, if_names); + + /* Remove mappings for if_names that are not tracked anymore. */ + struct simap_node *sn; + SIMAP_FOR_EACH_SAFE (sn, &monitor.ifname_to_ifindex) { + if (!sset_contains(&monitor.watched_interfaces, sn->name)) { + simap_delete(&monitor.ifname_to_ifindex, sn); + } + } + } + + if (!sset_is_empty(&monitor.watched_interfaces)) { + if (!monitor.link_notifier) { + VLOG_INFO_RL(&rl, "Enabling host interface monitor"); + monitor.link_notifier = + rtnetlink_notifier_create(if_notifier_cb, &monitor); + } + /* Get initial state for new interfaces. + * + * NOTE: it's important that we have the initial state (if-index) for + * newly watched interfaces because of two reasons: + * - we need to be able to reconcile and preserve still valid learned + * remote FDB entries and remote VTEPs + * - the if_notifier_cb is called only on updates of interfaces + * therefore if existing interfaces don't change the notifier + * callback is not called. + */ + SSET_FOR_EACH (if_name, &new_if_names) { + simap_put(&monitor.ifname_to_ifindex, if_name, + if_nametoindex(if_name)); + } + } else { + if (monitor.link_notifier) { + VLOG_INFO_RL(&rl, "Disabling host interface monitor"); + rtnetlink_notifier_destroy(monitor.link_notifier); + monitor.link_notifier = NULL; + } + } + + sset_destroy(&new_if_names); +} + +int32_t +host_if_monitor_ifname_toindex(const char *if_name) +{ + return simap_get(&monitor.ifname_to_ifindex, if_name); +} + +static void +if_notifier_cb(const struct rtnetlink_change *change, void *aux OVS_UNUSED) +{ + if (!change || change->irrelevant) { + return; + } + + /* NOTE: interface name changes are not supported as RTM_DELLINK is not + * sent in that case. In order to change the host interface name users + * should first bring the interface down and then change its name. */ + switch (change->nlmsg_type) { + case RTM_NEWLINK: + if ((change->ifi_flags & IFF_UP) + && sset_find(&monitor.watched_interfaces, change->ifname)) { + simap_put(&monitor.ifname_to_ifindex, + change->ifname, change->if_index); + monitor.changes_detected = true; + } + break; + case RTM_DELLINK: + if (sset_find(&monitor.watched_interfaces, change->ifname)) { + simap_find_and_delete(&monitor.ifname_to_ifindex, change->ifname); + monitor.changes_detected = true; + } + break; + default: + break; + } +} diff --git a/controller/host-if-monitor.h b/controller/host-if-monitor.h new file mode 100644 index 000000000..a41c5869c --- /dev/null +++ b/controller/host-if-monitor.h @@ -0,0 +1,30 @@ +/* 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 HOST_IF_MONITOR_H +#define HOST_IF_MONITOR_H 1 + +#include <stdint.h> +#include "lib/sset.h" +#include "lib/smap.h" + +void host_if_monitor_wait(void); +bool host_if_monitor_run(void); + +void host_if_monitor_update_watches(const struct sset *if_names); + +int32_t host_if_monitor_ifname_toindex(const char *if_name); + +#endif /* HOST_IF_MONITOR_H */ diff --git a/controller/test-ovn-netlink.c b/controller/test-ovn-netlink.c index a26e2d478..f5126ba52 100644 --- a/controller/test-ovn-netlink.c +++ b/controller/test-ovn-netlink.c @@ -20,6 +20,7 @@ #include "tests/ovstest.h" #include "tests/test-utils.h" +#include "host-if-monitor.h" #include "neighbor-exchange-netlink.h" #include "neighbor-table-notify.h" #include "neighbor.h" @@ -142,6 +143,40 @@ test_neighbor_table_notify(struct ovs_cmdl_context *ctx) neighbor_table_watch_request_cleanup(&table_watches); } +static void +test_host_if_monitor(struct ovs_cmdl_context *ctx) +{ + unsigned int shift = 1; + + const char *if_name = test_read_value(ctx, shift++, "if_name"); + if (!if_name) { + return; + } + + const char *cmd = test_read_value(ctx, shift++, "shell_command"); + if (!cmd) { + return; + } + + const char *notify = test_read_value(ctx, shift++, "should_notify"); + bool expect_notify = notify && !strcmp(notify, "true"); + + struct sset if_names = SSET_INITIALIZER(&if_names); + sset_add(&if_names, if_name); + host_if_monitor_update_watches(&if_names); + + host_if_monitor_run(); + host_if_monitor_wait(); + + int rc = system(cmd); + if (rc) { + exit(rc); + } + ovs_assert(host_if_monitor_run() == expect_notify); + printf("%"PRId32"\n", host_if_monitor_ifname_toindex(if_name)); + sset_destroy(&if_names); +} + static void test_ovn_netlink(int argc, char *argv[]) { @@ -150,6 +185,7 @@ test_ovn_netlink(int argc, char *argv[]) {"neighbor-sync", NULL, 2, INT_MAX, test_neighbor_sync, OVS_RO}, {"neighbor-table-notify", NULL, 3, 4, test_neighbor_table_notify, OVS_RO}, + {"host-if-monitor", NULL, 2, 3, test_host_if_monitor, OVS_RO}, {NULL, NULL, 0, 0, NULL, OVS_RO}, }; struct ovs_cmdl_context ctx; diff --git a/tests/automake.mk b/tests/automake.mk index ba6b6d7ba..04d90e659 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -318,6 +318,7 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ if HAVE_NETLINK tests_ovstest_LDADD += \ + controller/host-if-monitor.$(OBJEXT) \ controller/neighbor.$(OBJEXT) \ controller/neighbor-exchange-netlink.$(OBJEXT) \ controller/neighbor-table-notify.$(OBJEXT) \ diff --git a/tests/system-ovn-netlink.at b/tests/system-ovn-netlink.at index 068dfdc63..b732470ef 100644 --- a/tests/system-ovn-netlink.at +++ b/tests/system-ovn-netlink.at @@ -231,3 +231,27 @@ check ovstest test-ovn-netlink neighbor-table-notify br-test $br_if_index \ dev br-test-unused extern_learn' \ false AT_CLEANUP + +AT_SETUP([netlink - host-if-monitor]) +AT_KEYWORDS([netlink]) + +dnl Should notify if an interface whose name is monitored by OVN is added. +on_exit 'ip link del lo-test' +if_index=$(ovstest test-ovn-netlink host-if-monitor lo-test \ + 'ip link add lo-test type dummy && ip link set dev lo-test up' \ + true) +check test $? -eq 0 +AT_CHECK_UNQUOTED([netlink_if_index lo-test], [0], [dnl +$if_index +]) + +dnl Should NOT notify if an interface whose name is not monitored by OVN +dnl is added. +check ip link del lo-test +on_exit 'ip link del lo-test-unused' +AT_CHECK([ovstest test-ovn-netlink host-if-monitor lo-test \ + 'ip link add lo-test-unused type dummy && ip link set dev lo-test-unused up' \ + false], [0], [dnl +0 +]) +AT_CLEANUP -- 2.50.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev