Blob Blame History Raw
From 738b3feab24500406fc685ddddbe99975088d253 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Wed, 18 Sep 2019 10:54:13 +0200
Subject: [PATCH 1/6] device: reset ip6_mtu on cleanup

ip6_mtu contains the MTU received through IPv6 autoconfiguration; it
should be reset when the connection is deactivated.

https://bugzilla.redhat.com/show_bug.cgi?id=1753128
(cherry picked from commit 353c7c95c1c664c024c4267e67ed2d4b57b44437)
(cherry picked from commit f35783c3fba244fea926528bc6d0a2ba4d1c44ad)
---
 src/devices/nm-device.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index f07c4f09a..66bbf0a84 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -14781,6 +14781,7 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 	}
 
 	priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
+	priv->ip6_mtu = 0;
 	if (priv->mtu_initial || priv->ip6_mtu_initial) {
 		ifindex = nm_device_get_ip_ifindex (self);
 
-- 
2.21.0

From eac2aaa09ac60383d928831a257c6e0713e3fb38 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Wed, 11 Sep 2019 10:57:07 +0200
Subject: [PATCH 2/6] device: expand comment on MTU selection

(cherry picked from commit 6455a4e52852a936ae34e6596a2d309a44fb8fcc)
(cherry picked from commit 37720d856a1cbaf2bff31a080756ccaff247319b)
---
 src/devices/nm-device.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 66bbf0a84..52fe17771 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -9324,9 +9324,21 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 	{
 		guint32 mtu = 0;
 
-		/* preferably, get the MTU from explicit user-configuration.
-		 * Only if that fails, look at the current @config (which contains
-		 * MTUs from DHCP/PPP) or maybe fallback to a device-specific MTU. */
+		/* We take the MTU from various sources: (in order of increasing
+		 * priority) parent link, IP configuration (which contains the
+		 * MTU from DHCP/PPP), connection profile.
+		 *
+		 * We could just compare it with the platform MTU and apply it
+		 * when different, but this would revert at random times manual
+		 * changes done by the user with the MTU from the connection.
+		 *
+		 * Instead, we remember the source of the currently configured
+		 * MTU and apply the new one only when the new source has a
+		 * higher priority, so that we don't set a MTU from same source
+		 * multiple times. An exception to this is for the PARENT
+		 * source, since we need to keep tracking the parent MTU when it
+		 * changes.
+		 */
 
 		if (NM_DEVICE_GET_CLASS (self)->get_configured_mtu)
 			mtu = NM_DEVICE_GET_CLASS (self)->get_configured_mtu (self, &source);
-- 
2.21.0

From 0f64ecf35508654437ca82eb1d6a4c0e489a5017 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Thu, 12 Sep 2019 17:29:49 +0200
Subject: [PATCH 3/6] device: introduce generic function to inherit MTU from
 parent

Introduce a generic function to set a MTU based on parent's one. Also
define a device-specific @mtu_parent_delta value that specifies the
difference from parent MTU that should be set by default. For VLAN it
is zero but other interface types (for example MACsec) require a
positive value due to encapsulation overhead.

(cherry picked from commit 5cf57f452233b1211fcff7c8cf689d9d5cb48092)
(cherry picked from commit 73597864bb0a229ce33d9072a3f73f96443f1fe7)
---
 src/devices/nm-device-private.h |  3 +++
 src/devices/nm-device-vlan.c    | 23 ++---------------------
 src/devices/nm-device.c         | 27 +++++++++++++++++++++++++++
 src/devices/nm-device.h         |  4 ++++
 4 files changed, 36 insertions(+), 21 deletions(-)

diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 0a414af96..3caa82ee1 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -176,6 +176,9 @@ guint32 nm_device_get_configured_mtu_from_connection (NMDevice *device,
 
 guint32 nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_source);
 
+guint32 nm_device_get_configured_mtu_wired_parent (NMDevice *self,
+                                                   NMDeviceMtuSource *out_source);
+
 void nm_device_commit_mtu (NMDevice *self);
 
 /*****************************************************************************/
diff --git a/src/devices/nm-device-vlan.c b/src/devices/nm-device-vlan.c
index 0467a6e45..f8f6a5976 100644
--- a/src/devices/nm-device-vlan.c
+++ b/src/devices/nm-device-vlan.c
@@ -523,26 +523,6 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 	return ret;
 }
 
-static guint32
-get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source)
-{
-	guint32 mtu = 0;
-	int ifindex;
-
-	mtu = nm_device_get_configured_mtu_for_wired (self, out_source);
-	if (*out_source != NM_DEVICE_MTU_SOURCE_NONE)
-		return mtu;
-
-	/* Inherit the MTU from parent device, if any */
-	ifindex = nm_device_parent_get_ifindex (self);
-	if (ifindex > 0) {
-		mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)), ifindex);
-		*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
-	}
-
-	return mtu;
-}
-
 /*****************************************************************************/
 
 static void
