Blame SOURCES/0009-distinct-route-metric-rh1505893.patch

013a0d
From e7d51475a855f29f124b12e1de8709c2addb8be2 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Wed, 6 Dec 2017 13:16:30 +0100
013a0d
Subject: [PATCH 1/7] device: expose nm_device_get_route_metric_default()
013a0d
013a0d
(cherry picked from commit 989b5fabaac95f9367fb5f1c730db5dca7eab0de)
013a0d
(cherry picked from commit ea78f156f2ef3402a5c4dde9c395cbf720b7aa4c)
013a0d
---
013a0d
 src/devices/nm-device.c | 8 ++++----
013a0d
 src/devices/nm-device.h | 2 ++
013a0d
 2 files changed, 6 insertions(+), 4 deletions(-)
013a0d
013a0d
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
013a0d
index f75cc86e3..8179dbd3b 100644
013a0d
--- a/src/devices/nm-device.c
013a0d
+++ b/src/devices/nm-device.c
013a0d
@@ -1616,8 +1616,8 @@ nm_device_get_metered (NMDevice *self)
013a0d
 	return NM_DEVICE_GET_PRIVATE (self)->metered;
013a0d
 }
013a0d
 
013a0d
-static guint32
013a0d
-_get_route_metric_default (NMDevice *self)
013a0d
+guint32
013a0d
+nm_device_get_route_metric_default (NMDeviceType device_type)
013a0d
 {
013a0d
 	/* Device 'priority' is used for the default route-metric and is based on
013a0d
 	 * the device type. The settings ipv4.route-metric and ipv6.route-metric
013a0d
@@ -1636,7 +1636,7 @@ _get_route_metric_default (NMDevice *self)
013a0d
 	 * metrics (except for IPv6, where 0 means 1024).
013a0d
 	 */
013a0d
 
013a0d
-	switch (nm_device_get_device_type (self)) {
013a0d
+	switch (device_type) {
013a0d
 	/* 50 is reserved for VPN (NM_VPN_ROUTE_METRIC_DEFAULT) */
013a0d
 	case NM_DEVICE_TYPE_ETHERNET:
013a0d
 	case NM_DEVICE_TYPE_VETH:
013a0d
@@ -1765,7 +1765,7 @@ nm_device_get_route_metric (NMDevice *self,
013a0d
 		if (route_metric >= 0)
013a0d
 			goto out;
013a0d
 	}
013a0d
-	route_metric = _get_route_metric_default (self);
013a0d
+	route_metric = nm_device_get_route_metric_default (nm_device_get_device_type (self));
013a0d
 out:
013a0d
 	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
013a0d
 }
013a0d
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
013a0d
index 810a613dd..ac73ee0c4 100644
013a0d
--- a/src/devices/nm-device.h
013a0d
+++ b/src/devices/nm-device.h
013a0d
@@ -450,6 +450,8 @@ NMMetered       nm_device_get_metered           (NMDevice *dev);
013a0d
 guint32         nm_device_get_route_table       (NMDevice *self, int addr_family, gboolean fallback_main);
013a0d
 guint32         nm_device_get_route_metric      (NMDevice *dev, int addr_family);
013a0d
 
013a0d
+guint32         nm_device_get_route_metric_default (NMDeviceType device_type);
013a0d
+
013a0d
 const char *    nm_device_get_hw_address        (NMDevice *dev);
013a0d
 const char *    nm_device_get_permanent_hw_address (NMDevice *self);
013a0d
 const char *    nm_device_get_permanent_hw_address_full (NMDevice *self,
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From 009ce5a739ec760acc38e8265776570e5d1a6a34 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Wed, 6 Dec 2017 12:49:21 +0100
013a0d
Subject: [PATCH 2/7] core: add nm_config_keyfile_get_int64() util
013a0d
013a0d
(cherry picked from commit 3f38b765158c2ffcec7d1b314c3ba6aea3bc3e7b)
013a0d
(cherry picked from commit 42fbc9410ba5c131e84c21d073d3d818feb2666d)
013a0d
---
013a0d
 src/nm-config.c | 27 +++++++++++++++++++++++++++
013a0d
 src/nm-config.h |  7 +++++++
013a0d
 2 files changed, 34 insertions(+)
013a0d
013a0d
diff --git a/src/nm-config.c b/src/nm-config.c
013a0d
index de727c988..c344d5cd1 100644
013a0d
--- a/src/nm-config.c
013a0d
+++ b/src/nm-config.c
013a0d
@@ -182,6 +182,33 @@ nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
013a0d
 	return nm_config_parse_boolean (str, default_value);
013a0d
 }
013a0d
 
013a0d
+gint64
013a0d
+nm_config_keyfile_get_int64 (const GKeyFile *keyfile,
013a0d
+                             const char *section,
013a0d
+                             const char *key,
013a0d
+                             guint base,
013a0d
+                             gint64 min,
013a0d
+                             gint64 max,
013a0d
+                             gint64 fallback)
013a0d
+{
013a0d
+	gint64 v;
013a0d
+	int errsv;
013a0d
+	char *str;
013a0d
+
013a0d
+	g_return_val_if_fail (keyfile, fallback);
013a0d
+	g_return_val_if_fail (section, fallback);
013a0d
+	g_return_val_if_fail (key, fallback);
013a0d
+
013a0d
+	str = g_key_file_get_value ((GKeyFile *) keyfile, section, key, NULL);
013a0d
+	v = _nm_utils_ascii_str_to_int64 (str, base, min, max, fallback);
013a0d
+	if (str) {
013a0d
+		errsv = errno;
013a0d
+		g_free (str);
013a0d
+		errno = errsv;
013a0d
+	}
013a0d
+	return v;
013a0d
+}
013a0d
+
013a0d
 char *
013a0d
 nm_config_keyfile_get_value (const GKeyFile *keyfile,
013a0d
                              const char *section,
013a0d
diff --git a/src/nm-config.h b/src/nm-config.h
013a0d
index 8bdd5002d..5b2dc65c4 100644
013a0d
--- a/src/nm-config.h
013a0d
+++ b/src/nm-config.h
013a0d
@@ -165,6 +165,13 @@ gint nm_config_keyfile_get_boolean (const GKeyFile *keyfile,
013a0d
                                     const char *section,
013a0d
                                     const char *key,
013a0d
                                     gint default_value);
013a0d
+gint64 nm_config_keyfile_get_int64 (const GKeyFile *keyfile,
013a0d
+                                    const char *section,
013a0d
+                                    const char *key,
013a0d
+                                    guint base,
013a0d
+                                    gint64 min,
013a0d
+                                    gint64 max,
013a0d
+                                    gint64 fallback);
013a0d
 char *nm_config_keyfile_get_value (const GKeyFile *keyfile,
013a0d
                                    const char *section,
013a0d
                                    const char *key,
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From 412200a581b00c76757afb2250c3a9e3e719ca6b Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Wed, 6 Dec 2017 13:09:31 +0100
013a0d
Subject: [PATCH 3/7] core: cache device state in NMConfig and load all at once
013a0d
013a0d
NMManager will need to know the state of all device at once.
013a0d
Hence, load it once and cache it in NMConfig.
013a0d
013a0d
Note that this wastes a bit of memory in the order of
013a0d
O(number-of-interfaces). But each device state entry is
013a0d
rather small, and we always consume memory in the order
013a0d
of O(number-of-interfaces).
013a0d
013a0d
(cherry picked from commit ea08df925f6a01e30ddcea4c15cea98d532593c6)
013a0d
(cherry picked from commit 7b899334068c6945b0604b37510d91b02cbe62d9)
013a0d
---
013a0d
 src/devices/nm-device.c |  31 ++++----
013a0d
 src/nm-config.c         | 186 +++++++++++++++++++++++++++++++++++-------------
013a0d
 src/nm-config.h         |   5 ++
013a0d
 src/nm-manager.c        |   5 +-
013a0d
 4 files changed, 156 insertions(+), 71 deletions(-)
013a0d
013a0d
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
013a0d
index 8179dbd3b..755589674 100644
013a0d
--- a/src/devices/nm-device.c
013a0d
+++ b/src/devices/nm-device.c
013a0d
@@ -13512,6 +13512,7 @@ nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze)
013a0d
 	gboolean success_read;
013a0d
 	int ifindex;
013a0d
 	const NMPlatformLink *pllink;
013a0d
+	const NMConfigDeviceStateData *dev_state;
013a0d
 
013a0d
 	if (priv->hw_addr_perm) {
013a0d
 		/* the permanent hardware address is only read once and not
013a0d
@@ -13571,23 +13572,19 @@ nm_device_update_permanent_hw_address (NMDevice *self, gboolean force_freeze)
013a0d
 	/* We also persist our choice of the fake address to the device state
013a0d
 	 * file to use the same address on restart of NetworkManager.
013a0d
 	 * First, try to reload the address from the state file. */
013a0d
-	{
013a0d
-		gs_free NMConfigDeviceStateData *dev_state = NULL;
013a0d
-
013a0d
-		dev_state = nm_config_device_state_load (ifindex);
013a0d
-		if (   dev_state
013a0d
-		    && dev_state->perm_hw_addr_fake
013a0d
-		    && nm_utils_hwaddr_aton (dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len)
013a0d
-		    && !nm_utils_hwaddr_matches (buf, priv->hw_addr_len, priv->hw_addr, -1)) {
013a0d
-			_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use from statefile: %s, current: %s)",
013a0d
-			       success_read
013a0d
-			           ? "read HW addr length of permanent MAC address differs"
013a0d
-			           : "unable to read permanent MAC address",
013a0d
-			       dev_state->perm_hw_addr_fake,
013a0d
-			       priv->hw_addr);
013a0d
-			priv->hw_addr_perm = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
013a0d
-			goto notify_and_out;
013a0d
-		}
013a0d
+	dev_state = nm_config_device_state_get (nm_config_get (), ifindex);
013a0d
+	if (   dev_state
013a0d
+	    && dev_state->perm_hw_addr_fake
013a0d
+	    && nm_utils_hwaddr_aton (dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len)
013a0d
+	    && !nm_utils_hwaddr_matches (buf, priv->hw_addr_len, priv->hw_addr, -1)) {
013a0d
+		_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use from statefile: %s, current: %s)",
013a0d
+		       success_read
013a0d
+		           ? "read HW addr length of permanent MAC address differs"
013a0d
+		           : "unable to read permanent MAC address",
013a0d
+		       dev_state->perm_hw_addr_fake,
013a0d
+		       priv->hw_addr);
013a0d
+		priv->hw_addr_perm = nm_utils_hwaddr_ntoa (buf, priv->hw_addr_len);
013a0d
+		goto notify_and_out;
013a0d
 	}
013a0d
 
013a0d
 	_LOGD (LOGD_PLATFORM | LOGD_ETHER, "hw-addr: %s (use current: %s)",
013a0d
diff --git a/src/nm-config.c b/src/nm-config.c
013a0d
index c344d5cd1..771d74f2a 100644
013a0d
--- a/src/nm-config.c
013a0d
+++ b/src/nm-config.c
013a0d
@@ -121,6 +121,14 @@ typedef struct {
013a0d
 	 * because the state changes only on explicit actions from the daemon
013a0d
 	 * itself. */
013a0d
 	State *state;
013a0d
+
013a0d
+	/* the hash table of device states. It is only loaded from disk
013a0d
+	 * once and kept immutable afterwards.
013a0d
+	 *
013a0d
+	 * We also read all state file at once. We don't want to support
013a0d
+	 * that they are changed outside of NM (at least not while NM is running).
013a0d
+	 * Hence, we read them once, that's it. */
013a0d
+	GHashTable *device_states;
013a0d
 } NMConfigPrivate;
013a0d
 
013a0d
 struct _NMConfig {
013a0d
@@ -1945,46 +1953,45 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	gint nm_owned = -1;
013a0d
 	char *p;
013a0d
 
013a0d
+	nm_assert (kf);
013a0d
 	nm_assert (ifindex > 0);
013a0d
 
013a0d
-	if (kf) {
013a0d
-		switch (nm_config_keyfile_get_boolean (kf,
013a0d
-		                                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-		                                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
013a0d
-		                                       -1)) {
013a0d
-		case TRUE:
013a0d
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
013a0d
-			connection_uuid = nm_config_keyfile_get_value (kf,
013a0d
-			                                               DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-			                                               DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
013a0d
-			                                               NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
013a0d
-			break;
013a0d
-		case FALSE:
013a0d
-			managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
013a0d
-			break;
013a0d
-		case -1:
013a0d
-			/* missing property in keyfile. */
013a0d
-			break;
013a0d
-		}
013a0d
-
013a0d
-		perm_hw_addr_fake = nm_config_keyfile_get_value (kf,
013a0d
-		                                                 DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-		                                                 DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
013a0d
-		                                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
013a0d
-		if (perm_hw_addr_fake) {
013a0d
-			char *normalized;
013a0d
+	switch (nm_config_keyfile_get_boolean (kf,
013a0d
+	                                       DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+	                                       DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_MANAGED,
013a0d
+	                                       -1)) {
013a0d
+	case TRUE:
013a0d
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_MANAGED;
013a0d
+		connection_uuid = nm_config_keyfile_get_value (kf,
013a0d
+		                                               DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+		                                               DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID,
013a0d
+		                                               NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
013a0d
+		break;
013a0d
+	case FALSE:
013a0d
+		managed_type = NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNMANAGED;
013a0d
+		break;
013a0d
+	case -1:
013a0d
+		/* missing property in keyfile. */
013a0d
+		break;
013a0d
+	}
013a0d
 
013a0d
-			normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
013a0d
-			g_free (perm_hw_addr_fake);
013a0d
-			perm_hw_addr_fake = normalized;
013a0d
-		}
013a0d
+	perm_hw_addr_fake = nm_config_keyfile_get_value (kf,
013a0d
+	                                                 DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+	                                                 DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE,
013a0d
+	                                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
013a0d
+	if (perm_hw_addr_fake) {
013a0d
+		char *normalized;
013a0d
 
013a0d
-		nm_owned = nm_config_keyfile_get_boolean (kf,
013a0d
-		                                          DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-		                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
013a0d
-		                                          -1);
013a0d
+		normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
013a0d
+		g_free (perm_hw_addr_fake);
013a0d
+		perm_hw_addr_fake = normalized;
013a0d
 	}
013a0d
 
013a0d
+	nm_owned = nm_config_keyfile_get_boolean (kf,
013a0d
+	                                          DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+	                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
013a0d
+	                                          -1);
013a0d
+
013a0d
 	connection_uuid_len = connection_uuid ? strlen (connection_uuid) + 1 : 0;
013a0d
 	perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen (perm_hw_addr_fake) + 1 : 0;
013a0d
 
013a0d
@@ -2034,14 +2041,13 @@ nm_config_device_state_load (int ifindex)
013a0d
 
013a0d
 	kf = nm_config_create_keyfile ();
013a0d
 	if (!g_key_file_load_from_file (kf, path, G_KEY_FILE_NONE, NULL))
013a0d
-		g_clear_pointer (&kf, g_key_file_unref);
013a0d
+		return NULL;
013a0d
 
013a0d
 	device_state = _config_device_state_data_new (ifindex, kf);
013a0d
 	nm_owned_str = device_state->nm_owned == TRUE ?
013a0d
 	               ", nm-owned=1" :
013a0d
 	               (device_state->nm_owned == FALSE ? ", nm-owned=0" : "");
013a0d
 
013a0d
-
013a0d
 	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s",
013a0d
 	       kf ? "read" : "miss",
013a0d
 	       ifindex, path,
013a0d
@@ -2053,6 +2059,49 @@ nm_config_device_state_load (int ifindex)
013a0d
 	return device_state;
013a0d
 }
013a0d
 
013a0d
+static int
013a0d
+_device_state_parse_filename (const char *filename)
013a0d
+{
013a0d
+	if (!filename || !filename[0])
013a0d
+		return 0;
013a0d
+	if (!NM_STRCHAR_ALL (filename, ch, g_ascii_isdigit (ch)))
013a0d
+		return 0;
013a0d
+	return _nm_utils_ascii_str_to_int64 (filename, 10, 1, G_MAXINT, 0);
013a0d
+}
013a0d
+
013a0d
+GHashTable *
013a0d
+nm_config_device_state_load_all (void)
013a0d
+{
013a0d
+	GHashTable *states;
013a0d
+	GDir *dir;
013a0d
+	const char *fn;
013a0d
+	int ifindex;
013a0d
+
013a0d
+	states = g_hash_table_new_full (nm_direct_hash, NULL, NULL, g_free);
013a0d
+
013a0d
+	dir = g_dir_open (NM_CONFIG_DEVICE_STATE_DIR, 0, NULL);
013a0d
+	if (!dir)
013a0d
+		return states;
013a0d
+
013a0d
+	while ((fn = g_dir_read_name (dir))) {
013a0d
+		NMConfigDeviceStateData *state;
013a0d
+
013a0d
+		ifindex = _device_state_parse_filename (fn);
013a0d
+		if (ifindex <= 0)
013a0d
+			continue;
013a0d
+
013a0d
+		state = nm_config_device_state_load (ifindex);
013a0d
+		if (!state)
013a0d
+			continue;
013a0d
+
013a0d
+		if (!nm_g_hash_table_insert (states, GINT_TO_POINTER (ifindex), state))
013a0d
+			nm_assert_not_reached ();
013a0d
+	}
013a0d
+	g_dir_close (dir);
013a0d
+
013a0d
+	return states;
013a0d
+}
013a0d
+
013a0d
 gboolean
013a0d
 nm_config_device_state_write (int ifindex,
013a0d
                               NMConfigDeviceStateManagedType managed,
013a0d
@@ -2121,7 +2170,6 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
013a0d
 	const char *fn;
013a0d
 	int ifindex;
013a0d
 	gsize fn_len;
013a0d
-	gsize i;
013a0d
 	char buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/") + 30 + 3] = NM_CONFIG_DEVICE_STATE_DIR"/";
013a0d
 	char *buf_p = &buf[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR"/")];
013a0d
 
013a0d
@@ -2132,24 +2180,20 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
013a0d
 		return;
013a0d
 
013a0d
 	while ((fn = g_dir_read_name (dir))) {
013a0d
-		fn_len = strlen (fn);
013a0d
-
013a0d
-		/* skip over file names that are not plain integers. */
013a0d
-		for (i = 0; i < fn_len; i++) {
013a0d
-			if (!g_ascii_isdigit (fn[i]))
013a0d
-				break;
013a0d
-		}
013a0d
-		if (fn_len == 0 || i != fn_len)
013a0d
+		ifindex = _device_state_parse_filename (fn);
013a0d
+		if (ifindex <= 0)
013a0d
 			continue;
013a0d
-
013a0d
-		ifindex = _nm_utils_ascii_str_to_int64 (fn, 10, 1, G_MAXINT, 0);
013a0d
-		if (!ifindex)
013a0d
-			continue;
013a0d
-
013a0d
 		if (g_hash_table_contains (seen_ifindexes, GINT_TO_POINTER (ifindex)))
013a0d
 			continue;
013a0d
 
013a0d
-		memcpy (buf_p, fn, fn_len + 1);
013a0d
+		fn_len = strlen (fn) + 1;
013a0d
+		nm_assert (&buf_p[fn_len] < &buf[G_N_ELEMENTS (buf)]);
013a0d
+		memcpy (buf_p, fn, fn_len);
013a0d
+		nm_assert (({
013a0d
+		                char bb[30];
013a0d
+		                nm_sprintf_buf (bb, "%d", ifindex);
013a0d
+		                nm_streq0 (bb, buf_p);
013a0d
+		           }));
013a0d
 		_LOGT ("device-state: prune #%d (%s)", ifindex, buf);
013a0d
 		(void) unlink (buf);
013a0d
 	}
013a0d
@@ -2159,6 +2203,46 @@ nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes)
013a0d
 
013a0d
 /*****************************************************************************/
013a0d
 
013a0d
+static GHashTable *
013a0d
+_device_state_get_all (NMConfig *self)
013a0d
+{
013a0d
+	NMConfigPrivate *priv = NM_CONFIG_GET_PRIVATE (self);
013a0d
+
013a0d
+	if (G_UNLIKELY (!priv->device_states))
013a0d
+		priv->device_states = nm_config_device_state_load_all ();
013a0d
+	return priv->device_states;
013a0d
+}
013a0d
+
013a0d
+/**
013a0d
+ * nm_config_device_state_get_all:
013a0d
+ * @self: the #NMConfig
013a0d
+ *
013a0d
+ * This function exists to give convenient access to all
013a0d
+ * device states. Do not ever try to modify the returned
013a0d
+ * hash, it's supposed to be immutable.
013a0d
+ *
013a0d
+ * Returns: the internal #GHashTable object with all device states.
013a0d
+ */
013a0d
+const GHashTable *
013a0d
+nm_config_device_state_get_all (NMConfig *self)
013a0d
+{
013a0d
+	g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
013a0d
+
013a0d
+	return _device_state_get_all (self);
013a0d
+}
013a0d
+
013a0d
+const NMConfigDeviceStateData *
013a0d
+nm_config_device_state_get (NMConfig *self,
013a0d
+                            int ifindex)
013a0d
+{
013a0d
+	g_return_val_if_fail (NM_IS_CONFIG (self), NULL);
013a0d
+	g_return_val_if_fail (ifindex > 0 , NULL);
013a0d
+
013a0d
+	return g_hash_table_lookup (_device_state_get_all (self), GINT_TO_POINTER (ifindex));
013a0d
+}
013a0d
+
013a0d
+/*****************************************************************************/
013a0d
+
013a0d
 void
013a0d
 nm_config_reload (NMConfig *self, NMConfigChangeFlags reload_flags)
013a0d
 {
013a0d
diff --git a/src/nm-config.h b/src/nm-config.h
013a0d
index 5b2dc65c4..52a4099b2 100644
013a0d
--- a/src/nm-config.h
013a0d
+++ b/src/nm-config.h
013a0d
@@ -224,6 +224,7 @@ struct _NMConfigDeviceStateData {
013a0d
 };
013a0d
 
013a0d
 NMConfigDeviceStateData *nm_config_device_state_load (int ifindex);
013a0d
+GHashTable *nm_config_device_state_load_all (void);
013a0d
 gboolean nm_config_device_state_write (int ifindex,
013a0d
                                        NMConfigDeviceStateManagedType managed,
013a0d
                                        const char *perm_hw_addr_fake,
013a0d
@@ -231,6 +232,10 @@ gboolean nm_config_device_state_write (int ifindex,
013a0d
                                        gint nm_owned);
013a0d
 void nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes);
013a0d
 
013a0d
+const GHashTable *nm_config_device_state_get_all (NMConfig *self);
013a0d
+const NMConfigDeviceStateData *nm_config_device_state_get (NMConfig *self,
013a0d
+                                                           int ifindex);
013a0d
+
013a0d
 /*****************************************************************************/
013a0d
 
013a0d
 #endif /* __NETWORKMANAGER_CONFIG_H__ */
013a0d
diff --git a/src/nm-manager.c b/src/nm-manager.c
013a0d
index 7f1b9a9d9..9e5f7ad4c 100644
013a0d
--- a/src/nm-manager.c
013a0d
+++ b/src/nm-manager.c
013a0d
@@ -2640,10 +2640,9 @@ platform_query_devices (NMManager *self)
013a0d
 		return;
013a0d
 	for (i = 0; i < links->len; i++) {
013a0d
 		const NMPlatformLink *link = NMP_OBJECT_CAST_LINK (links->pdata[i]);
013a0d
-		gs_free NMConfigDeviceStateData *dev_state = NULL;
013a0d
-
013a0d
-		dev_state = nm_config_device_state_load (link->ifindex);
013a0d
+		const NMConfigDeviceStateData *dev_state;
013a0d
 
013a0d
+		dev_state = nm_config_device_state_get (priv->config, link->ifindex);
013a0d
 		platform_link_added (self,
013a0d
 		                     link->ifindex,
013a0d
 		                     link,
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From e6d73154f23bdd6e0c4e9b52a9ea64bd3c1d8a13 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Wed, 6 Dec 2017 15:51:18 +0100
013a0d
Subject: [PATCH 4/7] core: add read/write support for route-metric to
013a0d
 NMConfig's device state
013a0d
013a0d
(cherry picked from commit a90b523a3e09f68d5700e73981ba84d40e4682a5)
013a0d
(cherry picked from commit 282ed0d17501fda8b8c30020c029eb86f364e8bc)
013a0d
---
013a0d
 src/nm-config.c  | 29 ++++++++++++++++++++++++-----
013a0d
 src/nm-config.h  |  9 +++++++--
013a0d
 src/nm-manager.c |  6 +++++-
013a0d
 3 files changed, 36 insertions(+), 8 deletions(-)
013a0d
013a0d
diff --git a/src/nm-config.c b/src/nm-config.c
013a0d
index 771d74f2a..97ae3f6f4 100644
013a0d
--- a/src/nm-config.c
013a0d
+++ b/src/nm-config.c
013a0d
@@ -1933,6 +1933,7 @@ _nm_config_state_set (NMConfig *self,
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE   "perm-hw-addr-fake"
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID     "connection-uuid"
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED            "nm-owned"
013a0d
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT "route-metric-default"
013a0d
 
013a0d
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
013a0d
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
013a0d
@@ -1952,6 +1953,7 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	gsize perm_hw_addr_fake_len;
013a0d
 	gint nm_owned = -1;
013a0d
 	char *p;
013a0d
+	guint32 route_metric_default;
013a0d
 
013a0d
 	nm_assert (kf);
013a0d
 	nm_assert (ifindex > 0);
013a0d
@@ -1992,6 +1994,13 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	                                          DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
013a0d
 	                                          -1);
013a0d
 
013a0d
+	/* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias
013a0d
+	 * for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */
013a0d
+	route_metric_default = nm_config_keyfile_get_int64 (kf,
013a0d
+	                                                    DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+	                                                    DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
013a0d
+	                                                    10, 1, G_MAXUINT32, 0);
013a0d
+
013a0d
 	connection_uuid_len = connection_uuid ? strlen (connection_uuid) + 1 : 0;
013a0d
 	perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen (perm_hw_addr_fake) + 1 : 0;
013a0d
 
013a0d
@@ -2004,6 +2013,7 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	device_state->connection_uuid = NULL;
013a0d
 	device_state->perm_hw_addr_fake = NULL;
013a0d
 	device_state->nm_owned = nm_owned;
013a0d
+	device_state->route_metric_default = route_metric_default;
013a0d
 
013a0d
 	p = (char *) (&device_state[1]);
013a0d
 	if (connection_uuid) {
013a0d
@@ -2048,13 +2058,14 @@ nm_config_device_state_load (int ifindex)
013a0d
 	               ", nm-owned=1" :
013a0d
 	               (device_state->nm_owned == FALSE ? ", nm-owned=0" : "");
013a0d
 
013a0d
-	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s",
013a0d
+	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT,
013a0d
 	       kf ? "read" : "miss",
013a0d
 	       ifindex, path,
013a0d
 	       _device_state_managed_type_to_str (device_state->managed),
013a0d
 	       NM_PRINT_FMT_QUOTED (device_state->connection_uuid, ", connection-uuid=", device_state->connection_uuid, "", ""),
013a0d
 	       NM_PRINT_FMT_QUOTED (device_state->perm_hw_addr_fake, ", perm-hw-addr-fake=", device_state->perm_hw_addr_fake, "", ""),
013a0d
-	       nm_owned_str);
013a0d
+	       nm_owned_str,
013a0d
+	       device_state->route_metric_default);
013a0d
 
013a0d
 	return device_state;
013a0d
 }
013a0d
@@ -2107,7 +2118,8 @@ nm_config_device_state_write (int ifindex,
013a0d
                               NMConfigDeviceStateManagedType managed,
013a0d
                               const char *perm_hw_addr_fake,
013a0d
                               const char *connection_uuid,
013a0d
-                              gint nm_owned)
013a0d
+                              gint nm_owned,
013a0d
+                              guint32 route_metric_default)
013a0d
 {
013a0d
 	char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
013a0d
 	GError *local = NULL;
013a0d
@@ -2149,17 +2161,24 @@ nm_config_device_state_write (int ifindex,
013a0d
 		                        nm_owned);
013a0d
 	}
013a0d
 
013a0d
+	if (route_metric_default != 0) {
013a0d
+		g_key_file_set_int64 (kf,
013a0d
+		                      DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+		                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
013a0d
+		                      route_metric_default);
013a0d
+	}
013a0d
 
013a0d
 	if (!g_key_file_save_to_file (kf, path, &local)) {
013a0d
 		_LOGW ("device-state: write #%d (%s) failed: %s", ifindex, path, local->message);
013a0d
 		g_error_free (local);
013a0d
 		return FALSE;
013a0d
 	}
013a0d
-	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s",
013a0d
+	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT,
013a0d
 	       ifindex, path,
013a0d
 	       _device_state_managed_type_to_str (managed),
013a0d
 	       NM_PRINT_FMT_QUOTED (connection_uuid, ", connection-uuid=", connection_uuid, "", ""),
013a0d
-	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""));
013a0d
+	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""),
013a0d
+	       route_metric_default);
013a0d
 	return TRUE;
013a0d
 }
013a0d
 
013a0d
diff --git a/src/nm-config.h b/src/nm-config.h
013a0d
index 52a4099b2..4c6fcca2b 100644
013a0d
--- a/src/nm-config.h
013a0d
+++ b/src/nm-config.h
013a0d
@@ -212,6 +212,9 @@ struct _NMConfigDeviceStateData {
013a0d
 	int ifindex;
013a0d
 	NMConfigDeviceStateManagedType managed;
013a0d
 
013a0d
+	/* a value of zero means that no metric is set. */
013a0d
+	guint32 route_metric_default;
013a0d
+
013a0d
 	/* the UUID of the last settings-connection active
013a0d
 	 * on the device. */
013a0d
 	const char *connection_uuid;
013a0d
@@ -220,7 +223,7 @@ struct _NMConfigDeviceStateData {
013a0d
 
013a0d
 	/* whether the device was nm-owned (0/1) or -1 for
013a0d
 	 * non-software devices. */
013a0d
-	gint nm_owned;
013a0d
+	int nm_owned:3;
013a0d
 };
013a0d
 
013a0d
 NMConfigDeviceStateData *nm_config_device_state_load (int ifindex);
013a0d
@@ -229,7 +232,9 @@ gboolean nm_config_device_state_write (int ifindex,
013a0d
                                        NMConfigDeviceStateManagedType managed,
013a0d
                                        const char *perm_hw_addr_fake,
013a0d
                                        const char *connection_uuid,
013a0d
-                                       gint nm_owned);
013a0d
+                                       gint nm_owned,
013a0d
+                                       guint32 route_metric_default);
013a0d
+
013a0d
 void nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes);
