summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2014-03-14 14:08:51 +0100
committerThomas Haller <thaller@redhat.com>2014-03-14 19:59:30 +0100
commitd362384b8a2a34ee908ef5d50007533ba0abc8f1 (patch)
tree7fa8760b751fee57ed9032d86baa9ef74fa39e19
parent41aefd49d68a8f519506446ce5c4c03b262af4ea (diff)
downloadNetworkManager-th/sort_addresses.tar.gz
core: sort IPv6 addresses (add nm_ip6_config_addresses_sort())th/sort_addresses
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. Signed-off-by: Thomas Haller <thaller@redhat.com>
-rw-r--r--src/devices/nm-device.c5
-rw-r--r--src/nm-ip6-config.c142
-rw-r--r--src/nm-ip6-config.h4
-rw-r--r--src/tests/test-ip6-config.c68
4 files changed, 212 insertions, 7 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index d5c4386c5e..18235eb1b9 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -3046,6 +3046,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;
@@ -6912,7 +6915,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..938f50d104 100644
--- a/src/nm-ip6-config.c
+++ b/src/nm-ip6-config.c
@@ -172,8 +172,137 @@ 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_UNSPECIFIED (addr))
+ return 0;
+ if (IN6_IS_ADDR_LOOPBACK (addr))
+ return 1;
+ if (IN6_IS_ADDR_LINKLOCAL (addr))
+ return 2;
+ if (IN6_IS_ADDR_SITELOCAL (addr))
+ return 3;
+ if (IN6_IS_ADDR_V4MAPPED (addr))
+ return 4;
+ if (IN6_IS_ADDR_V4COMPAT (addr))
+ return 5;
+ return 6;
+}
+
+static gboolean
+_addresses_sort_cmp_is_slaac (const NMPlatformIP6Address *addr)
+{
+ if (addr->source == NM_PLATFORM_SOURCE_RDISC)
+ return TRUE;
+ if (addr->source == NM_PLATFORM_SOURCE_KERNEL)
+ return !!(addr->flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
+
+ return FALSE;
+}
+
+static gint
+_addresses_sort_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ gint p1, p2;
+ gint slaac1, slaac2;
+ gboolean perm1, perm2, tent1, tent2;
+ const NMPlatformIP6Address *a1 = a, *a2 = b;
+ const NMSettingIP6ConfigPrivacy *use_temporary = user_data;
+
+ /* sort the addresses baded on their source. */
+ if (a1->source != a2->source) {
+ /* SLAAC temporary addresses have NM_PLATFORM_SOURCE_KERNEL, so we ignore
+ * the source if both addresses are detected as being created from SLAAC. */
+ if (!_addresses_sort_cmp_is_slaac (a1) && !_addresses_sort_cmp_is_slaac (a2))
+ return a1->source > a2->source ? -1 : 1;
+ }
+
+ /* First we compare by address type. For example link local will always
+ * 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;
+
+ /* 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;
+
+ /* addresses that are not from SLAAC, are higher priority */
+ slaac1 = (a1->flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
+ slaac2 = (a2->flags & (IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY));
+ if ((!!slaac1) != (!!slaac2))
+ return slaac1 ? 1 : -1;
+
+ if (slaac1 && slaac1 != slaac2) {
+ /* both addresses are SLAAC, but one is public, one private. */
+
+ if (*use_temporary == NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR) {
+ /* private address has higher priority */
+ return slaac1 & IFA_F_TEMPORARY ? -1 : 1;
+ }
+
+ /* permanent address has higher priority */
+ return slaac1 & IFA_F_MANAGETEMPADDR ? -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;
+
+ /* sort addresses lexically */
+ return memcmp (&a1->address, &a2->address, sizeof (a2->address));
+}
+
+static void
+_addresses_sort (NMIP6Config *self, NMSettingIP6ConfigPrivacy use_temporary, gboolean *out_changed)
+{
+ NMIP6ConfigPrivate *priv;
+ size_t data_len = 0;
+ char *data_pre = NULL;
+
+ g_return_if_fail (NM_IS_IP6_CONFIG (self));
+
+ priv = NM_IP6_CONFIG_GET_PRIVATE (self);
+ if (priv->addresses->len <= 1) {
+ if (out_changed)
+ *out_changed = FALSE;
+ return;
+ }
+
+ if (out_changed) {
+ 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, &use_temporary);
+
+ if (out_changed) {
+ *out_changed = memcmp (data_pre, priv->addresses->data, data_len) != 0;
+ g_free (data_pre);
+ }
+}
+
+gboolean
+nm_ip6_config_addresses_sort (NMIP6Config *self, NMSettingIP6ConfigPrivacy use_temporary)
+{
+ gboolean changed;
+
+ _addresses_sort (self, use_temporary, &changed);
+
+ if (changed)
+ _NOTIFY (self, PROP_ADDRESSES);
+ return changed;
+}
+
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 +310,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 +361,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);
+
+ _addresses_sort (config, use_temporary, NULL);
/* 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 8d69f4f486..6755b53db4 100644
--- a/src/tests/test-ip6-config.c
+++ b/src/tests/test-ip6-config.c
@@ -231,6 +231,73 @@ test_add_route_with_source (void)
g_object_unref (a);
}
+static void
+test_nm_ip6_config_addresses_sort_test (NMIP6Config *config, NMSettingIP6ConfigPrivacy use_tempaddr, int repeat)
+{
+ int addr_count = nm_ip6_config_get_num_addresses (config);
+ int i;
+ NMIP6Config *copy = nm_ip6_config_new ();
+ NMIP6Config *copy2 = nm_ip6_config_new ();
+ int *idx = NULL;
+
+ while (repeat-- > 0) {
+ nm_ip6_config_replace (copy, config, NULL);
+
+ nm_ip6_config_reset_addresses (copy);
+
+ if (addr_count > 0) {
+ idx = g_new0 (int, addr_count);
+
+ for (i = 0; i < addr_count; i++)
+ idx[i] = i;
+ for (i = 0; i < addr_count; i++) {
+ int j = g_rand_int_range (nmtst_get_rand0 (), i, addr_count);
+ int tmp;
+
+ tmp = idx[i];
+ idx[i] = idx[j];
+ idx[j] = tmp;
+ }
+
+ for (i = 0; i < addr_count; i++)
+ nm_ip6_config_add_address (copy, nm_ip6_config_get_address (config, idx[i]));
+
+ g_free (idx);
+ }
+
+ nm_ip6_config_addresses_sort (copy, use_tempaddr);
+
+ /* check equality using nm_ip6_config_equal() */
+ g_assert (nm_ip6_config_equal (copy, config));
+
+ /* also check equality using nm_ip6_config_replace() */
+ nm_ip6_config_replace (copy2, config, NULL);
+ g_assert (nm_ip6_config_replace (copy2, copy, NULL) == FALSE);
+ }
+
+ 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::6", NULL, 64, 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);
+ test_nm_ip6_config_addresses_sort_test (config, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, 3);
+
+#undef ADDR_ADD
+ g_object_unref (config);
+}
+
/*******************************************/
NMTST_INIT();
@@ -246,6 +313,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 ();
}