summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2018-02-07 02:39:23 +0100
committerMichael Biebl <biebl@debian.org>2018-02-07 02:39:23 +0100
commit50f6b47074e01dffb8dc536c0a20961dcf28ae9b (patch)
treeb4bd771d78ebe38661b8c4ec8b9a8e0eb8261eb5 /src
parentafcd268ea7b1149fbfb66bce4eca659b675da0a2 (diff)
New upstream version 1.10.4 upstream/1.10.4
Diffstat (limited to 'src')
-rw-r--r--src/devices/nm-device.c67
-rw-r--r--src/devices/nm-device.h2
-rw-r--r--src/devices/ovs/nm-device-ovs-interface.c24
-rw-r--r--src/devices/tests/test-arping.c2
-rw-r--r--src/devices/wwan/nm-modem-broadband.c52
-rw-r--r--src/devices/wwan/nm-modem.c5
-rw-r--r--src/dhcp/nm-dhcp-client.c68
-rw-r--r--src/dhcp/nm-dhcp-client.h9
-rw-r--r--src/dhcp/nm-dhcp-dhclient-utils.c8
-rw-r--r--src/dhcp/nm-dhcp-systemd.c18
-rw-r--r--src/dhcp/nm-dhcp-utils.c9
-rw-r--r--src/nm-config.c266
-rw-r--r--src/nm-config.h23
-rw-r--r--src/nm-core-utils.c3
-rw-r--r--src/nm-manager.c264
-rw-r--r--src/nm-manager.h7
-rw-r--r--src/nm-policy.c2
-rw-r--r--src/nm-types.h2
-rw-r--r--src/platform/nm-linux-platform.c7
-rw-r--r--src/platform/nm-platform.c9
-rw-r--r--src/settings/nm-settings-connection.c24
-rw-r--r--src/settings/nm-settings-connection.h7
-rw-r--r--src/settings/nm-settings.c17
-rw-r--r--src/settings/plugins/ibft/nms-ibft-connection.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nm-inotify-helper.c13
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c2
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c6
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c17
-rw-r--r--src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c6
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-connection.c2
-rw-r--r--src/settings/plugins/ifnet/nms-ifnet-plugin.c6
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-connection.c2
-rw-r--r--src/settings/plugins/keyfile/nms-keyfile-plugin.c4
-rw-r--r--src/systemd/src/libsystemd-network/sd-dhcp-client.c1
34 files changed, 757 insertions, 199 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index afc81dcd..6f9289ee 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -87,7 +87,7 @@ _LOG_DECLARE_SELF (NMDevice);
 #define DHCP_NUM_TRIES_MAX     3
 #define DEFAULT_AUTOCONNECT    TRUE
 
-#define CARRIER_WAIT_TIME_MS 5000
+#define CARRIER_WAIT_TIME_MS 6000
 #define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
 
 #define NM_DEVICE_AUTH_RETRIES_UNSET    -1
@@ -650,6 +650,8 @@ NM_UTILS_LOOKUP_STR_DEFINE (nm_device_state_reason_to_str, NMDeviceStateReason,
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_CHANGED,                 "parent-changed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,         "parent-managed-changed"),
 	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_OVSDB_FAILED,                   "ovsdb-failed"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE,           "ip-address-duplicate"),
+	NM_UTILS_LOOKUP_STR_ITEM (NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED,          "ip-method-unsupported"),
 );
 
 #define reason_to_string(reason) \
@@ -1616,8 +1618,8 @@ nm_device_get_metered (NMDevice *self)
 	return NM_DEVICE_GET_PRIVATE (self)->metered;
 }
 
-static guint32
-_get_route_metric_default (NMDevice *self)
+guint32
+nm_device_get_route_metric_default (NMDeviceType device_type)
 {
 	/* Device 'priority' is used for the default route-metric and is based on
 	 * the device type. The settings ipv4.route-metric and ipv6.route-metric
@@ -1636,7 +1638,7 @@ _get_route_metric_default (NMDevice *self)
 	 * metrics (except for IPv6, where 0 means 1024).
 	 */
 
-	switch (nm_device_get_device_type (self)) {
+	switch (device_type) {
 	/* 50 is reserved for VPN (NM_VPN_ROUTE_METRIC_DEFAULT) */
 	case NM_DEVICE_TYPE_ETHERNET:
 	case NM_DEVICE_TYPE_VETH:
@@ -1765,7 +1767,10 @@ nm_device_get_route_metric (NMDevice *self,
 		if (route_metric >= 0)
 			goto out;
 	}
-	route_metric = _get_route_metric_default (self);
+
+	route_metric = nm_manager_device_route_metric_reserve (nm_manager_get (),
+	                                                       nm_device_get_ip_ifindex (self),
+	                                                       nm_device_get_device_type (self));
 out:
 	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
 }
@@ -5490,7 +5495,7 @@ ipv4_manual_method_apply (NMDevice *self, NMIP4Config **configs, gboolean succes
 		g_object_unref (empty);
 	} else {
 		nm_device_ip_method_failed (self, AF_INET,
-		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
 	}
 }
 
@@ -6074,7 +6079,7 @@ dhcp4_dad_cb (NMDevice *self, NMIP4Config **configs, gboolean success)
 		nm_device_activate_schedule_ip4_config_result (self, configs[1]);
 	else {
 		nm_device_ip_method_failed (self, AF_INET,
-		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		                            NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
 	}
 }
 
@@ -8156,6 +8161,12 @@ nm_device_activate_stage3_ip4_start (NMDevice *self)
 
 	g_assert (priv->ip4_state == IP_WAIT);
 
+	if (nm_device_sys_iface_state_is_external (self)) {
+		_set_ip_state (self, AF_INET, IP_DONE);
+		check_ip_state (self, FALSE);
+		return TRUE;
+	}
+
 	_set_ip_state (self, AF_INET, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &ip4_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
@@ -8197,6 +8208,12 @@ nm_device_activate_stage3_ip6_start (NMDevice *self)
 
 	g_assert (priv->ip6_state == IP_WAIT);
 
+	if (nm_device_sys_iface_state_is_external (self)) {
+		_set_ip_state (self, AF_INET6, IP_DONE);
+		check_ip_state (self, FALSE);
+		return TRUE;
+	}
+
 	_set_ip_state (self, AF_INET6, IP_CONF);
 	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &ip6_config, &failure_reason);
 	if (ret == NM_ACT_STAGE_RETURN_SUCCESS) {
@@ -12482,6 +12499,11 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
 
 	_cancel_activation (self);
 
+	if (cleanup_type != CLEANUP_TYPE_KEEP) {
+		nm_manager_device_route_metric_clear (nm_manager_get (),
+		                                      nm_device_get_ip_ifindex (self));
+	}
+
 	if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
 	    && priv->fw_state >= FIREWALL_STATE_INITIALIZED
 	    && priv->fw_mgr
@@ -13512,6 +13534,7 @@ nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze)
 	gboolean success_read;
 	int ifindex;
 	const NMPlatformLink *pllink;
+	const NMConfigDeviceStateData *dev_state;
 
 	if (priv->hw_addr_perm) {
 		/* the permanent hardware address is only read once and not
@@ -13571,23 +13594,19 @@ nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze)
 	/* We also persist our choice of the fake address to the device state
 	 * file to use the same address on restart of NetworkManager.
 	 * First, try to reload the address from the state file. */
-	{
-		gs_free NMConfigDeviceStateData *dev_state = NULL;
-
-		dev_state = nm_config_device_state_load (ifindex);
-		if (   dev_state
-		    && dev_state->perm_hw_addr_fake
-		    && nm_utils_hwaddr_aton (dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len)
-		    && !nm_utils_hwaddr_matches (buf, priv->hw_addr_len, priv->hw_addr, -1)) {
-			_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use from statefile: %s, current: %s)",
-			       success_read
-			           ? "read HW addr length of permanent MAC address differs"
-			           : "unable to read permanent MAC address",
-			       dev_state->perm_hw_addr_fake,
-			       priv->hw_addr);
-			priv->hw_addr_perm = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
-			goto notify_and_out;
-		}
+	dev_state = nm_config_device_state_get (nm_config_get (), ifindex);
+	if (   dev_state
+	    && dev_state->perm_hw_addr_fake
+	    && nm_utils_hwaddr_aton (dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len)
+	    && !nm_utils_hwaddr_matches (buf, priv->hw_addr_len, priv->hw_addr, -1)) {
+		_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use from statefile: %s, current: %s)",
+		       success_read
+		           ? "read HW addr length of permanent MAC address differs"
+		           : "unable to read permanent MAC address",
+		       dev_state->perm_hw_addr_fake,
+		       priv->hw_addr);
+		priv->hw_addr_perm = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
+		goto notify_and_out;
 	}
 
 	_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use current: %s)",
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 810a613d..ac73ee0c 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -450,6 +450,8 @@ NMMetered       nm_device_get_metered           (NMDevice *dev);
 guint32         nm_device_get_route_table       (NMDevice *self, int addr_family, gboolean fallback_main);
 guint32         nm_device_get_route_metric      (NMDevice *dev, int addr_family);
 