013a0d
 
013a0d
 const GHashTable *nm_config_device_state_get_all (NMConfig *self);
013a0d
diff --git a/src/nm-manager.c b/src/nm-manager.c
013a0d
index 9e5f7ad4c..8b0b8013b 100644
013a0d
--- a/src/nm-manager.c
013a0d
+++ b/src/nm-manager.c
013a0d
@@ -5198,6 +5198,7 @@ nm_manager_write_device_state (NMManager *self)
013a0d
 		const char *uuid = NULL;
013a0d
 		const char *perm_hw_addr_fake = NULL;
013a0d
 		gboolean perm_hw_addr_is_fake;
013a0d
+		guint32 route_metric_default;
013a0d
 
013a0d
 		ifindex = nm_device_get_ip_ifindex (device);
013a0d
 		if (ifindex <= 0)
013a0d
@@ -5227,11 +5228,14 @@ nm_manager_write_device_state (NMManager *self)
013a0d
 
013a0d
 		nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
013a0d
 
013a0d
+		route_metric_default = 0;
013a0d
+
013a0d
 		if (nm_config_device_state_write (ifindex,
013a0d
 		                                  managed_type,
013a0d
 		                                  perm_hw_addr_fake,
013a0d
 		                                  uuid,
013a0d
-		                                  nm_owned))
013a0d
+		                                  nm_owned,
013a0d
+		                                  route_metric_default))
013a0d
 			g_hash_table_add (seen_ifindexes, GINT_TO_POINTER (ifindex));
013a0d
 	}
013a0d
 
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From 747eb4bbb0f69f106e857e37256f26770bd9d518 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Tue, 5 Dec 2017 16:32:04 +0100
013a0d
Subject: [PATCH 5/7] device: generate unique default route-metrics per
013a0d
 interface
013a0d
013a0d
In the past we had NMDefaultRouteManager which would coordinate adding
013a0d
the default-route with identical metrics. That especially happened, when
013a0d
activating two devices of the same type, without explicitly specifying
013a0d
ipv4.route-metric. For example, with ethernet devices, the routes on
013a0d
both interfaces would get a metric of 100.
013a0d
013a0d
Coordinating routes was especially necessary, because we added
013a0d
routes with NLM_F_EXCL flag, akin to `ip route replace`. We not
013a0d
only had to avoid that activating two devices in NetworkManager would
013a0d
result in a fight over the default-route, but more importently
013a0d
to preserve externally added default-routes on unmanaged interfaces.
013a0d
013a0d
NMDefaultRouteManager would ensure that in case of duplicate
013a0d
metrics, that the device that activated first would keep the
013a0d
best default-route. It would do so by bumping the metric
013a0d
of the second device to find a unused metric. The bumping itself
013a0d
was not very important -- MDefaultRouteManager could also just not
013a0d
configure any default-routes that show up as second, the result
013a0d
would be quite similar. More important was to keep the best
013a0d
default-route on the first activating device until the device
013a0d
deactivates or a device activates that really has a better
013a0d
default-route..
013a0d
013a0d
Likewise, NMRouteManager would globally manage non-default-routes.
013a0d
It would not do any bumping of metrics, but it would also ensure that the routes
013a0d
of the device that activates first are not overwritten by a device activating
013a0d
later.
013a0d
013a0d
However, the `ip route replace` approach has downsides, especially
013a0d
that it messes with routes on other interfaces, interfaces that are
013a0d
possibly not managed by NetworkManager. Another downside is, that
013a0d
binding a socket to an interface might not result in correct
013a0d
routes, because the route might just not be there (in case of
013a0d
NMRouteManager, which wouldn't configure duplicate routes by bumping
013a0d
their metric).
013a0d
013a0d
Since commit 77ec302714795f905301d500b9aab6c88001f32e we would no longer
013a0d
use NLM_F_EXCL, but add routes akin to `ip route append`. When
013a0d
activating for example two ethernet devices with no explict route
013a0d
metric configuration, there are two routes like
013a0d
013a0d
   default via 10.16.122.254 dev eth0 proto dhcp metric 100
