summary refs log tree commit diff
path: root/src/libnm-platform/nm-linux-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-02-22 17:21:11 +0100
committerMichael Biebl <biebl@debian.org>2024-02-22 17:21:11 +0100
commitbba2e4b4de668db525cbfdfc35292e5a0b51671a (patch)
tree38d20cddfcc6f71572b9e169deefab5fa96e8d0c /src/libnm-platform/nm-linux-platform.c
parent6681f77b757bbc42ce5c8868ee9142b7ebc8c059 (diff)
New upstream version 1.46.0 upstream/1.46.0
Diffstat (limited to 'src/libnm-platform/nm-linux-platform.c')
-rw-r--r--src/libnm-platform/nm-linux-platform.c440
1 files changed, 347 insertions, 93 deletions
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index a7078280..9ecac2d9 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -41,6 +41,7 @@
 #include "libnm-platform/nm-netlink.h"
 #include "libnm-platform/nm-platform-utils.h"
 #include "libnm-platform/nmp-netns.h"
+#include "libnm-platform/devlink/nm-devlink.h"
 #include "libnm-platform/wifi/nm-wifi-utils-wext.h"
 #include "libnm-platform/wifi/nm-wifi-utils.h"
 #include "libnm-platform/wpan/nm-wpan-utils.h"
@@ -8881,141 +8882,394 @@ nla_put_failure:
     g_return_val_if_reached(FALSE);
 }
 