+guint32         nm_device_get_route_metric_default (NMDeviceType device_type);
+
 const char *    nm_device_get_hw_address        (NMDevice *dev);
 const char *    nm_device_get_permanent_hw_address (NMDevice *self);
 const char *    nm_device_get_permanent_hw_address_full (NMDevice *self,
diff --git a/src/devices/ovs/nm-device-ovs-interface.c b/src/devices/ovs/nm-device-ovs-interface.c
index 426521c5..e746a3fd 100644
--- a/src/devices/ovs/nm-device-ovs-interface.c
+++ b/src/devices/ovs/nm-device-ovs-interface.c
@@ -35,8 +35,13 @@ _LOG_DECLARE_SELF(NMDeviceOvsInterface);
 
 /*****************************************************************************/
 
+typedef struct {
+	bool waiting_for_interface:1;
+} NMDeviceOvsInterfacePrivate;
+
 struct _NMDeviceOvsInterface {
 	NMDevice parent;
+	NMDeviceOvsInterfacePrivate _priv;
 };
 
 struct _NMDeviceOvsInterfaceClass {
@@ -45,6 +50,8 @@ struct _NMDeviceOvsInterfaceClass {
 
 G_DEFINE_TYPE (NMDeviceOvsInterface, nm_device_ovs_interface, NM_TYPE_DEVICE)
 
+#define NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceOvsInterface, NM_IS_DEVICE_OVS_INTERFACE)
+
 /*****************************************************************************/
 
 static const char *
@@ -109,7 +116,10 @@ static void
 link_changed (NMDevice *device,
               const NMPlatformLink *pllink)
 {
-	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
+
+	if (priv->waiting_for_interface) {
+		priv->waiting_for_interface = FALSE;
 		nm_device_bring_up (device, TRUE, NULL);
 		nm_device_activate_schedule_stage3_ip_config_start (device);
 	}
@@ -131,11 +141,15 @@ act_stage3_ip4_config_start (NMDevice *device,
                              NMIP4Config **out_config,
                              NMDeviceStateReason *out_failure_reason)
 {
+	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
+
 	if (!_is_internal_interface (device))
 		return NM_ACT_STAGE_RETURN_IP_FAIL;
 
-	if (!nm_device_get_ip_ifindex (device))
+	if (!nm_device_get_ip_ifindex (device)) {
+		priv->waiting_for_interface = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
 
 	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip4_config_start (device, out_config, out_failure_reason);
 }
@@ -145,11 +159,15 @@ act_stage3_ip6_config_start (NMDevice *device,
                              NMIP6Config **out_config,
                              NMDeviceStateReason *out_failure_reason)
 {
+	NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE (device);
+
 	if (!_is_internal_interface (device))
 		return NM_ACT_STAGE_RETURN_IP_FAIL;
 
-	if (!nm_device_get_ip_ifindex (device))
+	if (!nm_device_get_ip_ifindex (device)) {
+		priv->waiting_for_interface = TRUE;
 		return NM_ACT_STAGE_RETURN_POSTPONE;
+	}
 
 	return NM_DEVICE_CLASS (nm_device_ovs_interface_parent_class)->act_stage3_ip6_config_start (device, out_config, out_failure_reason);
 }
diff --git a/src/devices/tests/test-arping.c b/src/devices/tests/test-arping.c
index 4b4642f3..bcbc1140 100644
--- a/src/devices/tests/test-arping.c
+++ b/src/devices/tests/test-arping.c
@@ -85,7 +85,7 @@ test_arping_common (test_fixture *fixture, TestInfo *info)
 	loop = g_main_loop_new (NULL, FALSE);
 	g_signal_connect (manager, NM_ARPING_MANAGER_PROBE_TERMINATED,
 	                  G_CALLBACK (arping_manager_probe_terminated), loop);
-	g_assert (nm_arping_manager_start_probe (manager, 100, NULL));
+	g_assert (nm_arping_manager_start_probe (manager, 250, NULL));
 	g_assert (nmtst_main_loop_run (loop, 2000));
 
 	for (i = 0; info->addresses[i]; i++) {
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 6e5f10a0..dc0ce303 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -875,32 +875,37 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	NMPlatformIP4Address address;
 	const gchar **dns;
 	guint i;
+	guint32 ip4_route_table, ip4_route_metric;
+	NMPlatformIP4Route *r;
 
 	g_assert (self->_priv.ipv4_config);
 	g_assert (self->_priv.bearer);
 
+	self->_priv.idle_id_ip4 = 0;
+
 	_LOGI ("IPv4 static configuration:");
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->_priv.ipv4_config);
-	if (!nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
+	if (   !address_string
+	    || !nm_utils_parse_inaddr_bin (AF_INET, address_string, &address_network)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-		                     "(%s) retrieving IP4 configuration failed: invalid address given '%s'",
+		                     "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s",
 		                     nm_modem_get_uid (NM_MODEM (self)),
-		                     address_string);
+		                     NM_PRINT_FMT_QUOTE_STRING (address_string));
 		goto out;
 	}
 
 	/* Missing gateway not a hard failure */
 	gw_string = mm_bearer_ip_config_get_gateway (self->_priv.ipv4_config);
-	if (   !gw_string
-	    || !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
+	if (   gw_string
+	    && !nm_utils_parse_inaddr_bin (AF_INET, gw_string, &gw)) {
 		error = g_error_new (NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INVALID_CONNECTION,
-		                     "(%s) retrieving IP4 configuration failed: invalid gateway address %s%s%s",
+		                     "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"",
 		                     nm_modem_get_uid (NM_MODEM (self)),
-		                     NM_PRINT_FMT_QUOTE_STRING (gw_string));
+		                     gw_string);
 		goto out;
 	}
 
@@ -919,26 +924,20 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	_LOGI ("  address %s/%d", address_string, address.plen);
 
-	if (gw) {
-		guint32 ip4_route_table, ip4_route_metric;
-
-		nm_modem_get_route_parameters (NM_MODEM (self),
-		                               &ip4_route_table,
-		                               &ip4_route_metric,
-		                               NULL,
-		                               NULL);
-		{
-			const NMPlatformIP4Route r = {
-				.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
-				.gateway = gw,
-				.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
-				.metric = ip4_route_metric,
-			};
 
-			_LOGI ("  gateway %s", gw_string);
-			nm_ip4_config_add_route (config, &r, NULL);
-		}
-	}
+	nm_modem_get_route_parameters (NM_MODEM (self),
+	                               &ip4_route_table,
+	                               &ip4_route_metric,
+	                               NULL,
+	                               NULL);
+	r = &(NMPlatformIP4Route) {
+		.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
+		.gateway = gw,
+		.table_coerced = nm_platform_route_table_coerce (ip4_route_table),
+		.metric = ip4_route_metric,
+	};
+	nm_ip4_config_add_route (config, r, NULL);
+	_LOGI ("  gateway %s", gw_string);
 
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->_priv.ipv4_config);
@@ -989,6 +988,7 @@ stage3_ip6_done (NMModemBroadband *self)
 
 	g_assert (self->_priv.ipv6_config);
 
+	self->_priv.idle_id_ip6 = 0;
 	memset (&address, 0, sizeof (address));
 
 	ip_method = get_bearer_ip_method (self->_priv.ipv6_config);
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index 1c32791f..010a2b60 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -685,9 +685,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) != 0) {
-		_LOGW ("unhandled WWAN IPv4 method '%s'; will fail",
-		       method);
-		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		_LOGE ("unhandled WWAN IPv4 method '%s'; will fail", method);
+		NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED);
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
diff --git a/src/dhcp/nm-dhcp-client.c b/src/dhcp/nm-dhcp-client.c
index 20ea092f..ea3938d6 100644
--- a/src/dhcp/nm-dhcp-client.c
+++ b/src/dhcp/nm-dhcp-client.c
@@ -186,19 +186,70 @@ nm_dhcp_client_get_client_id (NMDhcpClient *self)
 	return NM_DHCP_CLIENT_GET_PRIVATE (self)->client_id;
 }
 
+static void
+_set_client_id (NMDhcpClient *self, GBytes *client_id, gboolean take)
+{
+	NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+
+	nm_assert (!client_id || g_bytes_get_size (client_id) >= 2);
+
+	if (   priv->client_id == client_id
+	    || (   priv->client_id
+	        && client_id
+	        && g_bytes_equal (priv->client_id, client_id))) {
+		if (take && client_id)
+			g_bytes_unref (client_id);
+		return;
+	}
+
+	if (priv->client_id)
+		g_bytes_unref (priv->client_id);
+	priv->client_id = client_id;
+	if (!take && client_id)
+		g_bytes_ref (client_id);
+}
+
 void
 nm_dhcp_client_set_client_id (NMDhcpClient *self, GBytes *client_id)
 {
-	NMDhcpClientPrivate *priv;
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+	g_return_if_fail (!client_id || g_bytes_get_size (client_id) >= 2);
+
+	_set_client_id (self, client_id, FALSE);
+}
+
+void
+nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
+                                  guint8 type,
+                                  const guint8 *client_id,
+                                  gsize len)
+{
+	guint8 *buf;
+	GBytes *b;
 
 	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+	g_return_if_fail (client_id);
+	g_return_if_fail (len > 0);
+
+	buf = g_malloc (len + 1);
+	buf[0] = type;
+	memcpy (buf + 1, client_id, len);
+	b = g_bytes_new_take (buf, len + 1);
+	_set_client_id (self, b, TRUE);
+}
 
-	priv = NM_DHCP_CLIENT_GET_PRIVATE (self);
+void
+nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
+                                  const char *dhcp_client_id)
+{
+	g_return_if_fail (NM_IS_DHCP_CLIENT (self));
+	g_return_if_fail (!dhcp_client_id || dhcp_client_id[0]);
 
-	if (priv->client_id && client_id && g_bytes_equal (priv->client_id, client_id))
-		return;
-	g_clear_pointer (&priv->client_id, g_bytes_unref);
-	priv->client_id = client_id ? g_bytes_ref (client_id) : NULL;
+	_set_client_id (self,
+	                dhcp_client_id
+	                  ? nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id)
+	                  : NULL,
+	                TRUE);
 }
 
 const char *
@@ -448,7 +499,6 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
                           const char *last_ip4_address)
 {
 	NMDhcpClientPrivate *priv;
-	gs_unref_bytes GBytes *tmp = NULL;
 
 	g_return_val_if_fail (NM_IS_DHCP_CLIENT (self), FALSE);
 
@@ -462,9 +512,7 @@ nm_dhcp_client_start_ip4 (NMDhcpClient *self,
 	else
 		_LOGI ("activation: beginning transaction (timeout in %u seconds)", (guint) priv->timeout);
 
-	if (dhcp_client_id)
-		tmp = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id);
-	nm_dhcp_client_set_client_id (self, tmp);
+	nm_dhcp_client_set_client_id_str (self, dhcp_client_id);
 
 	g_clear_pointer (&priv->hostname, g_free);
 	priv->hostname = g_strdup (hostname);
diff --git a/src/dhcp/nm-dhcp-client.h b/src/dhcp/nm-dhcp-client.h
index 02804002..2c634168 100644
--- a/src/dhcp/nm-dhcp-client.h
+++ b/src/dhcp/nm-dhcp-client.h
@@ -173,7 +173,14 @@ gboolean nm_dhcp_client_handle_event (gpointer unused,
                                       const char *reason,
                                       NMDhcpClient *self);
 
-void nm_dhcp_client_set_client_id (NMDhcpClient *self, GBytes *client_id);
+void nm_dhcp_client_set_client_id (NMDhcpClient *self,
+                                   GBytes *client_id);
+void nm_dhcp_client_set_client_id_bin (NMDhcpClient *self,
+                                       guint8 type,
+                                       const guint8 *client_id,
+                                       gsize len);
+void nm_dhcp_client_set_client_id_str (NMDhcpClient *self,
+                                       const char *dhcp_client_id);
 
 /*****************************************************************************
  * Client data
diff --git a/src/dhcp/nm-dhcp-dhclient-utils.c b/src/dhcp/nm-dhcp-dhclient-utils.c
index e63e6a86..4df90d76 100644
--- a/src/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/dhcp/nm-dhcp-dhclient-utils.c
@@ -178,7 +178,7 @@ read_client_id (const char *str)
 	gs_free char *s = NULL;
 	char *p;
 
-	g_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
+	nm_assert (!strncmp (str, CLIENTID_TAG, NM_STRLEN (CLIENTID_TAG)));
 
 	str += NM_STRLEN (CLIENTID_TAG);
 	while (g_ascii_isspace (*str))
@@ -198,6 +198,9 @@ read_client_id (const char *str)
 	if (s[strlen (s) - 1] == ';')
 		s[strlen (s) - 1] = '\0';
 
+	if (!s[0])
+		return NULL;
+
 	return nm_dhcp_utils_client_id_string_to_bytes (s);
 }
 
@@ -329,8 +332,7 @@ nm_dhcp_dhclient_create_config (const char *interface,
 					continue;
 
 				/* Otherwise capture and return the existing client id */
-				if (out_new_client_id)
-					*out_new_client_id = read_client_id (p);
+				NM_SET_OUT (out_new_client_id, read_client_id (p));
 			}
 
 			/* Override config file hostname and use one from the connection */
diff --git a/src/dhcp/nm-dhcp-systemd.c b/src/dhcp/nm-dhcp-systemd.c
index 9b1a4433..f79b7cb1 100644
--- a/src/dhcp/nm-dhcp-systemd.c
+++ b/src/dhcp/nm-dhcp-systemd.c
@@ -489,19 +489,13 @@ _save_client_id (NMDhcpSystemd *self,
                  const uint8_t *client_id,
                  size_t len)
 {
-	gs_unref_bytes GBytes *b = NULL;
-	gs_free char *buf = NULL;
-
 	g_return_if_fail (self != NULL);
 	g_return_if_fail (client_id != NULL);
 	g_return_if_fail (len > 0);
 
 	if (!nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self))) {
-		buf = g_malloc (len + 1);
-		buf[0] = type;
-		memcpy (buf + 1, client_id, len);
-		b = g_bytes_new (buf, len + 1);
-		nm_dhcp_client_set_client_id (NM_DHCP_CLIENT (self), b);
+		nm_dhcp_client_set_client_id_bin (NM_DHCP_CLIENT (self),
+		                                  type, client_id, len);
 	}
 }
 
@@ -543,7 +537,7 @@ bound4_handle (NMDhcpSystemd *self)
 		add_requests_to_options (options, dhcp4_requests);
 		dhcp_lease_save (lease, priv->lease_file);
 
-		sd_dhcp_client_get_client_id(priv->client4, &type, &client_id, &client_id_len);
+		sd_dhcp_client_get_client_id (priv->client4, &type, &client_id, &client_id_len);
 		if (client_id)
 			_save_client_id (self, type, client_id, client_id_len);
 