013a0d
   default via 192.168.100.1 dev eth1 proto dhcp metric 100
013a0d
013a0d
This does not only affect default routes. In case of a multi-homing
013a0d
setup you'd get
013a0d
013a0d
  192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.1 metric 100
013a0d
  192.168.100.0/24 dev eth1 proto kernel scope link src 192.168.100.1 metric 100
013a0d
013a0d
but it's visible the most for default-routes.
013a0d
013a0d
Note that we would append the routes that are activated later, as the order
013a0d
of `ip route show` confirms. One might hence expect, that kernel selects
013a0d
a route based on the order in the routing tables. However, that isn't
013a0d
the case, and activating the second interface will non-deterministically
013a0d
re-route traffic via the new interface. That will interfere badly with
013a0d
with NAT, stateful firewalls, and existing connections (like TCP).
013a0d
013a0d
The solution is to have NMManager keep a global index of the default route-metrics
013a0d
currently in use. So, instead of determining the default-route metric based solely
013a0d
on the device-type, we now in addition generate default metrics that do not
013a0d
overlap. For example, if you activate eth0 first, it gets route-metric 100,
013a0d
and if you then activate eth1, it gets 101. Note that if you deactivate
013a0d
and re-activate eth0, then it will get route-metric 102, because the
013a0d
best route should stick on eth1 (which reserves the range 100 to 101).
013a0d
013a0d
Note that when a connection explititly selects a particular metric, then that
013a0d
choice is honored (contrary to NMDefaultRouteManager which was more concerned
013a0d
with avoiding conflicts, then keeping the exact metric).
013a0d
013a0d
https://bugzilla.redhat.com/show_bug.cgi?id=1505893
013a0d
(cherry picked from commit 6a32c64d8fb2a9c1cfb78ab7e2f0bb3a269c81d7)
013a0d
(cherry picked from commit bd2d71754b770b43a71d85ca5f79832bbdb6b77a)
013a0d
---
013a0d
 src/devices/nm-device.c |  10 +-
013a0d
 src/nm-manager.c        | 237 +++++++++++++++++++++++++++++++++++++++++++++++-
013a0d
 src/nm-manager.h        |  10 ++
013a0d
 3 files changed, 255 insertions(+), 2 deletions(-)
013a0d
013a0d
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
013a0d
index 755589674..40c425a5b 100644
013a0d
--- a/src/devices/nm-device.c
013a0d
+++ b/src/devices/nm-device.c
013a0d
@@ -1765,7 +1765,10 @@ nm_device_get_route_metric (NMDevice *self,
013a0d
 		if (route_metric >= 0)
013a0d
 			goto out;
013a0d
 	}
