summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2017-12-15 11:42:41 +0100
committerThomas Haller <thaller@redhat.com>2017-12-15 11:42:41 +0100
commitb2273ce3dd13c467be2f5b7bca82ee97b11cf805 (patch)
tree5b0e2eb1c81bad0ed1eaa36b9efb5faabde25591
parent54c0572de34d0bb0a8403c0da185d96ee997a83e (diff)
parent6a32c64d8fb2a9c1cfb78ab7e2f0bb3a269c81d7 (diff)
downloadNetworkManager-b2273ce3dd13c467be2f5b7bca82ee97b11cf805.tar.gz
core: merge branch 'th/device-route-metric-rh1505893'
https://bugzilla.redhat.com/show_bug.cgi?id=1505893
-rw-r--r--libnm-core/nm-core-internal.h8
-rw-r--r--libnm-core/nm-utils.c84
-rw-r--r--libnm-core/tests/test-general.c99
-rw-r--r--src/devices/nm-device.c47
-rw-r--r--src/devices/nm-device.h2
-rw-r--r--src/nm-config.c242
-rw-r--r--src/nm-config.h21
-rw-r--r--src/nm-manager.c246
-rw-r--r--src/nm-manager.h10
9 files changed, 652 insertions, 107 deletions
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 5a27d43886..15e5dc2a14 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -241,7 +241,13 @@ GPtrArray *_nm_utils_copy_object_array (const GPtrArray *array);
gssize _nm_utils_ptrarray_find_first (gconstpointer *list, gssize len, gconstpointer needle);
-gssize _nm_utils_ptrarray_find_binary_search (gconstpointer *list, gsize len, gconstpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
+gssize _nm_utils_ptrarray_find_binary_search (gconstpointer *list,
+ gsize len,
+ gconstpointer needle,
+ GCompareDataFunc cmpfcn,
+ gpointer user_data,
+ gssize *out_idx_first,
+ gssize *out_idx_last);
gssize _nm_utils_array_find_binary_search (gconstpointer list, gsize elem_size, gsize len, gconstpointer needle, GCompareDataFunc cmpfcn, gpointer user_data);
GSList * _nm_utils_strv_to_slist (char **strv, gboolean deep_copy);
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index 73035e5ecb..1e631c2c99 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -646,36 +646,82 @@ _nm_utils_ptrarray_find_first (gconstpointer *list, gssize len, gconstpointer ne
}
gssize
-_nm_utils_ptrarray_find_binary_search (gconstpointer *list, gsize len, gconstpointer needle, GCompareDataFunc cmpfcn, gpointer user_data)
-{
- gssize imin, imax, imid;
+_nm_utils_ptrarray_find_binary_search (gconstpointer *list,
+ gsize len,
+ gconstpointer needle,
+ GCompareDataFunc cmpfcn,
+ gpointer user_data,
+ gssize *out_idx_first,
+ gssize *out_idx_last)
+{
+ gssize imin, imax, imid, i2min, i2max, i2mid;
int cmp;
g_return_val_if_fail (list || !len, ~((gssize) 0));
g_return_val_if_fail (cmpfcn, ~((gssize) 0));
imin = 0;
- if (len == 0)
- return ~imin;
-
- imax = len - 1;
-
- while (imin <= imax) {
- imid = imin + (imax - imin) / 2;
-
- cmp = cmpfcn (list[imid], needle, user_data);
- if (cmp == 0)
- return imid;
+ if (len > 0) {
+ imax = len - 1;
+
+ while (imin <= imax) {
+ imid = imin + (imax - imin) / 2;
+
+ cmp = cmpfcn (list[imid], needle, user_data);
+ if (cmp == 0) {
+ /* we found a matching entry at index imid.
+ *
+ * Does the caller request the first/last index as well (in case that
+ * there are multiple entries which compare equal). */
+
+ if (out_idx_first) {
+ i2min = imin;
+ i2max = imid + 1;
+ while (i2min <= i2max) {
+ i2mid = i2min + (i2max - i2min) / 2;
+
+ cmp = cmpfcn (list[i2mid], needle, user_data);
+ if (cmp == 0)
+ i2max = i2mid -1;
+ else {
+ nm_assert (cmp < 0);
+ i2min = i2mid + 1;
+ }
+ }
+ *out_idx_first = i2min;
+ }
+ if (out_idx_last) {
+ i2min = imid + 1;
+ i2max = imax;
+ while (i2min <= i2max) {
+ i2mid = i2min + (i2max - i2min) / 2;
+
+ cmp = cmpfcn (list[i2mid], needle, user_data);
+ if (cmp == 0)
+ i2min = i2mid + 1;
+ else {
+ nm_assert (cmp > 0);
+ i2max = i2mid - 1;
+ }
+ }
+ *out_idx_last = i2min - 1;
+ }
+ return imid;
+ }
- if (cmp < 0)
- imin = imid + 1;
- else
- imax = imid - 1;
+ if (cmp < 0)
+ imin = imid + 1;
+ else
+ imax = imid - 1;
+ }
}
/* return the inverse of @imin. This is a negative number, but
* also is ~imin the position where the value should be inserted. */
- return ~imin;
+ imin = ~imin;
+ NM_SET_OUT (out_idx_first, imin);
+ NM_SET_OUT (out_idx_last, imin);
+ return imin;
}
gssize
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 21337a9d40..88eb4bc85f 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -6149,7 +6149,7 @@ static void
_test_find_binary_search_do (const int *array, gsize len)
{
gsize i;
- gssize idx;
+ gssize idx, idx_first, idx_last;
gs_free gconstpointer *parray = g_new (gconstpointer, len);
const int NEEDLE = 0;
gconstpointer pneedle = GINT_TO_POINTER (NEEDLE);
@@ -6160,10 +6160,10 @@ _test_find_binary_search_do (const int *array, gsize len)
expected_result = _nm_utils_ptrarray_find_first (parray, len, pneedle);
- idx = _nm_utils_ptrarray_find_binary_search (parray, len, pneedle, _test_find_binary_search_cmp, NULL);
- if (expected_result >= 0)
+ idx = _nm_utils_ptrarray_find_binary_search (parray, len, pneedle, _test_find_binary_search_cmp, NULL, &idx_first, &idx_last);
+ if (expected_result >= 0) {
g_assert_cmpint (expected_result, ==, idx);
- else {
+ } else {
gssize idx2 = ~idx;
g_assert_cmpint (idx, <, 0);
@@ -6172,6 +6172,8 @@ _test_find_binary_search_do (const int *array, gsize len)
g_assert (idx2 - 1 < 0 || _test_find_binary_search_cmp (parray[idx2 - 1], pneedle, NULL) < 0);
g_assert (idx2 >= len || _test_find_binary_search_cmp (parray[idx2], pneedle, NULL) > 0);
}
+ g_assert_cmpint (idx, ==, idx_first);
+ g_assert_cmpint (idx, ==, idx_last);
for (i = 0; i < len; i++) {
int cmp;
@@ -6273,6 +6275,94 @@ test_nm_utils_ptrarray_find_binary_search (void)
}
/*****************************************************************************/
+
+#define BIN_SEARCH_W_DUPS_LEN 100
+#define BIN_SEARCH_W_DUPS_JITTER 10
+
+static int
+_test_bin_search2_cmp (gconstpointer pa,
+ gconstpointer pb,
+ gpointer user_data)
+{
+ int a = GPOINTER_TO_INT (pa);
+ int b = GPOINTER_TO_INT (pb);
+
+ g_assert (a >= 0 && a <= BIN_SEARCH_W_DUPS_LEN + BIN_SEARCH_W_DUPS_JITTER);
+ g_assert (b >= 0 && b <= BIN_SEARCH_W_DUPS_LEN + BIN_SEARCH_W_DUPS_JITTER);
+ NM_CMP_DIRECT (a, b);
+ return 0;
+}
+
+static int
+_test_bin_search2_cmp_p (gconstpointer pa,
+ gconstpointer pb,
+ gpointer user_data)
+{
+ return _test_bin_search2_cmp (*((gpointer *) pa), *((gpointer *) pb), NULL);
+}
+
+static void
+test_nm_utils_ptrarray_find_binary_search_with_duplicates (void)
+{
+ gssize idx, idx2, idx_first2, idx_first, idx_last;
+ int i_test, i_len, i;
+ gssize j;
+ gconstpointer arr[BIN_SEARCH_W_DUPS_LEN];
+ const int N_TEST = 10;
+
+ for (i_test = 0; i_test < N_TEST; i_test++) {
+ for (i_len = 0; i_len < BIN_SEARCH_W_DUPS_LEN; i_len++) {
+
+ /* fill with random numbers... surely there are some duplicates
+ * there... or maybe even there are none... */
+ for (i = 0; i < i_len; i++)
+ arr[i] = GINT_TO_POINTER (nmtst_get_rand_int () % (i_len + BIN_SEARCH_W_DUPS_JITTER));
+ g_qsort_with_data (arr,
+ i_len,
+ sizeof (gpointer),
+ _test_bin_search2_cmp_p,
+ NULL);
+ for (i = 0; i < i_len + BIN_SEARCH_W_DUPS_JITTER; i++) {
+ gconstpointer p = GINT_TO_POINTER (i);
+
+ idx = _nm_utils_ptrarray_find_binary_search (arr, i_len, p, _test_bin_search2_cmp, NULL, &idx_first, &idx_last);
+
+ idx_first2 = _nm_utils_ptrarray_find_first (arr, i_len, p);
+
+ idx2 = _nm_utils_array_find_binary_search (arr, sizeof (gpointer), i_len, &p, _test_bin_search2_cmp_p, NULL);
+ g_assert_cmpint (idx, ==, idx2);
+
+ if (idx_first2 < 0) {
+ g_assert_cmpint (idx, <, 0);
+ g_assert_cmpint (idx, ==, idx_first);
+ g_assert_cmpint (idx, ==, idx_last);
+ idx = ~idx;
+ g_assert_cmpint (idx, >=, 0);
+ g_assert_cmpint (idx, <=, i_len);
+ if (i_len == 0)
+ g_assert_cmpint (idx, ==, 0);
+ else {
+ g_assert (idx == i_len || GPOINTER_TO_INT (arr[idx]) > i);
+ g_assert (idx == 0 || GPOINTER_TO_INT (arr[idx - 1]) < i);
+ }
+ } else {
+ g_assert_cmpint (idx_first, ==, idx_first2);
+ g_assert_cmpint (idx_first, >=, 0);
+ g_assert_cmpint (idx_last, <, i_len);
+ g_assert_cmpint (idx_first, <=, idx_last);
+ g_assert_cmpint (idx, >=, idx_first);
+ g_assert_cmpint (idx, <=, idx_last);
+ for (j = idx_first; j < idx_last; j++)
+ g_assert (GPOINTER_TO_INT (arr[j]) == i);
+ g_assert (idx_first == 0 || GPOINTER_TO_INT (arr[idx_first - 1]) < i);
+ g_assert (idx_last == i_len - 1 || GPOINTER_TO_INT (arr[idx_last + 1]) > i);
+ }
+ }
+ }
+ }
+}
+
+/*****************************************************************************/
static void
test_nm_utils_enum_from_str_do (GType type, const char *str,
gboolean exp_result, int exp_flags,
@@ -6962,6 +7052,7 @@ int main (int argc, char **argv)
g_test_add_func ("/core/general/_glib_compat_g_ptr_array_insert", test_g_ptr_array_insert);
g_test_add_func ("/core/general/_glib_compat_g_hash_table_get_keys_as_array", test_g_hash_table_get_keys_as_array);
g_test_add_func ("/core/general/_nm_utils_ptrarray_find_binary_search", test_nm_utils_ptrarray_find_binary_search);
+ g_test_add_func ("/core/general/_nm_utils_ptrarray_find_binary_search_with_duplicates", test_nm_utils_ptrarray_find_binary_search_with_duplicates);
g_test_add_func ("/core/general/_nm_utils_strstrdictkey", test_nm_utils_strstrdictkey);
g_test_add_func ("/core/general/nm_ptrarray_len", test_nm_ptrarray_len);
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index a1115e58ea..fda2126410 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -1721,8 +1721,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
@@ -1741,7 +1741,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:
@@ -1870,7 +1870,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);
}
@@ -12609,6 +12612,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
@@ -13639,6 +13647,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
@@ -13698,23 +13707,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 42107ce481..abb2420c14 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/nm-config.c b/src/nm-config.c
index de727c9882..97ae3f6f43 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,7 @@ _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 "route-metric-default"
NM_UTILS_LOOKUP_STR_DEFINE_STATIC (_device_state_managed_type_to_str, NMConfigDeviceStateManagedType,
NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT ("unknown"),
@@ -1917,47 +1953,54 @@ _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;
+ 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;
+ 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;
+ }
- normalized = nm_utils_hwaddr_canonical (perm_hw_addr_fake, -1);
- g_free (perm_hw_addr_fake);
- perm_hw_addr_fake = normalized;
- }
+ 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;
- nm_owned = nm_config_keyfile_get_boolean (kf,
- DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
- DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_NM_OWNED,
- -1);
+ 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 = nm_config_keyfile_get_int64 (kf,
+ DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+ DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
+ 10, 1, G_MAXUINT32, 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 +2013,7 @@ _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 = route_metric_default;
p = (char *) (&device_state[1]);
if (connection_uuid) {
@@ -2007,31 +2051,75 @@ 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,
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);
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)
{
char path[NM_STRLEN (NM_CONFIG_DEVICE_STATE_DIR) + 60];
GError *local = NULL;
@@ -2073,17 +2161,24 @@ nm_config_device_state_write (int ifindex,
nm_owned);
}
+ if (route_metric_default != 0) {
+ g_key_file_set_int64 (kf,
+ DEVICE_RUN_STATE_KEYFILE_GROUP_DEVICE,
+ DEVICE_RUN_STATE_KEYFILE_KEY_DEVICE_ROUTE_METRIC_DEFAULT,
+ route_metric_default);
+ }
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,
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);
return TRUE;
}
@@ -2094,7 +2189,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 +2199,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 +2222,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 3deec5fa72..e9e000932c 100644
--- a/src/nm-config.h
+++ b/src/nm-config.h
@@ -166,6 +166,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,
@@ -206,6 +213,9 @@ struct _NMConfigDeviceStateData {
int ifindex;
NMConfigDeviceStateManagedType managed;
+ /* a value of zero means that no metric is set. */
+ guint32 route_metric_default;
+
/* the UUID of the last settings-connection active
* on the device. */
const char *connection_uuid;
@@ -214,17 +224,24 @@ 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);
+
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-manager.c b/src/nm-manager.c
index 04d17f9b23..5c84dc463e 100644
--- a/src/nm-manager.c
+++ b/src/nm-manager.c
@@ -161,6 +161,8 @@ typedef struct {
NMAuthManager *auth_mgr;
+ GHashTable *device_route_metrics;
+
GSList *auth_chains;
GHashTable *sleep_devices;
@@ -325,6 +327,237 @@ 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 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 (metric > 0);
+
+ data = g_slice_new0 (DeviceRouteMetricData);
+ data->ifindex = ifindex;
+ data->aspired_metric = metric;
+ data->effective_metric = 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)
+{
+ 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;
+
+ g_return_val_if_fail (NM_IS_MANAGER (self), 0);
+
+ if (ifindex <= 0) {
+ if (lookup_only)
+ return 0;
+ return nm_device_get_route_metric_default (device_type);
+ }
+
+ 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)
+ 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)))
+ nm_assert_not_reached ();
+ }
+ }
+
+initited:
+ data_lookup.ifindex = ifindex;
+
+ data = g_hash_table_lookup (priv->device_route_metrics, &data_lookup);
+ if (data)
+ return data->effective_metric;
+ 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));
+
+ /* 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 :( */
+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;
+ }
+ data->effective_metric = d2->effective_metric;
+ if (data->effective_metric == G_MAXUINT32) {
+ /* we cannot bump any further. Done. */
+ break;
+ }
+
+ if (data->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
+ * juggle up.
+ *
+ * Limit this, don't bump the metric more then 50 times. */
+ break;
+ }
+
+ /* bump the metric, and search again. */
+ data->effective_metric++;
+ 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 ();
+
+ 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);
+ nm_assert (metric != 0);
+ return metric;
+}
+
+guint32
+nm_manager_device_route_metric_get (NMManager *self,
+ int ifindex)
+{
+ return _device_route_metric_get (self, ifindex, NM_DEVICE_TYPE_UNKNOWN, TRUE);
+}
+
+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)
@@ -2641,10 +2874,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,
@@ -5200,6 +5432,7 @@ 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;
ifindex = nm_device_get_ip_ifindex (device);
if (ifindex <= 0)
@@ -5229,11 +5462,14 @@ nm_manager_write_device_state (NMManager *self)
nm_owned = nm_device_is_software (device) ? nm_device_is_nm_owned (device) : -1;
+ route_metric_default = nm_manager_device_route_metric_get (self, ifindex);
+
if (nm_config_device_state_write (ifindex,
managed_type,
perm_hw_addr_fake,
uuid,
- nm_owned))
+ nm_owned,
+ route_metric_default))
g_hash_table_add (seen_ifindexes, GINT_TO_POINTER (ifindex));
}
@@ -6612,6 +6848,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 d52b2655de..34e868a9cb 100644
--- a/src/nm-manager.h
+++ b/src/nm-manager.h
@@ -115,6 +115,16 @@ 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);
+
+guint32 nm_manager_device_route_metric_get (NMManager *self,
+ int ifindex);
+
+void nm_manager_device_route_metric_clear (NMManager *self,
+ int ifindex);
+
char * nm_manager_get_connection_iface (NMManager *self,
NMConnection *connection,
NMDevice **out_parent,