@@ -691,14 +685,14 @@ ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last
 	override_client_id = nm_dhcp_client_get_client_id (client);
 	if (override_client_id) {
 		client_id = g_bytes_get_data (override_client_id, &client_id_len);
-		g_assert (client_id && client_id_len);
+		nm_assert (client_id && client_id_len >= 2);
 		sd_dhcp_client_set_client_id (priv->client4,
 		                              client_id[0],
 		                              client_id + 1,
-		                              client_id_len - 1);
+		                              NM_MIN (client_id_len - 1, _NM_SD_MAX_CLIENT_ID_LEN));
 	} else if (lease) {
 		r = sd_dhcp_lease_get_client_id (lease, (const void **) &client_id, &client_id_len);
-		if (r == 0 && client_id_len) {
+		if (r == 0 && client_id_len >= 2) {
 			sd_dhcp_client_set_client_id (priv->client4,
 			                              client_id[0],
 			                              client_id + 1,
diff --git a/src/dhcp/nm-dhcp-utils.c b/src/dhcp/nm-dhcp-utils.c
index 4b2d57b9..50ca2abe 100644
--- a/src/dhcp/nm-dhcp-utils.c
+++ b/src/dhcp/nm-dhcp-utils.c
@@ -750,8 +750,15 @@ nm_dhcp_utils_client_id_string_to_bytes (const char *client_id)
 	g_return_val_if_fail (client_id && client_id[0], NULL);
 
 	/* Try as hex encoded */
-	if (strchr (client_id, ':'))
+	if (strchr (client_id, ':')) {
 		bytes = nm_utils_hexstr2bin (client_id);
+
+		/* the result must be at least two bytes long,
+		 * because @client_id contains a delimiter
+		 * but nm_utils_hexstr2bin() does not allow
+		 * leading nor trailing delimiters. */
+		nm_assert (!bytes || g_bytes_get_size (bytes) >= 2);
+	}
 	if (!bytes) {
 		/* Fall back to string */
 		len = strlen (client_id);
diff --git a/src/nm-config.c b/src/nm-config.c
index de727c98..29f0d517 100644
--- a/src/nm-config.c
+++ b/src/nm-config.c
@@ -121,6 +121,14 @@ typedef struct {
 	 * because the state changes only on explicit actions from the daemon
 	 * itself. */
 	State *state;
+
+	/* the hash table of device states. It is only loaded from disk
+	 * once and kept immutable afterwards.
+	 *
+	 * We also read all state file at once. We don't want to support
+	 * that they are changed outside of NM (at least not while NM is running).
+	 * Hence, we read them once, that's it. */
+	GHashTable *device_states;
 } NMConfigPrivate;
 
 struct _NMConfig {
@@ -182,6 +190,33 @@ nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
 	return nm_config_parse_boolean (str, default_value);
 }
 
+gint64
+nm_config_keyfile_get_int64 (const GKeyFile *keyfile,
+                             const char *section,
+                             const char *key,
+                             guint base,
+                             gint64 min,
+                             gint64 max,
+                             gint64 fallback)
+{
+	gint64 v;
+	int errsv;
+	char *str;
+
+	g_return_val_if_fail (keyfile, fallback);
+	g_return_val_if_fail (section, fallback);
+	g_return_val_if_fail (key, fallback);
+
+	str = g_key_file_get_value ((GKeyFile *) keyfile, section, key, NULL);
+	v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
+	if (str) {
+		errsv = errno;
+		g_free (str);
+		errno = errsv;
+	}
+	return v;
+}
+
 char *
 nm_config_keyfile_get_value (const GKeyFile *keyfile,
                              const char *section,
@@ -1898,6 +1933,8 @@ _nm_config_state_set (NMConfig *self,
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE   "perm-hw-addr-fake"
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID     "connection-uuid"
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED            "nm-owned"
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED   "route-metric-default-aspired"
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE "route-metric-default-effective"
 
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
@@ -1917,47 +1954,63 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
 	gsize perm_hw_addr_fake_len;
 	gint nm_owned = -1;
 	char *p;
+	guint32 route_metric_default_effective;
+	guint32 route_metric_default_aspired;
 
+	nm_assert (kf);
 	nm_assert (ifindex > 0);
 
-	if (kf) {
-		switch (nm_config_keyfile_get_boolean (kf,
-		                                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
-		                                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
-		                                       -1)) {
-		case TRUE:
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
-			connection_uuid = nm_config_keyfile_get_value (kf,
-			                                               DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
-			                                               DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
-			                                               NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-			break;
-		case FALSE:
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
-			break;
-		case -1:
-			/* missing property in keyfile. */
-			break;
-		}
-
-		perm_hw_addr_fake = nm_config_keyfile_get_value (kf,
-		                                                 DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
-		                                                 DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
-		                                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-		if (perm_hw_addr_fake) {
-			char *normalized;
-
-			normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
-			g_free (perm_hw_addr_fake);
-			perm_hw_addr_fake = normalized;
-		}
-
-		nm_owned = nm_config_keyfile_get_boolean (kf,
-		                                          DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
-		                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
-		                                          -1);
+	switch (nm_config_keyfile_get_boolean (kf,
+	                                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+	                                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
+	                                       -1)) {
+	case TRUE:
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
+		connection_uuid = nm_config_keyfile_get_value (kf,
+		                                               DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+		                                               DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
+		                                               NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+		break;
+	case FALSE:
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
+		break;
+	case -1:
+		/* missing property in keyfile. */
+		break;
 	}
 
+	perm_hw_addr_fake = nm_config_keyfile_get_value (kf,
+	                                                 DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+	                                                 DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
+	                                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
+	if (perm_hw_addr_fake) {
+		char *normalized;
+
+		normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
+		g_free (perm_hw_addr_fake);
+		perm_hw_addr_fake = normalized;
+	}
+
+	nm_owned = nm_config_keyfile_get_boolean (kf,
+	                                          DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+	                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
+	                                          -1);
+
+	/* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias
+	 * for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */
+	route_metric_default_effective = nm_config_keyfile_get_int64 (kf,
+	                                                              DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+	                                                              DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
+	                                                              10, 1, G_MAXUINT32, 0);
+	if (route_metric_default_effective) {
+		route_metric_default_aspired = nm_config_keyfile_get_int64 (kf,
+		                                                            DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+		                                                            DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
+		                                                            10, 1, route_metric_default_effective,
+		                                                            route_metric_default_effective);
+	} else
+		route_metric_default_aspired = 0;
+
 	connection_uuid_len = connection_uuid ? strlen (connection_uuid) + 1 : 0;
 	perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen (perm_hw_addr_fake) + 1 : 0;
 
@@ -1970,6 +2023,8 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
 	device_state->connection_uuid = NULL;
 	device_state->perm_hw_addr_fake = NULL;
 	device_state->nm_owned = nm_owned;
+	device_state->route_metric_default_aspired = route_metric_default_aspired;
+	device_state->route_metric_default_effective = route_metric_default_effective;
 
 	p = (char *) (&device_state[1]);
 	if (connection_uuid) {
@@ -2007,31 +2062,77 @@ nm_config_device_state_load (int ifindex)
 
 	kf = nm_config_create_keyfile ();
 	if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, NULL))
-		g_clear_pointer (&kf, g_key_file_unref);
+		return NULL;
 
 	device_state = _config_device_state_data_new (ifindex, kf);
 	nm_owned_str = device_state->nm_owned == TRUE ?
 	               ", nm-owned=1" :
 	               (device_state->nm_owned == FALSE ? ", nm-owned=0" : "");
 
-
-	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s",
+	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"",
 	       kf ? "read" : "miss",
 	       ifindex, path,
 	       _device_state_managed_type_to_str (device_state->managed),
 	       NM_PRINT_FMT_QUOTED (device_state->connection_uuid, ", connection-uuid=", device_state->connection_uuid, "", ""),
 	       NM_PRINT_FMT_QUOTED (device_state->perm_hw_addr_fake, ", perm-hw-addr-fake=", device_state->perm_hw_addr_fake, "", ""),
-	       nm_owned_str);
+	       nm_owned_str,
+	       device_state->route_metric_default_aspired,
+	       device_state->route_metric_default_effective);
 
 	return device_state;
 }
 
+static int
+_device_state_parse_filename (const char *filename)
+{
+	if (!filename || !filename[0])
+		return 0;
+	if (!NM_STRCHAR_ALL (filename, ch, g_ascii_isdigit (ch)))
+		return 0;
+	return _nm_utils_ascii_str_to_int64 (filename, 10, 1, G_MAXINT, 0);
+}
+
+GHashTable *
+nm_config_device_state_load_all (void)
+{
+	GHashTable *states;
+	GDir *dir;
+	const char *fn;
+	int ifindex;
+
+	states = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_free);
+
+	dir = g_dir_open (NM_CONFIG_DEVICE_STATE_DIR, 0, NULL);
+	if (!dir)
+		return states;
+
+	while ((fn = g_dir_read_name (dir))) {
+		NMConfigDeviceStateData *state;
+
+		ifindex = _device_state_parse_filename (fn);
+		if (ifindex <= 0)
+			continue;
+
+		state = nm_config_device_state_load (ifindex);
+		if (!state)
+			continue;
+
+		if (!nm_g_hash_table_insert (states, GINT_TO_POINTER (ifindex), state))
+			nm_assert_not_reached ();
+	}
+	g_dir_close (dir);
+
+	return states;
+}
+
 gboolean
 nm_config_device_state_write (int ifindex,
                               NMConfigDeviceStateManagedType managed,
                               const char *perm_hw_addr_fake,
                               const char *connection_uuid,
-                              gint nm_owned)
+                              gint nm_owned,
+                              guint32 route_metric_default_aspired,
+                              guint32 route_metric_default_effective)
 {
 	char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
 	GError *local = NULL;
@@ -2073,17 +2174,31 @@ nm_config_device_state_write (int ifindex,
 		                        nm_owned);
 	}
 
+	if (route_metric_default_effective != 0) {
+		g_key_file_set_int64 (kf,
+		                      DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+		                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
+		                      route_metric_default_effective);
+		if (route_metric_default_aspired != route_metric_default_effective) {
+			g_key_file_set_int64 (kf,
+			                      DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+			                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED,
+			                      route_metric_default_aspired);
+		}
+	}
 
 	if (!g_key_file_save_to_file (kf, path, &local)) {
 		_LOGW ("device-state: write #%d (%s) failed: %s", ifindex, path, local->message);
 		g_error_free (local);
 		return FALSE;
 	}
-	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s",
+	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"",
 	       ifindex, path,
 	       _device_state_managed_type_to_str (managed),
 	       NM_PRINT_FMT_QUOTED (connection_uuid, ", connection-uuid=", connection_uuid, "", ""),
-	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""));
+	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""),
+	       route_metric_default_aspired,
+	       route_metric_default_effective);
 	return TRUE;
 }
 
@@ -2094,7 +2209,6 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
 	const char *fn;
 	int ifindex;
 	gsize fn_len;
-	gsize i;
 	char buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/") + 30 + 3] = NM_CONFIG_DEVICE_STATE_DIR"/";
 	char *buf_p = &buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/")];
 
@@ -2105,24 +2219,20 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
 		return;
 
 	while ((fn = g_dir_read_name (dir))) {
-		fn_len = strlen (fn);
-
-		/* skip over file names that are not plain integers. */
-		for (i = 0; i < fn_len; i++) {
-			if (!g_ascii_isdigit (fn[i]))
-				break;
-		}
-		if (fn_len == 0 || i != fn_len)
+		ifindex = _device_state_parse_filename (fn);
+		if (ifindex <= 0)
 			continue;
-
-		ifindex = _nm_utils_ascii_str_to_int64 (fn, 10, 1, G_MAXINT, 0);
-		if (!ifindex)
-			continue;
-
 		if (g_hash_table_contains (seen_ifindexes, GINT_TO_POINTER (ifindex)))
 			continue;
 
-		memcpy (buf_p, fn, fn_len + 1);
+		fn_len = strlen (fn) + 1;
+		nm_assert (&buf_p[fn_len] < &buf[G_N_ELEMENTS (buf)]);
+		memcpy (buf_p, fn, fn_len);
+		nm_assert (({
+		                char bb[30];
+		                nm_sprintf_buf (bb, "%d", ifindex);
+		                nm_streq0 (bb, buf_p);
+		           }));
 		_LOGT ("device-state: prune #%d (%s)", ifindex, buf);
 		(void) unlink (buf);
 	}
@@ -2132,6 +2242,46 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
 
 /*****************************************************************************/
 
+static GHashTable *
+_device_state_get_all (NMConfig *self)
+{
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
+
+	if (G_UNLIKELY (!priv->device_states))
+		priv->device_states = nm_config_device_state_load_all ();
+	return priv->device_states;
+}
+
+/**
+ * nm_config_device_state_get_all:
+ * @self: the #NMConfig
+ *
+ * This function exists to give convenient access to all
+ * device states. Do not ever try to modify the returned
+ * hash, it's supposed to be immutable.
+ *
+ * Returns: the internal #GHashTable object with all device states.
+ */
+const GHashTable *
+nm_config_device_state_get_all (NMConfig *self)
+{
+	g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
+
+	return _device_state_get_all (self);
+}
+
+const NMConfigDeviceStateData *
+nm_config_device_state_get (NMConfig *self,
+                            int ifindex)
+{
+	g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
+	g_return_val_if_fail (ifindex > 0 , NULL);
+
+	return g_hash_table_lookup (_device_state_get_all (self), GINT_TO_POINTER (ifindex));
+}
+
+/*****************************************************************************/
+
 void
 nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
 {
diff --git a/src/nm-config.h b/src/nm-config.h
index 8bdd5002..42ab4682 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -165,6 +165,13 @@ gint nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
                                     const char *section,
                                     const char *key,
                                     gint default_value);
+gint64 nm_config_keyfile_get_int64 (const GKeyFile *keyfile,
+                                    const char *section,
+                                    const char *key,
+                                    guint base,
+                                    gint64 min,
+                                    gint64 max,
+                                    gint64 fallback);
 char *nm_config_keyfile_get_value (const GKeyFile *keyfile,
                                    const char *section,
                                    const char *key,
@@ -205,6 +212,10 @@ struct _NMConfigDeviceStateData {
 	int ifindex;
 	NMConfigDeviceStateManagedType managed;
 
+	/* a value of zero means that no metric is set. */
+	guint32 route_metric_default_aspired;
+	guint32 route_metric_default_effective;
+
 	/* the UUID of the last settings-connection active
 	 * on the device. */
 	const char *connection_uuid;
@@ -213,17 +224,25 @@ struct _NMConfigDeviceStateData {
 
 	/* whether the device was nm-owned (0/1) or -1 for
 	 * non-software devices. */
-	gint nm_owned;
+	int nm_owned:3;
 };
 
 NMConfigDeviceStateData *nm_config_device_state_load (int ifindex);
+GHashTable *nm_config_device_state_load_all (void);
 gboolean nm_config_device_state_write (int ifindex,
                                        NMConfigDeviceStateManagedType managed,
                                        const char *perm_hw_addr_fake,
                                        const char *connection_uuid,
-                                       gint nm_owned);
+                                       gint nm_owned,
+                                       guint32 route_metric_default_aspired,
+                                       guint32 route_metric_default_effective);
+
 void nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes);
 
+const GHashTable *nm_config_device_state_get_all (NMConfig *self);
+const NMConfigDeviceStateData *nm_config_device_state_get (NMConfig *self,
+                                                           int ifindex);
+
 /*****************************************************************************/
 
 #endif /* __NETWORKMANAGER_CONFIG_H__ */
diff --git a/src/nm-core-utils.c b/src/nm-core-utils.c
index 40a0c352..f6b33a14 100644
--- a/src/nm-core-utils.c
+++ b/src/nm-core-utils.c
@@ -1960,8 +1960,7 @@ nm_utils_resolve_conf_parse (int addr_family,
 				gsize i_tokens;
 
 				tokens = nm_utils_strsplit_set (s, " \t");
-				nm_assert (tokens);
-				for (i_tokens = 0; tokens[i_tokens]; i_tokens++) {
+				for (i_tokens = 0; tokens && tokens[i_tokens]; i_tokens++) {
 					gs_free char *t = g_strstrip (g_strdup (tokens[i_tokens]));
 
 					if (   _nm_utils_dns_option_validate (t, NULL, NULL,
diff --git a/src/nm-manager.c b/src/nm-manager.c
index 7f1b9a9d..001fae55 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -160,6 +160,8 @@ typedef struct {
 
 	NMAuthManager *auth_mgr;
 
+	GHashTable *device_route_metrics;
+
 	GSList *auth_chains;
 	GHashTable *sleep_devices;
 
@@ -324,6 +326,250 @@ static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
 
 /*****************************************************************************/
 
+typedef struct {
+	int ifindex;
+	guint32 aspired_metric;
+	guint32 effective_metric;
+} DeviceRouteMetricData;
+
+static DeviceRouteMetricData *
+_device_route_metric_data_new (int ifindex, guint32 aspired_metric, guint32 effective_metric)
+{
+	DeviceRouteMetricData *data;
+
+	nm_assert (ifindex > 0);
+
+	/* For IPv4, metrics can use the entire uint32 bit range. For IPv6,
+	 * zero is treated like 1024. Since we handle IPv4 and IPv6 identically,
+	 * we cannot allow a zero metric here.
+	 */
+	nm_assert (aspired_metric > 0);
+	nm_assert (effective_metric == 0 || aspired_metric <= effective_metric);
+
+	data = g_slice_new0 (DeviceRouteMetricData);
+	data->ifindex = ifindex;
+	data->aspired_metric = aspired_metric;
+	data->effective_metric = effective_metric ?: aspired_metric;
+	return data;
+}
+
+static guint
+_device_route_metric_data_by_ifindex_hash (gconstpointer p)
+{
+	const DeviceRouteMetricData *data = p;
+	NMHashState h;
+
+	nm_hash_init (&h, 1030338191);
+	nm_hash_update_vals (&h, data->ifindex);
+	return nm_hash_complete (&h);
+}
+
+static gboolean
+_device_route_metric_data_by_ifindex_equal (gconstpointer pa, gconstpointer pb)
+{
+	const DeviceRouteMetricData *a = pa;
+	const DeviceRouteMetricData *b = pb;
+
+	return a->ifindex == b->ifindex;
+}
+
+static guint32
+_device_route_metric_get (NMManager *self,
+                          int ifindex,
+                          NMDeviceType device_type,
+                          gboolean lookup_only,
+                          guint32 *out_aspired_metric)
+{
+	NMManagerPrivate *priv;
+	const DeviceRouteMetricData *d2;
+	DeviceRouteMetricData *data;
+	DeviceRouteMetricData data_lookup;
+	const NMDedupMultiHeadEntry *all_links_head;
+	NMPObject links_needle;
+	guint n_links;
+	gboolean cleaned = FALSE;
+	GHashTableIter h_iter;
+	guint32 metric;
+
+	g_return_val_if_fail (NM_IS_MANAGER (self), 0);
+
+	NM_SET_OUT (out_aspired_metric, 0);
+
+	if (ifindex <= 0) {
+		if (lookup_only)
+			return 0;
+		metric = nm_device_get_route_metric_default (device_type);
+		NM_SET_OUT (out_aspired_metric, metric);
+		return metric;
+	}
+
+	priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (   lookup_only
+	    && !priv->device_route_metrics)
+		return 0;
+
+	if (G_UNLIKELY (!priv->device_route_metrics)) {
+		const GHashTable *h;
+		const NMConfigDeviceStateData *device_state;
+
+		priv->device_route_metrics = g_hash_table_new_full (_device_route_metric_data_by_ifindex_hash,
+		                                                    _device_route_metric_data_by_ifindex_equal,
+		                                                    NULL,
+		                                                    nm_g_slice_free_fcn (DeviceRouteMetricData));
+		cleaned = TRUE;
+
+		/* we need to pre-populate the cache for all (still existing) devices from the state-file */
+		h = nm_config_device_state_get_all (priv->config);
+		if (!h)
+			goto initited;
+
+		g_hash_table_iter_init (&h_iter, (GHashTable *) h);
+		while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) {
+			if (!device_state->route_metric_default_effective)
+				continue;
+			if (!nm_platform_link_get (priv->platform, device_state->ifindex)) {
+				/* we have the entry in the state file, but (currently) no such
+				 * ifindex exists in platform. Most likely the entry is obsolete,
+				 * hence we skip it. */
+				continue;
+			}
+			if (!nm_g_hash_table_add (priv->device_route_metrics,
+			                          _device_route_metric_data_new (device_state->ifindex,
+			                                                         device_state->route_metric_default_aspired,
+			                                                         device_state->route_metric_default_effective)))
+				nm_assert_not_reached ();
+		}
+	}
+
+initited:
+	data_lookup.ifindex = ifindex;
+
+	data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
+	if (data)
+		goto out;
+	if (lookup_only)
+		return 0;
+
+	if (!cleaned) {
+		/* get the number of all links in the platform cache. */
+		all_links_head = nm_platform_lookup_all (priv->platform,
+		                                         NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+		                                         nmp_object_stackinit_id_link (&links_needle, 1));
+		n_links = all_links_head ? all_links_head->len : 0;
+
+		/* on systems where a lot of devices are created and go away, the index contains
+		 * a lot of stale entries. We must from time to time clean them up.
+		 *
+		 * Do do this cleanup, whenever we have more enties then 2 times the number of links. */
+		if (G_UNLIKELY (g_hash_table_size (priv->device_route_metrics) > NM_MAX (20, n_links * 2))) {
+			/* from time to time, we need to do some house-keeping and prune stale entries.
+			 * Otherwise, on a system where interfaces frequently come and go (docker), we
+			 * keep growing this cache for ifindexes that no longer exist. */
+			g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
+			while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
+				if (!nm_platform_link_get (priv->platform, d2->ifindex))
+					g_hash_table_iter_remove (&h_iter);
+			}
+			cleaned = TRUE;
+		}
+	}
+
+	data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type), 0);
+
+	/* unfortunately, there is no stright forward way to lookup all reserved metrics.
+	 * Note, that we don't only have to know which metrics are currently reserved,
+	 * but also, which metrics are now seemingly un-used but caused another reserved
+	 * metric to be bumped. Hence, the naive O(n^2) search :(
+	 *
+	 * Well, technically, since we limit bumping the metric to 50, this entire
+	 * loop runs at most 50 times, so it's still O(n). Let's just say, it's not
+	 * very efficient. */
+again:
+	g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
+	while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
+		if (   data->effective_metric < d2->aspired_metric
+		    || data->effective_metric > d2->effective_metric) {
+			/* no overlap. Skip. */
+			continue;
+		}
+		if (   !cleaned
+		    && !nm_platform_link_get (priv->platform, d2->ifindex)) {
+			/* the metric seems taken, but there is no such interface. This entry
+			 * is stale, forget about it. */
+			g_hash_table_iter_remove (&h_iter);
+			continue;
+		}
+
+		if (d2->effective_metric == G_MAXUINT32) {
+			/* we cannot bump the metric any further. Done.
+			 *
+			 * Actually, this can currently not happen because the aspired_metric
+			 * are small numbers and we limit the bumping to 50. Still, for
+			 * completeness... */
+			data->effective_metric = G_MAXUINT32;
+			break;
+		}
+
+		if (d2->effective_metric - data->aspired_metric >= 50) {
+			/* as one active interface reserves an entire range of metrics
+			 * (from aspired_metric to effective_metric), that means if you
+			 * alternatingly activate two interfaces, their metric will
+			 * bump each other.
+			 *
+			 * Limit this, bump the metric at most 50 points. */
+			data->effective_metric = data->aspired_metric + 50;
+			break;
+		}
+
+		/* bump the metric, and search again. */
+		data->effective_metric = d2->effective_metric + 1;
+		goto again;
+	}
+
+	_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)",
+	       data->ifindex, data->effective_metric, data->aspired_metric);
+
+	if (!nm_g_hash_table_add (priv->device_route_metrics, data))
+		nm_assert_not_reached ();
+
+out:
+	NM_SET_OUT (out_aspired_metric, data->aspired_metric);
+	return data->effective_metric;
+}
+
+guint32
+nm_manager_device_route_metric_reserve (NMManager *self,
+                                        int ifindex,
+                                        NMDeviceType device_type)
+{
+	guint32 metric;
+
+	metric = _device_route_metric_get (self, ifindex, device_type, FALSE, NULL);
+	nm_assert (metric != 0);
+	return metric;
+}
+
+void
+nm_manager_device_route_metric_clear (NMManager *self,
+                                      int ifindex)
+{
+	NMManagerPrivate *priv;
+	DeviceRouteMetricData data_lookup;
+
+	priv = NM_MANAGER_GET_PRIVATE (self);
+
+	if (!priv->device_route_metrics)
+		return;
+	data_lookup.ifindex = ifindex;
+	if (g_hash_table_remove (priv->device_route_metrics, &data_lookup)) {
+		_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d released",
+		       ifindex);
+	}
+}
+
+/*****************************************************************************/
+
 static void
 _delete_volatile_connection_do (NMManager *self,
                                 NMSettingsConnection *connection)