013a0d
-	route_metric = nm_device_get_route_metric_default (nm_device_get_device_type (self));
013a0d
+
013a0d
+	route_metric = nm_manager_device_route_metric_reserve (nm_manager_get (),
013a0d
+	                                                       nm_device_get_ip_ifindex (self),
013a0d
+	                                                       nm_device_get_device_type (self));
013a0d
 out:
013a0d
 	return nm_utils_ip_route_metric_normalize (addr_family, route_metric);
013a0d
 }
013a0d
@@ -12482,6 +12485,11 @@ _cleanup_generic_pre (NMDevice *self, CleanupType cleanup_type)
013a0d
 
013a0d
 	_cancel_activation (self);
013a0d
 
013a0d
+	if (cleanup_type != CLEANUP_TYPE_KEEP) {
013a0d
+		nm_manager_device_route_metric_clear (nm_manager_get (),
013a0d
+		                                      nm_device_get_ip_ifindex (self));
013a0d
+	}
013a0d
+
013a0d
 	if (   cleanup_type == CLEANUP_TYPE_DECONFIGURE
013a0d
 	    && priv->fw_state >= FIREWALL_STATE_INITIALIZED
013a0d
 	    && priv->fw_mgr
013a0d
diff --git a/src/nm-manager.c b/src/nm-manager.c
013a0d
index 8b0b8013b..4d3416b37 100644
013a0d
--- a/src/nm-manager.c
013a0d
+++ b/src/nm-manager.c
013a0d
@@ -160,6 +160,8 @@ typedef struct {
013a0d
 
013a0d
 	NMAuthManager *auth_mgr;
013a0d
 
013a0d
+	GHashTable *device_route_metrics;
013a0d
+
013a0d
 	GSList *auth_chains;
013a0d
 	GHashTable *sleep_devices;
013a0d
 
013a0d
@@ -324,6 +326,237 @@ static NM_CACHED_QUARK_FCN ("autoconnect-root", autoconnect_root_quark)
013a0d
 
013a0d
 /*****************************************************************************/
013a0d
 
013a0d
+typedef struct {
013a0d
+	int ifindex;
013a0d
+	guint32 aspired_metric;
013a0d
+	guint32 effective_metric;
013a0d
+} DeviceRouteMetricData;
013a0d
+
013a0d
+static DeviceRouteMetricData *
013a0d
+_device_route_metric_data_new (int ifindex, guint32 metric)
013a0d
+{
013a0d
+	DeviceRouteMetricData *data;
013a0d
+
013a0d
+	nm_assert (ifindex > 0);
013a0d
+
013a0d
+	/* For IPv4, metrics can use the entire uint32 bit range. For IPv6,
013a0d
+	 * zero is treated like 1024. Since we handle IPv4 and IPv6 identically,
013a0d
+	 * we cannot allow a zero metric here.
013a0d
+	 */
013a0d
+	nm_assert (metric > 0);
013a0d
+
013a0d
+	data = g_slice_new0 (DeviceRouteMetricData);
013a0d
+	data->ifindex = ifindex;
013a0d
+	data->aspired_metric = metric;
013a0d
+	data->effective_metric = metric;
013a0d
+	return data;
013a0d
+}
013a0d
+
013a0d
+static guint
013a0d
+_device_route_metric_data_by_ifindex_hash (gconstpointer p)
013a0d
+{
013a0d
+	const DeviceRouteMetricData *data = p;
013a0d
+	NMHashState h;
013a0d
+
013a0d
+	nm_hash_init (&h, 1030338191);
013a0d
+	nm_hash_update_vals (&h, data->ifindex);
013a0d
+	return nm_hash_complete (&h);
013a0d
+}
013a0d
+
013a0d
+static gboolean
013a0d
+_device_route_metric_data_by_ifindex_equal (gconstpointer pa, gconstpointer pb)
013a0d
+{
013a0d
+	const DeviceRouteMetricData *a = pa;
013a0d
+	const DeviceRouteMetricData *b = pb;
013a0d
+
013a0d
+	return a->ifindex == b->ifindex;
013a0d
+}
013a0d
+
013a0d
+static guint32
013a0d
+_device_route_metric_get (NMManager *self,
013a0d
+                          int ifindex,
013a0d
+                          NMDeviceType device_type,
013a0d
+                          gboolean lookup_only)
013a0d
+{
013a0d
+	NMManagerPrivate *priv;
013a0d
+	const DeviceRouteMetricData *d2;
013a0d
+	DeviceRouteMetricData *data;
013a0d
+	DeviceRouteMetricData data_lookup;
013a0d
+	const NMDedupMultiHeadEntry *all_links_head;
013a0d
+	NMPObject links_needle;
013a0d
+	guint n_links;
013a0d
+	gboolean cleaned = FALSE;
013a0d
+	GHashTableIter h_iter;
013a0d
+
013a0d
+	g_return_val_if_fail (NM_IS_MANAGER (self), 0);
013a0d
+
013a0d
+	if (ifindex <= 0) {
013a0d
+		if (lookup_only)
013a0d
+			return 0;
013a0d
+		return nm_device_get_route_metric_default (device_type);
013a0d
+	}
013a0d
+
013a0d
+	priv = NM_MANAGER_GET_PRIVATE (self);
013a0d
+
013a0d
+	if (   lookup_only
013a0d
+	    && !priv->device_route_metrics)
013a0d
+		return 0;
013a0d
+
013a0d
+	if (G_UNLIKELY (!priv->device_route_metrics)) {
013a0d
+		const GHashTable *h;
013a0d
+		const NMConfigDeviceStateData *device_state;
013a0d
+
013a0d
+		priv->device_route_metrics = g_hash_table_new_full (_device_route_metric_data_by_ifindex_hash,
013a0d
+		                                                    _device_route_metric_data_by_ifindex_equal,
013a0d
+		                                                    NULL,
013a0d
+		                                                    nm_g_slice_free_fcn (DeviceRouteMetricData));
013a0d
+		cleaned = TRUE;
013a0d
+
013a0d
+		/* we need to pre-populate the cache for all (still existing) devices from the state-file */
013a0d
+		h = nm_config_device_state_get_all (priv->config);
013a0d
+		if (!h)
013a0d
+			goto initited;
013a0d
+
013a0d
+		g_hash_table_iter_init (&h_iter, (GHashTable *) h);
013a0d
+		while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) {
013a0d
+			if (!device_state->route_metric_default)
013a0d
+				continue;
013a0d
+			if (!nm_platform_link_get (priv->platform, device_state->ifindex)) {
013a0d
+				/* we have the entry in the state file, but (currently) no such
013a0d
+				 * ifindex exists in platform. Most likely the entry is obsolete,
013a0d
+				 * hence we skip it. */
013a0d
+				continue;
013a0d
+			}
013a0d
+			if (!nm_g_hash_table_add (priv->device_route_metrics,
013a0d
+			                          _device_route_metric_data_new (device_state->ifindex,
013a0d
+			                                                         device_state->route_metric_default)))
013a0d
+				nm_assert_not_reached ();
013a0d
+		}
013a0d
+	}
013a0d
+
013a0d
+initited:
013a0d
+	data_lookup.ifindex = ifindex;
013a0d
+
013a0d
+	data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
013a0d
+	if (data)
013a0d
+		return data->effective_metric;
013a0d
+	if (lookup_only)
013a0d
+		return 0;
013a0d
+
013a0d
+	if (!cleaned) {
013a0d
+		/* get the number of all links in the platform cache. */
013a0d
+		all_links_head = nm_platform_lookup_all (priv->platform,
013a0d
+		                                         NMP_CACHE_ID_TYPE_OBJECT_TYPE,
013a0d
+		                                         nmp_object_stackinit_id_link (&links_needle, 1));
013a0d
+		n_links = all_links_head ? all_links_head->len : 0;
013a0d
+
013a0d
+		/* on systems where a lot of devices are created and go away, the index contains
013a0d
+		 * a lot of stale entries. We must from time to time clean them up.
013a0d
+		 *
013a0d
+		 * Do do this cleanup, whenever we have more enties then 2 times the number of links. */
013a0d
+		if (G_UNLIKELY (g_hash_table_size (priv->device_route_metrics) > NM_MAX (20, n_links * 2))) {
013a0d
+			/* from time to time, we need to do some house-keeping and prune stale entries.
013a0d
+			 * Otherwise, on a system where interfaces frequently come and go (docker), we
013a0d
+			 * keep growing this cache for ifindexes that no longer exist. */
013a0d
+			g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
013a0d
+			while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
013a0d
+				if (!nm_platform_link_get (priv->platform, d2->ifindex))
013a0d
+					g_hash_table_iter_remove (&h_iter);
013a0d
+			}
013a0d
+			cleaned = TRUE;
013a0d
+		}
013a0d
+	}
013a0d
+
013a0d
+	data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type));
013a0d
+
013a0d
+	/* unfortunately, there is no stright forward way to lookup all reserved metrics.
013a0d
+	 * Note, that we don't only have to know which metrics are currently reserved,
013a0d
+	 * but also, which metrics are now seemingly un-used but caused another reserved
013a0d
+	 * metric to be bumped. Hence, the naive O(n^2) search :( */
013a0d
+again:
013a0d
+	g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
013a0d
+	while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
013a0d
+		if (   data->effective_metric < d2->aspired_metric
013a0d
+		    || data->effective_metric > d2->effective_metric) {
013a0d
+			/* no overlap. Skip. */
013a0d
+			continue;
013a0d
+		}
013a0d
+		if (   !cleaned
013a0d
+		    && !nm_platform_link_get (priv->platform, d2->ifindex)) {
013a0d
+			/* the metric seems taken, but there is no such interface. This entry
013a0d
+			 * is stale, forget about it. */
013a0d
+			g_hash_table_iter_remove (&h_iter);
013a0d
+			continue;
013a0d
+		}
013a0d
+		data->effective_metric = d2->effective_metric;
013a0d
+		if (data->effective_metric == G_MAXUINT32) {
013a0d
+			/* we cannot bump any further. Done. */
013a0d
+			break;
013a0d
+		}
013a0d
+
013a0d
+		if (data->effective_metric - data->aspired_metric > 50) {
013a0d
+			/* as one active interface reserves an entire range of metrics
013a0d
+			 * (from aspired_metric to effective_metric), that means if you
013a0d
+			 * alternatingly activate two interfaces, their metric will
013a0d
+			 * juggle up.
013a0d
+			 *
013a0d
+			 * Limit this, don't bump the metric more then 50 times. */
013a0d
+			break;
013a0d
+		}
013a0d
+
013a0d
+		/* bump the metric, and search again. */
013a0d
+		data->effective_metric++;
013a0d
+		goto again;
013a0d
+	}
013a0d
+
013a0d
+	_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d reserves metric %u (aspired %u)",
013a0d
+	       data->ifindex, data->effective_metric, data->aspired_metric);
013a0d
+
013a0d
+	if (!nm_g_hash_table_add (priv->device_route_metrics, data))
013a0d
+		nm_assert_not_reached ();
013a0d
+
013a0d
+	return data->effective_metric;
013a0d
+}
013a0d
+
013a0d
+guint32
013a0d
+nm_manager_device_route_metric_reserve (NMManager *self,
013a0d
+                                        int ifindex,
013a0d
+                                        NMDeviceType device_type)
013a0d
+{
013a0d
+	guint32 metric;
013a0d
+
013a0d
+	metric = _device_route_metric_get (self, ifindex, device_type, FALSE);
013a0d
+	nm_assert (metric != 0);
013a0d
+	return metric;
013a0d
+}
013a0d
+
013a0d
+guint32
013a0d
+nm_manager_device_route_metric_get (NMManager *self,
013a0d
+                                    int ifindex)
013a0d
+{
013a0d
+	return _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN, TRUE);
013a0d
+}
013a0d
+
013a0d
+void
013a0d
+nm_manager_device_route_metric_clear (NMManager *self,
013a0d
+                                      int ifindex)
013a0d
+{
013a0d
+	NMManagerPrivate *priv;
013a0d
+	DeviceRouteMetricData data_lookup;
013a0d
+
013a0d
+	priv = NM_MANAGER_GET_PRIVATE (self);
013a0d
+
013a0d
+	if (!priv->device_route_metrics)
013a0d
+		return;
013a0d
+	data_lookup.ifindex = ifindex;
013a0d
+	if (g_hash_table_remove (priv->device_route_metrics, &data_lookup)) {
013a0d
+		_LOGT (LOGD_DEVICE, "default-route-metric: ifindex %d released",
013a0d
+		       ifindex);
013a0d
+	}
013a0d
+}
013a0d
+
013a0d
+/*****************************************************************************/
013a0d
+
013a0d
 static void
