On 6/8/26 10:54 AM, Adrian Moreno via dev wrote:
> Now that netdev-linux flags are also cached, we need to ensure they are
> updated correctly.
>
> Currently, there are subsystems that rely on link state changes via
> rtnetlink notifiers (e.g: iface-notifier, route-table). In parallel,
> netdev-linux creates its own netlink socket to listen for link
> notifications based on which netdev's states are updated. This creates a
> potential race: some subsystems might be notified that a link changed
> but the internal netdev representation is still old.
>
> If we want to both cache netdev internal state (e.g: flags, ether_addr)
> effectively we need to use the same callback chain, i.e: we need to use
> rtnetlink to update netdev-linux netdevs as well when links change.
>
> The order of callbacks cannot be guaranteed so it could still be possible to
> have another subsystem be notified before netdev-linux has been given a chance
> to update its state. However, subsystems would likely want to aggregate
> reconfigurations and will likely have their own "_run()" function to operate.
>
> Therefore, it could be reasonalbe to assume the other subsystems would use the
> callback to set a flag that is then used to trigger reconfigurations in their
> own periodic "_run()" functions (this is exactly the case of the two existing
> users: iface-notifier, route-table).
>
> This patch makes netdev-linux use rtnetlink notifier for interface updates and
> independent nln notifiers for address changes.
>
> Signed-off-by: Adrian Moreno <[email protected]>
> ---
> lib/netdev-linux.c | 213 ++++++++++++++++++++---------------------
> lib/netdev-linux.h | 1 +
> lib/netlink-notifier.h | 3 +-
> lib/rtnetlink.c | 12 ++-
> lib/rtnetlink.h | 1 +
> 5 files changed, 117 insertions(+), 113 deletions(-)
>
> diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
> index a7c0f5451..351dfb735 100644
> --- a/lib/netdev-linux.c
> +++ b/lib/netdev-linux.c
> @@ -298,6 +298,10 @@ static struct ovs_mutex lag_mutex =
> OVS_MUTEX_INITIALIZER;
> static struct shash lag_shash OVS_GUARDED_BY(lag_mutex)
> = SHASH_INITIALIZER(&lag_shash);
>
> +static struct rtnetlink_change netlink_change;
> +static struct nln *nln = NULL;
> +static struct nln_notifier *notifiers[4] = {NULL, NULL, NULL, NULL};
> +
> /* Traffic control. */
>
> /* An instance of a traffic control class. Always associated with a
> particular
> @@ -651,40 +655,6 @@ static void netdev_linux_changed(struct netdev_linux
> *netdev,
> unsigned int mask)
> OVS_REQUIRES(netdev->mutex);
>
> -/* Returns a NETLINK_ROUTE socket listening for RTNLGRP_LINK,
> - * RTNLGRP_IPV4_IFADDR and RTNLGRP_IPV6_IFADDR changes, or NULL
> - * if no such socket could be created. */
> -static struct nl_sock *
> -netdev_linux_notify_sock(void)
> -{
> - static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
> - static struct nl_sock *sock;
> - unsigned int mcgroups[] = {RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR,
> - RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_IFINFO};
> -
> - if (ovsthread_once_start(&once)) {
> - int error;
> -
> - error = nl_sock_create(NETLINK_ROUTE, &sock);
> - if (!error) {
> - size_t i;
> -
> - nl_sock_listen_all_nsid(sock, true);
> - for (i = 0; i < ARRAY_SIZE(mcgroups); i++) {
> - error = nl_sock_join_mcgroup(sock, mcgroups[i]);
> - if (error) {
> - nl_sock_destroy(sock);
> - sock = NULL;
> - break;
> - }
> - }
> - }
> - ovsthread_once_done(&once);
> - }
> -
> - return sock;
> -}
> -
> static bool
> netdev_linux_miimon_enabled(void)
> {
> @@ -702,7 +672,7 @@ netdev_linux_kind_is_lag(const char *kind)
> }
>
> static void
> -netdev_linux_update_lag(struct rtnetlink_change *change)
> +netdev_linux_update_lag(const struct rtnetlink_change *change)
> OVS_REQUIRES(lag_mutex)
> {
> struct linux_lag_member *lag;
> @@ -766,101 +736,127 @@ netdev_linux_update_lag(struct rtnetlink_change
> *change)
> }
> }
>
> -void
> -netdev_linux_run(const struct netdev_class *netdev_class OVS_UNUSED)
> +static void
> +netdev_linux_rtnetlink_update(const struct rtnetlink_change *change,
> + void *aux OVS_UNUSED)
> {
> - struct nl_sock *sock;
> - int error;
> + struct netdev *netdev_ = NULL;
>
> - if (netdev_linux_miimon_enabled()) {
> - netdev_linux_miimon_run();
> - }
> + if (change && change->irrelevant) {
> + return;
> + } else if (!change) {
> + struct shash device_shash;
> + struct shash_node *node;
>
> - sock = netdev_linux_notify_sock();
> - if (!sock) {
> + shash_init(&device_shash);
> + netdev_get_devices(&netdev_linux_class, &device_shash);
> + SHASH_FOR_EACH (node, &device_shash) {
> + struct netdev_linux *netdev = netdev_linux_cast(netdev_);
> + netdev_ = node->data;
It feels like we'll crash somewhere in this loop, as netdev_ is NULL
at the time of casting.
Is there any reasonable way to test this code path?
> + int error;
> +
> + ovs_mutex_lock(&netdev->mutex);
> + error = netdev_linux_update_via_ioctl(netdev);
> + netdev_linux_changed(netdev, error ? 0 : VALID_FLAGS);
> + ovs_mutex_unlock(&netdev->mutex);
> +
> + netdev_close(netdev_);
> + }
> + shash_destroy(&device_shash);
> return;
> }
>
> - do {
> - uint64_t buf_stub[4096 / 8];
> - int nsid;
> - struct ofpbuf buf;
> + if (change->ifname) {
> + netdev_ = netdev_from_name(change->ifname);
> + }
>
> - ofpbuf_use_stub(&buf, buf_stub, sizeof buf_stub);
> - error = nl_sock_recv(sock, &buf, &nsid, false);
> - if (!error) {
> - struct rtnetlink_change change;
> + if (netdev_ && is_netdev_linux_class(netdev_->netdev_class)) {
> + struct netdev_linux *netdev = netdev_linux_cast(netdev_);
>
> - if (rtnetlink_parse(&buf, &change) && !change.irrelevant) {
> - struct netdev *netdev_ = NULL;
> - char dev_name[IFNAMSIZ];
> + ovs_mutex_lock(&netdev->mutex);
> + netdev_linux_update(netdev, change->nsid, change);
> + ovs_mutex_unlock(&netdev->mutex);
> + }
>
> - if (!change.ifname) {
> - change.ifname = if_indextoname(change.if_index,
> dev_name);
> - }
> + if (change->ifname &&
> + rtnetlink_type_is_rtnlgrp_link(change->nlmsg_type)) {
>
> - if (change.ifname) {
> - netdev_ = netdev_from_name(change.ifname);
> - }
> - if (netdev_ && is_netdev_linux_class(netdev_->netdev_class))
> {
> - struct netdev_linux *netdev = netdev_linux_cast(netdev_);
> + /* Need to try updating the LAG information. */
> + ovs_mutex_lock(&lag_mutex);
> + netdev_linux_update_lag(change);
> + ovs_mutex_unlock(&lag_mutex);
> + }
> + netdev_close(netdev_);
> +}
>
> - ovs_mutex_lock(&netdev->mutex);
> - netdev_linux_update(netdev, nsid, &change);
> - ovs_mutex_unlock(&netdev->mutex);
> - }
> +static void
> +netdev_linux_nln_cb(const void *change, void *aux)
> +{
> + netdev_linux_rtnetlink_update(change, aux);
> +}
>
> - if (change.ifname &&
> - rtnetlink_type_is_rtnlgrp_link(change.nlmsg_type)) {
> +static void
> +netdev_linux_rtnetlink_config(void)
> +{
> + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
>
> - /* Need to try updating the LAG information. */
> - ovs_mutex_lock(&lag_mutex);
> - netdev_linux_update_lag(&change);
> - ovs_mutex_unlock(&lag_mutex);
> - }
> - netdev_close(netdev_);
> - }
> - } else if (error == ENOBUFS) {
> - struct shash device_shash;
> - struct shash_node *node;
> + if (ovsthread_once_start(&once)) {
> + unsigned int extra_mcgroups[] = {RTNLGRP_IPV4_IFADDR,
> + RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_IFINFO};
> + struct nln_notifier *notifier;
> + int i;
>
> - nl_sock_drain(sock);
> + ovs_assert(ARRAY_SIZE(notifiers) == ARRAY_SIZE(extra_mcgroups) + 1);
>
> - shash_init(&device_shash);
> - netdev_get_devices(&netdev_linux_class, &device_shash);
> - SHASH_FOR_EACH (node, &device_shash) {
> - struct netdev *netdev_ = node->data;
> - struct netdev_linux *netdev = netdev_linux_cast(netdev_);
> -
> - ovs_mutex_lock(&netdev->mutex);
> - netdev_linux_update_via_ioctl(netdev);
> - netdev_linux_changed(netdev, VALID_FLAGS);
> - ovs_mutex_unlock(&netdev->mutex);
> -
> - netdev_close(netdev_);
> - }
> - shash_destroy(&device_shash);
> - } else if (error != EAGAIN) {
> - static struct vlog_rate_limit rll = VLOG_RATE_LIMIT_INIT(1, 5);
> - VLOG_WARN_RL(&rll, "error reading or parsing netlink (%s)",
> - ovs_strerror(error));
> + nln = nln_create(NETLINK_ROUTE, true, rtnetlink_parse_cb,
> + &netlink_change);
> + if (!nln) {
> + VLOG_ERR("failed to create nln notifier");
> }
> - ofpbuf_uninit(&buf);
> - } while (!error);
> +
> + for (i = 0; i < ARRAY_SIZE(extra_mcgroups); i++) {
> + notifier = nln_notifier_create(nln, extra_mcgroups[i],
> + netdev_linux_nln_cb, NULL);
> + if (!notifier) {
> + VLOG_ERR("failed to create nln notifier for mcgroup %d",
> + extra_mcgroups[i]);
> + }
> + notifiers[i] = notifier;
> + }
> +
> + notifier = rtnetlink_notifier_create(netdev_linux_rtnetlink_update,
> + NULL);
> + if (!notifier) {
> + VLOG_ERR("failed to create rtnetlink notifier");
> + }
> + notifiers[i] = notifier;
> +
> + ovsthread_once_done(&once);
> + }
> +}
> +
> +void
> +netdev_linux_run(const struct netdev_class *netdev_class OVS_UNUSED)
> +{
> + if (netdev_linux_miimon_enabled()) {
> + netdev_linux_miimon_run();
> + }
> + rtnetlink_run();
> + if (nln) {
> + nln_run(nln);
> + }
> }
>
> static void
> netdev_linux_wait(const struct netdev_class *netdev_class OVS_UNUSED)
> {
> - struct nl_sock *sock;
> -
> + rtnetlink_wait();
> + if (nln) {
> + nln_wait(nln);
> + }
> if (netdev_linux_miimon_enabled()) {
> netdev_linux_miimon_wait();
> }
> - sock = netdev_linux_notify_sock();
> - if (sock) {
> - nl_sock_wait(sock, POLLIN);
> - }
> }
>
> static void
> @@ -898,9 +894,6 @@ netdev_linux_update__(struct netdev_linux *dev,
> dev->etheraddr = change->mac;
> dev->cache_valid |= VALID_ETHERADDR;
> dev->ether_addr_error = 0;
> -
> - /* The mac addr has been changed, report it now. */
> - rtnetlink_report_link();
> }
>
> if (change->primary &&
> netdev_linux_kind_is_lag(change->primary)) {
> @@ -967,6 +960,8 @@ netdev_linux_common_construct(struct netdev *netdev_)
> return ENAMETOOLONG;
> }
>
> + netdev_linux_rtnetlink_config();
> +
> /* The device could be in the same network namespace or in another one.
> */
> netnsid_unset(&netdev->netnsid);
> ovs_mutex_init(&netdev->mutex);
> diff --git a/lib/netdev-linux.h b/lib/netdev-linux.h
> index ec19b0ded..81a5634f6 100644
> --- a/lib/netdev-linux.h
> +++ b/lib/netdev-linux.h
> @@ -25,6 +25,7 @@
> * Linux-specific code. */
>
> struct netdev;
> +struct rtnetlink_change;
>
> int netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
> const char *flag_name, bool enable);
> diff --git a/lib/netlink-notifier.h b/lib/netlink-notifier.h
> index 8b6c32048..d52e5f56c 100644
> --- a/lib/netlink-notifier.h
> +++ b/lib/netlink-notifier.h
> @@ -1,5 +1,4 @@
> -/*
> - * Copyright (c) 2009 Nicira, Inc.
> +/* Copyright (c) 2009 Nicira, Inc.
> *
> * Licensed under the Apache License, Version 2.0 (the "License");
> * you may not use this file except in compliance with the License.
Unrelated change.
> diff --git a/lib/rtnetlink.c b/lib/rtnetlink.c
> index 6b63afa06..4ae050ff0 100644
> --- a/lib/rtnetlink.c
> +++ b/lib/rtnetlink.c
> @@ -191,7 +191,7 @@ rtnetlink_parse(struct ofpbuf *buf, struct
> rtnetlink_change *change)
> }
>
> /* Return RTNLGRP_LINK on success, 0 on parse error. */
> -static int
> +int
> rtnetlink_parse_cb(struct ofpbuf *buf, int nsid, void *change)
> {
> bool ret = rtnetlink_parse(buf, change);
> @@ -212,12 +212,20 @@ rtnetlink_parse_cb(struct ofpbuf *buf, int nsid, void
> *change)
> *
> * xxx Joins more multicast groups when needed.
> *
> + * Callbacks might find that netdev-linux netdevs still hold outdated cached
> + * information. If the notification has to trigger some kind of
> reconfiguration
> + * that requires up-to-date netdev cache, it should do it asynchronously, for
> + * instance by setting a flag in the callback and acting on it during the
> + * normal "*_run()" operation.
> + *
> + * Notifications might come from any network namespace.
> + *
> * Returns an initialized nln_notifier if successful, NULL otherwise. */
> struct nln_notifier *
> rtnetlink_notifier_create(rtnetlink_notify_func *cb, void *aux)
> {
> if (!nln) {
> - nln = nln_create(NETLINK_ROUTE, false, rtnetlink_parse_cb,
> + nln = nln_create(NETLINK_ROUTE, true, rtnetlink_parse_cb,
> &rtn_change);
> }
>
> diff --git a/lib/rtnetlink.h b/lib/rtnetlink.h
> index 9b2397b4e..f0b49bf1a 100644
> --- a/lib/rtnetlink.h
> +++ b/lib/rtnetlink.h
> @@ -76,4 +76,5 @@ void rtnetlink_notifier_destroy(struct nln_notifier *);
> void rtnetlink_run(void);
> void rtnetlink_wait(void);
> void rtnetlink_report_link(void);
> +int rtnetlink_parse_cb(struct ofpbuf *buf, int nsid, void *change);
nit: 'buf' is not needed.
> #endif /* rtnetlink.h */
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev