diff options
author | Thomas Haller <thaller@redhat.com> | 2023-03-07 16:55:37 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2023-03-21 15:58:47 +0100 |
commit | e4ac0c407dc0cfd3978da8ddfb8eeb03e37ca604 (patch) | |
tree | b36439dd897ec514900b727e527a10c5b44ea042 | |
parent | 71b2d4c33a82f4d7d925c86de50327c67358c567 (diff) | |
download | NetworkManager-e4ac0c407dc0cfd3978da8ddfb8eeb03e37ca604.tar.gz |
core: watch IP addresses appearing/disappearing and recommit pref_src routes
Routes with pref_src (RTA_PREFSRC) can only be added when the
corresponding IP address is configured (and non-tentative, in case of
IPv6). Additionally, that address may be on any interface, not only on
the one we want to configure the route on. This means, when we first
activate a profile with a route that has a src attrbute, then that src
address might only be configured later. For example, with IPv6, it takes
a while for the address to become non-tentative. Or the address might
come from DHCP, and not be present initially. Or the address might even
be configured on another interface/profile. That means, while we might
be unable to configure the route now, we may become able any time later.
Solve that by subscribing to NMNetns to get notifications whenever such
an address gets added. In that case, schedule an idle commit, which may
then succeed.
-rw-r--r-- | src/core/nm-l3cfg.c | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 2bde7cc3e2..dc33a40be2 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -293,6 +293,14 @@ typedef struct _NML3CfgPrivate { gint8 commit_reentrant_count; + union { + struct { + gint8 commit_reentrant_count_ip_address_sync_6; + gint8 commit_reentrant_count_ip_address_sync_4; + }; + gint8 commit_reentrant_count_ip_address_sync_x[2]; + }; + /* The value that was set before we touched the sysctl (this only is * meaningful if "ip6_privacy_set" is true. At the end, we want to restore * this value. */ @@ -339,6 +347,9 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) #define _MPTCP_TAG(self, IS_IPv4) ((gconstpointer) (&(((const char *) (self))[2 + (!(IS_IPv4))]))) +#define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \ + ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)])) + /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE @@ -4399,6 +4410,89 @@ _rp_filter_update(NML3Cfg *self, gboolean reapply) /*****************************************************************************/ +static void +_routes_watch_ip_addrs_cb(NMNetns *netns, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + const NMNetnsWatcherEventData *event_data, + gpointer user_data) +{ + const int IS_IPv4 = NM_IS_IPv4(watcher_data->ip_addr.addr.addr_family); + NML3Cfg *self = user_data; + char sbuf[NM_INET_ADDRSTRLEN]; + + if (NMP_OBJECT_CAST_IP_ADDRESS(event_data->ip_addr.obj)->ifindex == self->priv.ifindex) { + if (self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4] > 0) { + /* We are currently commiting IP addresses on this very interface. + * We can ignore the event. Also, because we will sync the routes + * immediately after already. So even if somebody externally added + * the address just this very moment, we would still do the commit + * at the right time to ensure our routes are there. */ + return; + } + } + + if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED) + return; + + _LOGT("watched ip-address %s changed. Schedule an idle commit", + nm_inet_ntop(watcher_data->ip_addr.addr.addr_family, + &watcher_data->ip_addr.addr.addr, + sbuf)); + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); +} + +static void +_routes_watch_ip_addrs(NML3Cfg *self, int addr_family, GPtrArray *routes) +{ + gconstpointer const TAG = _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family); + NMNetnsWatcherData watcher_data = { + .ip_addr = + { + .addr = + { + .addr_family = addr_family, + }, + }, + }; + guint i; + + /* IP routes that have a pref_src, can only be configured in kernel if + * that address exists (and is non-tentative, in case of IPv6). + * That address might be on another interface. So we actually watch all + * other interfaces. */ + + if (!routes) + goto out; + + for (i = 0; i < routes->len; i++) { + const NMPlatformIPRoute *rt = NMP_OBJECT_CAST_IP_ROUTE(routes->pdata[i]); + gconstpointer pref_src; + + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(routes->pdata[i]) == addr_family); + + pref_src = nm_platform_ip_route_get_pref_src(addr_family, rt); + + if (nm_ip_addr_is_null(addr_family, pref_src)) + continue; + + nm_assert(watcher_data.ip_addr.addr.addr_family == addr_family); + nm_ip_addr_set(addr_family, &watcher_data.ip_addr.addr.addr, pref_src); + + nm_netns_watcher_add(self->priv.netns, + NM_NETNS_WATCHER_TYPE_IP_ADDR, + &watcher_data, + TAG, + _routes_watch_ip_addrs_cb, + self); + } + +out: + nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE); +} +/*****************************************************************************/ + static gboolean _global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family) { @@ -4697,9 +4791,14 @@ _l3_commit_one(NML3Cfg *self, } } } + + _routes_watch_ip_addrs(self, addr_family, routes); + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */ /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */ + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]++; + nm_platform_ip_address_sync(self->priv.platform, addr_family, self->priv.ifindex, @@ -4709,6 +4808,8 @@ _l3_commit_one(NML3Cfg *self, ? NMP_IP_ADDRESS_SYNC_FLAGS_NONE : NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE); + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--; + _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); if (!nm_platform_ip_route_sync(self->priv.platform, @@ -5212,6 +5313,15 @@ finalize(GObject *object) NML3Cfg *self = NM_L3CFG(object); gboolean changed; + if (self->priv.netns) { + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET), + TRUE); + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6), + TRUE); + } + nm_assert(c_list_is_empty(&self->internal_netns.signal_pending_lst)); nm_assert(c_list_is_empty(&self->internal_netns.ecmp_track_ifindex_lst_head)); |