013a0d
 _delete_volatile_connection_do (NMManager *self,
013a0d
                                 NMSettingsConnection *connection)
013a0d
@@ -5228,7 +5461,7 @@ nm_manager_write_device_state (NMManager *self)
013a0d
 
013a0d
 		nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
013a0d
 
013a0d
-		route_metric_default = 0;
013a0d
+		route_metric_default = nm_manager_device_route_metric_get (self, ifindex);
013a0d
 
013a0d
 		if (nm_config_device_state_write (ifindex,
013a0d
 		                                  managed_type,
013a0d
@@ -6607,6 +6840,8 @@ dispose (GObject *object)
013a0d
 
013a0d
 	nm_clear_g_source (&priv->timestamp_update_id);
013a0d
 
013a0d
+	g_clear_pointer (&priv->device_route_metrics, g_hash_table_destroy);
013a0d
+
013a0d
 	G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object);
013a0d
 }
013a0d
 
013a0d
diff --git a/src/nm-manager.h b/src/nm-manager.h
013a0d
index 2d463c718..d82570889 100644
013a0d
--- a/src/nm-manager.h
013a0d
+++ b/src/nm-manager.h
013a0d
@@ -114,6 +114,16 @@ NMDevice *          nm_manager_get_device_by_ifindex   (NMManager *manager,
013a0d
 NMDevice *          nm_manager_get_device_by_path      (NMManager *manager,
013a0d
                                                         const char *path);
013a0d
 
013a0d
+guint32             nm_manager_device_route_metric_reserve (NMManager *self,
013a0d
+                                                            int ifindex,
013a0d
+                                                            NMDeviceType device_type);
013a0d
+
013a0d
+guint32             nm_manager_device_route_metric_get (NMManager *self,
013a0d
+                                                        int ifindex);
013a0d
+
013a0d
+void                nm_manager_device_route_metric_clear (NMManager *self,
013a0d
+                                                          int ifindex);
013a0d
+
013a0d
 char *              nm_manager_get_connection_iface (NMManager *self,
013a0d
                                                      NMConnection *connection,
013a0d
                                                      NMDevice **out_parent,
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From f1728bdeac80fdc45ed969b8821a207383962296 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Tue, 19 Dec 2017 10:10:15 +0100
013a0d
Subject: [PATCH 6/7] core: ensure that the default route-metric bumps at most
013a0d
 50 points
013a0d
013a0d
First check that the limit of 50 metric points is not surpassed.
013a0d
Otherwise, if you have an ethernet device (aspired 100, effective
013a0d
130) and a MACSec devic (aspired 125, effective 155), activating a
013a0d
new ethernet device would bump it's metric to 155 -- more then
013a0d
the 50 points limit.
013a0d
013a0d
It doesn't matter too much, because the cases where the limit of
013a0d
50 could have been surpassed were very specific. Still, change
013a0d
it to ensure that the limit is always honored as one would expect.
013a0d
013a0d
Fixes: 6a32c64d8fb2a9c1cfb78ab7e2f0bb3a269c81d7
013a0d
(cherry picked from commit 2499d3bdc6007308bf282cb44462990a4cd03b0e)
013a0d
(cherry picked from commit 5fd91fb67d3e3865545f5cbc1f39292635440cd3)
013a0d
---
013a0d
 src/nm-manager.c | 26 ++++++++++++++++++--------
013a0d
 1 file changed, 18 insertions(+), 8 deletions(-)
013a0d
013a0d
diff --git a/src/nm-manager.c b/src/nm-manager.c
013a0d
index 4d3416b37..fad4f64b8 100644
013a0d
--- a/src/nm-manager.c
013a0d
+++ b/src/nm-manager.c
013a0d
@@ -472,7 +472,11 @@ initited:
013a0d
 	/* unfortunately, there is no stright forward way to lookup all reserved metrics.
013a0d
 	 * Note, that we don't only have to know which metrics are currently reserved,
013a0d
 	 * but also, which metrics are now seemingly un-used but caused another reserved
013a0d
-	 * metric to be bumped. Hence, the naive O(n^2) search :( */
013a0d
+	 * metric to be bumped. Hence, the naive O(n^2) search :(
013a0d
+	 *
013a0d
+	 * Well, technically, since we limit bumping the metric to 50, this entire
013a0d
+	 * loop runs at most 50 times, so it's still O(n). Let's just say, it's not
013a0d
+	 * very efficient. */
013a0d
 again:
013a0d
 	g_hash_table_iter_init (&h_iter, priv->device_route_metrics);
013a0d
 	while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &d2)) {
013a0d
@@ -488,24 +492,30 @@ again:
013a0d
 			g_hash_table_iter_remove (&h_iter);
013a0d
 			continue;
013a0d
 		}
013a0d
-		data->effective_metric = d2->effective_metric;
013a0d
-		if (data->effective_metric == G_MAXUINT32) {
013a0d
-			/* we cannot bump any further. Done. */
013a0d
+
013a0d
+		if (d2->effective_metric == G_MAXUINT32) {
013a0d
+			/* we cannot bump the metric any further. Done.
013a0d
+			 *
013a0d
+			 * Actually, this can currently not happen because the aspired_metric
013a0d
+			 * are small numbers and we limit the bumping to 50. Still, for
013a0d
+			 * completeness... */
013a0d
+			data->effective_metric = G_MAXUINT32;
013a0d
 			break;
013a0d
 		}
013a0d
 
013a0d
-		if (data->effective_metric - data->aspired_metric > 50) {
013a0d
+		if (d2->effective_metric - data->aspired_metric >= 50) {
013a0d
 			/* as one active interface reserves an entire range of metrics
013a0d
 			 * (from aspired_metric to effective_metric), that means if you
013a0d
 			 * alternatingly activate two interfaces, their metric will
013a0d
-			 * juggle up.
013a0d
+			 * bump each other.
013a0d
 			 *
013a0d
-			 * Limit this, don't bump the metric more then 50 times. */
013a0d
+			 * Limit this, bump the metric at most 50 points. */
013a0d
+			data->effective_metric = data->aspired_metric + 50;
013a0d
 			break;
013a0d
 		}
013a0d
 
013a0d
 		/* bump the metric, and search again. */
013a0d
-		data->effective_metric++;
013a0d
+		data->effective_metric = d2->effective_metric + 1;
013a0d
 		goto again;
013a0d
 	}
013a0d
 
013a0d
-- 
013a0d
2.14.3
013a0d
013a0d
013a0d
From a245f5eb91e8c30ae19403586f5ef9c14ef5f6d2 Mon Sep 17 00:00:00 2001
013a0d
From: Thomas Haller <thaller@redhat.com>
013a0d
Date: Wed, 20 Dec 2017 12:45:02 +0100
013a0d
Subject: [PATCH 7/7] core: persist aspired default route-metric in device's
013a0d
 state file
013a0d
013a0d
NMManager tries to assign unique route-metrics in an increasing manner
013a0d
so that the device which activates first keeps to have the best routes.
013a0d
013a0d
This information is also persisted in the device's state file, however
013a0d
we not only need to persist the effective route-metric which was
013a0d
eventually chosen by NMManager, but also the aspired metric.
013a0d
013a0d
The reason is that when a metric is chosen for a device, the entire
013a0d
range between aspired and effective route-metric is reserved for that
013a0d
device. We must remember the entire range so that after restart the
013a0d
entire range is still considered to be in use.
013a0d
013a0d
Fixes: 6a32c64d8fb2a9c1cfb78ab7e2f0bb3a269c81d7
013a0d
(cherry picked from commit 4277bc0ee0479ad62369c3a261ea8d098e5e25ad)
013a0d
(cherry picked from commit fa53c715d1b929516e5e32e13ebc630a2c339394)
013a0d
---
013a0d
 src/nm-config.c  | 50 +++++++++++++++++++++++++++++++++++---------------
013a0d
 src/nm-config.h  |  6 ++++--
013a0d
 src/nm-manager.c | 48 +++++++++++++++++++++++++++---------------------
013a0d
 src/nm-manager.h |  3 ---
013a0d
 4 files changed, 66 insertions(+), 41 deletions(-)
013a0d
013a0d
diff --git a/src/nm-config.c b/src/nm-config.c
013a0d
index 97ae3f6f4..29f0d5177 100644
013a0d
--- a/src/nm-config.c
013a0d
+++ b/src/nm-config.c
013a0d
@@ -1933,7 +1933,8 @@ _nm_config_state_set (NMConfig *self,
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_PERM_HW_ADDR_FAKE   "perm-hw-addr-fake"
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_CONNECTION_UUID     "connection-uuid"
013a0d
 #define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED            "nm-owned"
013a0d
-#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT "route-metric-default"
013a0d
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED   "route-metric-default-aspired"
013a0d
+#define DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE "route-metric-default-effective"
013a0d
 
013a0d
 NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
013a0d
 	NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
013a0d
@@ -1953,7 +1954,8 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	gsize perm_hw_addr_fake_len;
013a0d
 	gint nm_owned = -1;
013a0d
 	char *p;
013a0d
-	guint32 route_metric_default;
013a0d
+	guint32 route_metric_default_effective;
013a0d
+	guint32 route_metric_default_aspired;
013a0d
 
013a0d
 	nm_assert (kf);
013a0d
 	nm_assert (ifindex > 0);
013a0d
@@ -1996,10 +1998,18 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 
013a0d
 	/* metric zero is not a valid metric. While zero valid for IPv4, for IPv6 it is an alias
013a0d
 	 * for 1024. Since we handle here IPv4 and IPv6 the same, we cannot allow zero. */
013a0d
-	route_metric_default = nm_config_keyfile_get_int64 (kf,
013a0d
-	                                                    DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-	                                                    DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
013a0d
-	                                                    10, 1, G_MAXUINT32, 0);
013a0d
+	route_metric_default_effective = nm_config_keyfile_get_int64 (kf,
013a0d
+	                                                              DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+	                                                              DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
013a0d
+	                                                              10, 1, G_MAXUINT32, 0);
013a0d
+	if (route_metric_default_effective) {
013a0d
+		route_metric_default_aspired = nm_config_keyfile_get_int64 (kf,
013a0d
+		                                                            DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+		                                                            DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
013a0d
+		                                                            10, 1, route_metric_default_effective,
013a0d
+		                                                            route_metric_default_effective);
013a0d
+	} else
013a0d
+		route_metric_default_aspired = 0;
013a0d
 
013a0d
 	connection_uuid_len = connection_uuid ? strlen (connection_uuid) + 1 : 0;
013a0d
 	perm_hw_addr_fake_len = perm_hw_addr_fake ? strlen (perm_hw_addr_fake) + 1 : 0;
013a0d
@@ -2013,7 +2023,8 @@ _config_device_state_data_new (int ifindex, GKeyFile *kf)
013a0d
 	device_state->connection_uuid = NULL;
013a0d
 	device_state->perm_hw_addr_fake = NULL;
013a0d
 	device_state->nm_owned = nm_owned;
013a0d
-	device_state->route_metric_default = route_metric_default;
013a0d
+	device_state->route_metric_default_aspired = route_metric_default_aspired;
013a0d
+	device_state->route_metric_default_effective = route_metric_default_effective;
013a0d
 
013a0d
 	p = (char *) (&device_state[1]);
013a0d
 	if (connection_uuid) {
013a0d
@@ -2058,14 +2069,15 @@ nm_config_device_state_load (int ifindex)
013a0d
 	               ", nm-owned=1" :
013a0d
 	               (device_state->nm_owned == FALSE ? ", nm-owned=0" : "");
013a0d
 
013a0d
-	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT,
013a0d
+	_LOGT ("device-state: %s #%d (%s); managed=%s%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"",
013a0d
 	       kf ? "read" : "miss",
013a0d
 	       ifindex, path,
013a0d
 	       _device_state_managed_type_to_str (device_state->managed),
013a0d
 	       NM_PRINT_FMT_QUOTED (device_state->connection_uuid, ", connection-uuid=", device_state->connection_uuid, "", ""),
013a0d
 	       NM_PRINT_FMT_QUOTED (device_state->perm_hw_addr_fake, ", perm-hw-addr-fake=", device_state->perm_hw_addr_fake, "", ""),
013a0d
 	       nm_owned_str,
013a0d
-	       device_state->route_metric_default);
013a0d
+	       device_state->route_metric_default_aspired,
013a0d
+	       device_state->route_metric_default_effective);
013a0d
 
013a0d
 	return device_state;
013a0d
 }
013a0d
@@ -2119,7 +2131,8 @@ nm_config_device_state_write (int ifindex,
013a0d
                               const char *perm_hw_addr_fake,
013a0d
                               const char *connection_uuid,
013a0d
                               gint nm_owned,
013a0d
-                              guint32 route_metric_default)
013a0d
+                              guint32 route_metric_default_aspired,
013a0d
+                              guint32 route_metric_default_effective)
013a0d
 {
013a0d
 	char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
013a0d
 	GError *local = NULL;
013a0d
@@ -2161,11 +2174,17 @@ nm_config_device_state_write (int ifindex,
013a0d
 		                        nm_owned);
013a0d
 	}
013a0d
 
013a0d
-	if (route_metric_default != 0) {
013a0d
+	if (route_metric_default_effective != 0) {
013a0d
 		g_key_file_set_int64 (kf,
013a0d
 		                      DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
-		                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
013a0d
-		                      route_metric_default);
013a0d
+		                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_EFFECTIVE,
013a0d
+		                      route_metric_default_effective);
013a0d
+		if (route_metric_default_aspired != route_metric_default_effective) {
013a0d
+			g_key_file_set_int64 (kf,
013a0d
+			                      DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
013a0d
+			                      DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT_ASPIRED,
013a0d
+			                      route_metric_default_aspired);
013a0d
+		}
013a0d
 	}
013a0d
 
013a0d
 	if (!g_key_file_save_to_file (kf, path, &local)) {
013a0d
@@ -2173,12 +2192,13 @@ nm_config_device_state_write (int ifindex,
013a0d
 		g_error_free (local);
013a0d
 		return FALSE;
013a0d
 	}
013a0d
-	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT,
013a0d
+	_LOGT ("device-state: write #%d (%s); managed=%s%s%s%s%s%s%s, route-metric-default=%"G_GUINT32_FORMAT"-%"G_GUINT32_FORMAT"",
013a0d
 	       ifindex, path,
013a0d
 	       _device_state_managed_type_to_str (managed),
013a0d
 	       NM_PRINT_FMT_QUOTED (connection_uuid, ", connection-uuid=", connection_uuid, "", ""),
013a0d
 	       NM_PRINT_FMT_QUOTED (perm_hw_addr_fake, ", perm-hw-addr-fake=", perm_hw_addr_fake, "", ""),
013a0d
-	       route_metric_default);
013a0d
+	       route_metric_default_aspired,
013a0d
+	       route_metric_default_effective);
013a0d
 	return TRUE;
013a0d
 }
