Commit 2cc18133 authored by Thomas Haller's avatar Thomas Haller
Browse files

core: workaround configuring IPv6 routes with "src" (RTA_PREFSRC)

Kernel does not allow to add IPv6 routes with "src", as long as the
corresponding address is still tentative (related bug rh#1457196).

The workaround for this is cumbersome. First, when we fail to add such a
route with "pref_src", we guess that it happend due to this issue. In
that case, nm_ip6_config_commit() returns the list of routes that could
not be added for the moment (but hopefully can be added later).

We track this list in NMDevice, and keep trying to merge the routes
back into ip6_config. In order to not try indefinitely, keep track of a
timestamp when we tried to add this route for the first time.

Another uglyness is that pending tentative routes don't explicitly block
activation. In practice they may do, because for these routes we also have
an IPv6 address that is still doing DAD, so the IP configuration is
still pending due to that.

https://bugzilla.redhat.com/show_bug.cgi?id=1452684
parent 1cb4832f
......@@ -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; \
} \
......
......@@ -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);
......
......@@ -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");
}
......
......@@ -837,6 +837,7 @@ nm_ip4_config_commit (const NMIP4Config *self,
ifindex,
routes,
nm_platform_lookup_predicate_routes_main,
NULL,
NULL))
success = FALSE;
......
......@@ -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;
......
......@@ -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);
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment