summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2014-03-14 14:08:51 +0100
committerThomas Haller <thaller@redhat.com>2014-04-11 11:13:06 +0200
commit86f8066177ffdfd2890774f4f230919dfd322c1d (patch)
tree41ff4d3e49131d477c77d8fa487bf7c8ef52e8bc
parent65bc042e8f1dc0f7b757d6450e436697d7e7b7b1 (diff)
core: sort IPv6 addresses (add nm_ip6_config_addresses_sort())
Clients such as gnome-control-center or nm-applet show at some places only one (IPv6) address. They most likely just pick the first address from the list of addresses, so we should order them. Sorting has the advantage to make the order deterministic -- contrary to before where the order depended on run time conditions. Note, that it might be desirable to show the address that the kernel will use as source address for new connections. However, this depends on routing and cannot be easily determined in general. Still, the ordering tries to account for this and sorts the addresses accordingly. https://bugzilla.gnome.org/show_bug.cgi?id=726525 Signed-off-by: Thomas Haller <thaller@redhat.com>
-rw-r--r--src/devices/nm-device.c5
-rw-r--r--src/nm-ip6-config.c120
-rw-r--r--src/nm-ip6-config.h4
-rw-r--r--src/tests/test-ip6-config.c87
4 files changed, 209 insertions, 7 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 817b924334..b6e5a20203 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -3062,6 +3062,9 @@ ip6_config_merge_and_apply (NMDevice *self,
if (connection)
nm_ip6_config_merge_setting (composite, nm_connection_get_setting_ip6_config (connection));
+ nm_ip6_config_addresses_sort (composite,
+ priv->rdisc ? priv->rdisc_use_tempaddr : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+
success = nm_device_set_ip6_config (self, composite, commit, out_reason);
g_object_unref (composite);
return success;
@@ -7037,7 +7040,7 @@ update_ip_config (NMDevice *self, gboolean initial)
/* IPv6 */
g_clear_object (&priv->ext_ip6_config);
- priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf);
+ priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
if (priv->ext_ip6_config) {
/* Check this before modifying ext_ip6_config */
diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c
index 8ca7c0cd05..36f5e72fdc 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -172,8 +172,115 @@ routes_are_duplicate (const NMPlatformIP6Route *a, const NMPlatformIP6Route *b,
(!consider_gateway_and_metric || (IN6_ARE_ADDR_EQUAL (&a->gateway, &b->gateway) && a->metric == b->metric));
}
+static gint
+_addresses_sort_cmp_get_prio (const struct in6_addr *addr)
+{
+ if (IN6_IS_ADDR_V4MAPPED (addr))
+ return 0;
+ if (IN6_IS_ADDR_V4COMPAT (addr))
+ return 1;
+ if (IN6_IS_ADDR_UNSPECIFIED (addr))
+ return 2;
+ if (IN6_IS_ADDR_LOOPBACK (addr))
+ return 3;
+ if (IN6_IS_ADDR_LINKLOCAL (addr))
+ return 4;
+ if (IN6_IS_ADDR_SITELOCAL (addr))
+ return 5;
+ return 6;
+}
+
+static gint
+_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ gint p1, p2, c;
+ gboolean perm1, perm2, tent1, tent2;
+ gboolean ipv6_privacy1, ipv6_privacy2;
+ const NMPlatformIP6Address *a1 = a, *a2 = b;
+
+ /* tentative addresses are always sorted back... */
+ /* sort tentative addresses after non-tentative. */
+ tent1 = (a1->flags & IFA_F_TENTATIVE);
+ tent2 = (a2->flags & IFA_F_TENTATIVE);
+ if (tent1 != tent2)
+ return tent1 ? 1 : -1;
+
+ /* Sort by address type. For example link local will
+ * be sorted *after* site local or global. */
+ p1 = _addresses_sort_cmp_get_prio (&a1->address);
+ p2 = _addresses_sort_cmp_get_prio (&a2->address);
+ if (p1 != p2)
+ return p1 > p2 ? -1 : 1;
+
+ ipv6_privacy1 = !!(a1->flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
+ ipv6_privacy2 = !!(a2->flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
+ if (ipv6_privacy1 || ipv6_privacy2) {
+ gboolean prefer_temp = ((NMSettingIP6ConfigPrivacy) GPOINTER_TO_INT (user_data)) == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR;
+ gboolean public1 = TRUE, public2 = TRUE;
+
+ if (ipv6_privacy1) {
+ if (a1->flags & IFA_F_TEMPORARY)
+ public1 = prefer_temp;
+ else
+ public1 = !prefer_temp;
+ }
+ if (ipv6_privacy2) {
+ if (a2->flags & IFA_F_TEMPORARY)
+ public2 = prefer_temp;
+ else
+ public2 = !prefer_temp;
+ }
+
+ if (public1 != public2)
+ return public1 ? -1 : 1;
+ }
+
+ /* Sort the addresses based on their source. */
+ if (a1->source != a2->source)
+ return a1->source > a2->source ? -1 : 1;
+
+ /* sort permanent addresses before non-permanent. */
+ perm1 = (a1->flags & IFA_F_PERMANENT);
+ perm2 = (a2->flags & IFA_F_PERMANENT);
+ if (perm1 != perm2)
+ return perm1 ? -1 : 1;
+
+ /* finally sort addresses lexically */
+ c = memcmp (&a1->address, &a2->address, sizeof (a2->address));
+ return c != 0 ? c : memcmp (a1, a2, sizeof (*a1));
+}
+
+gboolean
+nm_ip6_config_addresses_sort (NMIP6Config *self, NMSettingIP6ConfigPrivacy use_temporary)
+{
+ NMIP6ConfigPrivate *priv;
+ size_t data_len = 0;
+ char *data_pre = NULL;
+ gboolean changed;
+
+ g_return_val_if_fail (NM_IS_IP6_CONFIG (self), FALSE);
+
+ priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+ if (priv->addresses->len > 1) {
+ data_len = priv->addresses->len * g_array_get_element_size (priv->addresses);
+ data_pre = g_new (char, data_len);
+ memcpy (data_pre, priv->addresses->data, data_len);
+
+ g_array_sort_with_data (priv->addresses, _addresses_sort_cmp, GINT_TO_POINTER (use_temporary));
+
+ changed = memcmp (data_pre, priv->addresses->data, data_len) != 0;
+ g_free (data_pre);
+
+ if (changed) {
+ _NOTIFY (self, PROP_ADDRESSES);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
NMIP6Config *
-nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf)
+nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary)
{
NMIP6Config *config;
NMIP6ConfigPrivate *priv;
@@ -181,6 +288,7 @@ nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf)
guint lowest_metric = G_MAXUINT;
struct in6_addr old_gateway = IN6ADDR_ANY_INIT;
gboolean has_gateway = FALSE;
+ gboolean notify_nameservers = FALSE;
/* Slaves have no IP configuration */
if (nm_platform_link_get_master (ifindex) > 0)
@@ -231,12 +339,14 @@ nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf)
/* If the interface has the default route, and has IPv6 addresses, capture
* nameservers from /etc/resolv.conf.
*/
- if (priv->addresses->len && has_gateway && capture_resolv_conf) {
- if (nm_ip6_config_capture_resolv_conf (priv->nameservers, NULL))
- _NOTIFY (config, PROP_NAMESERVERS);
- }
+ if (priv->addresses->len && has_gateway && capture_resolv_conf)
+ notify_nameservers = nm_ip6_config_capture_resolv_conf (priv->nameservers, NULL);
+
+ g_array_sort_with_data (priv->addresses, _addresses_sort_cmp, GINT_TO_POINTER (use_temporary));
/* actually, nobody should be connected to the signal, just to be sure, notify */
+ if (notify_nameservers)
+ _NOTIFY (config, PROP_NAMESERVERS);
_NOTIFY (config, PROP_ADDRESSES);
_NOTIFY (config, PROP_ROUTES);
if (!IN6_ARE_ADDR_EQUAL (&priv->gateway, &old_gateway))
diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h
index eb93d0789f..32a3b21fd0 100644
--- a/src/nm-ip6-config.h
+++ b/src/nm-ip6-config.h
@@ -58,7 +58,7 @@ void nm_ip6_config_export (NMIP6Config *config);
const char * nm_ip6_config_get_dbus_path (const NMIP6Config *config);
/* Integration with nm-platform and nm-setting */
-NMIP6Config *nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf);
+NMIP6Config *nm_ip6_config_capture (int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary);
gboolean nm_ip6_config_commit (const NMIP6Config *config, int ifindex, int priority);
void nm_ip6_config_merge_setting (NMIP6Config *config, NMSettingIP6Config *setting);
void nm_ip6_config_update_setting (const NMIP6Config *config, NMSettingIP6Config *setting);
@@ -83,6 +83,8 @@ void nm_ip6_config_del_address (NMIP6Config *config, guint i);
guint nm_ip6_config_get_num_addresses (const NMIP6Config *config);
const NMPlatformIP6Address *nm_ip6_config_get_address (const NMIP6Config *config, guint i);
gboolean nm_ip6_config_address_exists (const NMIP6Config *config, const NMPlatformIP6Address *address);
+gboolean nm_ip6_config_addresses_sort (NMIP6Config *config, NMSettingIP6ConfigPrivacy use_temporary);
+
/* Routes */
void nm_ip6_config_reset_routes (NMIP6Config *config);
diff --git a/src/tests/test-ip6-config.c b/src/tests/test-ip6-config.c
index b804891d9f..21fa9b0c7a 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -231,6 +231,92 @@ test_add_route_with_source (void)
g_object_unref (a);
}
+static void
+test_nm_ip6_config_addresses_sort_check (NMIP6Config *config, NMSettingIP6ConfigPrivacy use_tempaddr, int repeat)
+{
+ int addr_count = nm_ip6_config_get_num_addresses (config);
+ int i, irepeat;
+ NMIP6Config *copy = nmtst_ip6_config_clone (config);
+ NMIP6Config *copy2 = nmtst_ip6_config_clone (config);
+ int *idx = g_new (int, addr_count);
+
+ /* initialize the array of indeces, and keep shuffling them for every @repeat iteration. */
+ for (i = 0; i < addr_count; i++)
+ idx[i] = i;
+
+ for (irepeat = 0; irepeat < repeat; irepeat++) {
+ /* randomly shuffle the addresses. */
+ nm_ip6_config_reset_addresses (copy);
+ for (i = 0; i < addr_count; i++) {
+ int j = g_rand_int_range (nmtst_get_rand (), i, addr_count);
+
+ NMTST_SWAP (idx[i], idx[j]);
+ nm_ip6_config_add_address (copy, nm_ip6_config_get_address (config, idx[i]));
+ }
+
+ /* reorder them again */
+ nm_ip6_config_addresses_sort (copy, use_tempaddr);
+
+ /* check equality using nm_ip6_config_equal() */
+ if (!nm_ip6_config_equal (copy, config)) {
+ g_message ("%s", "SORTING yields unexpected output:");
+ for (i = 0; i < addr_count; i++) {
+ g_message (" >> [%d] = %s", i, nm_platform_ip6_address_to_string (nm_ip6_config_get_address (config, i)));
+ g_message (" << [%d] = %s", i, nm_platform_ip6_address_to_string (nm_ip6_config_get_address (copy, i)));
+ }
+ g_assert_not_reached ();
+ }
+
+ /* also check equality using nm_ip6_config_replace() */
+ g_assert (nm_ip6_config_replace (copy2, copy, NULL) == FALSE);
+ }
+
+ g_free (idx);
+ g_object_unref (copy);
+ g_object_unref (copy2);
+}
+
+static void
+test_nm_ip6_config_addresses_sort (void)
+{
+ NMIP6Config *config = build_test_config ();
+
+#define ADDR_ADD(...) nm_ip6_config_add_address (config, nmtst_platform_ip6_address_full (__VA_ARGS__))
+
+ nm_ip6_config_reset_addresses (config);
+ ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::6", NULL, 64, 0, NM_PLATFORM_SOURCE_RDISC, 0, 0, 0, IFA_F_MANAGETEMPADDR);
+ ADDR_ADD("2607:f0d0:1002:51::3", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("2607:f0d0:1002:51::8", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("2607:f0d0:1002:51::0", NULL, 64, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("fec0::1", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("::1", NULL, 128, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::2", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_TENTATIVE);
+ test_nm_ip6_config_addresses_sort_check (config, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, 8);
+ test_nm_ip6_config_addresses_sort_check (config, NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED, 8);
+ test_nm_ip6_config_addresses_sort_check (config, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, 8);
+
+ nm_ip6_config_reset_addresses (config);
+ ADDR_ADD("2607:f0d0:1002:51::3", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::8", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("2607:f0d0:1002:51::0", NULL, 64, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, IFA_F_SECONDARY);
+ ADDR_ADD("2607:f0d0:1002:51::6", NULL, 64, 0, NM_PLATFORM_SOURCE_RDISC, 0, 0, 0, IFA_F_MANAGETEMPADDR);
+ ADDR_ADD("fec0::1", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_PLATFORM_SOURCE_KERNEL, 0, 0, 0, 0);
+ ADDR_ADD("::1", NULL, 128, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, 0);
+ ADDR_ADD("2607:f0d0:1002:51::2", NULL, 64, 0, NM_PLATFORM_SOURCE_USER, 0, 0, 0, IFA_F_TENTATIVE);
+ test_nm_ip6_config_addresses_sort_check (config, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, 8);
+
+#undef ADDR_ADD
+ g_object_unref (config);
+}
+
/*******************************************/
NMTST_DEFINE();
@@ -244,6 +330,7 @@ main (int argc, char **argv)
g_test_add_func ("/ip6-config/compare-with-source", test_compare_with_source);
g_test_add_func ("/ip6-config/add-address-with-source", test_add_address_with_source);
g_test_add_func ("/ip6-config/add-route-with-source", test_add_route_with_source);
+ g_test_add_func ("/ip6-config/test_nm_ip6_config_addresses_sort", test_nm_ip6_config_addresses_sort);
return g_test_run ();
}