From 6d8613e8fec765759e0396d185a386d125de52fe Mon Sep 17 00:00:00 2001 From: Aashay Tambi Date: Fri, 8 May 2026 19:53:49 -0700 Subject: [PATCH] wwan: respect NM_UNMANAGED udev flag for ModemManager devices Currently, NetworkManager does not check Udev rules for cellular modems because it relies exclusively on ModemManager's D-Bus announcements. This causes NetworkManager to aggressively grab modems that were explicitly tagged with ENV{NM_UNMANAGED}="1" in udev rules. This adds a check using sd_device to evaluate the sysfs path provided by MMModem before creating the NMModemBroadband object, bringing cellular modem handling to parity with ethernet and Wi-Fi. --- NEWS | 1 + src/core/devices/wwan/nm-device-modem.c | 12 +++++++++ src/core/devices/wwan/nm-modem-broadband.c | 31 ++++++++++++++++++++-- src/core/devices/wwan/nm-modem.c | 27 ++++++++++++++++++- src/core/devices/wwan/nm-modem.h | 2 ++ 5 files changed, 70 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 214b65a91f..bda8039ea4 100644 --- a/NEWS +++ b/NEWS @@ -64,6 +64,7 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Allow configuring all bond options in nmtui by introducing a "other options" field, which covers options not already covered by a dedicated input field. +* Respect the NM_UNMANAGED udev flag for cellular modems managed by ModemManager. ============================================= NetworkManager-1.56 diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c index 929bda15a7..df7e94d783 100644 --- a/src/core/devices/wwan/nm-device-modem.c +++ b/src/core/devices/wwan/nm-device-modem.c @@ -666,11 +666,23 @@ static void set_modem(NMDeviceModem *self, NMModem *modem) { NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self); + gboolean udev_unmanaged; g_return_if_fail(modem != NULL); priv->modem = nm_modem_claim(modem); + /* For modem devices, check the modem's udev unmanaged property + * and set it immediately after the modem is associated with the device. + * This allows the standard managed flag infrastructure to work properly. */ + udev_unmanaged = nm_modem_get_udev_unmanaged(modem); + if (udev_unmanaged) { + nm_device_set_unmanaged_by_flags(NM_DEVICE(self), + NM_UNMANAGED_USER_UDEV, + NM_UNMAN_FLAG_OP_SET_UNMANAGED, + NM_DEVICE_STATE_REASON_USER_REQUESTED); + } + g_signal_connect(modem, NM_MODEM_PPP_FAILED, G_CALLBACK(ppp_failed), self); g_signal_connect(modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK(modem_prepare_result), self); g_signal_connect(modem, NM_MODEM_NEW_CONFIG, G_CALLBACK(modem_new_config), self); diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c index 4bd0a45c51..950b593b3c 100644 --- a/src/core/devices/wwan/nm-modem-broadband.c +++ b/src/core/devices/wwan/nm-modem-broadband.c @@ -10,6 +10,7 @@ #include #include +#include #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" @@ -1612,8 +1613,10 @@ nm_modem_broadband_new(GObject *object, GError **error) MMModem *modem_iface; MMModem3gpp *modem_3gpp_iface; const char *const *drivers; - const char *operator_code = NULL; - gs_free char *driver = NULL; + const char *operator_code = NULL; + const char *sysfs_path = NULL; + gs_free char *driver = NULL; + gboolean udev_unmanaged = FALSE; g_return_val_if_fail(MM_IS_OBJECT(object), NULL); modem_object = MM_OBJECT(object); @@ -1632,6 +1635,28 @@ nm_modem_broadband_new(GObject *object, GError **error) if (modem_3gpp_iface) operator_code = mm_modem_3gpp_get_operator_code(modem_3gpp_iface); + /* Check udev NM_UNMANAGED property */ + sysfs_path = mm_modem_get_device(modem_iface); + if (sysfs_path) { + struct udev *udev; + struct udev_device *udev_device; + + udev = udev_new(); + if (udev) { + udev_device = udev_device_new_from_syspath(udev, sysfs_path); + if (udev_device) { + const char *nm_unmanaged; + + nm_unmanaged = udev_device_get_property_value(udev_device, "NM_UNMANAGED"); + if (_nm_utils_ascii_str_to_bool(nm_unmanaged, FALSE)) + udev_unmanaged = TRUE; + + udev_device_unref(udev_device); + } + udev_unref(udev); + } + } + return g_object_new(NM_TYPE_MODEM_BROADBAND, NM_MODEM_PATH, mm_object_get_path(modem_object), @@ -1653,6 +1678,8 @@ nm_modem_broadband_new(GObject *object, GError **error) operator_code, NM_MODEM_DEVICE_UID, mm_modem_get_device(modem_iface), + NM_MODEM_UDEV_UNMANAGED, + udev_unmanaged, NULL); } diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c index 9d8f61c55c..1c8341d53d 100644 --- a/src/core/devices/wwan/nm-modem.c +++ b/src/core/devices/wwan/nm-modem.c @@ -40,7 +40,8 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMModem, PROP_SIM_OPERATOR_ID, PROP_OPERATOR_CODE, PROP_APN, - PROP_DEVICE_UID, ); + PROP_DEVICE_UID, + PROP_UDEV_UNMANAGED, ); enum { PPP_STATS, @@ -92,6 +93,7 @@ typedef struct _NMModemPrivate { guint mm_ip_timeout; bool claimed : 1; + bool udev_unmanaged : 1; union { struct { @@ -171,6 +173,7 @@ static const char *state_table[] = { [NM_MODEM_STATE_DISCONNECTING] = "disconnecting", [NM_MODEM_STATE_CONNECTING] = "connecting", [NM_MODEM_STATE_CONNECTED] = "connected", + [NM_MODEM_STATE_UNMANAGED] = "unmanaged", }; const char * @@ -626,6 +629,14 @@ nm_modem_get_device_uid(NMModem *self) return NM_MODEM_GET_PRIVATE(self)->device_uid; } +gboolean +nm_modem_get_udev_unmanaged(NMModem *self) +{ + g_return_val_if_fail(NM_IS_MODEM(self), FALSE); + + return NM_MODEM_GET_PRIVATE(self)->udev_unmanaged; +} + /*****************************************************************************/ static void @@ -1671,6 +1682,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) case PROP_DEVICE_UID: g_value_set_string(value, priv->device_uid); break; + case PROP_UDEV_UNMANAGED: + g_value_set_boolean(value, priv->udev_unmanaged); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -1730,6 +1744,10 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps /* construct-only */ priv->device_uid = g_value_dup_string(value); break; + case PROP_UDEV_UNMANAGED: + /* construct-only */ + priv->udev_unmanaged = g_value_get_boolean(value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; @@ -1902,6 +1920,13 @@ nm_modem_class_init(NMModemClass *klass) NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_properties[PROP_UDEV_UNMANAGED] = + g_param_spec_boolean(NM_MODEM_UDEV_UNMANAGED, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); signals[PPP_STATS] = g_signal_new(NM_MODEM_PPP_STATS, diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h index 1f54b0b69a..51ba0aefba 100644 --- a/src/core/devices/wwan/nm-modem.h +++ b/src/core/devices/wwan/nm-modem.h @@ -31,6 +31,7 @@ #define NM_MODEM_OPERATOR_CODE "operator-code" #define NM_MODEM_APN "apn" #define NM_MODEM_DEVICE_UID "device-uid" +#define NM_MODEM_UDEV_UNMANAGED "udev-unmanaged" /* Signals */ #define NM_MODEM_PPP_STATS "ppp-stats" @@ -156,6 +157,7 @@ const char *nm_modem_get_sim_operator_id(NMModem *modem); const char *nm_modem_get_operator_code(NMModem *modem); const char *nm_modem_get_apn(NMModem *modem); const char *nm_modem_get_device_uid(NMModem *modem); +gboolean nm_modem_get_udev_unmanaged(NMModem *modem); gboolean nm_modem_set_data_port(NMModem *self, NMPlatform *platform,