+static gint64
+sriov_read_sysctl_uint(NMPlatform *platform,
+                       int         dirfd,
+                       const char *ifname,
+                       const char *dev_file,
+                       GError    **error)
+{
+    const char *path;
+    gint64      val;
+
+    nm_assert(NM_STRLEN("device/%s") + strlen(dev_file));
+
+    path = nm_sprintf_bufa(256, "device/%s", dev_file);
+    val  = nm_platform_sysctl_get_int_checked(platform,
+                                             NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname, path),
+                                             10,
+                                             0,
+                                             G_MAXUINT,
+                                             -1);
+
+    if (val < 0) {
+        g_set_error(error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "couldn't read %s: %s",
+                    dev_file,
+                    nm_strerror_native(errno));
+        return -errno;
+    }
+
+    return val;
+}
+
+static gboolean
+sriov_set_autoprobe(NMPlatform  *platform,
+                    int          dirfd,
+                    const char  *ifname,
+                    NMOptionBool autoprobe,
+                    GError     **error)
+{
+    int current_autoprobe =
+        (int) sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_drivers_autoprobe", error);
+
+    if (current_autoprobe == -ENOENT) {
+        /* older kernel versions don't have this sysctl. Assume the value is "1". */
+        current_autoprobe = 1;
+        g_clear_error(error);
+    }
+
+    if (current_autoprobe < 0)
+        return FALSE;
+
+    if (autoprobe != NM_OPTION_BOOL_DEFAULT && current_autoprobe != autoprobe) {
+        if (!nm_platform_sysctl_set(
+                platform,
+                NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"),
+                autoprobe == 1 ? "1" : "0")) {
+            g_set_error(error,
+                        NM_UTILS_ERROR,
+                        NM_UTILS_ERROR_UNKNOWN,
+                        "couldn't set SR-IOV drivers-autoprobe to %d: %s",
+                        (int) autoprobe,
+                        nm_strerror_native(errno));
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+#define _SRIOV_ASYNC_MAX_STEPS 4
+
+typedef struct _SriovAsyncState {
+    NMPlatform           *platform;
+    int                   ifindex;
+    NMPlatformSriovParams sriov_params;
+    void (*steps[_SRIOV_ASYNC_MAX_STEPS])(struct _SriovAsyncState *);
+    int                     current_step;
+    NMPlatformAsyncCallback callback;
+    gpointer                data;
+    GCancellable           *cancellable;
+} SriovAsyncState;
+
 static void
-sriov_idle_cb(gpointer user_data, GCancellable *cancellable)
+sriov_async_invoke_callback(gpointer user_data, GCancellable *cancellable)
 {
-    gs_unref_object NMPlatform *platform        = NULL;
-    gs_free_error GError       *cancelled_error = NULL;
-    gs_free_error GError       *error           = NULL;
-    NMPlatformAsyncCallback     callback;
-    gpointer                    callback_data;
+    gs_free_error GError   *cancelled_error = NULL;
+    gs_free_error GError   *error           = NULL;
+    NMPlatformAsyncCallback callback;
+    gpointer                callback_data;
 
     g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error);
-    nm_utils_user_data_unpack(user_data, &platform, &error, &callback, &callback_data);
+    nm_utils_user_data_unpack(user_data, &error, &callback, &callback_data);
     callback(cancelled_error ?: error, callback_data);
 }
 
 static void
+sriov_async_finish_err(SriovAsyncState *async_state, GError *error)
+{
+    NMPlatform *platform = async_state->platform;
+
+    _LOGD("finished configuring SR-IOV, error: %s", error ? error->message : "none");
+
+    if (async_state->callback) {
+        /* nm_platform_link_set_sriov_params() promises to always call the callback,
+         * and always asynchronously. We might have reached here without doing
+         * any asynchronous task, so invoke the user's callback in the idle task
+         * to make it asynchronous. Actually, let's make it simple and do it
+         * always in this way, even if asynchronous tasks were made.
+         */
+        gpointer packed = nm_utils_user_data_pack(g_steal_pointer(&error),
+                                                  async_state->callback,
+                                                  async_state->data);
+        nm_utils_invoke_on_idle(async_state->cancellable, sriov_async_invoke_callback, packed);
+    }
+
+    g_object_unref(async_state->platform);
+    g_object_unref(async_state->cancellable);
+    g_free(async_state);
+    g_free(error);
+}
+
+static void
+sriov_async_call_next_step(SriovAsyncState *async_state)
+{
+    if (g_cancellable_is_cancelled(async_state->cancellable)) {
+        sriov_async_finish_err(async_state, NULL); /* The error will be set later */
+        return;
+    }
+
+    async_state->current_step++;
+
+    nm_assert(async_state->current_step >= 0);
+    nm_assert(async_state->current_step < _SRIOV_ASYNC_MAX_STEPS);
+    nm_assert(async_state->steps[async_state->current_step] != NULL);
+
+    async_state->steps[async_state->current_step](async_state);
+}
+
+static void
+sriov_async_sysctl_done_cb(GError *error, gpointer data)
+{
+    SriovAsyncState *async_state = data;
+
+    if (error)
+        sriov_async_finish_err(async_state, g_error_copy(error));
+    else
+        sriov_async_call_next_step(async_state);
+}
+
+static void
+sriov_async_set_num_vfs(SriovAsyncState *async_state, const char *val)
+{
+    NMPlatform           *platform = async_state->platform;
+    const char           *values[] = {val, NULL};
+    nm_auto_close int     dirfd    = -1;
+    char                  ifname[IFNAMSIZ];
+    gs_free_error GError *error = NULL;
+
+    dirfd = nm_platform_sysctl_open_netdir(platform, async_state->ifindex, ifname);
+    if (!dirfd) {
+        g_set_error(&error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "couldn't open netdir for device with ifindex %d",
+                    async_state->ifindex);
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
+    }
+
+    sysctl_set_async(platform,
+                     NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
+                     values,
+                     sriov_async_sysctl_done_cb,
+                     async_state,
+                     async_state->cancellable);
+}
+
+static void
+sriov_async_step1_destroy_vfs(SriovAsyncState *async_state)
+{
+    NMPlatform *platform = async_state->platform;
+
+    _LOGD("destroying VFs before configuring SR-IOV");
+
+    sriov_async_set_num_vfs(async_state, "0");
+}
+
+static void
+sriov_async_step2_set_eswitch_mode(SriovAsyncState *async_state)
+{
+    NMPlatform             *platform       = async_state->platform;
+    NMLinuxPlatformPrivate *priv           = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    gs_free NMDevlink      *devlink        = NULL;
+    gs_free_error GError   *error          = NULL;
+    NMDevlinkEswitchParams  eswitch_params = {
+         .mode        = async_state->sriov_params.eswitch_mode,
+         .inline_mode = async_state->sriov_params.eswitch_inline_mode,
+         .encap_mode  = async_state->sriov_params.eswitch_encap_mode,
+    };
+
+    _LOGD("setting eswitch params (mode=%d, inline-mode=%d, encap-mode=%d)",
+          (int) eswitch_params.mode,
+          (int) eswitch_params.inline_mode,
+          (int) eswitch_params.encap_mode);
+
+    /* We set eswitch mode as a sriov_async step because it's in the middle of
+     * other steps that are async. However, this step itself is synchronous. */
+    devlink = nm_devlink_new(platform, priv->sk_genl_sync, async_state->ifindex);
+    if (!nm_devlink_set_eswitch_params(devlink, eswitch_params, &error)) {
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
+    }
+
+    sriov_async_call_next_step(async_state);
+}
+
+static void
+sriov_async_step3_create_vfs(SriovAsyncState *async_state)
+{
+    NMPlatform *platform = async_state->platform;
+    const char *val      = nm_sprintf_bufa(32, "%u", async_state->sriov_params.num_vfs);
+
+    _LOGD("setting sriov_numvfs to %u", async_state->sriov_params.num_vfs);
+
+    sriov_async_set_num_vfs(async_state, val);
+}
+
+static void
+sriov_async_step_finish_ok(SriovAsyncState *async_state)
+{
+    sriov_async_finish_err(async_state, NULL);
+}
+
+static int
+sriov_eswitch_get_needs_change(SriovAsyncState *async_state,
+                               gboolean        *out_needs_change,
+                               GError         **error)
+{
+    NMPlatform               *platform    = async_state->platform;
+    NMLinuxPlatformPrivate   *priv        = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    _NMSriovEswitchMode       mode        = async_state->sriov_params.eswitch_mode;
+    _NMSriovEswitchInlineMode inline_mode = async_state->sriov_params.eswitch_inline_mode;
+    _NMSriovEswitchEncapMode  encap_mode  = async_state->sriov_params.eswitch_encap_mode;
+    NMDevlinkEswitchParams    current_params;
+    gs_free NMDevlink        *devlink = NULL;
+
+    nm_assert(out_needs_change);
+
+    if (mode == _NM_SRIOV_ESWITCH_MODE_PRESERVE
+        && inline_mode == _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE
+        && encap_mode == _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE) {
+        *out_needs_change = FALSE;
+        return 0;
+    }
+
+    devlink = nm_devlink_new(platform, priv->sk_genl_sync, async_state->ifindex);
+
+    if (!nm_devlink_get_eswitch_params(devlink, &current_params, error))
+        return -1;
+
+    *out_needs_change = (mode != _NM_SRIOV_ESWITCH_MODE_PRESERVE && mode != current_params.mode)
+                        || (inline_mode != _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE
+                            && inline_mode != current_params.inline_mode)
+                        || (encap_mode != _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE
+                            && encap_mode != current_params.encap_mode);
+    return 0;
+}
+
+/*
+ * Take special care when setting new values:
+ *  - don't touch anything if the right values are already set
+ *  - to change the number of VFs, eswitch mode or autoprobe we need to destroy existing VFs
+ *  - the autoprobe setting is irrelevant when numvfs is zero
+ */
+static void
 link_set_sriov_params_async(NMPlatform             *platform,
                             int                     ifindex,
-                            guint                   num_vfs,
-                            NMOptionBool            autoprobe,
+                            NMPlatformSriovParams   sriov_params,
                             NMPlatformAsyncCallback callback,
                             gpointer                data,
                             GCancellable           *cancellable)
 {
+    SriovAsyncState            *async_state;
     nm_auto_pop_netns NMPNetns *netns = NULL;
     gs_free_error GError       *error = NULL;
     nm_auto_close int           dirfd = -1;
-    int                         current_autoprobe;
-    guint                       i, total;
-    gint64                      current_num;
     char                        ifname[IFNAMSIZ];
-    gpointer                    packed;
-    const char                 *values[3];
-    char                        buf[64];
+    int                         max_vfs;
+    int                         current_num_vfs;
+    gboolean                    need_change_eswitch_params;
+    gboolean                    need_change_vfs;
+    gboolean                    need_destroy_vfs;
+    gboolean                    need_create_vfs;
+    int                         i;
 
     g_return_if_fail(callback || !data);
     g_return_if_fail(cancellable);
 
+    async_state               = g_new0(SriovAsyncState, 1);
+    async_state->platform     = g_object_ref(platform);
+    async_state->ifindex      = ifindex;
+    async_state->sriov_params = sriov_params;
+    async_state->current_step = -1;
+    async_state->callback     = callback;
+    async_state->data         = data;
+    async_state->cancellable  = g_object_ref(cancellable);
+
     if (!nm_platform_netns_push(platform, &netns)) {
         g_set_error_literal(&error,
                             NM_UTILS_ERROR,
                             NM_UTILS_ERROR_UNKNOWN,
-                            "couldn't change namespace");
-        goto out_idle;
+                            "couldn't change network namespace");
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
     }
 
     dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname);
     if (!dirfd) {
-        g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "couldn't open netdir");
-        goto out_idle;
+        g_set_error(&error,
+                    NM_UTILS_ERROR,
+                    NM_UTILS_ERROR_UNKNOWN,
+                    "couldn't open netdir for device with ifindex %d",
+                    ifindex);
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
     }
 