@@ -2640,10 +2886,9 @@ platform_query_devices (NMManager *self)
 		return;
 	for (i = 0; i < links->len; i++) {
 		const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]);
-		gs_free NMConfigDeviceStateData *dev_state = NULL;
-
-		dev_state = nm_config_device_state_load (link->ifindex);
+		const NMConfigDeviceStateData *dev_state;
 
+		dev_state = nm_config_device_state_get (priv->config, link->ifindex);
 		platform_link_added (self,
 		                     link->ifindex,
 		                     link,
@@ -5199,6 +5444,8 @@ nm_manager_write_device_state (NMManager *self)
 		const char *uuid = NULL;
 		const char *perm_hw_addr_fake = NULL;
 		gboolean perm_hw_addr_is_fake;
+		guint32 route_metric_default_aspired;
+		guint32 route_metric_default_effective;
 
 		ifindex = nm_device_get_ip_ifindex (device);
 		if (ifindex <= 0)
@@ -5228,11 +5475,16 @@ nm_manager_write_device_state (NMManager *self)
 
 		nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
 
+		route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
+		                                                           TRUE, &route_metric_default_aspired);
+
 		if (nm_config_device_state_write (ifindex,
 		                                  managed_type,
 		                                  perm_hw_addr_fake,
 		                                  uuid,
-		                                  nm_owned))
+		                                  nm_owned,
+		                                  route_metric_default_aspired,
+		                                  route_metric_default_effective))
 			g_hash_table_add (seen_ifindexes, GINT_TO_POINTER (ifindex));
 	}
 
