platform: fix the order of addition of primary and secondary IPv4 addresses

nm_platform_ip4_address_sync() tries to apply the new configuration
with the minimum effort and doesn't delete addresses if they are
already present on the interface. This can break the ordering, as an
existing address would be promoted by kernel to primary, even if it
was last in our configuration.

Add some logic to ensure the correct order of addresses is always
enforced. This fixes situations like:

 # nmcli connection add type ethernet ifname eth0 con-name t \
                        ipv4.method manual \
                        ipv4.addresses "1.1.1.1/24,1.1.1.2/24,1.1.1.5/24"
 # nmcli connection up t

  => addresses are applied in the right order:
     inet 1.1.1.1/24 brd 1.1.1.255 scope global eth0
     inet 1.1.1.2/24 brd 1.1.1.255 scope global secondary eth0
     inet 1.1.1.5/24 brd 1.1.1.255 scope global secondary eth0

 # nmcli connection mod t ipv4.addresses "1.1.1.5/24,1.1.1.2/24,1.1.1.1/24"
 # nmcli device reapply eth0

  => order is wrong:
     inet 1.1.1.2/24 brd 1.1.1.255 scope global eth0
     inet 1.1.1.5/24 brd 1.1.1.255 scope global secondary eth0
     inet 1.1.1.1/24 brd 1.1.1.255 scope global secondary eth0

Co-Authored-By: Thomas Haller <thaller@redhat.com>
This commit is contained in:
Beniamino Galvani 2016-11-14 20:52:24 +01:00
parent 0a0bca9c7f
commit 2f68a50041

View file

@ -2672,14 +2672,14 @@ nm_platform_ip6_address_get (NMPlatform *self, int ifindex, struct in6_addr addr
return klass->ip6_address_get (self, ifindex, address, plen);
}
static gboolean
static const NMPlatformIP4Address *
array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address *address, gint32 now)
{
guint len = addresses ? addresses->len : 0;
guint i;
for (i = 0; i < len; i++) {
NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
const NMPlatformIP4Address *candidate = &g_array_index (addresses, NMPlatformIP4Address, i);
if ( candidate->address == address->address
&& candidate->plen == address->plen
@ -2688,11 +2688,11 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
if (nm_utils_lifetime_get (candidate->timestamp, candidate->lifetime, candidate->preferred,
now, &lifetime, &preferred))
return TRUE;
return candidate;
}
}
return FALSE;
return NULL;
}
static gboolean
@ -2716,6 +2716,97 @@ array_contains_ip6_address (const GArray *addresses, const NMPlatformIP6Address
return FALSE;
}
static gboolean
_ptr_inside_ip4_addr_array (const GArray *array, gconstpointer needle)
{
return needle >= (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, 0)
&& needle < (gconstpointer) &g_array_index (array, const NMPlatformIP4Address, array->len);
}
static void
ip4_addr_subnets_destroy_index (GHashTable *ht, const GArray *addresses)
{
GHashTableIter iter;
gpointer p;
g_hash_table_iter_init (&iter, ht);
while (g_hash_table_iter_next (&iter, NULL, &p)) {
if (!_ptr_inside_ip4_addr_array (addresses, p)) {
g_ptr_array_free ((GPtrArray *) p, TRUE);
}
}
g_hash_table_unref (ht);
}
static GHashTable *
ip4_addr_subnets_build_index (const GArray *addresses, gboolean consider_flags)
{
const NMPlatformIP4Address *address;
gpointer p;
GHashTable *subnets;
GPtrArray *ptr;
guint32 net;
guint i;
gint position;
if (!addresses)
return NULL;
subnets = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
NULL);
/* Build a hash table of all addresses per subnet */
for (i = 0; i < addresses->len; i++) {
address = &g_array_index (addresses, const NMPlatformIP4Address, i);
net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
if (!g_hash_table_lookup_extended (subnets, GUINT_TO_POINTER (net), NULL, &p)) {
g_hash_table_insert (subnets, GUINT_TO_POINTER (net), (gpointer) address);
continue;
}
if (_ptr_inside_ip4_addr_array (addresses, p)) {
ptr = g_ptr_array_new ();
g_hash_table_insert (subnets, GUINT_TO_POINTER (net), ptr);
g_ptr_array_add (ptr, p);
} else
ptr = p;
if (!consider_flags || NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
position = -1; /* append */
else
position = 0; /* prepend */
g_ptr_array_insert (ptr, position, (gpointer) address);
}
return subnets;
}
static gboolean
ip4_addr_subnets_is_secondary (const NMPlatformIP4Address *address, GHashTable *subnets, const GArray *addresses, GPtrArray **out_addr_list)
{
GPtrArray *addr_list;
gpointer p;
guint32 net;
net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
p = g_hash_table_lookup (subnets, GUINT_TO_POINTER (net));
nm_assert (p);
if (!_ptr_inside_ip4_addr_array (addresses, p)) {
addr_list = p;
if (addr_list->pdata[0] != address) {
NM_SET_OUT (out_addr_list, addr_list);
return TRUE;
}
} else
nm_assert ((gconstpointer) address == p);
NM_SET_OUT (out_addr_list, NULL);
return FALSE;
}
/**
* nm_platform_ip4_address_sync:
* @self: platform instance
@ -2737,19 +2828,64 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
{
GArray *addresses;
NMPlatformIP4Address *address;
const NMPlatformIP4Address *known_address;
gint32 now = nm_utils_get_monotonic_timestamp_s ();
int i;
GHashTable *plat_subnets;
GHashTable *known_subnets;
GPtrArray *ptr;
int i, j;
_CHECK_SELF (self, klass, FALSE);
/* Delete unknown addresses */
addresses = nm_platform_ip4_address_get_all (self, ifindex);
plat_subnets = ip4_addr_subnets_build_index (addresses, TRUE);
known_subnets = ip4_addr_subnets_build_index (known_addresses, FALSE);
/* Delete unknown addresses */
for (i = 0; i < addresses->len; i++) {
address = &g_array_index (addresses, NMPlatformIP4Address, i);
if (!array_contains_ip4_address (known_addresses, address, now))
nm_platform_ip4_address_delete (self, ifindex, address->address, address->plen, address->peer_address);
if (!address->ifindex) {
/* Already deleted */
continue;
}
known_address = array_contains_ip4_address (known_addresses, address, now);
if (known_address) {
gboolean secondary;
secondary = ip4_addr_subnets_is_secondary (known_address, known_subnets, known_addresses, NULL);
/* Ignore the matching address if it has a different primary/slave
* role. */
if (secondary != NM_FLAGS_HAS (address->n_ifa_flags, IFA_F_SECONDARY))
known_address = NULL;
}
if (!known_address) {
nm_platform_ip4_address_delete (self, ifindex,
address->address,
address->plen,
address->peer_address);
if ( !ip4_addr_subnets_is_secondary (address, plat_subnets, addresses, &ptr)
&& ptr) {
/* If we just deleted a primary addresses and there were
* secondary ones the kernel can do two things, depending on
* version and sysctl setting: delete also secondary addresses
* or promote a secondary to primary. Ensure that secondary
* addresses are deleted, so that we can start with a clean
* slate and add addresses in the right order. */
for (j = 1; j < ptr->len; j++) {
address = ptr->pdata[j];
nm_platform_ip4_address_delete (self, ifindex,
address->address,
address->plen,
address->peer_address);
address->ifindex = 0;
}
}
}
}
ip4_addr_subnets_destroy_index (plat_subnets, addresses);
g_array_free (addresses, TRUE);
if (out_added_addresses)
@ -2760,17 +2896,20 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
/* Add missing addresses */
for (i = 0; i < known_addresses->len; i++) {
const NMPlatformIP4Address *known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
guint32 lifetime, preferred;
known_address = &g_array_index (known_addresses, NMPlatformIP4Address, i);
if (!nm_utils_lifetime_get (known_address->timestamp, known_address->lifetime, known_address->preferred,
now, &lifetime, &preferred))
continue;
if (!nm_platform_ip4_address_add (self, ifindex, known_address->address, known_address->plen,
known_address->peer_address, lifetime, preferred,
0, known_address->label))
0, known_address->label)) {
ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
return FALSE;
}
if (out_added_addresses) {
if (!*out_added_addresses)
@ -2779,6 +2918,8 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
}
}
ip4_addr_subnets_destroy_index (known_subnets, known_addresses);
return TRUE;
}