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.
This commit is contained in:
Aashay Tambi 2026-05-08 19:53:49 -07:00 committed by Íñigo Huguet
parent 81209b4688
commit 6d8613e8fe
5 changed files with 70 additions and 3 deletions

1
NEWS
View file

@ -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

View file

@ -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);

View file

@ -10,6 +10,7 @@
#include <arpa/inet.h>
#include <libmm-glib.h>
#include <libudev.h>
#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);
}

View file

@ -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,

View file

@ -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,