@@ -6400,7 +6652,7 @@ get_property (GObject *object, guint prop_id,
 #else
 		vbool = FALSE;
 #endif
-		g_value_set_boolean (value, FALSE);
+		g_value_set_boolean (value, vbool);
 		break;
 	case PROP_PRIMARY_CONNECTION:
 		nm_utils_g_value_set_object_path (value, priv->primary_connection);
@@ -6604,6 +6856,8 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&priv->timestamp_update_id);
 
+	g_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);
+
 	G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
 }
 
diff --git a/src/nm-manager.h b/src/nm-manager.h
index 2d463c71..b4587e08 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -114,6 +114,13 @@ NMDevice *          nm_manager_get_device_by_ifindex   (NMManager *manager,
 NMDevice *          nm_manager_get_device_by_path      (NMManager *manager,
                                                         const char *path);
 
+guint32             nm_manager_device_route_metric_reserve (NMManager *self,
+                                                            int ifindex,
+                                                            NMDeviceType device_type);
+
+void                nm_manager_device_route_metric_clear (NMManager *self,
+                                                          int ifindex);
+
 char *              nm_manager_get_connection_iface (NMManager *self,
                                                      NMConnection *connection,
                                                      NMDevice **out_parent,
diff --git a/src/nm-policy.c b/src/nm-policy.c
index 8dfb0ab0..4d0ef91a 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -1409,7 +1409,7 @@ reset_autoconnect_all (NMPolicy *self,
 	NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (self);
 	NMSettingsConnection *const*connections = NULL;
 	guint i;
-	gboolean changed;
+	gboolean changed = FALSE;
 
 	_LOGD (LOGD_DEVICE, "re-enabling autoconnect for all connections%s%s%s",
 	       device ? " on " : "",
diff --git a/src/nm-types.h b/src/nm-types.h
index cc657397..02163f87 100644
--- a/src/nm-types.h
+++ b/src/nm-types.h
@@ -25,6 +25,8 @@
 #error "nm-utils-private.h" must not be used outside of libnm-core/. Do you want "nm-core-internal.h"?
 #endif
 
+#define _NM_SD_MAX_CLIENT_ID_LEN (sizeof (guint32) + 128)
+
 /* core */
 typedef struct _NMExportedObject     NMExportedObject;
 typedef struct _NMActiveConnection   NMActiveConnection;
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index 10d1a6ef..fe270e88 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -4680,7 +4680,10 @@ do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *
 	        wait_for_nl_response_to_string (seq_result, s_buf, sizeof (s_buf)),
 	        log_detail);
 
-	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
+	if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
+	               NMP_OBJECT_TYPE_IP6_ADDRESS,
+	               NMP_OBJECT_TYPE_QDISC,
+	               NMP_OBJECT_TYPE_TFILTER)) {
 		/* In rare cases, the object is still there after we receive the ACK from
 		 * kernel. Need to refetch.
 		 *
@@ -6413,7 +6416,7 @@ ip_route_get (NMPlatform *platform,
 	int try_count = 0;
 	WaitForNlResponseResult seq_result;
 	int nle;
-	nm_auto_nlmsg NMPObject *route = NULL;
+	nm_auto_nmpobj NMPObject *route = NULL;
 
 	nm_assert (NM_IS_LINUX_PLATFORM (platform));
 	nm_assert (NM_IN_SET (addr_family, AF_INET, AF_INET6));
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 2ca379e4..c7ed90e3 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -667,6 +667,7 @@ skip:
 			/* There is a loop, pop the first (remaining) element from the list.
 			 * This can happen for veth pairs where each peer is parent of the other end. */
 			item = NMP_OBJECT_CAST_LINK (links->pdata[first_idx]);
+			nm_assert (item);
 			g_hash_table_remove (unseen, GINT_TO_POINTER (item->ifindex));
 			g_ptr_array_add (result, links->pdata[first_idx]);
 			links->pdata[first_idx] = NULL;
@@ -5320,7 +5321,7 @@ nm_platform_qdisc_to_string (const NMPlatformQdisc *qdisc, char *buf, gsize len)
 void
 nm_platform_qdisc_hash_update (const NMPlatformQdisc *obj, NMHashState *h)
 {
-	nm_hash_update_str (h, obj->kind);
+	nm_hash_update_str0 (h, obj->kind);
 	nm_hash_update_vals (h,
 	                     obj->ifindex,
 	                     obj->addr_family,
@@ -5387,17 +5388,17 @@ nm_platform_tfilter_to_string (const NMPlatformTfilter *tfilter, char *buf, gsiz
 void
 nm_platform_tfilter_hash_update (const NMPlatformTfilter *obj, NMHashState *h)
 {
-	nm_hash_update_str (h, obj->kind);
+	nm_hash_update_str0 (h, obj->kind);
 	nm_hash_update_vals (h,
 	                     obj->ifindex,
 	                     obj->addr_family,
 	                     obj->handle,
 	                     obj->parent,
 	                     obj->info);
-	nm_hash_update_str (h, obj->action.kind);
 	if (obj->action.kind) {
+		nm_hash_update_str (h, obj->action.kind);
 		if (nm_streq (obj->action.kind, NM_PLATFORM_ACTION_KIND_SIMPLE))
-			nm_hash_update_str (h, obj->action.simple.sdata);
+			nm_hash_update_strarr (h, obj->action.simple.sdata);
 	}
 }
 
diff --git a/src/settings/nm-settings-connection.c b/src/settings/nm-settings-connection.c
index b6e89404..37a0b3a6 100644
--- a/src/settings/nm-settings-connection.c
+++ b/src/settings/nm-settings-connection.c
@@ -517,6 +517,7 @@ set_persist_mode (NMSettingsConnection *self, NMSettingsConnectionPersistMode pe
 		                                  TRUE);
 		return;
 	case NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP:
+	case NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED:
 		/* Nothing to do */
 		return;
 	}
@@ -793,7 +794,7 @@ nm_settings_connection_delete (NMSettingsConnection *self,
 	/* Remove connection from seen-bssids database file */
 	remove_entry_from_db (self, "seen-bssids");
 
-	nm_settings_connection_signal_remove (self, FALSE);
+	nm_settings_connection_signal_remove (self);
 	return TRUE;
 }
 
@@ -2187,15 +2188,24 @@ impl_settings_connection_clear_secrets (NMSettingsConnection *self,
 /*****************************************************************************/
 
 void
-nm_settings_connection_signal_remove (NMSettingsConnection *self, gboolean allow_reuse)
+nm_settings_connection_added (NMSettingsConnection *self)
 {
 	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
 
-	if (!allow_reuse) {
-		if (priv->removed)
-			g_return_if_reached ();
-		priv->removed = TRUE;
-	}
+	/* FIXME: we should always dispose connections that are removed
+	 * and not reuse them, but currently plugins keep alive unmanaged
+	 * (e.g. NM_CONTROLLED=no) connections. */
+	priv->removed = FALSE;
+}
+
+void
+nm_settings_connection_signal_remove (NMSettingsConnection *self)
+{
+	NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE (self);
+
+	if (priv->removed)
+		return;
+	priv->removed = TRUE;
 	g_signal_emit_by_name (self, NM_SETTINGS_CONNECTION_REMOVED);
 }
 
diff --git a/src/settings/nm-settings-connection.h b/src/settings/nm-settings-connection.h
index fc8ad1de..29ec05dd 100644
--- a/src/settings/nm-settings-connection.h
+++ b/src/settings/nm-settings-connection.h
@@ -137,6 +137,9 @@ gboolean nm_settings_connection_has_unmodified_applied_connection (NMSettingsCon
 
 typedef enum {
 	NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+
+	/* like KEEP, but always clears the UNSAVED flag */
+	NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 	NM_SETTINGS_CONNECTION_PERSIST_MODE_DISK,
 
 	/* unsaved, only sets the unsaved flag, but it doesn't touch
@@ -190,7 +193,9 @@ void nm_settings_connection_recheck_visibility (NMSettingsConnection *self);
 gboolean nm_settings_connection_check_permission (NMSettingsConnection *self,
                                                   const char *permission);
 
-void nm_settings_connection_signal_remove (NMSettingsConnection *self, gboolean allow_reuse);
+void nm_settings_connection_added (NMSettingsConnection *self);
+
+void nm_settings_connection_signal_remove (NMSettingsConnection *self);
 
 gboolean nm_settings_connection_get_unsaved (NMSettingsConnection *self);
 
diff --git a/src/settings/nm-settings.c b/src/settings/nm-settings.c
index 21fdf9e0..51b7bea7 100644
--- a/src/settings/nm-settings.c
+++ b/src/settings/nm-settings.c
@@ -89,21 +89,6 @@ EXPORT(nm_settings_connection_update)
 /*****************************************************************************/
 
 static NM_CACHED_QUARK_FCN ("plugin-module-path", plugin_module_path_quark)
-
-#if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) + defined(HOSTNAME_PERSIST_GENTOO)) > 1
-#error "Can only define one of HOSTNAME_PERSIST_*"
-#endif
-
-#if defined(HOSTNAME_PERSIST_SUSE)
-#define HOSTNAME_FILE           HOSTNAME_FILE_UCASE_HOSTNAME
-#elif defined(HOSTNAME_PERSIST_SLACKWARE)
-#define HOSTNAME_FILE           HOSTNAME_FILE_UCASE_HOSTNAME
-#elif defined(HOSTNAME_PERSIST_GENTOO)
-#define HOSTNAME_FILE           HOSTNAME_FILE_GENTOO
-#else
-#define HOSTNAME_FILE           HOSTNAME_FILE_DEFAULT
-#endif
-
 static NM_CACHED_QUARK_FCN ("default-wired-connection", _default_wired_connection_quark)
 static NM_CACHED_QUARK_FCN ("default-wired-device", _default_wired_device_quark)
 
@@ -1004,6 +989,8 @@ claim_connection (NMSettings *self, NMSettingsConnection *connection)
 		/* Exported D-Bus signal */
 		g_signal_emit (self, signals[NEW_CONNECTION], 0, connection);
 	}
+
+	nm_settings_connection_added (connection);
 }
 
 static gboolean
diff --git a/src/settings/plugins/ibft/nms-ibft-connection.c b/src/settings/plugins/ibft/nms-ibft-connection.c
index 2a7c5f4a..fb7f18f8 100644
--- a/src/settings/plugins/ibft/nms-ibft-connection.c
+++ b/src/settings/plugins/ibft/nms-ibft-connection.c
@@ -62,7 +62,7 @@ nms_ibft_connection_new (const GPtrArray *block, GError **error)
 	/* Update settings with what was read from iscsiadm */
 	if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
 	                                    source,
-	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 	                                    NULL,
 	                                    error))
diff --git a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
index 4c65b02d..97417db9 100644
--- a/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
+++ b/src/settings/plugins/ifcfg-rh/nm-inotify-helper.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2008 Red Hat, Inc.
+ * (C) Copyright 2008 - 2017 Red Hat, Inc.
  */
 
 #include "nm-default.h"
@@ -44,6 +44,7 @@ static guint signals[LAST_SIGNAL] = { 0 };
 
 typedef struct {
 	int ifd;
+	guint inotify_id;
 	GHashTable *wd_refs;
 } NMInotifyHelperPrivate;
 
@@ -142,7 +143,6 @@ init_inotify (NMInotifyHelper *self)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE (self);
 	GIOChannel *channel;
-	guint source_id;
 
 	priv->ifd = inotify_init1 (IN_CLOEXEC);
 	if (priv->ifd == -1) {
@@ -157,10 +157,10 @@ init_inotify (NMInotifyHelper *self)
 	g_io_channel_set_flags (channel, G_IO_FLAG_NONBLOCK, NULL);
 	g_io_channel_set_encoding (channel, NULL, NULL);
 
-	source_id = g_io_add_watch (channel,
-	                            G_IO_IN | G_IO_ERR,
-	                            (GIOFunc) inotify_event_handler,
-	                            (gpointer) self);
+	priv->inotify_id = g_io_add_watch (channel,
+	                                   G_IO_IN | G_IO_ERR,
+	                                   (GIOFunc) inotify_event_handler,
+	                                   (gpointer) self);
 	g_io_channel_unref (channel);
 	return TRUE;
 }
@@ -188,6 +188,7 @@ finalize (GObject *object)
 {
 	NMInotifyHelperPrivate *priv = NM_INOTIFY_HELPER_GET_PRIVATE ((NMInotifyHelper *) object);
 
+	nm_clear_g_source (&priv->inotify_id);
 	nm_close (priv->ifd);
 
 	g_hash_table_destroy (priv->wd_refs);
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
index 3cf5c978..6979fdaa 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-connection.c
@@ -442,7 +442,7 @@ nm_ifcfg_connection_new (NMConnection *source,
 	if (nm_settings_connection_update (NM_SETTINGS_CONNECTION (object),
 	                                   tmp,
 	                                   full_path
-	                                     ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP /* connection is already on disk */
+	                                     ? NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED
 	                                     : NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED,
 	                                   NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 	                                   NULL,
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 04e74bbd..0743fc9f 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -161,7 +161,7 @@ remove_connection (SettingsPluginIfcfg *self, NMIfcfgConnection *connection)
 	g_object_ref (connection);
 	g_hash_table_remove (priv->connections, nm_connection_get_uuid (NM_CONNECTION (connection)));
 	if (!unmanaged && !unrecognized)
-		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection), FALSE);
+		nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
 	g_object_unref (connection);
 
 	/* Emit changes _after_ removing the connection */
@@ -315,7 +315,7 @@ update_connection (SettingsPluginIfcfg *self,
 
 			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
 			                                    NM_CONNECTION (connection_new),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 			                                    "ifcfg-update",
 			                                    &local)) {
@@ -331,7 +331,7 @@ update_connection (SettingsPluginIfcfg *self,
 					/* Unexport the connection by telling the settings service it's
 					 * been removed.
 					 */
-					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection_by_uuid), TRUE);
+					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection_by_uuid));
 					/* Remove the path so that claim_connection() doesn't complain later when
 					 * interface gets managed and connection is re-added. */
 					nm_connection_set_path (NM_CONNECTION (connection_by_uuid), NULL);
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index b9900eec..bdd3ee0a 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -294,6 +294,23 @@ make_connection_setting (const char *file,
 	check_if_team_slave (ifcfg, s_con);
 
 	nm_clear_g_free (&value);
+	v = svGetValueStr (ifcfg, "OVS_PORT_UUID", &value);
+	if (!v)
+		v = svGetValueStr (ifcfg, "OVS_PORT", &value);
+	if (v) {
+		const char *old_value;
+
+		if ((old_value = nm_setting_connection_get_master (s_con))) {
+			PARSE_WARNING ("Already configured as slave of %s. Ignoring OVS_PORT=\"%s\"",
+			               old_value, v);
+		} else {
+			g_object_set (s_con, NM_SETTING_CONNECTION_MASTER, v, NULL);
+			g_object_set (s_con, NM_SETTING_CONNECTION_SLAVE_TYPE,
+			              NM_SETTING_OVS_PORT_SETTING_NAME, NULL);
+		}
+	}
+
+	nm_clear_g_free (&value);
 	v = svGetValueStr (ifcfg, "GATEWAY_PING_TIMEOUT", &value);
 	if (v) {
 		gint64 tmp;
diff --git a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 5cb8ee98..e9dd08b7 100644
--- a/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -1832,6 +1832,12 @@ write_connection_setting (NMSettingConnection *s_con, shvarFile *ifcfg)
 			                  NM_SETTING_WIRED_SETTING_NAME,
 			                  NM_SETTING_VLAN_SETTING_NAME))
 				svUnsetValue (ifcfg, "TYPE");
+		} else if (nm_setting_connection_is_slave_type (s_con, NM_SETTING_OVS_PORT_SETTING_NAME)) {
+			svSetValueStr (ifcfg, "OVS_PORT_UUID", master);
+			svSetValueStr (ifcfg, "OVS_PORT", master_iface);
+		} else {
+			_LOGW ("don't know how to set master for a %s slave",
+			       nm_setting_connection_get_slave_type (s_con));
 		}
 	}
 
diff --git a/src/settings/plugins/ifnet/nms-ifnet-connection.c b/src/settings/plugins/ifnet/nms-ifnet-connection.c
index e7cd19cd..ce0b3f2b 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-connection.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-connection.c
@@ -190,7 +190,7 @@ nm_ifnet_connection_new (NMConnection *source, const char *conn_name)
 	                                    tmp,
 	                                    update_unsaved
 	                                      ? NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED
-	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 	                                    NULL,
 	                                    NULL)) {
diff --git a/src/settings/plugins/ifnet/nms-ifnet-plugin.c b/src/settings/plugins/ifnet/nms-ifnet-plugin.c
index 38d23f30..8332358f 100644
--- a/src/settings/plugins/ifnet/nms-ifnet-plugin.c
+++ b/src/settings/plugins/ifnet/nms-ifnet-plugin.c
@@ -262,7 +262,7 @@ reload_connections (NMSettingsPlugin *config)
 				                              NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) {
 					nm_log_info (LOGD_SETTINGS, "Auto refreshing %s", conn_name);
 
-					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (old), FALSE);
+					nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (old));
 					track_new_connection (self, new);
 					if (is_managed_plugin () && is_managed (conn_name))
 						g_signal_emit_by_name (self, NM_SETTINGS_PLUGIN_CONNECTION_ADDED, new);
