diff options
Diffstat (limited to 'src/platform/nm-platform.c')
-rw-r--r-- | src/platform/nm-platform.c | 63 |
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); |