summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-07-13 22:03:16 +0200
committerMichael Biebl <biebl@debian.org>2020-07-13 22:03:16 +0200
commit136d191f1c96dbae1489fed7c2565f5e1b1f8d40 (patch)
treeb219a4d9541be3533b0ea62d99a41828e148e3ef /src
parent10ae7d8cd706062742d0cdb1803d49909aef9e06 (diff)
New upstream version 1.26.0 upstream/1.26.0
Diffstat (limited to 'src')
-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
-rw-r--r--src/dhcp/nm-dhcp-utils.c6
-rw-r--r--src/initrd/nm-initrd-generator.c31
-rw-r--r--src/initrd/nmi-cmdline-reader.c49
-rw-r--r--src/initrd/tests/test-cmdline-reader.c200
-rw-r--r--src/ndisc/tests/test-ndisc-fake.c79
-rw-r--r--src/nm-core-utils.h12
-rw-r--r--src/nm-ip4-config.c55
-rw-r--r--src/nm-ip6-config.c65
-rw-r--r--src/nm-manager.c7
-rw-r--r--src/nm-policy.c6
-rw-r--r--src/platform/nm-linux-platform.c25
-rw-r--r--src/platform/nm-platform.c41
-rw-r--r--src/platform/nm-platform.h6
-rw-r--r--src/platform/tests/test-route.c18
21 files changed, 781 insertions, 299 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))
 
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index d8e5a653..1c305238 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -440,10 +440,10 @@ nm_dhcp_utils_ip4_config_from_options (NMDedupMultiIndex *multi_idx,
 
 	if (gateway_has) {
 		const NMPlatformIP4Route r = {
-			.rt_source = NM_IP_CONFIG_SOURCE_DHCP,
-			.gateway = gateway,
+			.rt_source     = NM_IP_CONFIG_SOURCE_DHCP,
+			.gateway       = gateway,
 			.table_coerced = nm_platform_route_table_coerce (route_table),
-			.metric = route_metric,
+			.metric        = route_metric,
 		};
 
 		nm_ip4_config_add_route (ip4_config, &r, NULL);
diff --git a/src/initrd/nm-initrd-generator.c b/src/initrd/nm-initrd-generator.c
index ac81dde0..f984ed73 100644
--- a/src/initrd/nm-initrd-generator.c
+++ b/src/initrd/nm-initrd-generator.c
@@ -130,22 +130,27 @@ main (int argc, char *argv[])
 	g_hash_table_foreach (connections, output_conn, connections_dir);
 	g_hash_table_destroy (connections);
 
-	if (g_mkdir_with_parents (initrd_dir, 0755) != 0) {
-		errsv = errno;
-		_LOGW (LOGD_CORE, "%s: %s", initrd_dir, nm_strerror_native (errsv));
-		return 1;
-	}
+	if (dump_to_stdout) {
+		if (hostname)
+			g_print ("\n*** Hostname '%s' ***\n", hostname);
+	} else {
+		if (g_mkdir_with_parents (initrd_dir, 0755) != 0) {
+			errsv = errno;
+			_LOGW (LOGD_CORE, "%s: %s", initrd_dir, nm_strerror_native (errsv));
+			return 1;
+		}
 
-	if (hostname) {
-		gs_free char *hostname_file = NULL;
-		gs_free char *data = NULL;
+		if (hostname) {
+			gs_free char *hostname_file = NULL;
+			gs_free char *data = NULL;
 
-		hostname_file = g_strdup_printf ("%s/hostname", initrd_dir);
-		data = g_strdup_printf ("%s\n", hostname);
+			hostname_file = g_strdup_printf ("%s/hostname", initrd_dir);
+			data = g_strdup_printf ("%s\n", hostname);
 
-		if (!g_file_set_contents (hostname_file, data, strlen (data), &error)) {
-			_LOGW (LOGD_CORE, "%s: %s", hostname_file, error->message);
-			return 1;
+			if (!g_file_set_contents (hostname_file, data, strlen (data), &error)) {
+				_LOGW (LOGD_CORE, "%s: %s", hostname_file, error->message);
+				return 1;
+			}
 		}
 	}
 
diff --git a/src/initrd/nmi-cmdline-reader.c b/src/initrd/nmi-cmdline-reader.c
index 69e5e56d..17f9e1df 100644
--- a/src/initrd/nmi-cmdline-reader.c
+++ b/src/initrd/nmi-cmdline-reader.c
@@ -132,6 +132,7 @@ reader_get_default_connection (Reader *reader)
 		                                NULL,
 		                                NM_SETTING_WIRED_SETTING_NAME,
 		                                NM_CONNECTION_MULTI_CONNECT_MULTIPLE);
+		nm_connection_add_setting (con, nm_setting_wired_new ());
 		reader->default_connection = con;
 	}
 	return reader->default_connection;
@@ -237,20 +238,24 @@ get_word (char **argument, const char separator)
 }
 
 static void
-_base_setting_set (NMConnection *connection, const char *property, const char *value)
+connection_set (NMConnection *connection, const char *setting_name, const char *property, const char *value)
 {
 	NMSetting *setting;
-	const char *type_name = nm_connection_get_connection_type (connection);
-	GObjectClass *object_class = g_type_class_ref (nm_setting_lookup_type (type_name));
-	GParamSpec *spec = g_object_class_find_property (object_class, property);
-
-	if (!spec) {
-		_LOGW (LOGD_CORE, "'%s' does not support setting %s", type_name, property);
-		return;
+	GType setting_type;
+	nm_auto_unref_gtypeclass GObjectClass *object_class = NULL;
+	GParamSpec *spec;
+
+	setting_type = nm_setting_lookup_type (setting_name);
+	object_class = g_type_class_ref (setting_type);
+	spec = g_object_class_find_property (object_class, property);
+	nm_assert (spec);
+
+	setting = nm_connection_get_setting_by_name (connection, setting_name);
+	if (!setting) {
+		setting = g_object_new (setting_type, NULL);
+		nm_connection_add_setting (connection, setting);
 	}
 
-	setting = nm_connection_get_setting_by_name (connection, type_name);
-
 	if (G_IS_PARAM_SPEC_UINT (spec)) {
 		guint v;
 
@@ -259,14 +264,12 @@ _base_setting_set (NMConnection *connection, const char *property, const char *v
 		    || !nm_g_object_set_property_uint (G_OBJECT (setting), property, v, NULL)) {
 			_LOGW (LOGD_CORE,
 			       "Could not set property '%s.%s' to '%s'",
-			       type_name, property, value);
+			       setting_name, property, value);
 		}
 	} else if (G_IS_PARAM_SPEC_STRING (spec))
 		g_object_set (setting, property, value, NULL);
 	else
-		_LOGW (LOGD_CORE, "Don't know how to set '%s' of %s", property, type_name);
-
-	g_type_class_unref (object_class);
+		_LOGW (LOGD_CORE, "Don't know how to set '%s' of %s", property, setting_name);
 }
 
 static void
@@ -482,17 +485,7 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO,
 			              NULL);
 		}
-	} else if (nm_streq0 (kind, "dhcp6")) {
-		g_object_set (s_ip6,
-		              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DHCP,
-		              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
-		              NULL);
-		if (nm_setting_ip_config_get_num_addresses (s_ip4) == 0) {
-			g_object_set (s_ip4,
-			              NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
-			              NULL);
-		}
-	} else if (nm_streq0 (kind, "auto6")) {
+	} else if (NM_IN_STRSET (kind, "auto6", "dhcp6")) {
 		g_object_set (s_ip4,
 		              NM_SETTING_IP_CONFIG_MAY_FAIL, FALSE,
 		              NULL);
@@ -577,10 +570,10 @@ reader_parse_ip (Reader *reader, const char *sysfs_dir, char *argument)
 	}
 
 	if (mtu && *mtu)
-		_base_setting_set (connection, "mtu", mtu);
+		connection_set (connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu);
 
 	if (macaddr && *macaddr)
-		_base_setting_set (connection, "cloned-mac-address", macaddr);
+		connection_set (connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, macaddr);
 }
 
 static void
@@ -635,7 +628,7 @@ reader_parse_master (Reader *reader,
 		              NM_SETTING_CONNECTION_MASTER, master,
 		              NULL);
 		if (mtu)
-			_base_setting_set (connection, "mtu", mtu);
+			connection_set (connection, NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU, mtu);
 	} while (slaves && *slaves != '\0');
 
 	if (argument && *argument)
diff --git a/src/initrd/tests/test-cmdline-reader.c b/src/initrd/tests/test-cmdline-reader.c
index 11077055..04594c48 100644
--- a/src/initrd/tests/test-cmdline-reader.c
+++ b/src/initrd/tests/test-cmdline-reader.c
@@ -138,7 +138,7 @@ test_if_dhcp6 (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 }
 
@@ -569,6 +569,94 @@ test_bond (void)
 }
 
 static void
+test_bond_ip (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*ARGV = NM_MAKE_STRV ("bond=bond0:eth0,eth1",
+	                                       "ip=192.168.1.1::192.168.1.254:24::bond0:none:1480:01:02:03:04:05:06",
+	                                       "nameserver=4.8.15.16");
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+	NMSettingWired *s_wired;
+	NMSettingBond *s_bond;
+	NMIPAddress *ip_addr;
+	const char *master_uuid;
+	gs_free char *hostname = NULL;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 3);
+	g_assert_cmpstr (hostname, ==, NULL);
+
+	connection = g_hash_table_lookup (connections, "bond0");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_connection_type (connection), ==, NM_SETTING_BOND_SETTING_NAME);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "bond0");
+	master_uuid = nm_connection_get_uuid (connection);
+	g_assert (master_uuid);
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	g_assert (s_wired);
+	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 1480);
+	g_assert_cmpstr (nm_setting_wired_get_cloned_mac_address (s_wired), ==, "01:02:03:04:05:06");
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip4);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
+	g_assert_cmpint (nm_setting_ip_config_get_num_addresses (s_ip4), ==, 1);
+	ip_addr = nm_setting_ip_config_get_address (s_ip4, 0);
+	g_assert (ip_addr);
+	g_assert_cmpstr (nm_ip_address_get_address (ip_addr), ==, "192.168.1.1");
+	g_assert_cmpint (nm_ip_address_get_prefix (ip_addr), ==, 24);
+	g_assert_cmpstr (nm_setting_ip_config_get_gateway (s_ip4), ==, "192.168.1.254");
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip4), ==, 1);
+	g_assert_cmpstr (nm_setting_ip_config_get_dns (s_ip4, 0), ==, "4.8.15.16");
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip4), ==, 0);
+
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
+	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 0);
+	g_assert (!nm_setting_ip_config_get_gateway (s_ip6));
+	g_assert_cmpint (nm_setting_ip_config_get_num_routes (s_ip6), ==, 0);
+
+	s_bond = nm_connection_get_setting_bond (connection);
+	g_assert (s_bond);
+	g_assert_cmpint (nm_setting_bond_get_num_options (s_bond), ==, 1);
+	g_assert_cmpstr (nm_setting_bond_get_option_by_name (s_bond, "mode"), ==, "balance-rr");
+
+	connection = g_hash_table_lookup (connections, "eth0");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth0");
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth0");
+	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BOND_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
+
+	connection = g_hash_table_lookup (connections, "eth1");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "eth1");
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+	g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, "eth1");
+	g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BOND_SETTING_NAME);
+	g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+	g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
+}
+
+static void
 test_bond_default (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
@@ -775,6 +863,71 @@ test_bridge_default (void)
 }
 
 static void