@@ -271,7 +271,7 @@ reload_connections (NMSettingsPlugin *config)
 				/* Update existing connection with new settings */
 				if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (old),
 				                                    NM_CONNECTION (new),
-				                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+				                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 				                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 				                                    "ifnet-update",
 				                                    &error)) {
@@ -305,7 +305,7 @@ reload_connections (NMSettingsPlugin *config)
 		 */
 		if (   nm_ifnet_connection_get_conn_name (NM_IFNET_CONNECTION (candidate))
 		    && !g_hash_table_lookup (new_connections, uuid)) {
-			nm_settings_connection_signal_remove (candidate, FALSE);
+			nm_settings_connection_signal_remove (candidate);
 			g_hash_table_iter_remove (&iter);
 		}
 	}
diff --git a/src/settings/plugins/keyfile/nms-keyfile-connection.c b/src/settings/plugins/keyfile/nms-keyfile-connection.c
index 5a6d8a76..5f72a9fa 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-connection.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-connection.c
@@ -163,7 +163,7 @@ nms_keyfile_connection_new (NMConnection *source,
 	                                    tmp,
 	                                    update_unsaved
 	                                      ? NM_SETTINGS_CONNECTION_PERSIST_MODE_UNSAVED
-	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+	                                      : NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 	                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 	                                    NULL,
 	                                    error)) {
diff --git a/src/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
index bc64b3ca..cb5f2c9f 100644
--- a/src/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -107,7 +107,7 @@ remove_connection (NMSKeyfilePlugin *self, NMSKeyfileConnection *connection)
 	g_signal_handlers_disconnect_by_func (connection, connection_removed_cb, self);
 	removed = g_hash_table_remove (NMS_KEYFILE_PLUGIN_GET_PRIVATE (self)->connections,
 	                               nm_connection_get_uuid (NM_CONNECTION (connection)));
-	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection), FALSE);
+	nm_settings_connection_signal_remove (NM_SETTINGS_CONNECTION (connection));
 	g_object_unref (connection);
 
 	g_return_if_fail (removed);
@@ -260,7 +260,7 @@ update_connection (NMSKeyfilePlugin *self,
 
 			if (!nm_settings_connection_update (NM_SETTINGS_CONNECTION (connection_by_uuid),
 			                                    NM_CONNECTION (connection_new),
-			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+			                                    NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP_SAVED,
 			                                    NM_SETTINGS_CONNECTION_COMMIT_REASON_NONE,
 			                                    "keyfile-update",
 			                                    &local)) {
diff --git a/src/systemd/src/libsystemd-network/sd-dhcp-client.c b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
index fc6ad422..5eab0050 100644
--- a/src/systemd/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/systemd/src/libsystemd-network/sd-dhcp-client.c
@@ -311,6 +311,7 @@ int sd_dhcp_client_set_client_id(
         assert_return(client, -EINVAL);
         assert_return(data, -EINVAL);
         assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL);
+        G_STATIC_ASSERT_EXPR (_NM_SD_MAX_CLIENT_ID_LEN == MAX_CLIENT_ID_LEN);
 
         switch (type) {