013a0d
 
013a0d
diff --git a/src/nm-config.h b/src/nm-config.h
013a0d
index 4c6fcca2b..42ab4682d 100644
013a0d
--- a/src/nm-config.h
013a0d
+++ b/src/nm-config.h
013a0d
@@ -213,7 +213,8 @@ struct _NMConfigDeviceStateData {
013a0d
 	NMConfigDeviceStateManagedType managed;
013a0d
 
013a0d
 	/* a value of zero means that no metric is set. */
013a0d
-	guint32 route_metric_default;
013a0d
+	guint32 route_metric_default_aspired;
013a0d
+	guint32 route_metric_default_effective;
013a0d
 
013a0d
 	/* the UUID of the last settings-connection active
013a0d
 	 * on the device. */
013a0d
@@ -233,7 +234,8 @@ gboolean nm_config_device_state_write (int ifindex,
013a0d
                                        const char *perm_hw_addr_fake,
013a0d
                                        const char *connection_uuid,
013a0d
                                        gint nm_owned,
013a0d
-                                       guint32 route_metric_default);
013a0d
+                                       guint32 route_metric_default_aspired,
013a0d
+                                       guint32 route_metric_default_effective);
013a0d
 
013a0d
 void nm_config_device_state_prune_unseen (GHashTable *seen_ifindexes);
