summaryrefslogtreecommitdiff
path: root/src/platform/nm-platform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/nm-platform.c')
-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);