-    total = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_totalvfs"),
-        10,
-        0,
-        G_MAXUINT,
-        0);
-    if (!errno && num_vfs > total) {
-        _LOGW("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
-        num_vfs = total;
+    current_num_vfs = sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_numvfs", &error);
+    if (current_num_vfs < 0) {
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
     }
 
-    /*
-     * Take special care when setting new values:
-     *  - don't touch anything if the right values are already set
-     *  - to change the number of VFs or autoprobe we need to destroy existing VFs
-     *  - the autoprobe setting is irrelevant when numvfs is zero
-     */
-    current_num = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
-        10,
-        0,
-        G_MAXUINT,
-        -1);
-    current_autoprobe = nm_platform_sysctl_get_int_checked(
-        platform,
-        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"),
-        10,
-        0,
-        1,
-        -1);
-
-    if (current_autoprobe == -1 && errno == ENOENT) {
-        /* older kernel versions don't have this sysctl. Assume the value is
-         * "1". */
-        current_autoprobe = 1;
+    max_vfs = sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_totalvfs", &error);
+    if (max_vfs < 0) {
+        _LOGD("link: can't read max VFs (%s)", error->message);
+        g_clear_error(&error);
+        max_vfs = sriov_params.num_vfs; /* Try to create all */
     }
 
-    if (current_num == num_vfs
-        && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe))
-        goto out_idle;
+    if (sriov_params.num_vfs > max_vfs) {
+        _LOGW("link: device %d only supports %u VFs (requested %u)",
+              ifindex,
+              max_vfs,
+              sriov_params.num_vfs);
+        _LOGW("link: reducing num_vfs to %u for device %d", max_vfs, ifindex);
+        sriov_params.num_vfs              = max_vfs;
+        async_state->sriov_params.num_vfs = max_vfs;
+    }
 
-    if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE)
-        && current_autoprobe != autoprobe
-        && !nm_platform_sysctl_set(
-            platform,
-            NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"),
-            nm_sprintf_buf(buf, "%d", (int) autoprobe))) {
-        g_set_error(&error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "couldn't set SR-IOV drivers-autoprobe to %d: %s",
-                    (int) autoprobe,
-                    nm_strerror_native(errno));
-        goto out_idle;
+    /* Setting autoprobe goes first, we can do it synchronously */
+    if (sriov_params.num_vfs > 0
+        && !sriov_set_autoprobe(platform, dirfd, ifname, sriov_params.autoprobe, &error)) {
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
     }
 
-    if (current_num == 0 && num_vfs == 0)
-        goto out_idle;
+    /* Decide what actions we must do. Note that we might need to destroy the VFs even
+     * if num_vfs == current_num_vfs, for example to change the eswitch mode. Because of
+     * that, we might need to create VFs even if num_vfs == current_num_vfs.
+     * Steps in order (unnecessary steps are skipped):
+     *   1. Destroy VFs
+     *   2. Set eswitch mode
+     *   3. Create VFs
+     *   4. Invoke caller's callback
+     */
+    if (sriov_eswitch_get_needs_change(async_state, &need_change_eswitch_params, &error) < 0) {
+        sriov_async_finish_err(async_state, g_steal_pointer(&error));
+        return;
+    }
+    need_change_vfs  = sriov_params.num_vfs != current_num_vfs;
+    need_destroy_vfs = current_num_vfs > 0 && (need_change_eswitch_params || need_change_vfs);
+    need_create_vfs  = (current_num_vfs == 0 || need_destroy_vfs) && sriov_params.num_vfs > 0;
 
     i = 0;
-    if (current_num != 0)
-        values[i++] = "0";
-    if (num_vfs != 0)
-        values[i++] = nm_sprintf_bufa(32, "%u", num_vfs);
-    values[i++] = NULL;
+    if (need_destroy_vfs)
+        async_state->steps[i++] = sriov_async_step1_destroy_vfs;
+    if (need_change_eswitch_params)
+        async_state->steps[i++] = sriov_async_step2_set_eswitch_mode;
+    if (need_create_vfs)
+        async_state->steps[i++] = sriov_async_step3_create_vfs;
 
-    sysctl_set_async(platform,
-                     NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
-                     values,
-                     callback,
-                     data,
-                     cancellable);
-    return;
-
-out_idle:
-    if (callback) {
-        packed = nm_utils_user_data_pack(g_object_ref(platform),
-                                         g_steal_pointer(&error),
-                                         callback,
-                                         data);
-        nm_utils_invoke_on_idle(cancellable, sriov_idle_cb, packed);
-    }
+    nm_assert(i < _SRIOV_ASYNC_MAX_STEPS);
+
+    async_state->steps[i] = sriov_async_step_finish_ok;
+
+    sriov_async_call_next_step(async_state);
 }
 
 static gboolean