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 (); }