013a0d
 
013a0d
diff --git a/src/nm-manager.c b/src/nm-manager.c
013a0d
index fad4f64b8..c011bfd0e 100644
013a0d
--- a/src/nm-manager.c
013a0d
+++ b/src/nm-manager.c
013a0d
@@ -333,7 +333,7 @@ typedef struct {
013a0d
 } DeviceRouteMetricData;
013a0d
 
013a0d
 static DeviceRouteMetricData *
013a0d
-_device_route_metric_data_new (int ifindex, guint32 metric)
013a0d
+_device_route_metric_data_new (int ifindex, guint32 aspired_metric, guint32 effective_metric)
013a0d
 {
013a0d
 	DeviceRouteMetricData *data;
013a0d
 
013a0d
@@ -343,12 +343,13 @@ _device_route_metric_data_new (int ifindex, guint32 metric)
013a0d
 	 * zero is treated like 1024. Since we handle IPv4 and IPv6 identically,
013a0d
 	 * we cannot allow a zero metric here.
013a0d
 	 */
013a0d
-	nm_assert (metric > 0);
013a0d
+	nm_assert (aspired_metric > 0);
013a0d
+	nm_assert (effective_metric == 0 || aspired_metric <= effective_metric);
013a0d
 
013a0d
 	data = g_slice_new0 (DeviceRouteMetricData);
013a0d
 	data->ifindex = ifindex;
013a0d
-	data->aspired_metric = metric;
013a0d
-	data->effective_metric = metric;
013a0d
+	data->aspired_metric = aspired_metric;
013a0d
+	data->effective_metric = effective_metric ?: aspired_metric;
013a0d
 	return data;
013a0d
 }
013a0d
 
013a0d
@@ -376,7 +377,8 @@ static guint32
013a0d
 _device_route_metric_get (NMManager *self,
013a0d
                           int ifindex,
013a0d
                           NMDeviceType device_type,
013a0d
-                          gboolean lookup_only)
013a0d
+                          gboolean lookup_only,
013a0d
+                          guint32 *out_aspired_metric)
013a0d
 {
013a0d
 	NMManagerPrivate *priv;
013a0d
 	const DeviceRouteMetricData *d2;
013a0d
@@ -387,13 +389,18 @@ _device_route_metric_get (NMManager *self,
013a0d
 	guint n_links;
013a0d
 	gboolean cleaned = FALSE;
013a0d
 	GHashTableIter h_iter;
013a0d
+	guint32 metric;
013a0d
 
013a0d
 	g_return_val_if_fail (NM_IS_MANAGER (self), 0);
013a0d
 
013a0d
+	NM_SET_OUT (out_aspired_metric, 0);
013a0d
+
013a0d
 	if (ifindex <= 0) {
013a0d
 		if (lookup_only)
013a0d
 			return 0;
013a0d
-		return nm_device_get_route_metric_default (device_type);
013a0d
+		metric = nm_device_get_route_metric_default (device_type);
013a0d
+		NM_SET_OUT (out_aspired_metric, metric);
013a0d
+		return metric;
013a0d
 	}
013a0d
 
013a0d
 	priv = NM_MANAGER_GET_PRIVATE (self);
013a0d
@@ -419,7 +426,7 @@ _device_route_metric_get (NMManager *self,
013a0d
 
013a0d
 		g_hash_table_iter_init (&h_iter, (GHashTable *) h);
013a0d
 		while (g_hash_table_iter_next (&h_iter, NULL, (gpointer *) &device_state)) {
013a0d
-			if (!device_state->route_metric_default)
013a0d
+			if (!device_state->route_metric_default_effective)
013a0d
 				continue;
013a0d
 			if (!nm_platform_link_get (priv->platform, device_state->ifindex)) {
013a0d
 				/* we have the entry in the state file, but (currently) no such
013a0d
@@ -429,7 +436,8 @@ _device_route_metric_get (NMManager *self,
013a0d
 			}
013a0d
 			if (!nm_g_hash_table_add (priv->device_route_metrics,
013a0d
 			                          _device_route_metric_data_new (device_state->ifindex,
013a0d
-			                                                         device_state->route_metric_default)))
013a0d
+			                                                         device_state->route_metric_default_aspired,
013a0d
+			                                                         device_state->route_metric_default_effective)))
013a0d
 				nm_assert_not_reached ();
013a0d
 		}
013a0d
 	}
013a0d
@@ -439,7 +447,7 @@ initited:
013a0d
 
013a0d
 	data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
013a0d
 	if (data)
013a0d
-		return data->effective_metric;
013a0d
+		goto out;
013a0d
 	if (lookup_only)
013a0d
 		return 0;
013a0d
 
013a0d
@@ -467,7 +475,7 @@ initited:
013a0d
 		}
013a0d
 	}
013a0d
 
013a0d
-	data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type));
013a0d
+	data = _device_route_metric_data_new (ifindex, nm_device_get_route_metric_default (device_type), 0);
013a0d
 
013a0d
 	/* unfortunately, there is no stright forward way to lookup all reserved metrics.
013a0d
 	 * Note, that we don't only have to know which metrics are currently reserved,
013a0d
@@ -525,6 +533,8 @@ again:
013a0d
 	if (!nm_g_hash_table_add (priv->device_route_metrics, data))
013a0d
 		nm_assert_not_reached ();
013a0d
 
013a0d
+out:
013a0d
+	NM_SET_OUT (out_aspired_metric, data->aspired_metric);
013a0d
 	return data->effective_metric;
013a0d
 }
013a0d
 
013a0d
@@ -535,18 +545,11 @@ nm_manager_device_route_metric_reserve (NMManager *self,
013a0d
 {
013a0d
 	guint32 metric;
013a0d
 
013a0d
-	metric = _device_route_metric_get (self, ifindex, device_type, FALSE);
013a0d
+	metric = _device_route_metric_get (self, ifindex, device_type, FALSE, NULL);
013a0d
 	nm_assert (metric != 0);
013a0d
 	return metric;
013a0d
 }
013a0d
 
013a0d
-guint32
013a0d
-nm_manager_device_route_metric_get (NMManager *self,
013a0d
-                                    int ifindex)
013a0d
-{
013a0d
-	return _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN, TRUE);
013a0d
-}
013a0d
-
013a0d
 void
013a0d
 nm_manager_device_route_metric_clear (NMManager *self,
013a0d
                                       int ifindex)
013a0d
@@ -5441,7 +5444,8 @@ nm_manager_write_device_state (NMManager *self)
013a0d
 		const char *uuid = NULL;
013a0d
 		const char *perm_hw_addr_fake = NULL;
013a0d
 		gboolean perm_hw_addr_is_fake;
013a0d
-		guint32 route_metric_default;
013a0d
+		guint32 route_metric_default_aspired;
013a0d
+		guint32 route_metric_default_effective;
013a0d
 
013a0d
 		ifindex = nm_device_get_ip_ifindex (device);
013a0d
 		if (ifindex <= 0)
013a0d
@@ -5471,14 +5475,16 @@ nm_manager_write_device_state (NMManager *self)
013a0d
 
013a0d
 		nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
013a0d
 
013a0d
-		route_metric_default = nm_manager_device_route_metric_get (self, ifindex);
013a0d
+		route_metric_default_effective = _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN,
013a0d
+		                                                           TRUE, &route_metric_default_aspired);
013a0d
 
013a0d
 		if (nm_config_device_state_write (ifindex,
013a0d
 		                                  managed_type,
013a0d
 		                                  perm_hw_addr_fake,
013a0d
 		                                  uuid,
013a0d
 		                                  nm_owned,
013a0d
-		                                  route_metric_default))
013a0d
+		                                  route_metric_default_aspired,
013a0d
+		                                  route_metric_default_effective))
013a0d
 			g_hash_table_add (seen_ifindexes, GINT_TO_POINTER (ifindex));
013a0d
 	}
013a0d
 
013a0d
diff --git a/src/nm-manager.h b/src/nm-manager.h
013a0d
index d82570889..b4587e088 100644
013a0d
--- a/src/nm-manager.h
013a0d
+++ b/src/nm-manager.h
013a0d
@@ -118,9 +118,6 @@ guint32             nm_manager_device_route_metric_reserve (NMManager *self,
013a0d
                                                             int ifindex,
013a0d
                                                             NMDeviceType device_type);
013a0d
 
013a0d
-guint32             nm_manager_device_route_metric_get (NMManager *self,
013a0d
-                                                        int ifindex);
013a0d
-
013a0d
 void                nm_manager_device_route_metric_clear (NMManager *self,
013a0d
                                                           int ifindex);
013a0d
 
013a0d
-- 
013a0d
2.14.3
013a0d