+test_bridge_ip (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*ARGV = NM_MAKE_STRV ("ip=bridge123:auto:1280:00:11:22:33:CA:fe",
+	                                       "bridge=bridge123:eth0,eth1,eth2,eth3,eth4,eth5,eth6,eth7,eth8,eth9");
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+	NMSettingWired *s_wired;
+	NMSettingBridge *s_bridge;
+	const char *master_uuid;
+	gs_free char *hostname = NULL;
+	guint i;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 11);
+	g_assert_cmpstr (hostname, ==, NULL);
+
+	connection = g_hash_table_lookup (connections, "bridge123");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_connection_type (connection), ==, NM_SETTING_BRIDGE_SETTING_NAME);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "bridge123");
+	master_uuid = nm_connection_get_uuid (connection);
+	g_assert (master_uuid);
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	g_assert (s_wired);
+	g_assert_cmpint (nm_setting_wired_get_mtu (s_wired), ==, 1280);
+	g_assert_cmpstr (nm_setting_wired_get_cloned_mac_address (s_wired), ==, "00:11:22:33:CA:FE");
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip4);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+
+	s_bridge = nm_connection_get_setting_bridge (connection);
+	g_assert (s_bridge);
+
+	for (i = 0; i < 10; i++) {
+		char ifname[16];
+
+		nm_sprintf_buf (ifname, "eth%u", i);
+
+		connection = g_hash_table_lookup (connections, ifname);
+		g_assert (connection);
+		nmtst_assert_connection_verifies_without_normalization (connection);
+		g_assert_cmpstr (nm_connection_get_id (connection), ==, ifname);
+
+		s_con = nm_connection_get_setting_connection (connection);
+		g_assert (s_con);
+		g_assert_cmpstr (nm_setting_connection_get_connection_type (s_con), ==, NM_SETTING_WIRED_SETTING_NAME);
+		g_assert_cmpstr (nm_setting_connection_get_id (s_con), ==, ifname);
+		g_assert_cmpstr (nm_setting_connection_get_slave_type (s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
+		g_assert_cmpstr (nm_setting_connection_get_master (s_con), ==, master_uuid);
+		g_assert_cmpint (nm_setting_connection_get_multi_connect (s_con), ==, NM_CONNECTION_MULTI_CONNECT_SINGLE);
+	}
+}
+
+static void
 test_team (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
@@ -810,7 +963,7 @@ test_team (void)
 
 	s_ip6 = nm_connection_get_setting_ip6_config (connection);
 	g_assert (s_ip6);
-	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
 	g_assert (!nm_setting_ip_config_get_ignore_auto_dns (s_ip6));
 	g_assert_cmpint (nm_setting_ip_config_get_num_dns (s_ip6), ==, 0);
 	g_assert (!nm_setting_ip_config_get_gateway (s_ip6));
@@ -1064,7 +1217,7 @@ test_rd_znet_no_ip (void)
 }
 
 static void
-test_bootif (void)
+test_bootif_ip (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
 	const char *const*ARGV = NM_MAKE_STRV ("BOOTIF=00:53:AB:cd:02:03",
@@ -1102,6 +1255,42 @@ test_bootif (void)
 }
 
 static void
+test_bootif_no_ip (void)
+{
+	gs_unref_hashtable GHashTable *connections = NULL;
+	const char *const*ARGV = NM_MAKE_STRV ("BOOTIF=00:53:AB:cd:02:03");
+	NMConnection *connection;
+	NMSettingWired *s_wired;
+	NMSettingIPConfig *s_ip4;
+	NMSettingIPConfig *s_ip6;
+	gs_free char *hostname = NULL;
+
+	connections = nmi_cmdline_reader_parse (TEST_INITRD_DIR "/sysfs", ARGV, &hostname);
+	g_assert (connections);
+	g_assert_cmpint (g_hash_table_size (connections), ==, 1);
+	g_assert_cmpstr (hostname, ==, NULL);
+
+	connection = g_hash_table_lookup (connections, "default_connection");
+	g_assert (connection);
+	nmtst_assert_connection_verifies_without_normalization (connection);
+	g_assert_cmpstr (nm_connection_get_id (connection), ==, "Wired Connection");
+
+	s_wired = nm_connection_get_setting_wired (connection);
+	g_assert_cmpstr (nm_setting_wired_get_mac_address (s_wired), ==, "00:53:AB:CD:02:03");
+	g_assert (s_wired);
+
+	s_ip4 = nm_connection_get_setting_ip4_config (connection);
+	g_assert (s_ip4);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+	g_assert (nm_setting_ip_config_get_may_fail (s_ip4));
+
+	s_ip6 = nm_connection_get_setting_ip6_config (connection);
+	g_assert (s_ip6);
+	g_assert_cmpstr (nm_setting_ip_config_get_method (s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+	g_assert (nm_setting_ip_config_get_may_fail (s_ip6));
+}
+
+static void
 test_bootif_hwtype (void)
 {
 	gs_unref_hashtable GHashTable *connections = NULL;
@@ -1244,10 +1433,12 @@ int main (int argc, char **argv)
 	g_test_add_func ("/initrd/cmdline/some_more", test_some_more);
 	g_test_add_func ("/initrd/cmdline/bootdev", test_bootdev);
 	g_test_add_func ("/initrd/cmdline/bond", test_bond);
+	g_test_add_func ("/initrd/cmdline/bond/ip", test_bond_ip);
 	g_test_add_func ("/initrd/cmdline/bond/default", test_bond_default);
 	g_test_add_func ("/initrd/cmdline/team", test_team);
 	g_test_add_func ("/initrd/cmdline/bridge", test_bridge);
 	g_test_add_func ("/initrd/cmdline/bridge/default", test_bridge_default);
+	g_test_add_func ("/initrd/cmdline/bridge/ip", test_bridge_ip);
 	g_test_add_func ("/initrd/cmdline/ibft/ip_dev", test_ibft_ip_dev);
 	g_test_add_func ("/initrd/cmdline/ibft/ip", test_ibft_ip);
 	g_test_add_func ("/initrd/cmdline/ibft/rd_iscsi_ibft", test_ibft_rd_iscsi_ibft);
@@ -1255,7 +1446,8 @@ int main (int argc, char **argv)
 	g_test_add_func ("/initrd/cmdline/rd_znet", test_rd_znet);
 	g_test_add_func ("/initrd/cmdline/rd_znet/legacy", test_rd_znet_legacy);
 	g_test_add_func ("/initrd/cmdline/rd_znet/no_ip", test_rd_znet_no_ip);
-	g_test_add_func ("/initrd/cmdline/bootif", test_bootif);
+	g_test_add_func ("/initrd/cmdline/bootif/ip", test_bootif_ip);
+	g_test_add_func ("/initrd/cmdline/bootif/no_ip", test_bootif_no_ip);
 	g_test_add_func ("/initrd/cmdline/bootif/hwtype", test_bootif_hwtype);
 	g_test_add_func ("/initrd/cmdline/bootif/off", test_bootif_off);
 
diff --git a/src/ndisc/tests/test-ndisc-fake.c b/src/ndisc/tests/test-ndisc-fake.c
index 91fe9802..b65511e0 100644
--- a/src/ndisc/tests/test-ndisc-fake.c
+++ b/src/ndisc/tests/test-ndisc-fake.c
@@ -47,44 +47,47 @@ match_gateway (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts
 	g_assert_cmpint (gw->preference, ==, pref);
 }
 
-static void
-match_address (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt, guint32 preferred)
-{
-	const NMNDiscAddress *a;
-	char buf[INET6_ADDRSTRLEN];
-
-	g_assert (rdata);
-	g_assert_cmpint (idx, <, rdata->addresses_n);
-	g_assert (rdata->addresses);
-
-	a = &rdata->addresses[idx];
-
-	g_assert_cmpstr (inet_ntop (AF_INET6, &a->address, buf, sizeof (buf)), ==, addr);
-	g_assert_cmpint (a->timestamp, ==, ts);
-	g_assert_cmpint (a->lifetime, ==, lt);
-	g_assert_cmpint (a->preferred, ==, preferred);
-}
-
-static void
-match_route (const NMNDiscData *rdata, guint idx, const char *nw, int plen, const char *gw, guint32 ts, guint32 lt, NMIcmpv6RouterPref pref)
-{
-	const NMNDiscRoute *route;
-	char buf[INET6_ADDRSTRLEN];
-
-	g_assert (rdata);
-	g_assert_cmpint (idx, <, rdata->routes_n);
-	g_assert (rdata->routes);
-	g_assert (plen > 0 && plen <= 128);
-
-	route = &rdata->routes[idx];
-
-	g_assert_cmpstr (inet_ntop (AF_INET6, &route->network, buf, sizeof (buf)), ==, nw);
-	g_assert_cmpint ((int) route->plen, ==, plen);
-	g_assert_cmpstr (inet_ntop (AF_INET6, &route->gateway, buf, sizeof (buf)), ==, gw);
-	g_assert_cmpint (route->timestamp, ==, ts);
-	g_assert_cmpint (route->lifetime, ==, lt);
-	g_assert_cmpint (route->preference, ==, pref);
-}
+#define match_address(rdata, idx, addr, ts, lt, pref) \
+	G_STMT_START { \
+		const NMNDiscData *_rdata = (rdata); \
+		guint _idx = (idx); \
+		const NMNDiscAddress *_a; \
+		guint _ts = (ts); \
+		\
+		g_assert (_rdata); \
+		g_assert_cmpint (_idx, <, _rdata->addresses_n); \
+		g_assert (_rdata->addresses); \
+		\
+		_a = &_rdata->addresses[_idx]; \
+		\
+		nmtst_assert_ip6_address (&_a->address, (addr)); \
+		g_assert_cmpint (_a->timestamp, >=, _ts); \
+		g_assert_cmpint (_a->timestamp, <=, _ts + 1); \
+		g_assert_cmpint (_a->timestamp + _a->lifetime, ==, _ts + (lt)); \
+		g_assert_cmpint (_a->timestamp + _a->preferred, ==, _ts + (pref)); \
+	} G_STMT_END
+
+#define match_route(rdata, idx, nw, pl, gw, ts, lt, pref) \
+	G_STMT_START { \
+		const NMNDiscData *_rdata = (rdata); \
+		guint _idx = (idx); \
+		const NMNDiscRoute *_r; \
+		int _plen = (pl); \
+		\
+		g_assert (_rdata); \
+		g_assert_cmpint (_idx, <, _rdata->routes_n); \
+		g_assert (_rdata->routes); \
+		g_assert (_plen > 0 && _plen <= 128); \
+		\
+		_r = &_rdata->routes[idx]; \
+		\
+		nmtst_assert_ip6_address (&_r->network, (nw)); \
+		g_assert_cmpint ((int) _r->plen, ==, _plen); \
+		nmtst_assert_ip6_address (&_r->gateway, (gw)); \
+		g_assert_cmpint (_r->timestamp, ==, (ts)); \
+		g_assert_cmpint (_r->lifetime, ==, (lt)); \
+		g_assert_cmpint (_r->preference, ==, (pref)); \
+	} G_STMT_END
 
 static void
 match_dns_server (const NMNDiscData *rdata, guint idx, const char *addr, guint32 ts, guint32 lt)
diff --git a/src/nm-core-utils.h b/src/nm-core-utils.h
index fae7adbf..55792d57 100644
--- a/src/nm-core-utils.h
+++ b/src/nm-core-utils.h
@@ -156,8 +156,13 @@ double nm_utils_exp10 (gint16 e);
  * nm_utils_ip6_route_metric_normalize:
  * @metric: the route metric
  *
- * For IPv6 route, kernel treats the value 0 as IP6_RT_PRIO_USER (1024).
- * Thus, when comparing metric (values), we want to treat zero as NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6.
+ * For IPv6 route, when adding a route via netlink, kernel treats the value 0 as IP6_RT_PRIO_USER (1024).
+ * So, user space cannot add routes with such a metric, and 0 gets "normalized"
+ * to NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6.
+ *
+ * Note that kernel itself can add IPv6 routes with metric zero. Also, you can delete
+ * them, but mostly because with `ip -6 route delete ... metric 0` the 0 acts as a wildcard
+ * and kills the first matching route.
  *
  * Returns: @metric, if @metric is not zero, otherwise 1024.
  */
@@ -174,9 +179,8 @@ nm_utils_ip_route_metric_normalize (int addr_family, guint32 metric)
 }
 
 static inline guint32
-nm_utils_ip_route_metric_penalize (int addr_family, guint32 metric, guint32 penalty)
+nm_utils_ip_route_metric_penalize (guint32 metric, guint32 penalty)
 {
-	metric = nm_utils_ip_route_metric_normalize (addr_family, metric);
 	if (metric < G_MAXUINT32 - penalty)
 		return metric + penalty;
 	return G_MAXUINT32;
diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c
index 62b41478..490296c8 100644
--- a/src/nm-ip4-config.c
+++ b/src/nm-ip4-config.c
@@ -393,10 +393,6 @@ nm_ip_config_iter_ip4_route_init (NMDedupMultiIter *ipconf_iter, const NMIP4Conf
 const NMPObject *
 _nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NMPObject *obj_cmp)
 {
-	int addr_family;
-	int c;
-	guint metric_cur, metric_cmp;
-
 	nm_assert (   !obj_cur
 	           || NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_cur), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 	nm_assert (   !obj_cmp
@@ -410,17 +406,20 @@ _nm_ip_config_best_default_route_find_better (const NMPObject *obj_cur, const NM
 	 * @obj_cmp is also a default route and returns the best of both. */
 	if (   obj_cmp
 	    && nm_ip_config_best_default_route_is (obj_cmp)) {
+		guint32 metric_cur, metric_cmp;
+
 		if (!obj_cur)
 			return obj_cmp;
 
-		addr_family = NMP_OBJECT_GET_CLASS (obj_cmp)->addr_family;
-		metric_cur = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cur)->metric);
-		metric_cmp = nm_utils_ip_route_metric_normalize (addr_family, NMP_OBJECT_CAST_IP_ROUTE (obj_cmp)->metric);
+		metric_cur = NMP_OBJECT_CAST_IP_ROUTE (obj_cur)->metric;
+		metric_cmp = NMP_OBJECT_CAST_IP_ROUTE (obj_cmp)->metric;
 
 		if (metric_cmp < metric_cur)
 			return obj_cmp;
 
 		if (metric_cmp == metric_cur) {
+			int c;
+
 			/* Routes have the same metric. We still want to deterministically
 			 * prefer one or the other. It's important to consistently choose one
 			 * or the other, so that the order doesn't matter how routes are added
@@ -672,6 +671,28 @@ nm_ip4_config_update_routes_metric (NMIP4Config *self, gint64 metric)
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
+static void
+_add_local_route_from_addr4 (NMIP4Config *self,
+                            const NMPlatformIP4Address *addr,
+                            int ifindex)
+{
+	nm_auto_nmpobj NMPObject *r = NULL;
+	NMPlatformIP4Route *route;
+
+	r = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, NULL);
+	route = NMP_OBJECT_CAST_IP4_ROUTE (r);
+	route->ifindex = ifindex;
+	route->rt_source = NM_IP_CONFIG_SOURCE_KERNEL;
+	route->network = addr->address;
+	route->plen = 32;
+	route->pref_src = addr->address;
+	route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL);
+	route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL);
+	route->scope_inv = nm_platform_route_scope_inv (RT_SCOPE_HOST);
+
+	_add_route (self, r, NULL, NULL);
+}
+
 void
 nm_ip4_config_add_dependent_routes (NMIP4Config *self,
                                     guint32 route_table,
@@ -708,6 +729,8 @@ nm_ip4_config_add_dependent_routes (NMIP4Config *self,
 		if (my_addr->external)
 			continue;
 
+		_add_local_route_from_addr4 (self, my_addr, ifindex);
+
 		if (_ipv4_is_zeronet (network)) {
 			/* Kernel doesn't add device-routes for destinations that
 			 * start with 0.x.y.z. Skip them. */
@@ -1002,6 +1025,7 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 	for (i = 0; i < nroutes; i++) {
 		NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
 		NMPlatformIP4Route route;
+		gint64 m;
 
 		if (nm_ip_route_get_family (s_route) != AF_INET) {
 			nm_assert_not_reached ();
@@ -1015,10 +1039,11 @@ nm_ip4_config_merge_setting (NMIP4Config *self,
 		nm_assert (route.plen <= 32);
 
 		nm_ip_route_get_next_hop_binary (s_route, &route.gateway);
-		if (nm_ip_route_get_metric (s_route) == -1)
+		m = nm_ip_route_get_metric (s_route);
+		if (m < 0)
 			route.metric = route_metric;
 		else
-			route.metric = nm_ip_route_get_metric (s_route);
+			route.metric = m;
 		route.rt_source = NM_IP_CONFIG_SOURCE_USER;
 
 		route.network = nm_utils_ip4_address_clear_host_address (route.network, route.plen);
@@ -1141,8 +1166,10 @@ nm_ip4_config_create_setting (const NMIP4Config *self)
 			continue;
 
 		s_route = nm_ip_route_new_binary (AF_INET,
-		                                  &route->network, route->plen,
-		                                  &route->gateway, route->metric,
+		                                  &route->network,
+		                                  route->plen,
+		                                  &route->gateway,
+		                                  route->metric,
 		                                  NULL);
 		nm_setting_ip_config_add_route (s_ip4, s_route);
 		nm_ip_route_unref (s_route);
@@ -1227,7 +1254,7 @@ nm_ip4_config_merge (NMIP4Config *dst,
 				if (default_route_metric_penalty) {
 					NMPlatformIP4Route r = *r_src;
 
-					r.metric = nm_utils_ip_route_metric_penalize (AF_INET, r.metric, default_route_metric_penalty);
+					r.metric = nm_utils_ip_route_metric_penalize (r.metric, default_route_metric_penalty);
 					_add_route (dst, NULL, &r, NULL);
 					continue;
 				}
@@ -1459,7 +1486,7 @@ nm_ip4_config_subtract (NMIP4Config *dst,
 			 * the routes. */
 			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_src);
 			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
-			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+			rr->metric = nm_utils_ip_route_metric_penalize (rr->metric, default_route_metric_penalty);
 		} else
 			o_lookup = o_src;
 
@@ -1610,7 +1637,7 @@ _nm_ip4_config_intersect_helper (NMIP4Config *dst,
 			 * the routes. */
 			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_dst);
 			rr = NMP_OBJECT_CAST_IP4_ROUTE (&o_lookup_copy);
-			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET, rr->metric, default_route_metric_penalty);
+			rr->metric = nm_utils_ip_route_metric_penalize (rr->metric, default_route_metric_penalty);
 		} else
 			o_lookup = o_dst;
 
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index b60e8057..4911ec1d 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -456,6 +456,42 @@ nm_ip6_config_update_routes_metric (NMIP6Config *self, gint64 metric)
 	g_object_thaw_notify (G_OBJECT (self));
 }
 
+static void
+_add_multicast_route6 (NMIP6Config *self, int ifindex)
+{
+	nm_auto_nmpobj NMPObject *r = NULL;
+	NMPlatformIP6Route *route;
+
+	r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+	route = NMP_OBJECT_CAST_IP6_ROUTE (r);
+	route->ifindex = ifindex;
+	route->network.s6_addr[0] = 0xffu;
+	route->plen = 8;
+	route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL);
+	route->type_coerced = nm_platform_route_type_coerce (RTN_UNICAST);
+	route->metric = 256;
+
+	_add_route (self, r, NULL, NULL);
+}
+
+static void
+_add_local_route_from_addr6 (NMIP6Config *self, const NMPlatformIP6Address *addr, int ifindex)
+{
+	nm_auto_nmpobj NMPObject *r = NULL;
+	NMPlatformIP6Route *route;
+
+	r = nmp_object_new (NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+	route = NMP_OBJECT_CAST_IP6_ROUTE (r);
+	route->ifindex = ifindex;
+	route->network = addr->address;
+	route->plen = 128;
+	route->table_coerced = nm_platform_route_table_coerce (RT_TABLE_LOCAL);
+	route->type_coerced = nm_platform_route_type_coerce (RTN_LOCAL);
+	route->metric = 0;
+
+	_add_route (self, r, NULL, NULL);
+}
+
 void
 nm_ip6_config_add_dependent_routes (NMIP6Config *self,
                                     guint32 route_table,
@@ -476,6 +512,9 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self,
 	 *
 	 * For manually added IPv6 routes, add the device routes explicitly. */
 
+	/* Pre-generate multicast route */
+	_add_multicast_route6 (self, ifindex);
+
 	nm_ip_config_iter_ip6_address_for_each (&iter, self, &my_addr) {
 		NMPlatformIP6Route *route;
 		gboolean has_peer;
@@ -483,6 +522,10 @@ nm_ip6_config_add_dependent_routes (NMIP6Config *self,
 
 		if (my_addr->external)
 			continue;
+
+		/* Pre-generate local route added by kernel */
+		_add_local_route_from_addr6 (self, my_addr, ifindex);
+
 		if (NM_FLAGS_HAS (my_addr->n_ifa_flags, IFA_F_NOPREFIXROUTE))
 			continue;
 		if (my_addr->plen == 0)
@@ -650,6 +693,7 @@ nm_ip6_config_merge_setting (NMIP6Config *self,
 	for (i = 0; i < nroutes; i++) {
 		NMIPRoute *s_route = nm_setting_ip_config_get_route (setting, i);
 		NMPlatformIP6Route route;
+		gint64 m;
 
 		if (nm_ip_route_get_family (s_route) != AF_INET6) {
 			nm_assert_not_reached ();
@@ -663,10 +707,11 @@ nm_ip6_config_merge_setting (NMIP6Config *self,
 		nm_assert (route.plen <= 128);
 
 		nm_ip_route_get_next_hop_binary (s_route, &route.gateway);
-		if (nm_ip_route_get_metric (s_route) == -1)
+		m = nm_ip_route_get_metric (s_route);
+		if (m < 0)
 			route.metric = route_metric;
 		else
-			route.metric = nm_ip_route_get_metric (s_route);
+			route.metric = nm_utils_ip6_route_metric_normalize (m);
 		route.rt_source = NM_IP_CONFIG_SOURCE_USER;
 
 		nm_utils_ip6_address_clear_host_address (&route.network, &route.network, route.plen);
@@ -797,8 +842,10 @@ nm_ip6_config_create_setting (const NMIP6Config *self)
 			continue;
 
 		s_route = nm_ip_route_new_binary (AF_INET6,
-		                                  &route->network, route->plen,
-		                                  &route->gateway, route->metric,
+		                                  &route->network,
+		                                  route->plen,
+		                                  &route->gateway,
+		                                  route->metric,
 		                                  NULL);
 		nm_setting_ip_config_add_route (s_ip6, s_route);
 		nm_ip_route_unref (s_route);
@@ -882,7 +929,7 @@ nm_ip6_config_merge (NMIP6Config *dst,
 				if (default_route_metric_penalty) {
 					NMPlatformIP6Route r = *r_src;
 
-					r.metric = nm_utils_ip_route_metric_penalize (AF_INET6, r.metric, default_route_metric_penalty);
+					r.metric = nm_utils_ip_route_metric_penalize (r.metric, default_route_metric_penalty);
 					_add_route (dst, NULL, &r, NULL);
 					continue;
 				}
@@ -1052,7 +1099,7 @@ nm_ip6_config_subtract (NMIP6Config *dst,
 			 * the routes. */
 			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_src);
 			rr = NMP_OBJECT_CAST_IP6_ROUTE (&o_lookup_copy);
-			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET6, rr->metric, default_route_metric_penalty);
+			rr->metric = nm_utils_ip_route_metric_penalize (rr->metric, default_route_metric_penalty);
 		} else
 			o_lookup = o_src;
 
@@ -1173,7 +1220,7 @@ _nm_ip6_config_intersect_helper (NMIP6Config *dst,
 			 * the routes. */
 			o_lookup = nmp_object_stackinit_obj (&o_lookup_copy, o_dst);
 			rr = NMP_OBJECT_CAST_IP6_ROUTE (&o_lookup_copy);
-			rr->metric = nm_utils_ip_route_metric_penalize (AF_INET6, rr->metric, default_route_metric_penalty);
+			rr->metric = nm_utils_ip_route_metric_penalize (rr->metric, default_route_metric_penalty);
 		} else
 			o_lookup = o_dst;
 
@@ -2052,8 +2099,8 @@ nm_ip6_config_get_direct_route_for_host (const NMIP6Config *self,
 		if (!nm_utils_ip6_address_same_prefix (host, &item->network, item->plen))
 			continue;
 
-		if (best_route &&
-		    nm_utils_ip6_route_metric_normalize (best_route->metric) <= nm_utils_ip6_route_metric_normalize (item->metric))
+		if (   best_route
+		    && best_route->metric <= item->metric)
 			continue;
 
 		best_route = item;
diff --git a/src/nm-manager.c b/src/nm-manager.c
index ae78b7c9..92112532 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -3044,10 +3044,9 @@ _get_best_connectivity (NMManager *self, int addr_family)
 		gint64 metric;
 
 		r = nm_device_get_best_default_route (dev, addr_family);
-		if (r) {
-			metric = nm_utils_ip_route_metric_normalize (addr_family,
-			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
-		} else {
+		if (r)
+			metric = NMP_OBJECT_CAST_IP_ROUTE (r)->metric;
+		else {
 			/* if all devices have no default-route, we still include the best
 			 * of all connectivity state of all the devices. */
 			metric = G_MAXINT64;
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 2a9f6c05..62ead242 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -442,14 +442,12 @@ get_best_active_connection (NMPolicy *self,
 			 *
 			 * In this case, is it really the best device? Why do we even need the best
 			 * device?? */
-			metric = nm_utils_ip_route_metric_normalize (addr_family,
-			                                             NMP_OBJECT_CAST_IP_ROUTE (r)->metric);
+			metric = NMP_OBJECT_CAST_IP_ROUTE (r)->metric;
 			is_fully_activated = TRUE;
 		} else if (   !fully_activated
 		           && (connection = nm_device_get_applied_connection (device))
 		           && nm_utils_connection_has_default_route (connection, addr_family, NULL)) {
-			metric = nm_utils_ip_route_metric_normalize (addr_family,
-			                                             nm_device_get_route_metric (device, addr_family));
+			metric = nm_device_get_route_metric (device, addr_family);
 			is_fully_activated = FALSE;
 		} else
 			continue;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 710a6f91..fbcc2b31 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -3398,6 +3398,7 @@ rta_multipath_done:
 
 	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
 
+	obj->ip_route.type_coerced = nm_platform_route_type_coerce (rtm->rtm_type);
 	obj->ip_route.table_coerced = nm_platform_route_table_coerce (  tb[RTA_TABLE]
 	                                                              ? nla_get_u32 (tb[RTA_TABLE])
 	                                                              : (guint32) rtm->rtm_table);
@@ -7249,7 +7250,7 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	nm_auto_pop_netns NMPNetns *netns = NULL;
 	nm_auto_close int dirfd = -1;
 	char ifname[IFNAMSIZ];
-	int total = -1;
+	int num = -1;
 
 	if (!nm_platform_netns_push (platform, &netns))
 		return FALSE;
@@ -7258,13 +7259,13 @@ link_supports_sriov (NMPlatform *platform, int ifindex)
 	if (dirfd < 0)
 		return FALSE;
 
-	total = nm_platform_sysctl_get_int32 (platform,
-	                                      NMP_SYSCTL_PATHID_NETDIR (dirfd,
-	                                                                ifname,
-	                                                                "device/sriov_totalvfs"),
-	                                      -1);
+	num = nm_platform_sysctl_get_int32 (platform,
+	                                    NMP_SYSCTL_PATHID_NETDIR (dirfd,
+	                                                              ifname,
+	                                                              "device/sriov_numvfs"),
+	                                    -1);
 
-	return total > 0;
+	return num != -1;
 }
 
 static int
@@ -7407,15 +7408,7 @@ link_set_sriov_params_async (NMPlatform *platform,
 	                                                                      ifname,
 	                                                                      "device/sriov_totalvfs"),
 	                                            10, 0, G_MAXUINT, 0);
-	if (errno) {
-		g_set_error (&error,
-		             NM_UTILS_ERROR,
-		             NM_UTILS_ERROR_UNKNOWN,
-		             "failed reading sriov_totalvfs value: %s",
-		             nm_strerror_native (errno));
-		goto out_idle;
-	}
-	if (num_vfs > total) {
+	if (!errno && num_vfs > total) {
 		_LOGW ("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs);
 		num_vfs = total;
 	}
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index c8b8c6e8..4702ba97 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -4314,6 +4314,13 @@ nm_platform_ip_route_sync (NMPlatform *self,
 				continue;
 			}
 
+			if (   !IS_IPv4
+			    && NMP_OBJECT_CAST_IP6_ROUTE (conf_o)->metric == 0) {
+				/* User space cannot add routes with metric 0. However, kernel can, and we might track such
+				 * routes in @route as they are present external. Skip them silently. */
+				continue;
+			}
+
 			plat_entry = nm_platform_lookup_entry (self,
 			                                       NMP_CACHE_ID_TYPE_OBJECT_TYPE,
 			                                       conf_o);
@@ -4571,7 +4578,6 @@ nm_platform_ip_route_normalize (int addr_family,
 		r6->table_coerced = nm_platform_route_table_coerce (nm_platform_route_table_uncoerce (r6->table_coerced, TRUE));
 		nm_utils_ip6_address_clear_host_address (&r6->network, &r6->network, r6->plen);
 		r6->rt_source = nmp_utils_ip_config_source_round_trip_rtprot (r6->rt_source),
-		r6->metric = nm_utils_ip6_route_metric_normalize (r6->metric);
 		nm_utils_ip6_address_clear_host_address (&r6->src, &r6->src, r6->src_plen);
 		break;
 	default:
@@ -6083,11 +6089,11 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	char s_network[INET_ADDRSTRLEN], s_gateway[INET_ADDRSTRLEN];
 	char s_pref_src[INET_ADDRSTRLEN];
 	char str_dev[TO_STRING_DEV_BUF_SIZE];
-	char str_type[30];
 	char str_table[30];
 	char str_scope[30], s_source[50];
 	char str_tos[32], str_window[32], str_cwnd[32], str_initcwnd[32], str_initrwnd[32], str_mtu[32];
 	char str_rtm_flags[_RTM_FLAGS_TO_STRING_MAXLEN];
+	char str_type[30];
 
 	if (!nm_utils_to_string_buffer_init_null (route, &buf, &len))
 		return buf;
@@ -6098,7 +6104,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
 	g_snprintf (buf, len,
-	            "%s" /* type */
+	            "type %s " /* type */
 	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
@@ -6116,7 +6122,7 @@ nm_platform_ip4_route_to_string (const NMPlatformIP4Route *route, char *buf, gsi
 	            "%s" /* initrwnd */
 	            "%s" /* mtu */
 	            "",
-	            route->type_coerced ? nm_sprintf_buf (str_type, "type %s ", nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), NULL, 0)) : "",
+	            nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), str_type, sizeof (str_type)),
 	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
@@ -6186,7 +6192,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	_to_string_dev (NULL, route->ifindex, str_dev, sizeof (str_dev));
 
 	g_snprintf (buf, len,
-	            "%s" /* type */
+	            "type %s " /* type */
 	            "%s" /* table */
 	            "%s/%d"
 	            " via %s"
@@ -6204,7 +6210,7 @@ nm_platform_ip6_route_to_string (const NMPlatformIP6Route *route, char *buf, gsi
 	            "%s" /* mtu */
 	            "%s" /* pref */
 	            "",
-	            route->type_coerced ? nm_sprintf_buf (str_type, "type %s ", nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), NULL, 0)) : "",
+	            nm_utils_route_type2str (nm_platform_route_type_uncoerce (route->type_coerced), str_type, sizeof (str_type)),
 	            route->table_coerced ? nm_sprintf_buf (str_table, "table %u ", nm_platform_route_table_uncoerce (route->table_coerced, FALSE)) : "",
 	            s_network,
 	            route->plen,
@@ -7463,7 +7469,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
 		                     obj->plen,
-		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     obj->metric,
 		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
 		                     obj->src_plen);
 		break;
@@ -7473,7 +7479,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		                     nm_platform_route_table_uncoerce (obj->table_coerced, TRUE),
 		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
 		                     obj->plen,
-		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     obj->metric,
 		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
 		                     obj->src_plen,
 		                     /* on top of WEAK_ID: */
@@ -7487,7 +7493,7 @@ nm_platform_ip6_route_hash_update (const NMPlatformIP6Route *obj, NMPlatformIPRo
 		                     obj->ifindex,
 		                     *nm_utils_ip6_address_clear_host_address (&a1, &obj->network, obj->plen),
 		                     obj->plen,
-		                     nm_utils_ip6_route_metric_normalize (obj->metric),
+		                     obj->metric,
 		                     obj->gateway,
 		                     obj->pref_src,
 		                     *nm_utils_ip6_address_clear_host_address (&a2, &obj->src, obj->src_plen),
@@ -7550,7 +7556,7 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 		               nm_platform_route_table_uncoerce (b->table_coerced, TRUE));
 		NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->network, &b->network, MIN (a->plen, b->plen));
 		NM_CMP_FIELD (a, b, plen);
-		NM_CMP_DIRECT (nm_utils_ip6_route_metric_normalize (a->metric), nm_utils_ip6_route_metric_normalize (b->metric));
+		NM_CMP_FIELD (a, b, metric);
 		NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX (&a->src, &b->src, MIN (a->src_plen, b->src_plen));
 		NM_CMP_FIELD (a, b, src_plen);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
@@ -7573,10 +7579,7 @@ nm_platform_ip6_route_cmp (const NMPlatformIP6Route *a, const NMPlatformIP6Route
 		else
 			NM_CMP_FIELD_IN6ADDR (a, b, network);
 		NM_CMP_FIELD (a, b, plen);
-		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-			NM_CMP_DIRECT (nm_utils_ip6_route_metric_normalize (a->metric), nm_utils_ip6_route_metric_normalize (b->metric));
-		else
-			NM_CMP_FIELD (a, b, metric);
+		NM_CMP_FIELD (a, b, metric);
 		NM_CMP_FIELD_IN6ADDR (a, b, gateway);
 		NM_CMP_FIELD_IN6ADDR (a, b, pref_src);
 		if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
@@ -8054,14 +8057,6 @@ nm_platform_netns_push (NMPlatform *self, NMPNetns **netns)
 
 /*****************************************************************************/
 
-static guint32
-_vtr_v4_metric_normalize (guint32 metric)
-{
-	return metric;
-}
-
-/*****************************************************************************/
-
 const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
 	.v4 = {
 		.is_ip4                         = TRUE,
@@ -8070,7 +8065,6 @@ const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
 		.sizeof_route                   = sizeof (NMPlatformIP4Route),
 		.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip4_route_cmp,
 		.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip4_route_to_string,
-		.metric_normalize               = _vtr_v4_metric_normalize,
 	},
 	.v6 = {
 		.is_ip4                         = FALSE,
@@ -8079,7 +8073,6 @@ const _NMPlatformVTableRouteUnion nm_platform_vtable_route = {
 		.sizeof_route                   = sizeof (NMPlatformIP6Route),
 		.route_cmp                      = (int (*) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type)) nm_platform_ip6_route_cmp,
 		.route_to_string                = (const char *(*) (const NMPlatformIPXRoute *route, char *buf, gsize len)) nm_platform_ip6_route_to_string,
-		.metric_normalize               = nm_utils_ip6_route_metric_normalize,
 	},
 };
 
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 3e6ef84c..2b5d4914 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -382,7 +382,10 @@ typedef union {
 #define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 0
 
 /* Default value for adding an IPv6 route. This is also what iproute2 does.
- * Adding an IPv6 route with metric 0, kernel translates to IP6_RT_PRIO_USER (1024). */
+ * Adding an IPv6 route with metric 0, kernel translates to IP6_RT_PRIO_USER (1024).
+ *
+ * Note that kernel doesn't allow adding IPv6 routes with metric zero via netlink.
+ * It however can itself add routes with metric zero. */
 #define NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 1024
 
 /* For IPv4, kernel adds a device route (subnet routes) with metric 0 when user
@@ -720,7 +723,6 @@ typedef struct {
 	gsize sizeof_route;
 	int (*route_cmp) (const NMPlatformIPXRoute *a, const NMPlatformIPXRoute *b, NMPlatformIPRouteCmpType cmp_type);
 	const char *(*route_to_string) (const NMPlatformIPXRoute *route, char *buf, gsize len);
-	guint32 (*metric_normalize) (guint32 metric);
 } NMPlatformVTableRoute;
 
 typedef union {
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 19debfb5..42b5baf0 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -319,7 +319,7 @@ test_ip6_route (void)
 	guint8 plen = 64;
 	struct in6_addr gateway, pref_src;
 	/* Choose a high metric so that we hopefully don't conflict. */
-	int metric = 22987;
+	const int metric = 22987;
 	int mss = 1000;
 
 	inet_pton (AF_INET6, "2001:db8:a:b:0:0:0:0", &network);
@@ -328,7 +328,7 @@ test_ip6_route (void)
 
 	g_assert (nm_platform_ip6_address_add (NM_PLATFORM_GET, ifindex, pref_src, 128, in6addr_any,
 	                                       NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0));
-	accept_signals (route_added, 0, 2);
+	accept_signals (route_added, 0, 3);
 
 	_wait_for_ipv6_addr_non_tentative (NM_PLATFORM_GET, 200, ifindex, 1, &pref_src);
 
@@ -365,7 +365,7 @@ test_ip6_route (void)
 	rts[0].ifindex = ifindex;
 	rts[0].gateway = in6addr_any;
 	rts[0].pref_src = in6addr_any;
-	rts[0].metric = nm_utils_ip6_route_metric_normalize (metric);
+	rts[0].metric = metric;
 	rts[0].mss = mss;
 	rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[1].network = network;
@@ -373,7 +373,7 @@ test_ip6_route (void)
 	rts[1].ifindex = ifindex;
 	rts[1].gateway = gateway;
 	rts[1].pref_src = pref_src;
-	rts[1].metric = nm_utils_ip6_route_metric_normalize (metric);
+	rts[1].metric = metric;
 	rts[1].mss = mss;
 	rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot (NM_IP_CONFIG_SOURCE_USER);
 	rts[2].network = in6addr_any;
@@ -381,7 +381,7 @@ test_ip6_route (void)
 	rts[2].ifindex = ifindex;
 	rts[2].gateway = gateway;
 	rts[2].pref_src = in6addr_any;
-	rts[2].metric = nm_utils_ip6_route_metric_normalize (metric);
+	rts[2].metric = metric;
 	rts[2].mss = mss;
 	g_assert_cmpint (routes->len, ==, 3);
 	nmtst_platform_ip6_routes_equal_aptr ((const NMPObject *const*) routes->pdata, rts, routes->len, TRUE);
@@ -586,7 +586,13 @@ test_ip6_route_get (void)
 
 	NMTST_WAIT_ASSERT (100, {
 		nmtstp_wait_for_signal (NM_PLATFORM_GET, 10);
-		if (nmtstp_ip6_route_get (NM_PLATFORM_GET, ifindex, nmtst_inet6_from_string ("fd01:abcd::"), 64, 0, NULL, 0))
+		if (nmtstp_ip6_route_get (NM_PLATFORM_GET,
+		                          ifindex,
+		                          nmtst_inet6_from_string ("fd01:abcd::"),
+		                          64,
+		                          NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+		                          NULL,
+		                          0))
 			break;
 	});