From 6af2fb351c9530699358de1f3726edc144169679 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Fri, 8 Mar 2024 19:29:36 +0100 Subject: [PATCH] core, libnm: expose the reason for unmanaged devices A common source for doubts and questions from users is about why devices are unmanaged. Unfortunately NM doesn't expose that information properly via D-Bus and so it's not available in nmcli. The device D-Bus object has two properties that are strictly related: "state" and "state-reason". The latter represents the reason for the current state. Introduce new reasons to indicate the possible causes for the unmanaged state. Note that a device can be unmanaged because of multiple reasons at the same time, we only return one. Before: $ nmcli -f GENERAL.DEVICE,GENERAL.TYPE,GENERAL.STATE,GENERAL.reason device show GENERAL.DEVICE: enp7s0 GENERAL.TYPE: ethernet GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 0 (No reason given) GENERAL.DEVICE: tun0 GENERAL.TYPE: tun GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 0 (No reason given) GENERAL.DEVICE: hwsim0 GENERAL.TYPE: unknown GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 0 (No reason given) After: $ nmcli -f GENERAL.DEVICE,GENERAL.TYPE,GENERAL.STATE,GENERAL.reason device show GENERAL.DEVICE: enp7s0 GENERAL.TYPE: ethernet GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 76 (The device is unmanaged by user decision via settings plugin ("unmanaged-devices" for keyfile or "NM_CONTROLLED=no" for ifcfg-rh)) GENERAL.DEVICE: tun0 GENERAL.TYPE: tun GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 75 (The device is unmanaged by explicit user decision (e.g. 'nmcli device set $DEV managed no') GENERAL.DEVICE: hwsim0 GENERAL.TYPE: unknown GENERAL.STATE: 10 (unmanaged) GENERAL.REASON: 69 (The device is unmanaged because the device type is unmanaged by default) https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1887 --- NEWS | 3 ++ src/core/devices/nm-device-utils.c | 16 +++++- src/core/devices/nm-device.c | 61 ++++++++++++++++++++--- src/libnm-core-public/nm-dbus-interface.h | 28 +++++++++++ src/libnmc-base/nm-client-utils.c | 29 ++++++++++- 5 files changed, 128 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index b97c26e0b7..9382f95c59 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Support changing the OpenSSL ciphers for 802.1X authentication via connection property "802-1x.openssl-ciphers". +* The reason why a device is unmanaged is now properly set in the + "StateReason" property of the "Device" D-Bus object. The property is + visible in nmcli via "nmcli -f all device show $DEV". ============================================= NetworkManager-1.46 diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c index ed0a27382a..80909805c7 100644 --- a/src/core/devices/nm-device-utils.c +++ b/src/core/devices/nm-device-utils.c @@ -128,8 +128,20 @@ NM_UTILS_LOOKUP_STR_DEFINE( NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED, "sriov-configuration-failed"), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), - NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED, - "device-handler-failed"), ); + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED, "device-handler-failed"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_BY_DEFAULT, "unmanaged-by-default"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN, + "unmanaged-external-down"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT, + "unmanaged-link-not-init"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING, "unmanaged-quitting"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING, "unmanaged-sleeping"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF, "unmanaged-user-conf"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT, + "unmanaged-user-explicit"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS, + "unmanaged-user-settings"), + NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV, "unmanaged-user-udev"), ); NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_string, NMDeviceMtuSource, diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index aaba79c02e..7f26bc4156 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -15008,8 +15008,39 @@ _unmanaged_flags2str(NMUnmanagedFlags flags, NMUnmanagedFlags mask, char *buf, g return buf; } +static NMDeviceStateReason +unmanaged_flags_to_reason(NMUnmanagedFlags flags) +{ + /* Even if there are multiple flags, we can only return one reason. + * Return the most important reason. + */ + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_SLEEPING)) + return NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_QUITTING)) + return NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_SETTINGS)) + return NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_PLATFORM_INIT)) + return NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_UDEV)) + return NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_EXPLICIT)) + return NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_CONF)) + return NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_BY_DEFAULT)) + return NM_DEVICE_STATE_REASON_UNMANAGED_BY_DEFAULT; + if (NM_FLAGS_HAS(flags, NM_UNMANAGED_EXTERNAL_DOWN)) + return NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN; + + return NM_DEVICE_STATE_REASON_NOW_UNMANAGED; +} + static gboolean -_get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean for_user_request) +_get_managed_by_flags(NMUnmanagedFlags flags, + NMUnmanagedFlags mask, + gboolean for_user_request, + NMDeviceStateReason *unmanaged_reason) { /* Evaluate the managed state based on the unmanaged flags. * @@ -15028,7 +15059,7 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo * * Effectively, this check is redundant, as the code below already * already ensures that. Still, express this invariant explicitly here. */ - if (_get_managed_by_flags(flags, mask, FALSE)) + if (_get_managed_by_flags(flags, mask, FALSE, unmanaged_reason)) return TRUE; /* A for-user-request, is effectively the same as pretending @@ -15071,7 +15102,12 @@ _get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean fo | NM_UNMANAGED_EXTERNAL_DOWN); } - return flags == NM_UNMANAGED_NONE; + if (flags == NM_UNMANAGED_NONE) { + return TRUE; + } else { + NM_SET_OUT(unmanaged_reason, unmanaged_flags_to_reason(flags)); + return FALSE; + } } /** @@ -15100,7 +15136,10 @@ nm_device_get_managed(NMDevice *self, gboolean for_user_request) priv = NM_DEVICE_GET_PRIVATE(self); - return _get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, for_user_request); + return _get_managed_by_flags(priv->unmanaged_flags, + priv->unmanaged_mask, + for_user_request, + NULL); } /** @@ -15239,9 +15278,9 @@ _set_unmanaged_flags(NMDevice *self, (priv->unmanaged_flags | priv->unmanaged_mask) ? "=" : "", (guint) priv->unmanaged_flags, (guint) priv->unmanaged_mask, - (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE) + (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE, NULL) ? "managed" - : (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, TRUE) + : (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, TRUE, NULL) ? "manageable" : "unmanaged")), priv->real ? "" : "/unrealized", @@ -15260,6 +15299,9 @@ _set_unmanaged_flags(NMDevice *self, if (transition_state) { new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE; if (new_state == NM_DEVICE_STATE_UNMANAGED) { + /* In state UNMANAGED, the reason always depends on current flags, not on what + * the caller passed. */ + _get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE, &reason); _cancel_activation(self); } else { /* The assume check should happen before the device transitions to @@ -15274,6 +15316,13 @@ _set_unmanaged_flags(NMDevice *self, nm_device_state_changed(self, new_state, reason); else nm_device_queue_state(self, new_state, reason); + } else { + /* No state change, but possibly update the reason in UNMANAGED */ + if (!_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE, &reason) + && reason != priv->state_reason) { + priv->state_reason = reason; + _notify(self, PROP_STATE_REASON); + } } } diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h index ab94244c21..cf6e8090aa 100644 --- a/src/libnm-core-public/nm-dbus-interface.h +++ b/src/libnm-core-public/nm-dbus-interface.h @@ -612,6 +612,25 @@ typedef enum { * @NM_DEVICE_STATE_REASON_PEER_NOT_FOUND: The Wi-Fi P2P peer could not be found * @NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED: The device handler dispatcher returned an * error. Since: 1.46 + * @NM_DEVICE_STATE_REASON_UNMANAGED_BY_DEFAULT: The device is unmanaged because the device type + * is unmanaged by default. Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN: The device is unmanaged because it is an + * external device and is unconfigured (down or without addresses). Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT: The device is unmanaged because the link is + * not initialized by udev. Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING: The device is unmanaged because NetworkManager is + * quitting. Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING: The device is unmanaged because networking is + * disabled or the system is suspended. Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF: The device is unmanaged by user decision in + * NetworkManager.conf ('unmanaged' in a [device*] section). Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT: The device is unmanaged by explicit user + * decision (e.g. 'nmcli device set $DEV managed no'). Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS: The device is unmanaged by user decision + * via settings plugin ('unmanaged-devices' for keyfile or 'NM_CONTROLLED=no' for ifcfg-rh). + * Since: 1.48 + * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV: The device is unmanaged via udev rule. Since: 1.48 + * * Device state change reason codes */ @@ -685,6 +704,15 @@ typedef enum { NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED = 66, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND = 67, NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED = 68, + NM_DEVICE_STATE_REASON_UNMANAGED_BY_DEFAULT = 69, + NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN = 70, + NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT = 71, + NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING = 72, + NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING = 73, + NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF = 74, + NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT = 75, + NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS = 76, + NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV = 77, } NMDeviceStateReason; /** diff --git a/src/libnmc-base/nm-client-utils.c b/src/libnmc-base/nm-client-utils.c index 30213e41b5..4c180e099b 100644 --- a/src/libnmc-base/nm-client-utils.c +++ b/src/libnmc-base/nm-client-utils.c @@ -466,7 +466,34 @@ NM_UTILS_LOOKUP_STR_DEFINE( NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, N_("The Wi-Fi P2P peer could not be found")), NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED, - N_("The device handler dispatcher returned an error")), ); + N_("The device handler dispatcher returned an error")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING, + N_("The device is unmanaged because networking is disabled " + "or the system is suspended")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING, + N_("The device is unmanaged because NetworkManager is quitting")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT, + N_("The device is unmanaged because the link is not initialized by udev")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT, + N_("The device is unmanaged by explicit user decision (e.g. 'nmcli device " + "set $DEV managed no'")), + NM_UTILS_LOOKUP_ITEM( + NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS, + N_("The device is unmanaged by user decision via settings plugin " + "(\"unmanaged-devices\" for keyfile or \"NM_CONTROLLED=no\" for ifcfg-rh)")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF, + N_("The device is unmanaged by user decision in NetworkManager.conf " + "('unmanaged' in a [device*] section")), + NM_UTILS_LOOKUP_ITEM( + NM_DEVICE_STATE_REASON_UNMANAGED_BY_DEFAULT, + N_("The device is unmanaged because the device type is unmanaged by default")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV, + N_("The device is unmanaged via udev rule")), + NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN, + N_("The device is unmanaged because it is an external device and is " + "unconfigured (down or without addresses)")), + +); NM_UTILS_LOOKUP_STR_DEFINE( nm_active_connection_state_reason_to_string,