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:
@@ -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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user