diff options
-rw-r--r-- | shared/nm-utils/nm-shared-utils.h | 2 | ||||
-rw-r--r-- | src/devices/nm-device.c | 125 | ||||
-rw-r--r-- | src/nm-iface-helper.c | 2 | ||||
-rw-r--r-- | src/nm-ip4-config.c | 1 | ||||
-rw-r--r-- | src/nm-ip6-config.c | 6 | ||||
-rw-r--r-- | src/nm-ip6-config.h | 3 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 54 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 3 | ||||
-rw-r--r-- | src/vpn/nm-vpn-connection.c | 3 |
9 files changed, 189 insertions, 10 deletions
diff --git a/shared/nm-utils/nm-shared-utils.h b/shared/nm-utils/nm-shared-utils.h index 8d1085f6a5..065e629b4e 100644 --- a/shared/nm-utils/nm-shared-utils.h +++ b/shared/nm-utils/nm-shared-utils.h @@ -178,6 +178,7 @@ _nm_g_slice_free_fcn_define (1) _nm_g_slice_free_fcn_define (2) _nm_g_slice_free_fcn_define (4) _nm_g_slice_free_fcn_define (8) +_nm_g_slice_free_fcn_define (12) _nm_g_slice_free_fcn_define (16) #define _nm_g_slice_free_fcn1(mem_size) \ @@ -192,6 +193,7 @@ _nm_g_slice_free_fcn_define (16) case 2: _fcn = _nm_g_slice_free_fcn_2; break; \ case 4: _fcn = _nm_g_slice_free_fcn_4; break; \ case 8: _fcn = _nm_g_slice_free_fcn_8; break; \ + case 12: _fcn = _nm_g_slice_free_fcn_12; break; \ case 16: _fcn = _nm_g_slice_free_fcn_16; break; \ default: g_assert_not_reached (); _fcn = NULL; break; \ } \ diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index cd523b9dad..ae227cd95e 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -400,6 +400,7 @@ typedef struct _NMDevicePrivate { /* IPv4LL stuff */ sd_ipv4ll * ipv4ll; guint ipv4ll_timeout; + guint rt6_temporary_not_available_id; /* IPv4 DAD stuff */ struct { @@ -421,6 +422,8 @@ typedef struct _NMDevicePrivate { bool nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */ NMIP6Config * dad6_ip6_config; + GHashTable * rt6_temporary_not_available; + NMNDisc * ndisc; gulong ndisc_changed_id; gulong ndisc_timeout_id; @@ -6350,6 +6353,18 @@ ip6_config_merge_and_apply (NMDevice *self, | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0)); } + if (priv->rt6_temporary_not_available) { + const NMPObject *o; + GHashTableIter hiter; + + g_hash_table_iter_init (&hiter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next (&hiter, (gpointer *) &o, NULL)) { + nm_ip6_config_add_route (composite, + NMP_OBJECT_CAST_IP6_ROUTE (o), + NULL); + } + } + /* Merge user overrides into the composite config. For assumed connections, * con_ip6_config is empty. */ if (priv->con_ip6_config) @@ -7452,6 +7467,9 @@ addrconf6_start (NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr) priv->ac_ip6_config = NULL; } + g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source (&priv->rt6_temporary_not_available_id); + s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting_ip6_config (connection)); g_assert (s_ip6); @@ -7505,6 +7523,8 @@ addrconf6_cleanup (NMDevice *self) nm_device_remove_pending_action (self, NM_PENDING_ACTION_AUTOCONF6, FALSE); g_clear_object (&priv->ac_ip6_config); + g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source (&priv->rt6_temporary_not_available_id); g_clear_object (&priv->ndisc); } @@ -9396,6 +9416,97 @@ impl_device_get_applied_connection (NMDevice *self, /*****************************************************************************/ +typedef struct { + gint64 timestamp_ms; + bool dirty; +} IP6RoutesTemporaryNotAvailableData; + +static gboolean +_rt6_temporary_not_available_timeout (gpointer user_data) +{ + NMDevice *self = NM_DEVICE (user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + + priv->rt6_temporary_not_available_id = 0; + nm_device_activate_schedule_ip6_config_result (self); + + return G_SOURCE_REMOVE; +} + +static gboolean +_rt6_temporary_not_available_set (NMDevice *self, + GPtrArray *temporary_not_available) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + IP6RoutesTemporaryNotAvailableData *data; + GHashTableIter iter; + gint64 now_ms, oldest_ms; + const gint64 MAX_AGE_MS = 20000; + guint i; + gboolean success = TRUE; + + if ( !temporary_not_available + || !temporary_not_available->len) { + /* nothing outstanding. Clear tracking the routes. */ + g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source (&priv->rt6_temporary_not_available_id); + return success; + } + + if (priv->rt6_temporary_not_available) { + g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data)) + data->dirty = TRUE; + } else { + priv->rt6_temporary_not_available = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + nm_g_slice_free_fcn (IP6RoutesTemporaryNotAvailableData)); + } + + now_ms = nm_utils_get_monotonic_timestamp_ms (); + oldest_ms = now_ms; + + for (i = 0; i < temporary_not_available->len; i++) { + const NMPObject *o = temporary_not_available->pdata[i]; + + data = g_hash_table_lookup (priv->rt6_temporary_not_available, o); + if (data) { + if (!data->dirty) + continue; + data->dirty = FALSE; + nm_assert (data->timestamp_ms > 0 && data->timestamp_ms <= now_ms); + if (now_ms > data->timestamp_ms + MAX_AGE_MS) { + /* timeout. Could not add this address. */ + _LOGW (LOGD_DEVICE, "failure to add IPv6 route: %s", + nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0)); + success = FALSE; + } else + oldest_ms = MIN (data->timestamp_ms, oldest_ms); + continue; + } + + data = g_slice_new0 (IP6RoutesTemporaryNotAvailableData); + data->timestamp_ms = now_ms; + g_hash_table_insert (priv->rt6_temporary_not_available, (gpointer) nmp_object_ref (o), data); + } + + g_hash_table_iter_init (&iter, priv->rt6_temporary_not_available); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data)) { + if (data->dirty) + g_hash_table_iter_remove (&iter); + } + + nm_clear_g_source (&priv->rt6_temporary_not_available_id); + priv->rt6_temporary_not_available_id = g_timeout_add (oldest_ms + MAX_AGE_MS - now_ms, + _rt6_temporary_not_available_timeout, + self); + + return success; +} + +/*****************************************************************************/ + static void disconnect_cb (NMDevice *self, GDBusMethodInvocation *context, @@ -9941,9 +10052,16 @@ nm_device_set_ip6_config (NMDevice *self, /* Always commit to nm-platform to update lifetimes */ if (commit && new_config) { + gs_unref_ptrarray GPtrArray *temporary_not_available = NULL; + _commit_mtu (self, priv->ip4_config); + success = nm_ip6_config_commit (new_config, - nm_device_get_platform (self)); + nm_device_get_platform (self), + &temporary_not_available); + + if (!_rt6_temporary_not_available_set (self, temporary_not_available)) + success = FALSE; } if (new_config) { @@ -10876,6 +10994,8 @@ queued_ip6_config_change (gpointer user_data) g_clear_object (&priv->dad6_ip6_config); _set_ip_state (self, AF_INET6, IP_DONE); check_ip_state (self, FALSE); + if (priv->rt6_temporary_not_available) + nm_device_activate_schedule_ip6_config_result (self); } } @@ -12016,6 +12136,9 @@ _cleanup_generic_post (NMDevice *self, CleanupType cleanup_type) g_clear_object (&priv->ip6_config); g_clear_object (&priv->dad6_ip6_config); + g_clear_pointer (&priv->rt6_temporary_not_available, g_hash_table_unref); + nm_clear_g_source (&priv->rt6_temporary_not_available_id); + g_slist_free_full (priv->vpn4_configs, g_object_unref); priv->vpn4_configs = NULL; g_slist_free_full (priv->vpn6_configs, g_object_unref); diff --git a/src/nm-iface-helper.c b/src/nm-iface-helper.c index ae76ee94ef..43c2ae478b 100644 --- a/src/nm-iface-helper.c +++ b/src/nm-iface-helper.c @@ -226,7 +226,7 @@ ndisc_config_changed (NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_in } nm_ip6_config_merge (existing, ndisc_config, NM_IP_CONFIG_MERGE_DEFAULT); - if (!nm_ip6_config_commit (existing, NM_PLATFORM_GET)) + if (!nm_ip6_config_commit (existing, NM_PLATFORM_GET, NULL)) _LOGW (LOGD_IP6, "failed to apply IPv6 config"); } diff --git a/src/nm-ip4-config.c b/src/nm-ip4-config.c index f3d87814ee..90407e34a1 100644 --- a/src/nm-ip4-config.c +++ b/src/nm-ip4-config.c @@ -837,6 +837,7 @@ nm_ip4_config_commit (const NMIP4Config *self, ifindex, routes, nm_platform_lookup_predicate_routes_main, + NULL, NULL)) success = FALSE; diff --git a/src/nm-ip6-config.c b/src/nm-ip6-config.c index ce77394256..acb9ccb646 100644 --- a/src/nm-ip6-config.c +++ b/src/nm-ip6-config.c @@ -529,7 +529,8 @@ nm_ip6_config_capture (NMDedupMultiIndex *multi_idx, NMPlatform *platform, int i gboolean nm_ip6_config_commit (const NMIP6Config *self, - NMPlatform *platform) + NMPlatform *platform, + GPtrArray **out_temporary_not_available) { gs_unref_ptrarray GPtrArray *addresses = NULL; gs_unref_ptrarray GPtrArray *routes = NULL; @@ -552,7 +553,8 @@ nm_ip6_config_commit (const NMIP6Config *self, ifindex, routes, nm_platform_lookup_predicate_routes_main, - NULL)) + NULL, + out_temporary_not_available)) success = FALSE; return success; diff --git a/src/nm-ip6-config.h b/src/nm-ip6-config.h index ec8bee881d..71ae1e469c 100644 --- a/src/nm-ip6-config.h +++ b/src/nm-ip6-config.h @@ -108,7 +108,8 @@ struct _NMDedupMultiIndex *nm_ip6_config_get_multi_idx (const NMIP6Config *self) NMIP6Config *nm_ip6_config_capture (struct _NMDedupMultiIndex *multi_idx, NMPlatform *platform, int ifindex, gboolean capture_resolv_conf, NMSettingIP6ConfigPrivacy use_temporary); gboolean nm_ip6_config_commit (const NMIP6Config *self, - NMPlatform *platform); + NMPlatform *platform, + GPtrArray **out_temporary_not_available); void nm_ip6_config_merge_setting (NMIP6Config *self, NMSettingIPConfig *setting, guint32 default_route_metric); NMSetting *nm_ip6_config_create_setting (const NMIP6Config *self); diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 47ac8bfd52..c3b123b79f 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -3547,6 +3547,42 @@ nm_platform_ip_address_flush (NMPlatform *self, /*****************************************************************************/ +static gboolean +_err_inval_due_to_ipv6_tentative_pref_src (NMPlatform *self, const NMPObject *obj) +{ + const NMPlatformIP6Route *r; + const NMPlatformIP6Address *a; + + nm_assert (NM_IS_PLATFORM (self)); + nm_assert (NMP_OBJECT_IS_VALID (obj)); + + /* trying to add an IPv6 route with pref-src fails, if the address is + * still tentative (rh#1452684). We need to hack around that. + * + * Detect it, by guessing whether that's the case. */ + + if (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_IP6_ROUTE) + return FALSE; + + r = NMP_OBJECT_CAST_IP6_ROUTE (obj); + + /* we only allow this workaround for routes added manually by the user. */ + if (r->rt_source != NM_IP_CONFIG_SOURCE_USER) + return FALSE; + + if (IN6_IS_ADDR_UNSPECIFIED (&r->pref_src)) + return FALSE; + + a = nm_platform_ip6_address_get (self, r->ifindex, r->pref_src); + if (!a) + return FALSE; + if ( !NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_TENTATIVE) + || NM_FLAGS_HAS (a->n_ifa_flags, IFA_F_DADFAILED)) + return FALSE; + + return TRUE; +} + /** * nm_platform_ip_route_sync: * @self: the #NMPlatform instance. @@ -3560,6 +3596,8 @@ nm_platform_ip_address_flush (NMPlatform *self, * routes. For example by passing @nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel, * routes with "proto kernel" will be left untouched. * @kernel_delete_userdata: user data for @kernel_delete_predicate. + * @out_temporary_not_available: (allow-none): (out): routes that could + * currently not be synced. The caller shall keep them and try later again. * * Returns: %TRUE on success. */ @@ -3569,7 +3607,8 @@ nm_platform_ip_route_sync (NMPlatform *self, int ifindex, GPtrArray *routes, NMPObjectPredicateFunc kernel_delete_predicate, - gpointer kernel_delete_userdata) + gpointer kernel_delete_userdata, + GPtrArray **out_temporary_not_available) { const NMPlatformVTableRoute *vt; gs_unref_ptrarray GPtrArray *plat_routes = NULL; @@ -3662,6 +3701,15 @@ nm_platform_ip_route_sync (NMPlatform *self, nmp_object_to_string (plat_entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf2, sizeof (sbuf2))); } } + } else if ( -((int) plerr) == EINVAL + && out_temporary_not_available + && _err_inval_due_to_ipv6_tentative_pref_src (self, conf_o)) { + _LOGD ("route-sync: ignore failure to add IPv6 route with tentative IPv6 pref-src: %s: %s", + nmp_object_to_string (conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof (sbuf1)), + nm_platform_error_to_string (plerr, sbuf_err, sizeof (sbuf_err))); + if (!*out_temporary_not_available) + *out_temporary_not_available = g_ptr_array_new_full (0, (GDestroyNotify) nmp_object_unref); + g_ptr_array_add (*out_temporary_not_available, (gpointer) nmp_object_ref (conf_o)); } else if (NMP_OBJECT_CAST_IP_ROUTE (conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { _LOGD ("route-sync: ignore failure to add IPv%c route: %s: %s", vt->is_ip4 ? '4' : '6', @@ -3723,9 +3771,9 @@ nm_platform_ip_route_flush (NMPlatform *self, AF_INET6)); if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) - success &= nm_platform_ip_route_sync (self, AF_INET, ifindex, NULL, NULL, NULL); + success &= nm_platform_ip_route_sync (self, AF_INET, ifindex, NULL, NULL, NULL, NULL); if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) - success &= nm_platform_ip_route_sync (self, AF_INET6, ifindex, NULL, NULL, NULL); + success &= nm_platform_ip_route_sync (self, AF_INET6, ifindex, NULL, NULL, NULL, NULL); return success; } diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index 917f623d01..e68457ca2b 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -1177,7 +1177,8 @@ gboolean nm_platform_ip_route_sync (NMPlatform *self, int ifindex, GPtrArray *routes, NMPObjectPredicateFunc kernel_delete_predicate, - gpointer kernel_delete_userdata); + gpointer kernel_delete_userdata, + GPtrArray **out_temporary_not_available); gboolean nm_platform_ip_route_flush (NMPlatform *self, int addr_family, int ifindex); diff --git a/src/vpn/nm-vpn-connection.c b/src/vpn/nm-vpn-connection.c index 517e7eb97a..0006cf3cc2 100644 --- a/src/vpn/nm-vpn-connection.c +++ b/src/vpn/nm-vpn-connection.c @@ -1159,7 +1159,8 @@ nm_vpn_connection_apply_config (NMVpnConnection *self) if (priv->ip6_config) { nm_assert (priv->ip_ifindex == nm_ip6_config_get_ifindex (priv->ip6_config)); if (!nm_ip6_config_commit (priv->ip6_config, - nm_netns_get_platform (priv->netns))) + nm_netns_get_platform (priv->netns), + NULL)) return FALSE; } |