summary refs log tree commit diff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/nm-device-bond.c100
-rw-r--r--src/devices/nm-device-ppp.c6
-rw-r--r--src/devices/nm-device-private.h2
-rw-r--r--src/devices/nm-device.c224
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c15
-rw-r--r--src/devices/ovs/nm-ovsdb.c114
-rw-r--r--src/devices/wwan/nm-modem-broadband.c19
7 files changed, 350 insertions, 130 deletions
diff --git a/src/devices/nm-device-bond.c b/src/devices/nm-device-bond.c
index c15605ce..2fedc753 100644
--- a/src/devices/nm-device-bond.c
+++ b/src/devices/nm-device-bond.c
@@ -85,6 +85,16 @@ _set_bond_attr (NMDevice *device, const char *attr, const char *value)
 	return ret;
 }
 
+#define _set_bond_attr_take(device, attr, value) \
+	G_STMT_START { \
+		gs_free char *_tmp = (value); \
+		\
+		_set_bond_attr (device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, _tmp); \
+	} G_STMT_END
+
+#define _set_bond_attr_printf(device, attr, fmt, ...) \
+	_set_bond_attr_take ((device), (attr), g_strdup_printf (fmt, __VA_ARGS__))
+
 static gboolean
 ignore_option (NMSettingBond *s_bond, const char *option, const char *value)
 {
@@ -173,22 +183,59 @@ master_update_slave_connection (NMDevice *self,
 static void
 set_arp_targets (NMDevice *device,
                  NMBondMode mode,
-                 const char *value,
-                 const char *delim,
-                 const char *prefix)
+                 const char *cur_arp_ip_target,
+                 const char *new_arp_ip_target)
 {
-	gs_free const char **value_v = NULL;
+	gs_unref_ptrarray GPtrArray *free_list = NULL;
+	gs_free const char **cur_strv = NULL;
+	gs_free const char **new_strv = NULL;
+	gsize cur_len;
+	gsize new_len;
 	gsize i;
+	gsize j;
 
-	value_v = nm_utils_strsplit_set (value, delim);
-	if (!value_v)
-		return;
-	for (i = 0; value_v[i]; i++) {
-		gs_free char *tmp = NULL;
+	cur_strv = nm_utils_strsplit_set_full (cur_arp_ip_target, NM_ASCII_SPACES, NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
+	new_strv = nm_utils_bond_option_arp_ip_targets_split (new_arp_ip_target);
+
+	cur_len = NM_PTRARRAY_LEN (cur_strv);
+	new_len = NM_PTRARRAY_LEN (new_strv);
+
+	if (new_len > 0) {
+		for (j = 0, i = 0; i < new_len; i++) {
+			const char *s;
+			in_addr_t a4;
 
-		tmp = g_strdup_printf ("%s%s", prefix, value_v[i]);
-		_set_bond_attr (device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, tmp);
+			s = new_strv[i];
+			if (nm_utils_parse_inaddr_bin (AF_INET, s, NULL, &a4)) {
+				char sbuf[INET_ADDRSTRLEN];
+
+				_nm_utils_inet4_ntop (a4, sbuf);
+				if (!nm_streq (s, sbuf)) {
+					if (!free_list)
+						free_list = g_ptr_array_new_with_free_func (g_free);
+					s = g_strdup (sbuf);
+					g_ptr_array_add (free_list, (gpointer) s);
+				}
+			}
+
+			if (nm_utils_strv_find_first ((char **) new_strv, i, s) < 0)
+				new_strv[j++] = s;
+		}
+		new_strv[j] = NULL;
+		new_len = j;
 	}
+
+	if (   cur_len == 0
+	    && new_len == 0)
+		return;
+
+	if (_nm_utils_strv_equal ((char **) cur_strv, (char **) new_strv))
+		return;
+
+	for (i = 0; i < cur_len; i++)
+		_set_bond_attr_printf (device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "-%s", cur_strv[i]);
+	for (i = 0; i < new_len; i++)
+		_set_bond_attr_printf (device, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, "+%s", new_strv[i]);
 }
 
 /*
@@ -201,13 +248,17 @@ set_bond_attr_or_default (NMDevice *device,
                           const char *opt)
 {
 	NMDeviceBond *self = NM_DEVICE_BOND (device);
-	const char *value = nm_setting_bond_get_option_or_default (s_bond, opt);
+	const char *value;
 
-	if (value) {
-		_set_bond_attr (device, opt, value);
-	} else {
-		_LOGD (LOGD_BOND, "bond option %s rejected due to incompatibility", opt);
+	value = nm_setting_bond_get_option_or_default (s_bond, opt);
+	if (!value) {
+		if (   _LOGT_ENABLED (LOGD_BOND)
+		    && nm_setting_bond_get_option_by_name (s_bond, opt))
+			_LOGT (LOGD_BOND, "bond option '%s' not set as it conflicts with other options", opt);
+		return;
 	}
+
+	_set_bond_attr (device, opt, value);
 }
 
 static gboolean
@@ -218,8 +269,7 @@ apply_bonding_config (NMDeviceBond *self)
 	NMSettingBond *s_bond;
 	NMBondMode mode;
 	const char *mode_str;
-	const char *value;
-	char *contents;
+	gs_free char *cur_arp_ip_target = NULL;
 
 	s_bond = nm_device_get_applied_setting (device, NM_TYPE_SETTING_BOND);
 	g_return_val_if_fail (s_bond, FALSE);
@@ -241,13 +291,13 @@ apply_bonding_config (NMDeviceBond *self)
 	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
 
 	/* ARP targets: clear and initialize the list */
-	contents = nm_platform_sysctl_master_get_option (nm_device_get_platform (device),
-	                                                 ifindex,
-	                                                 NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-	set_arp_targets (device, mode, contents, " \n", "-");
-	value = nm_setting_bond_get_option_or_default (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-	set_arp_targets (device, mode, value, ",", "+");
-	g_free (contents);
+	cur_arp_ip_target = nm_platform_sysctl_master_get_option (nm_device_get_platform (device),
+	                                                          ifindex,
+	                                                          NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+	set_arp_targets (device,
+	                 mode,
+	                 cur_arp_ip_target,
+	                 nm_setting_bond_get_option_or_default (s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET));
 
 	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM);
 	set_bond_attr_or_default (device, s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
diff --git a/src/devices/nm-device-ppp.c b/src/devices/nm-device-ppp.c
index 52784143..cbc87141 100644
--- a/src/devices/nm-device-ppp.c
+++ b/src/devices/nm-device-ppp.c
@@ -71,9 +71,13 @@ ppp_ifindex_set (NMPPPManager *ppp_manager,
                  gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
+	NMDevicePpp *self = NM_DEVICE_PPP (device);
 	gs_free char *old_name = NULL;
+	gs_free_error GError *error = NULL;
 
-	if (!nm_device_take_over_link (device, ifindex, &old_name)) {
+	if (!nm_device_take_over_link (device, ifindex, &old_name, &error)) {
+		_LOGW (LOGD_DEVICE | LOGD_PPP, "could not take control of link %d: %s",
+		       ifindex, error->message);
 		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED,
 		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return;
diff --git a/src/devices/nm-device-private.h b/src/devices/nm-device-private.h
index 4e260da2..cd72f3d7 100644
--- a/src/devices/nm-device-private.h
+++ b/src/devices/nm-device-private.h
@@ -62,7 +62,7 @@ gboolean nm_device_bring_up (NMDevice *self, gboolean wait, gboolean *no_firmwar
 
 void nm_device_take_down (NMDevice *self, gboolean block);
 
-gboolean nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name);
+gboolean nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name, GError **error);
 
 gboolean nm_device_hw_addr_set (NMDevice *device,
                                 const char *addr,
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 90178b68..de09e480 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -411,8 +411,8 @@ typedef struct _NMDevicePrivate {
 	bool            v4_route_table_initialized:1;
 	bool            v6_route_table_initialized:1;
 
-	bool            v4_route_table_full_sync_before:1;
-	bool            v6_route_table_full_sync_before:1;
+	bool            v4_route_table_all_sync_before:1;
+	bool            v6_route_table_all_sync_before:1;
 
 	NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags:5;
 
@@ -614,6 +614,7 @@ typedef struct _NMDevicePrivate {
 		SriovOp *pending;    /* SR-IOV operation currently running */
 		SriovOp *next;       /* next SR-IOV operation scheduled */
 	} sriov;
+	guint sriov_reset_pending;
 
 	struct {
 		guint timeout_id;
@@ -1746,25 +1747,51 @@ nm_device_get_iface (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->iface;
 }
 
+/**
+ * nm_device_take_over_link:
+ * @self: the #NMDevice
+ * @ifindex: a ifindex
+ * @old_name: (transfer full): on return, the name of the old link, if
+ *   the link was renamed
+ * @error: location to store error, or %NULL
+ *
+ * Given an existing link, move it under the control of a device. In
+ * particular, the link will be renamed to match the device name. If the
+ * link was renamed, the old name is returned in @old_name.
+ *
+ * Returns: %TRUE if the device took control of the link, %FALSE otherwise
+ */
 gboolean
-nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name)
+nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name, GError **error)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
 	const NMPlatformLink *plink;
 	NMPlatform *platform;
-	gboolean up, success = TRUE;
-	gs_free char *name = NULL;
-
-	g_return_val_if_fail (priv->ifindex <= 0, FALSE);
 
+	nm_assert (ifindex > 0);
 	NM_SET_OUT (old_name, NULL);
 
+	if (   priv->ifindex > 0
+	    && priv->ifindex != ifindex) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "the device already has ifindex %d",
+		                    priv->ifindex);
+		return FALSE;
+	}
+
 	platform = nm_device_get_platform (self);
 	plink = nm_platform_link_get (platform, ifindex);
-	if (!plink)
+	if (!plink) {
+		nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+		                    "link %d not found", ifindex);
 		return FALSE;
+	}
 
 	if (!nm_streq (plink->name, nm_device_get_iface (self))) {
+		gboolean up;
+		gboolean success;
+		gs_free char *name = NULL;
+
 		up = NM_FLAGS_HAS (plink->n_ifi_flags, IFF_UP);
 		name = g_strdup (plink->name);
 
@@ -1775,16 +1802,21 @@ nm_device_take_over_link (NMDevice *self, int ifindex, char **old_name)
 		if (up)
 			nm_platform_link_set_up (platform, ifindex, NULL);
 
-		if (success)
-			NM_SET_OUT (old_name, g_steal_pointer (&name));
+		if (!success) {
+			nm_utils_error_set (error, NM_UTILS_ERROR_UNKNOWN,
+			                    "failure renaming link %d", ifindex);
+			return FALSE;
+		}
+
+		NM_SET_OUT (old_name, g_steal_pointer (&name));
 	}
 
-	if (success) {
+	if (priv->ifindex != ifindex) {
 		priv->ifindex = ifindex;
 		_notify (self, PROP_IFINDEX);
 	}
 
-	return success;
+	return TRUE;
 }
 
 int
@@ -2706,34 +2738,59 @@ _get_route_table_sync_mode_stateful (NMDevice *self,
                                      int addr_family)
 {
 	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
-	gboolean full_sync_now;
-	gboolean full_sync_eff;
+	NMDedupMultiIter ipconf_iter;
+	gboolean all_sync_now;
+	gboolean all_sync_eff;
+
+	all_sync_now = _get_route_table (self, addr_family) != 0u;
+
+	if (!all_sync_now) {
+		/* If there's a local route switch to all-sync in order
+		 * to properly manage the local table */
+		if (addr_family == AF_INET) {
+			const NMPlatformIP4Route *route;
 
-	full_sync_now = _get_route_table (self, addr_family) != 0u;
+			nm_ip_config_iter_ip4_route_for_each (&ipconf_iter, priv->con_ip_config_4, &route) {
+				if (nm_platform_route_type_uncoerce (route->type_coerced) == RTN_LOCAL) {
+					all_sync_now = TRUE;
+					break;
+				}
+			}
+		} else {
+			const NMPlatformIP6Route *route;
+
+			nm_ip_config_iter_ip6_route_for_each (&ipconf_iter, priv->con_ip_config_6, &route) {
+				if (nm_platform_route_type_uncoerce (route->type_coerced) == RTN_LOCAL) {
+					all_sync_now = TRUE;
+					break;
+				}
+			}
+		}
+	}
 
-	if (full_sync_now)
-		full_sync_eff = TRUE;
+	if (all_sync_now)
+		all_sync_eff = TRUE;
 	else {
-		/* When we change from full-sync to no full-sync, we do a last full-sync one
-		 * more time. For that, we determine the effective full-state based on the
-		 * cached/previous full-sync flag.
+		/* When we change from all-sync to no all-sync, we do a last all-sync one
+		 * more time. For that, we determine the effective all-state based on the
+		 * cached/previous all-sync flag.
 		 *
 		 * The purpose of this is to support reapply of route-table (and thus the
-		 * full-sync mode). If reapply toggles from full-sync to no-full-sync, we must
+		 * all-sync mode). If reapply toggles from all-sync to no-all-sync, we must
 		 * sync one last time. */
 		if (addr_family == AF_INET)
-			full_sync_eff = priv->v4_route_table_full_sync_before;
+			all_sync_eff = priv->v4_route_table_all_sync_before;
 		else
-			full_sync_eff = priv->v6_route_table_full_sync_before;
+			all_sync_eff = priv->v6_route_table_all_sync_before;
 	}
 
 	if (addr_family == AF_INET)
-		priv->v4_route_table_full_sync_before = full_sync_now;
+		priv->v4_route_table_all_sync_before = all_sync_now;
 	else
-		priv->v6_route_table_full_sync_before = full_sync_now;
+		priv->v6_route_table_all_sync_before = all_sync_now;
 
-	return   full_sync_eff
-	       ? NM_IP_ROUTE_TABLE_SYNC_MODE_FULL
+	return   all_sync_eff
+	       ? NM_IP_ROUTE_TABLE_SYNC_MODE_ALL
 	       : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN;
 }
 
@@ -3822,6 +3879,10 @@ nm_device_update_dynamic_ip_setup (NMDevice *self)
 
 	priv = NM_DEVICE_GET_PRIVATE (self);
 
+	if (   priv->state < NM_DEVICE_STATE_IP_CONFIG
+	    || priv->state > NM_DEVICE_STATE_ACTIVATED)
+		return;
+
 	g_hash_table_remove_all (priv->ip6_saved_properties);
 
 	if (priv->dhcp_data_4.client) {
@@ -4705,15 +4766,12 @@ sriov_op_cb (GError *error, gpointer user_data)
 
 	nm_assert (op == priv->sriov.pending);
 
-	priv->sriov.pending = NULL;
-
 	g_clear_object (&op->cancellable);
 
 	if (op->callback)
 		op->callback (error, op->callback_data);
 
-	nm_assert (!priv->sriov.pending);
-
+	priv->sriov.pending = NULL;
 	nm_g_slice_free (op);
 
 	if (priv->sriov.next) {
@@ -4731,6 +4789,8 @@ sriov_op_queue_op (NMDevice *self,
 	if (priv->sriov.next) {
 		SriovOp *op_next = g_steal_pointer (&priv->sriov.next);
 
+		priv->sriov.next = op;
+
 		/* Cancel the next operation immediately */
 		if (op_next->callback) {
 			gs_free_error GError *error = NULL;
@@ -4740,17 +4800,10 @@ sriov_op_queue_op (NMDevice *self,
 		}
 
 		nm_g_slice_free (op_next);
+		return;
+	}
 
-		if (!priv->sriov.pending) {
-			/* This (having "next" set but "pending" not) can only happen if we are
-			 * called from inside the callback again.
-			 *
-			 * That means we append the new request as "next" and return. Once
-			 * the callback returns, it will schedule the request. */
-			priv->sriov.next = op;
-			return;
-		}
-	} else if (priv->sriov.pending) {
+	if (priv->sriov.pending) {
 		priv->sriov.next = op;
 		g_cancellable_cancel (priv->sriov.pending->cancellable);
 		return;
@@ -13209,7 +13262,8 @@ nm_device_set_ip_config (NMDevice *self,
 		if (IS_IPv4) {
 			success = nm_ip4_config_commit (NM_IP4_CONFIG (new_config),
 			                                nm_device_get_platform (self),
-			                                _get_route_table_sync_mode_stateful (self, addr_family));
+			                                _get_route_table_sync_mode_stateful (self,
+			                                                                     AF_INET));
 			nm_platform_ip4_dev_route_blacklist_set (nm_device_get_platform (self),
 			                                         nm_ip_config_get_ifindex (new_config),
 			                                         ip4_dev_route_blacklist);
@@ -13218,7 +13272,8 @@ nm_device_set_ip_config (NMDevice *self,
 
 			success = nm_ip6_config_commit (NM_IP6_CONFIG (new_config),
 			                                nm_device_get_platform (self),
-			                                _get_route_table_sync_mode_stateful (self, addr_family),
+			                                _get_route_table_sync_mode_stateful (self,
+			                                                                     AF_INET6),
 			                                &temporary_not_available);
 
 			if (!_rt6_temporary_not_available_set (self, temporary_not_available))
@@ -15463,8 +15518,8 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type)
 	priv->v4_route_table_initialized = FALSE;
 	priv->v6_route_table_initialized = FALSE;
 
-	priv->v4_route_table_full_sync_before = FALSE;
-	priv->v6_route_table_full_sync_before = FALSE;
+	priv->v4_route_table_all_sync_before = FALSE;
+	priv->v6_route_table_all_sync_before = FALSE;
 
 	priv->default_route_metric_penalty_ip4_has = FALSE;
 	priv->default_route_metric_penalty_ip6_has = FALSE;
@@ -15602,17 +15657,19 @@ nm_device_cleanup (NMDevice *self, NMDeviceStateReason reason, CleanupType clean
 
 	nm_device_update_metered (self);
 
-	/* during device cleanup, we want to reset the MAC address of the device
-	 * to the initial state.
-	 *
-	 * We certainly want to do that when reaching the UNMANAGED state... */
-	if (nm_device_get_state (self) <= NM_DEVICE_STATE_UNMANAGED)
-		nm_device_hw_addr_reset (self, "unmanage");
-	else {
-		/* for other device states (UNAVAILABLE, DISCONNECTED), allow the
-		 * device to overwrite the reset behavior, so that Wi-Fi can set
-		 * a randomized MAC address used during scanning. */
-		NM_DEVICE_GET_CLASS (self)->deactivate_reset_hw_addr (self);
+	if (ifindex > 0) {
+		/* during device cleanup, we want to reset the MAC address of the device
+		 * to the initial state.
+		 *
+		 * We certainly want to do that when reaching the UNMANAGED state... */
+		if (nm_device_get_state (self) <= NM_DEVICE_STATE_UNMANAGED)
+			nm_device_hw_addr_reset (self, "unmanage");
+		else {
+			/* for other device states (UNAVAILABLE, DISCONNECTED), allow the
+			 * device to overwrite the reset behavior, so that Wi-Fi can set
+			 * a randomized MAC address used during scanning. */
+			NM_DEVICE_GET_CLASS (self)->deactivate_reset_hw_addr (self);
+		}
 	}
 
 	priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
@@ -15863,27 +15920,51 @@ deactivate_ready (NMDevice *self, NMDeviceStateReason reason)
 	if (priv->dispatcher.call_id)
 		return;
 
-	if (   priv->sriov.pending
-	    || priv->sriov.next)
+	if (priv->sriov_reset_pending > 0)
 		return;
 
-	nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
+	if (priv->state == NM_DEVICE_STATE_DEACTIVATING)
+		nm_device_queue_state (self, NM_DEVICE_STATE_DISCONNECTED, reason);
 }
 
 static void
-sriov_deactivate_cb (GError *error, gpointer user_data)
+sriov_reset_on_deactivate_cb (GError *error, gpointer user_data)
 {
 	NMDevice *self;
+	NMDevicePrivate *priv;
 	gpointer reason;
 
-	if (nm_utils_error_is_cancelled_or_disposing (error))
+	nm_utils_user_data_unpack (user_data, &self, &reason);
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	nm_assert (priv->sriov_reset_pending > 0);
+	priv->sriov_reset_pending--;
+
+	if (nm_utils_error_is_cancelled (error))
 		return;
 
-	nm_utils_user_data_unpack (user_data, &self, &reason);
 	deactivate_ready (self, (NMDeviceStateReason) reason);
 }
 
 static void
+sriov_reset_on_failure_cb (GError *error, gpointer user_data)
+{
+	NMDevice *self = user_data;
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_assert (priv->sriov_reset_pending > 0);
+	priv->sriov_reset_pending--;
+
+	if (nm_utils_error_is_cancelled (error))
+		return;
+
+	if (priv->state == NM_DEVICE_STATE_FAILED) {
+		nm_device_queue_state (self,
+		                       NM_DEVICE_STATE_DISCONNECTED,
+		                       NM_DEVICE_STATE_REASON_NONE);
+	}
+}
+
+static void
 deactivate_async_ready (NMDevice *self,
                         GError *error,
                         gpointer user_data)
@@ -16209,10 +16290,11 @@ _set_state_full (NMDevice *self,
 
 			if (   priv->ifindex > 0
 			    && (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+				priv->sriov_reset_pending++;
 				sriov_op_queue (self,
 				                0,
 				                NM_TERNARY_TRUE,
-				                sriov_deactivate_cb,
+				                sriov_reset_on_deactivate_cb,
 				                nm_utils_user_data_pack (self, (gpointer) reason));
 			}
 		}
@@ -16264,6 +16346,16 @@ _set_state_full (NMDevice *self,
 		if (sett_conn && !nm_settings_connection_get_timestamp (sett_conn, NULL))
 			nm_settings_connection_update_timestamp (sett_conn, (guint64) 0);
 
+		if (   priv->ifindex > 0
+		    && (s_sriov = nm_device_get_applied_setting (self, NM_TYPE_SETTING_SRIOV))) {
+			priv->sriov_reset_pending++;
+			sriov_op_queue (self,
+			                0,
+			                NM_TERNARY_TRUE,
+			                sriov_reset_on_failure_cb,
+			                self);
+			break;
+		}
 		/* Schedule the transition to DISCONNECTED.  The device can't transition
 		 * immediately because we can't change states again from the state
 		 * handler for a variety of reasons.
@@ -17828,6 +17920,12 @@ dispose (GObject *object)
 	nm_clear_g_source (&priv->concheck_x[0].p_cur_id);
 	nm_clear_g_source (&priv->concheck_x[1].p_cur_id);
 
+	nm_assert (!priv->sriov.pending);
+	if (priv->sriov.next) {
+		nm_g_slice_free (priv->sriov.next);
+		priv->sriov.next = NULL;
+	}
+
 	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
 
 	if (nm_clear_g_source (&priv->queued_state.id)) {
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 10f9fa94..83954cf0 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -104,6 +104,14 @@ link_changed (NMDevice *device,
 	priv->waiting_for_interface = FALSE;
 
 	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		if (!nm_device_hw_addr_set_cloned (device,
+		                                   nm_device_get_applied_connection (device),
+		                                   FALSE)) {
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+			return;
+		}
 		nm_device_bring_up (device, TRUE, NULL);
 		nm_device_activate_schedule_stage3_ip_config_start (device);
 	}
@@ -176,6 +184,13 @@ act_stage3_ip_config_start (NMDevice *device,
 		return NM_ACT_STAGE_RETURN_POSTPONE;
 	}
 
+	if (!nm_device_hw_addr_set_cloned (device,
+	                                   nm_device_get_applied_connection (device),
+	                                   FALSE)) {
+		*out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
 	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip_config_start (device, addr_family, out_config, out_failure_reason);
 }
 
diff --git a/src/devices/ovs/nm-ovsdb.c b/src/devices/ovs/nm-ovsdb.c
index e1865f9d..0b3fa3fd 100644
--- a/src/devices/ovs/nm-ovsdb.c
+++ b/src/devices/ovs/nm-ovsdb.c
@@ -310,6 +310,18 @@ _set_bridge_ports (json_t *params, const char *ifname, json_t *new_ports)
 	);
 }
 
+static void
+_set_bridge_mac (json_t *params, const char *ifname, const char *mac)
+{
+	json_array_append_new (params,
+		json_pack ("{s:s, s:s, s:{s:[s, [[s, s]]]}, s:[[s, s, s]]}",
+		           "op", "update", "table", "Bridge",
+		           "row", "other_config", "map",
+		           "hwaddr", mac,
+		           "where", "name", "==", ifname)
+	);
+}
+
 /**
  * _expect_port_interfaces:
  *
@@ -353,15 +365,16 @@ _set_port_interfaces (json_t *params, const char *ifname, json_t *new_interfaces
  * Returns an commands that adds new interface from a given connection.
  */
 static void
-_insert_interface (json_t *params, NMConnection *interface, NMDevice *interface_device)
+_insert_interface (json_t *params,
+                   NMConnection *interface,
+                   NMDevice *interface_device,
+                   const char *cloned_mac)
 {
 	const char *type = NULL;
 	NMSettingOvsInterface *s_ovs_iface;
 	NMSettingOvsDpdk *s_ovs_dpdk;
 	NMSettingOvsPatch *s_ovs_patch;
 	json_t *options = json_array ();
-	gs_free char *cloned_mac = NULL;
-	gs_free_error GError *error = NULL;
 	json_t *row;
 	guint32 mtu = 0;
 
@@ -377,18 +390,6 @@ _insert_interface (json_t *params, NMConnection *interface, NMDevice *interface_
 			mtu = nm_setting_wired_get_mtu (s_wired);
 	}
 
-	if (!nm_device_hw_addr_get_cloned (interface_device,
-	                                   interface,
-	                                   FALSE,
-	                                   &cloned_mac,
-	                                   NULL,
-	                                   &error)) {
-		_LOGW ("Cannot determine cloned mac for OVS %s '%s': %s",
-		       "interface",
-		       nm_connection_get_interface_name (interface),
-		       error->message);
-	}
-
 	json_array_append_new (options, json_string ("map"));
 
 	s_ovs_dpdk = (NMSettingOvsDpdk *) nm_connection_get_setting (interface,
@@ -490,7 +491,11 @@ _insert_port (json_t *params, NMConnection *port, json_t *new_interfaces)
  * Returns an commands that adds new bridge from a given connection.
  */
 static void
-_insert_bridge (json_t *params, NMConnection *bridge, NMDevice *bridge_device, json_t *new_ports)
+_insert_bridge (json_t *params,
+                NMConnection *bridge,
+                NMDevice *bridge_device,
+                json_t *new_ports,
+                const char *cloned_mac)
 {
 	NMSettingOvsBridge *s_ovs_bridge;
 	const char *fail_mode = NULL;
@@ -499,23 +504,9 @@ _insert_bridge (json_t *params, NMConnection *bridge, NMDevice *bridge_device, j
 	gboolean stp_enable = FALSE;
 	const char *datapath_type = NULL;
 	json_t *row;
-	gs_free_error GError *error = NULL;
-	gs_free char *cloned_mac = NULL;
 
 	s_ovs_bridge = nm_connection_get_setting_ovs_bridge (bridge);
 
-	if (!nm_device_hw_addr_get_cloned (bridge_device,
-	                                   bridge,
-	                                   FALSE,
-	                                   &cloned_mac,
-	                                   NULL,
-	                                   &error)) {
-		_LOGW ("Cannot determine cloned mac for OVS %s '%s': %s",
-		       "bridge",
-		       nm_connection_get_interface_name (bridge),
-		       error->message);
-	}
-
 	row = json_object ();
 
 	if (s_ovs_bridge) {
@@ -586,6 +577,9 @@ _add_interface (NMOvsdb *self, json_t *params,
 	const char *bridge_uuid;
 	const char *port_uuid;
 	const char *interface_uuid;
+	const char *bridge_name;
+	const char *port_name;
+	const char *interface_name;
 	OpenvswitchBridge *ovs_bridge = NULL;
 	OpenvswitchPort *ovs_port = NULL;
 	OpenvswitchInterface *ovs_interface = NULL;
@@ -596,6 +590,10 @@ _add_interface (NMOvsdb *self, json_t *params,
 	nm_auto_decref_json json_t *interfaces = NULL;
 	nm_auto_decref_json json_t *new_interfaces = NULL;
 	gboolean has_interface = FALSE;
+	gboolean interface_is_internal;
+	gs_free char *bridge_cloned_mac = NULL;
+	gs_free char *interface_cloned_mac = NULL;
+	GError *error = NULL;
 	int pi;
 	int ii;
 
@@ -606,11 +604,51 @@ _add_interface (NMOvsdb *self, json_t *params,
 	new_ports = json_array ();
 	new_interfaces = json_array ();
 
+	bridge_name = nm_connection_get_interface_name (bridge);
+	port_name = nm_connection_get_interface_name (port);
+	interface_name = nm_connection_get_interface_name (interface);
+	interface_is_internal = nm_streq0 (bridge_name, interface_name);
+
+	/* Determine cloned MAC addresses */
+	if (!nm_device_hw_addr_get_cloned (bridge_device,
+	                                   bridge,
+	                                   FALSE,
+	                                   &bridge_cloned_mac,
+	                                   NULL,
+	                                   &error)) {
+		_LOGW ("Cannot determine cloned mac for OVS %s '%s': %s",
+		       "bridge",
+		       bridge_name,
+		       error->message);
+		g_clear_error (&error);
+	}
+
+	if (!nm_device_hw_addr_get_cloned (interface_device,
+	                                   interface,
+	                                   FALSE,
+	                                   &interface_cloned_mac,
+	                                   NULL,
+	                                   &error)) {
+		_LOGW ("Cannot determine cloned mac for OVS %s '%s': %s",
+		       "interface",
+		       interface_name,
+		       error->message);
+		g_clear_error (&error);
+	}
+
+	if (   interface_is_internal
+	    && !bridge_cloned_mac
+	    && interface_cloned_mac) {
+		_LOGT ("'%s' is a local ovs-interface, the MAC will be set on ovs-bridge '%s'",
+		       interface_name, bridge_name);
+		bridge_cloned_mac = g_steal_pointer (&interface_cloned_mac);
+	}
+
 	g_hash_table_iter_init (&iter, priv->bridges);
 	while (g_hash_table_iter_next (&iter, (gpointer) &bridge_uuid, (gpointer) &ovs_bridge)) {
 		json_array_append_new (bridges, json_pack ("[s, s]", "uuid", bridge_uuid));
 
-		if (   g_strcmp0 (ovs_bridge->name, nm_connection_get_interface_name (bridge)) != 0
+		if (   g_strcmp0 (ovs_bridge->name, bridge_name) != 0
 		    || g_strcmp0 (ovs_bridge->connection_uuid, nm_connection_get_uuid (bridge)) != 0)
 			continue;
 
@@ -624,7 +662,7 @@ _add_interface (NMOvsdb *self, json_t *params,
 				/* This would be a violation of ovsdb's reference integrity (a bug). */
 				_LOGW ("Unknown port '%s' in bridge '%s'", port_uuid, bridge_uuid);
 				continue;
-			} else if (   strcmp (ovs_port->name, nm_connection_get_interface_name (port)) != 0
+			} else if (   strcmp (ovs_port->name, port_name) != 0
 			           || g_strcmp0 (ovs_port->connection_uuid, nm_connection_get_uuid (port)) != 0) {
 				continue;
 			}
@@ -638,7 +676,7 @@ _add_interface (NMOvsdb *self, json_t *params,
 				if (!ovs_interface) {
 					/* This would be a violation of ovsdb's reference integrity (a bug). */
 					_LOGW ("Unknown interface '%s' in port '%s'", interface_uuid, port_uuid);
-				} else if (   strcmp (ovs_interface->name, nm_connection_get_interface_name (interface)) == 0
+				} else if (   strcmp (ovs_interface->name, interface_name) == 0
 				           && g_strcmp0 (ovs_interface->connection_uuid, nm_connection_get_uuid (interface)) == 0) {
 					has_interface = TRUE;
 				}
@@ -661,12 +699,14 @@ _add_interface (NMOvsdb *self, json_t *params,
 			_expect_ovs_bridges (params, priv->db_uuid, bridges);
 			json_array_append_new (new_bridges, json_pack ("[s, s]", "named-uuid", "rowBridge"));
 			_set_ovs_bridges (params, priv->db_uuid, new_bridges);
-			_insert_bridge (params, bridge, bridge_device, new_ports);
+			_insert_bridge (params, bridge, bridge_device, new_ports, bridge_cloned_mac);
 		} else {
 			/* Bridge already exists. */
 			g_return_if_fail (ovs_bridge);
 			_expect_bridge_ports (params, ovs_bridge->name, ports);
-			_set_bridge_ports (params, nm_connection_get_interface_name (bridge), new_ports);
+			_set_bridge_ports (params, bridge_name, new_ports);
+			if (bridge_cloned_mac && interface_is_internal)
+				_set_bridge_mac (params, bridge_name, bridge_cloned_mac);
 		}
 
 		json_array_append_new (new_ports, json_pack ("[s, s]", "named-uuid", "rowPort"));
@@ -675,11 +715,11 @@ _add_interface (NMOvsdb *self, json_t *params,
 		/* Port already exists */
 		g_return_if_fail (ovs_port);
 		_expect_port_interfaces (params, ovs_port->name, interfaces);
-		_set_port_interfaces (params, nm_connection_get_interface_name (port), new_interfaces);
+		_set_port_interfaces (params, port_name, new_interfaces);
 	}
 
 	if (!has_interface) {
-		_insert_interface (params, interface, interface_device);
+		_insert_interface (params, interface, interface_device, interface_cloned_mac);
 		json_array_append_new (new_interfaces, json_pack ("[s, s]", "named-uuid", "rowInterface"));
 	}
 }
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index f172e57f..0929adaf 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -20,9 +20,22 @@
 
 #define NM_MODEM_BROADBAND_MODEM "modem"
 
-#define MODEM_CAPS_3GPP(caps) (caps & (MM_MODEM_CAPABILITY_GSM_UMTS |    \
-                                       MM_MODEM_CAPABILITY_LTE |         \
-                                       MM_MODEM_CAPABILITY_LTE_ADVANCED))
+static gboolean
+MODEM_CAPS_3GPP (MMModemCapability caps)
+{
+	G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+	/* MM_MODEM_CAPABILITY_LTE_ADVANCED is marked as deprecated since ModemManager 1.14.0.
+	 *
+	 * The flag probably was never used, it certainly isn't used since 1.14.0.
+	 *
+	 * Still, just to be sure, there is no harm in checking it here. Suppress the
+	 * warning, it should have no bad effect.
+	 */
+	return NM_FLAGS_ANY (caps, (  MM_MODEM_CAPABILITY_GSM_UMTS
+	                            | MM_MODEM_CAPABILITY_LTE
+	                            | MM_MODEM_CAPABILITY_LTE_ADVANCED));
+	G_GNUC_END_IGNORE_DEPRECATIONS
+}
 
 #define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO))