summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBeniamino Galvani <bgalvani@redhat.com>2016-11-14 20:52:24 +0100
committerBeniamino Galvani <bgalvani@redhat.com>2016-11-14 21:38:15 +0100
commit969350812b052833b10c7d1c5f831832039b2d65 (patch)
tree13c3cdd119ed99bb12cc9e8be982f1861ad91ea7
parent4c0f29f7da673cf8177537b1d18d9a8f19518e10 (diff)
downloadNetworkManager-bg/rh1394500.tar.gz
platform: fix the order of addition of primary and secondary IPv4 addressesbg/rh1394500
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.
-rw-r--r--src/platform/nm-platform.c63
1 files changed, 59 insertions, 4 deletions
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 0b1d25d302..48e2de6812 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -2672,6 +2672,7 @@ array_contains_ip4_address (const GArray *addresses, const NMPlatformIP4Address
if ( candidate->address == address->address
&& candidate->plen == address->plen
+ && (candidate->n_ifa_flags & IFA_F_SECONDARY) == (address->n_ifa_flags & IFA_F_SECONDARY)
&& ((candidate->peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (address->plen)) == 0) {
guint32 lifetime, preferred;
@@ -2727,17 +2728,71 @@ nm_platform_ip4_address_sync (NMPlatform *self, int ifindex, const GArray *known
GArray *addresses;
NMPlatformIP4Address *address;
gint32 now = nm_utils_get_monotonic_timestamp_s ();
- int i;
+ gs_unref_hashtable GHashTable *subnets = NULL;
+ GPtrArray *ptr;
+ guint32 net;
+ int i, j;
_CHECK_SELF (self, klass, FALSE);
- /* Delete unknown addresses */
+ subnets = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ (GDestroyNotify) g_ptr_array_unref);
addresses = nm_platform_ip4_address_get_all (self, ifindex);
+
+ /* Build a hash table of all addresses per subnet */
+ for (i = 0; i < addresses->len; i++) {
+ address = &g_array_index (addresses, NMPlatformIP4Address, i);
+ net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
+ ptr = g_hash_table_lookup (subnets, GUINT_TO_POINTER (net));
+ if (!ptr) {
+ ptr = g_ptr_array_new ();
+ g_hash_table_insert (subnets, GUINT_TO_POINTER (net), ptr);
+ }
+ g_ptr_array_insert (ptr,
+ (address->n_ifa_flags & IFA_F_SECONDARY) ? -1 : 0,
+ address);
+ }
+
+ /* 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;
+ }
+
+ if (!array_contains_ip4_address (known_addresses, address, now)) {
+ nm_platform_ip4_address_delete (self, ifindex,
+ address->address,
+ address->plen,
+ address->peer_address);
+
+ /* Check if we just deleted a primary addresses with secondary ones ... */
+ net = address->address & nm_utils_ip4_prefix_to_netmask (address->plen);
+ ptr = g_hash_table_lookup (subnets, GUINT_TO_POINTER (net));
+ g_return_val_if_fail (ptr, FALSE);
+
+ if (ptr->len > 1 && ptr->pdata[0] == address) {
+ /* ... if so, the kernel can do two things, depending on version
+ * and sysctl setting: delete also secondary addresses or
+ * promote a secondary to primary. We could resync the platform
+ * cache to know what happened, but probably it's not a good
+ * idea doing it here since it would cause the execution of
+ * handlers. Instead, just ensure that secondary addresses are
+ * deleted, so that we can start with a clean slate. */
+ 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;
+ }
+ }
+ }
}
g_array_free (addresses, TRUE);