@@ -598,13 +578,14 @@ nm_device_vlan_class_init (NMDeviceVlanClass *klass)
 	device_class->connection_type_supported = NM_SETTING_VLAN_SETTING_NAME;
 	device_class->connection_type_check_compatible = NM_SETTING_VLAN_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_VLAN);
+	device_class->mtu_parent_delta = 0; /* VLANs can have the same MTU of parent */
 
 	device_class->create_and_realize = create_and_realize;
 	device_class->link_changed = link_changed;
 	device_class->unrealize_notify = unrealize_notify;
 	device_class->get_generic_capabilities = get_generic_capabilities;
 	device_class->act_stage1_prepare = act_stage1_prepare;
-	device_class->get_configured_mtu = get_configured_mtu;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
 	device_class->is_available = is_available;
 	device_class->parent_changed_notify = parent_changed_notify;
 
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 52fe17771..570f57146 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -9272,6 +9272,33 @@ nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_s
 	                                                     out_source);
 }
 
+guint32
+nm_device_get_configured_mtu_wired_parent (NMDevice *self,
+                                           NMDeviceMtuSource *out_source)
+{
+	guint32 mtu = 0;
+	int ifindex;
+
+	mtu = nm_device_get_configured_mtu_for_wired (self, out_source);
+	if (*out_source != NM_DEVICE_MTU_SOURCE_NONE) {
+		nm_assert (mtu > 0);
+		return mtu;
+	}
+
+	/* Inherit the MTU from parent device, if any */
+	ifindex = nm_device_parent_get_ifindex (self);
+	if (ifindex > 0) {
+		mtu = nm_platform_link_get_mtu (nm_device_get_platform (self), ifindex);
+		if (mtu >= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta) {
+			mtu -= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta;
+			*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
+		} else
+			mtu = 0;
+	}
+
+	return mtu;
+}
+
 /*****************************************************************************/
 
 static void
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index ae6aab392..32efbd3de 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -239,6 +239,10 @@ typedef struct _NMDeviceClass {
 
 	const NMLinkType *link_types;
 
+	/* if the device MTU is set based on parent's one, this specifies
+	 * a delta in the MTU allowed value due the encapsulation overhead */
+	guint16 mtu_parent_delta;
+
 	/* Whether the device type is a master-type. This depends purely on the
 	 * type (NMDeviceClass), not the actual device instance. */
 	bool is_master:1;
-- 
2.21.0

From 0f5e91d45a9e8fd61bbe18b077d346ec0402eb70 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Thu, 12 Sep 2019 17:33:49 +0200
Subject: [PATCH 4/6] macsec: update MTU according to parent's one

A MACsec connection doesn't have an ordering dependency with its
parent connection and so it's possible that the parent gets activated
later and sets a greater MTU than the original one.

It is reasonable and useful to keep the MACsec MTU configured by
default as the maximum allowed by the parent interface, that is the
parent MTU minus the encapsulation overhead (32). The user can of
course override this by setting an explicit value in the
connection. We already do something similar for VLANs.

https://bugzilla.redhat.com/show_bug.cgi?id=1723690
(cherry picked from commit 438a0a9ad5287a82b1872c9783cc8fa4b548d49e)
(cherry picked from commit c58ce8945def15192bd9d70ef039fd71e846fdcf)
---
 src/devices/nm-device-macsec.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/src/devices/nm-device-macsec.c b/src/devices/nm-device-macsec.c
index e3e3a895b..13798f5e7 100644
--- a/src/devices/nm-device-macsec.c
+++ b/src/devices/nm-device-macsec.c
@@ -66,6 +66,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceMacsec,
 typedef struct {
 	NMPlatformLnkMacsec props;
 	gulong parent_state_id;
+	gulong parent_mtu_id;
 	Supplicant supplicant;
 	guint supplicant_timeout_id;
 	NMActRequestGetSecretsCallId *macsec_secrets_id;
@@ -113,6 +114,17 @@ parent_state_changed (NMDevice *parent,
 	nm_device_set_unmanaged_by_flags (NM_DEVICE (self), NM_UNMANAGED_PARENT, !nm_device_get_managed (parent, FALSE), reason);
 }
 
+static void
+parent_mtu_maybe_changed (NMDevice *parent,
+                          GParamSpec *pspec,
+                          gpointer user_data)
+{
+	/* the MTU of a MACsec device is limited by the parent's MTU.
+	 *
+	 * When the parent's MTU changes, try to re-set the MTU. */
+	nm_device_commit_mtu (user_data);
+}
+
 static void
 parent_changed_notify (NMDevice *device,
                        int old_ifindex,
@@ -133,12 +145,16 @@ parent_changed_notify (NMDevice *device,
 	 *  because NMDevice's dispose() will unset the parent, which in turn calls
 	 *  parent_changed_notify(). */
 	nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
+	nm_clear_g_signal_handler (old_parent, &priv->parent_mtu_id);
 
 	if (new_parent) {
 		priv->parent_state_id = g_signal_connect (new_parent,
 		                                          NM_DEVICE_STATE_CHANGED,
 		                                          G_CALLBACK (parent_state_changed),
 		                                          device);
+		priv->parent_mtu_id = g_signal_connect (new_parent, "notify::" NM_DEVICE_MTU,
+		                                        G_CALLBACK (parent_mtu_maybe_changed), device);
+
 
 		/* Set parent-dependent unmanaged flag */
 		nm_device_set_unmanaged_by_flags (device,
@@ -791,11 +807,15 @@ static void
 dispose (GObject *object)
 {
 	NMDeviceMacsec *self = NM_DEVICE_MACSEC (object);
+	NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
 
 	macsec_secrets_cancel (self);
 	supplicant_interface_release (self);
 
 	G_OBJECT_CLASS (nm_device_macsec_parent_class)->dispose (object);
+
+	nm_assert (priv->parent_state_id == 0);
+	nm_assert (priv->parent_mtu_id == 0);
 }
 
 static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
@@ -838,6 +858,7 @@ nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 	device_class->connection_type_supported = NM_SETTING_MACSEC_SETTING_NAME;
 	device_class->connection_type_check_compatible = NM_SETTING_MACSEC_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACSEC);
+	device_class->mtu_parent_delta = 32;
 
 	device_class->act_stage2_config = act_stage2_config;
 	device_class->create_and_realize = create_and_realize;
@@ -847,7 +868,7 @@ nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
 	device_class->is_available = is_available;
 	device_class->parent_changed_notify = parent_changed_notify;
 	device_class->state_changed = device_state_changed;
-	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
 
 	obj_properties[PROP_SCI] =
 	    g_param_spec_uint64 (NM_DEVICE_MACSEC_SCI, "", "",
-- 
2.21.0

From 2bbe0e352b9f6f0bc417f6c5a8c376ee750272cb Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Fri, 13 Sep 2019 11:40:51 +0200
Subject: [PATCH 5/6] macvlan: update MTU according to parent's one

(cherry picked from commit 4875745bc09c2f535cd28c13ea864cffbce37b0a)
(cherry picked from commit 9133ba90033188ae5a11ab0897a2640d1c6c33bd)
---
 src/devices/nm-device-macvlan.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/src/devices/nm-device-macvlan.c b/src/devices/nm-device-macvlan.c
index 709f98dab..260231215 100644
--- a/src/devices/nm-device-macvlan.c
+++ b/src/devices/nm-device-macvlan.c
@@ -48,6 +48,7 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceMacvlan,
 
 typedef struct {
 	gulong parent_state_id;
+	gulong parent_mtu_id;
 	NMPlatformLnkMacvlan props;
 } NMDeviceMacvlanPrivate;
 
@@ -134,6 +135,17 @@ parent_state_changed (NMDevice *parent,
 	nm_device_set_unmanaged_by_flags (NM_DEVICE (self), NM_UNMANAGED_PARENT, !nm_device_get_managed (parent, FALSE), reason);
 }
 
+static void
+parent_mtu_maybe_changed (NMDevice *parent,
+                          GParamSpec *pspec,
+                          gpointer user_data)
+{
+	/* the MTU of a macvlan/macvtap device is limited by the parent's MTU.
+	 *
+	 * When the parent's MTU changes, try to re-set the MTU. */
+	nm_device_commit_mtu (user_data);
+}
+
 static void
 parent_changed_notify (NMDevice *device,
                        int old_ifindex,
@@ -150,12 +162,15 @@ parent_changed_notify (NMDevice *device,
 	 *  because NMDevice's dispose() will unset the parent, which in turn calls
 	 *  parent_changed_notify(). */
 	nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
+	nm_clear_g_signal_handler (old_parent, &priv->parent_mtu_id);
 
 	if (new_parent) {
 		priv->parent_state_id = g_signal_connect (new_parent,
 		                                          NM_DEVICE_STATE_CHANGED,
 		                                          G_CALLBACK (parent_state_changed),
 		                                          device);
+		priv->parent_mtu_id = g_signal_connect (new_parent, "notify::" NM_DEVICE_MTU,
+		                                        G_CALLBACK (parent_mtu_maybe_changed), device);
 
 		/* Set parent-dependent unmanaged flag */
 		nm_device_set_unmanaged_by_flags (device,
@@ -475,6 +490,17 @@ nm_device_macvlan_init (NMDeviceMacvlan *self)
 {
 }
 
+static void
+dispose (GObject *object)
+{
+	NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE (object);
+
+	G_OBJECT_CLASS (nm_device_macvlan_parent_class)->dispose (object);
+
+	nm_assert (priv->parent_state_id == 0);
+	nm_assert (priv->parent_mtu_id == 0);
+}
+
 static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
 	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
 		NM_DBUS_INTERFACE_DEVICE_MACVLAN,
@@ -498,6 +524,7 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
+	object_class->dispose = dispose;
 	object_class->get_property = get_property;
 	object_class->set_property = set_property;
 
@@ -506,13 +533,14 @@ nm_device_macvlan_class_init (NMDeviceMacvlanClass *klass)
 	device_class->connection_type_supported = NM_SETTING_MACVLAN_SETTING_NAME;
 	device_class->connection_type_check_compatible = NM_SETTING_MACVLAN_SETTING_NAME;
 	device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACVLAN, NM_LINK_TYPE_MACVTAP);
+	device_class->mtu_parent_delta = 0;
 
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->complete_connection = complete_connection;
 	device_class->create_and_realize = create_and_realize;
 	device_class->get_generic_capabilities = get_generic_capabilities;
-	device_class->get_configured_mtu = nm_device_get_configured_mtu_for_wired;
+	device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
 	device_class->is_available = is_available;
 	device_class->link_changed = link_changed;
 	device_class->parent_changed_notify = parent_changed_notify;
-- 
2.21.0

From 5544b621a3e8aa8e547cdaf2bfeaa208a0d89349 Mon Sep 17 00:00:00 2001
From: Beniamino Galvani <bgalvani@redhat.com>
Date: Wed, 11 Sep 2019 10:57:07 +0200
Subject: [PATCH 6/6] device: fix setting MTU from connection when limited by
 parent

We try to set only one time the MTU from the connection to not
interfere with manual user changes.

If at some point the parent interface changes temporarily MTU to a
lower value (for example, because the connection was reactivated), the
kernel will also lower the MTU on child interface and we will not
update it ever again.

Add a workaround to this. If we detect that the MTU we want to set
from connection is higher that the allowed one, go into a state where
we follow the parent MTU until it is possible to set again the desired
MTU. This is a bit ugly, but I can't think of any nicer way to do it.

https://bugzilla.redhat.com/show_bug.cgi?id=1751079
(cherry picked from commit ec28f5b343816d6696f6a4994efcfa919b6f02b2)
(cherry picked from commit 49857ed279323e8b25d4120644add660f5816acb)
---
 src/devices/nm-device-ethernet.c      |  6 ++-
 src/devices/nm-device-infiniband.c    |  4 +-
 src/devices/nm-device-ip-tunnel.c     |  4 +-
 src/devices/nm-device-private.h       |  5 ++-
 src/devices/nm-device-wireguard.c     |  2 +-
 src/devices/nm-device.c               | 58 ++++++++++++++++++++-------
 src/devices/nm-device.h               |  4 +-
 src/devices/wifi/nm-device-iwd.c      |  4 +-
 src/devices/wifi/nm-device-wifi-p2p.c |  4 +-
 src/devices/wifi/nm-device-wifi.c     |  4 +-
 src/devices/wwan/nm-modem.c           |  4 +-
 src/devices/wwan/nm-modem.h           |  2 +-
 12 files changed, 73 insertions(+), 28 deletions(-)

diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c
index 3e84847ed..6e01b0934 100644
--- a/src/devices/nm-device-ethernet.c
+++ b/src/devices/nm-device-ethernet.c
@@ -1335,13 +1335,15 @@ act_stage3_ip_config_start (NMDevice *device,
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	/* MTU only set for plain ethernet */
 	if (NM_DEVICE_ETHERNET_GET_PRIVATE ((NMDeviceEthernet *) device)->ppp_manager)
 		return 0;
 
-	return nm_device_get_configured_mtu_for_wired (device, out_source);
+	return nm_device_get_configured_mtu_for_wired (device, out_source, out_force);
 }
 
 static void
diff --git a/src/devices/nm-device-infiniband.c b/src/devices/nm-device-infiniband.c
index 89db2af29..e39329a9c 100644
--- a/src/devices/nm-device-infiniband.c
+++ b/src/devices/nm-device-infiniband.c
@@ -116,7 +116,9 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	return nm_device_get_configured_mtu_from_connection (device,
 	                                                     NM_TYPE_SETTING_INFINIBAND,
diff --git a/src/devices/nm-device-ip-tunnel.c b/src/devices/nm-device-ip-tunnel.c
index ede4487e3..f070dd5f9 100644
--- a/src/devices/nm-device-ip-tunnel.c
+++ b/src/devices/nm-device-ip-tunnel.c
@@ -854,7 +854,9 @@ create_and_realize (NMDevice *device,
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	return nm_device_get_configured_mtu_from_connection (device,
 	                                                     NM_TYPE_SETTING_IP_TUNNEL,
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 3caa82ee1..83feb130e 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -174,10 +174,11 @@ guint32 nm_device_get_configured_mtu_from_connection (NMDevice *device,
                                                       GType setting_type,
                                                       NMDeviceMtuSource *out_source);
 
-guint32 nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_source);
+guint32 nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force);
 
 guint32 nm_device_get_configured_mtu_wired_parent (NMDevice *self,
-                                                   NMDeviceMtuSource *out_source);
+                                                   NMDeviceMtuSource *out_source,
+                                                   gboolean *out_force);
 
 void nm_device_commit_mtu (NMDevice *self);
 
diff --git a/src/devices/nm-device-wireguard.c b/src/devices/nm-device-wireguard.c
index e3b8e8b4c..5d640b622 100644
--- a/src/devices/nm-device-wireguard.c
+++ b/src/devices/nm-device-wireguard.c
@@ -1714,7 +1714,7 @@ act_stage3_ip_config_start (NMDevice *device,
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
 {
 	/* When "MTU" for `wg-quick up` is unset, it calls `ip route get` for
 	 * each configured endpoint, to determine the suitable MTU how to reach
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 570f57146..0da8049bd 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -9265,7 +9265,9 @@ nm_device_get_configured_mtu_from_connection (NMDevice *self,
 }
 
 guint32
-nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_source)
+nm_device_get_configured_mtu_for_wired (NMDevice *self,
+                                        NMDeviceMtuSource *out_source,
+                                        gboolean *out_force)
 {
 	return nm_device_get_configured_mtu_from_connection (self,
 	                                                     NM_TYPE_SETTING_WIRED,
@@ -9274,26 +9276,43 @@ nm_device_get_configured_mtu_for_wired (NMDevice *self, NMDeviceMtuSource *out_s
 
 guint32
 nm_device_get_configured_mtu_wired_parent (NMDevice *self,
-                                           NMDeviceMtuSource *out_source)
+                                           NMDeviceMtuSource *out_source,
+                                           gboolean *out_force)
 {
 	guint32 mtu = 0;
+	guint32 parent_mtu = 0;
 	int ifindex;
 
-	mtu = nm_device_get_configured_mtu_for_wired (self, out_source);
+	ifindex = nm_device_parent_get_ifindex (self);
+	if (ifindex > 0) {
+		parent_mtu = nm_platform_link_get_mtu (nm_device_get_platform (self), ifindex);
+		if (parent_mtu >= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta)
+			parent_mtu -= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta;
+		else
+			parent_mtu = 0;
+	}
+
+	mtu = nm_device_get_configured_mtu_for_wired (self, out_source, NULL);
+
+	if (parent_mtu && mtu > parent_mtu) {
+		/* Trying to set a MTU that is out of range from configuration:
+		 * fall back to the parent MTU and set force flag so that it
+		 * overrides an MTU with higher priority already configured.
+		 */
+		 *out_source = NM_DEVICE_MTU_SOURCE_PARENT;
+		 *out_force = TRUE;
+		 return parent_mtu;
+	}
+
 	if (*out_source != NM_DEVICE_MTU_SOURCE_NONE) {
 		nm_assert (mtu > 0);
 		return mtu;
 	}
 
 	/* Inherit the MTU from parent device, if any */
-	ifindex = nm_device_parent_get_ifindex (self);
-	if (ifindex > 0) {
-		mtu = nm_platform_link_get_mtu (nm_device_get_platform (self), ifindex);
-		if (mtu >= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta) {
-			mtu -= NM_DEVICE_GET_CLASS (self)->mtu_parent_delta;
-			*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
-		} else
-			mtu = 0;
+	if (parent_mtu) {
+		mtu = parent_mtu;
+		*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
 	}
 
 	return mtu;
@@ -9350,6 +9369,7 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 
 	{
 		guint32 mtu = 0;
+		gboolean force = FALSE;
 
 		/* We take the MTU from various sources: (in order of increasing
 		 * priority) parent link, IP configuration (which contains the
@@ -9365,12 +9385,18 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 		 * multiple times. An exception to this is for the PARENT
 		 * source, since we need to keep tracking the parent MTU when it
 		 * changes.
+		 *
+		 * The subclass can set the @force argument to TRUE to signal that the
+		 * returned MTU should be applied even if it has a lower priority. This
+		 * is useful when the value from a lower source should
+		 * preempt the one from higher ones.
 		 */
 
 		if (NM_DEVICE_GET_CLASS (self)->get_configured_mtu)
-			mtu = NM_DEVICE_GET_CLASS (self)->get_configured_mtu (self, &source);
+			mtu = NM_DEVICE_GET_CLASS (self)->get_configured_mtu (self, &source, &force);
 
 		if (   config
+		    && !force
 		    && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG
 		    && nm_ip4_config_get_mtu (config)) {
 			mtu = nm_ip4_config_get_mtu (config);
@@ -9379,14 +9405,16 @@ _commit_mtu (NMDevice *self, const NMIP4Config *config)
 
 		if (mtu != 0) {
 			_LOGT (LOGD_DEVICE,
-			       "mtu: value %u from source '%s' (%u), current source '%s' (%u)",
+			       "mtu: value %u from source '%s' (%u), current source '%s' (%u)%s",
 			       (guint) mtu,
 			       mtu_source_to_str (source), (guint) source,
-			       mtu_source_to_str (priv->mtu_source), (guint) priv->mtu_source);
+			       mtu_source_to_str (priv->mtu_source), (guint) priv->mtu_source,
+			       force ? " (forced)" : "");
 		}
 
 		if (   mtu != 0
-		    && (   source > priv->mtu_source
+		    && (   force
+		        || source > priv->mtu_source
 		        || (priv->mtu_source == NM_DEVICE_MTU_SOURCE_PARENT && source == priv->mtu_source)))
 			mtu_desired = mtu;
 		else {
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 32efbd3de..412a57a0a 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -339,7 +339,9 @@ typedef struct _NMDeviceClass {
 	                                  NMSettingsConnection *sett_conn,
 	                                  char **specific_object);
 
-	guint32     (*get_configured_mtu) (NMDevice *self, NMDeviceMtuSource *out_source);
+	guint32     (*get_configured_mtu) (NMDevice *self,
+	                                   NMDeviceMtuSource *out_source,
+	                                   gboolean *out_force);
 
 	/* allow the subclass to overwrite the routing table. This is mainly useful
 	 * to change from partial mode (route-table=0) to full-sync mode (route-table=254). */
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 9cfe5f70a..82b69ccfb 100644
--- a/src/devices/wifi/nm-device-iwd.c
+++ b/src/devices/wifi/nm-device-iwd.c
@@ -1881,7 +1881,9 @@ out:
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	return nm_device_get_configured_mtu_from_connection (device,
 	                                                     NM_TYPE_SETTING_WIRELESS,
diff --git a/src/devices/wifi/nm-device-wifi-p2p.c b/src/devices/wifi/nm-device-wifi-p2p.c
index c5826e24a..fcd1630cb 100644
--- a/src/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/devices/wifi/nm-device-wifi-p2p.c
@@ -629,7 +629,9 @@ deactivate (NMDevice *device)
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	*out_source = NM_DEVICE_MTU_SOURCE_NONE;
 	return 0;
diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c
index 2ffba47a2..5cffb260a 100644
--- a/src/devices/wifi/nm-device-wifi.c
+++ b/src/devices/wifi/nm-device-wifi.c
@@ -2969,7 +2969,9 @@ act_stage3_ip_config_start (NMDevice *device,
 }
 
 static guint32
-get_configured_mtu (NMDevice *device, NMDeviceMtuSource *out_source)
+get_configured_mtu (NMDevice *device,
+                    NMDeviceMtuSource *out_source,
+                    gboolean *out_force)
 {
 	return nm_device_get_configured_mtu_from_connection (device,
 	                                                     NM_TYPE_SETTING_WIRELESS,
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 617096a76..fb2316d51 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -875,7 +875,9 @@ nm_modem_stage3_ip6_config_start (NMModem *self,
 }
 
 guint32
-nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source)
+nm_modem_get_configured_mtu (NMDevice *self,
+                             NMDeviceMtuSource *out_source,
+                             gboolean *out_force)
 {
 	NMConnection *connection;
 	NMSetting *setting;
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index f5b386e8b..fccb4fec4 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -287,7 +287,7 @@ void nm_modem_emit_ip6_config_result (NMModem *self,
 
 const char *nm_modem_ip_type_to_string (NMModemIPType ip_type);
 
-guint32 nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source);
+guint32 nm_modem_get_configured_mtu (NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force);
 
 void _nm_modem_set_operator_code (NMModem *self, const char *operator_code);
 void _nm_modem_set_apn           (NMModem *self, const char *apn);
-- 
2.21.0