mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-02-08 20:50:32 +01:00
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>
This commit is contained in:
parent
65bc042e8f
commit
86f8066177
4 changed files with 209 additions and 7 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue