From f5487645d8a849b5f0569874d5de9778678506f1 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:21:48 +0200 Subject: [PATCH 1/9] nmcs-http: fix multiple HTTP request bug Since just a single pointer is used to store the socket's GSource if more than 1 consecutive request was done through the same HTTP provider the 2nd request would clear the GSource associated to the second request causing the 1st HTTP request to never complete and end up in a expired timeout. Use a hashtable instead so we can correctly track all requests. https://bugzilla.redhat.com/show_bug.cgi?id=1821787 Fixes: 69f048bf0ca3 ('cloud-setup: add tool for automatic IP configuration in cloud') (cherry picked from commit 427fbc85f0f325e3ff4c887ffd0d145cc1112306) --- clients/cloud-setup/nm-http-client.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/clients/cloud-setup/nm-http-client.c b/clients/cloud-setup/nm-http-client.c index 94f3fe2b8a..16dbd5bbe0 100644 --- a/clients/cloud-setup/nm-http-client.c +++ b/clients/cloud-setup/nm-http-client.c @@ -16,7 +16,7 @@ typedef struct { GMainContext *context; CURLM *mhandle; GSource *mhandle_source_timeout; - GSource *mhandle_source_socket; + GHashTable *source_sockets_hashtable; } NMHttpClientPrivate; struct _NMHttpClient { @@ -615,12 +615,13 @@ _mhandle_socket_cb (int fd, static int _mhandle_socketfunction_cb (CURL *e_handle, curl_socket_t fd, int what, void *user_data, void *socketp) { + GSource *source_socket; NMHttpClient *self = user_data; NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); (void) _NM_ENSURE_TYPE (int, fd); - nm_clear_g_source_inst (&priv->mhandle_source_socket); + g_hash_table_remove (priv->source_sockets_hashtable, GINT_TO_POINTER (fd)); if (what != CURL_POLL_REMOVE) { GIOCondition condition = 0; @@ -635,13 +636,17 @@ _mhandle_socketfunction_cb (CURL *e_handle, curl_socket_t fd, int what, void *us condition = 0; if (condition) { - priv->mhandle_source_socket = nm_g_unix_fd_source_new (fd, - condition, - G_PRIORITY_DEFAULT, - _mhandle_socket_cb, - self, + source_socket = nm_g_unix_fd_source_new (fd, + condition, + G_PRIORITY_DEFAULT, + _mhandle_socket_cb, + self, NULL); - g_source_attach (priv->mhandle_source_socket, priv->context); + g_source_attach (source_socket, priv->context); + + g_hash_table_insert (priv->source_sockets_hashtable, + GINT_TO_POINTER (fd), + source_socket); } } @@ -678,6 +683,11 @@ _mhandle_timerfunction_cb (CURLM *multi, long timeout_msec, void *user_data) static void nm_http_client_init (NMHttpClient *self) { + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + priv->source_sockets_hashtable = g_hash_table_new_full (nm_direct_hash, + NULL, + NULL, + (GDestroyNotify) nm_g_source_destroy_and_unref); } static void @@ -714,9 +724,9 @@ dispose (GObject *object) NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); nm_clear_pointer (&priv->mhandle, curl_multi_cleanup); + nm_clear_pointer (&priv->source_sockets_hashtable, g_hash_table_unref); nm_clear_g_source_inst (&priv->mhandle_source_timeout); - nm_clear_g_source_inst (&priv->mhandle_source_socket); G_OBJECT_CLASS (nm_http_client_parent_class)->dispose (object); } From b7d53f0d3a7fcec3e5b8ebce477750cd8ef9d166 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:26:59 +0200 Subject: [PATCH 2/9] nmcs-http: remove the timeout once expired libcurl's documentation for CURLMOPT_TIMERFUNCTION requires the application to install a non-repeating timer. https://curl.haxx.se/libcurl/c/CURLMOPT_TIMERFUNCTION.html So let's remove the GSource once expired. Fixes: 69f048bf0ca3 ('cloud-setup: add tool for automatic IP configuration in cloud') (cherry picked from commit e09bd2339a224a13067b0adcf9cb84a4f13ef003) --- clients/cloud-setup/nm-http-client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/cloud-setup/nm-http-client.c b/clients/cloud-setup/nm-http-client.c index 16dbd5bbe0..817f0e2eba 100644 --- a/clients/cloud-setup/nm-http-client.c +++ b/clients/cloud-setup/nm-http-client.c @@ -657,7 +657,7 @@ static gboolean _mhandle_timeout_cb (gpointer user_data) { _mhandle_action (user_data, CURL_SOCKET_TIMEOUT, 0); - return G_SOURCE_CONTINUE; + return G_SOURCE_REMOVE; } static int From 20a6fa7d1b1e632f7eaee783528c2a44cfe282f7 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:15:58 +0200 Subject: [PATCH 3/9] nmcs: add error message when a HTTP request times out https://bugzilla.redhat.com/show_bug.cgi?id=1821787 (cherry picked from commit 3bd30f6064c3e879427bc47e0c258c7797cd3fe6) --- clients/cloud-setup/nm-cloud-setup-utils.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/cloud-setup/nm-cloud-setup-utils.c b/clients/cloud-setup/nm-cloud-setup-utils.c index 13c9566a8b..a32003cb35 100644 --- a/clients/cloud-setup/nm-cloud-setup-utils.c +++ b/clients/cloud-setup/nm-cloud-setup-utils.c @@ -345,7 +345,8 @@ _poll_timeout_cb (gpointer user_data) { PollTaskData *poll_task_data = user_data; - _poll_return (poll_task_data, FALSE, NULL); + _poll_return (poll_task_data, FALSE, nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "timeout expired")); return G_SOURCE_CONTINUE; } From 0d22e6b2fe689b97f22e42a77b70b1ba7b218abc Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Tue, 23 Jun 2020 17:58:42 +0200 Subject: [PATCH 4/9] nm-shared-utils: add util to parse out lines from a string https://bugzilla.redhat.com/show_bug.cgi?id=1821787 (cherry picked from commit aa5959a595d12697c5714d50a19d0fdc69b69cff) --- shared/nm-glib-aux/nm-shared-utils.c | 41 ++++++++++++++++++++++++++++ shared/nm-glib-aux/nm-shared-utils.h | 5 ++++ 2 files changed, 46 insertions(+) diff --git a/shared/nm-glib-aux/nm-shared-utils.c b/shared/nm-glib-aux/nm-shared-utils.c index 23577a0d7c..e29520b286 100644 --- a/shared/nm-glib-aux/nm-shared-utils.c +++ b/shared/nm-glib-aux/nm-shared-utils.c @@ -1069,6 +1069,47 @@ nm_utils_parse_inaddr_prefix (int addr_family, return TRUE; } +gboolean +nm_utils_parse_next_line (const char **inout_ptr, + gsize *inout_len, + const char **out_line, + gsize *out_line_len) +{ + const char *line_start; + const char *line_end; + + g_return_val_if_fail (inout_ptr, FALSE); + g_return_val_if_fail (inout_len, FALSE); + g_return_val_if_fail (out_line, FALSE); + + if (*inout_len <= 0) + goto error; + + line_start = *inout_ptr; + line_end = memchr (line_start, '\n', *inout_len); + if (!line_end) + line_end = memchr (line_start, '\0', *inout_len); + if (!line_end) { + line_end = line_start + *inout_len; + NM_SET_OUT (inout_len, 0); + } else + NM_SET_OUT (inout_len, *inout_len - (line_end - line_start) - 1); + + NM_SET_OUT (out_line, line_start); + NM_SET_OUT (out_line_len, (gsize) (line_end - line_start)); + + if (*inout_len > 0) + NM_SET_OUT (inout_ptr, line_end + 1); + else + NM_SET_OUT (inout_ptr, NULL); + return TRUE; + +error: + NM_SET_OUT (out_line, NULL); + NM_SET_OUT (out_line_len, 0); + return FALSE; +} + /*****************************************************************************/ gboolean diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h index 6fb26192d3..b17c8d1a09 100644 --- a/shared/nm-glib-aux/nm-shared-utils.h +++ b/shared/nm-glib-aux/nm-shared-utils.h @@ -807,6 +807,11 @@ gboolean nm_utils_parse_inaddr_prefix (int addr_family, char **out_addr, int *out_prefix); +gboolean nm_utils_parse_next_line (const char **inout_ptr, + gsize *inout_len, + const char **out_line, + gsize *out_line_len); + gint64 nm_g_ascii_strtoll (const char *nptr, char **endptr, guint base); From 1f27c36288f5ffdf7aeaf367d6f7c21959171d88 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:17:33 +0200 Subject: [PATCH 5/9] nmcs: fix indentation (cherry picked from commit d46da9072a0ea009a30cd0ffe6379cab5a8125d2) --- clients/cloud-setup/nm-http-client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/cloud-setup/nm-http-client.c b/clients/cloud-setup/nm-http-client.c index 817f0e2eba..bf42906271 100644 --- a/clients/cloud-setup/nm-http-client.c +++ b/clients/cloud-setup/nm-http-client.c @@ -159,8 +159,8 @@ _ehandle_complete (EHandleData *edata, nm_clear_pointer (&edata->timeout_source, nm_g_source_destroy_and_unref); - nm_clear_g_cancellable_disconnect (g_task_get_cancellable (edata->task), - &edata->cancellable_id); + nm_clear_g_cancellable_disconnect (g_task_get_cancellable (edata->task), + &edata->cancellable_id); if (error_take) { if (nm_utils_error_is_cancelled (error_take)) From c8965f906e00ba1c071c97f3d4f45b107fc85a54 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 11 Jun 2020 16:35:14 +0200 Subject: [PATCH 6/9] main: remove unused argument (cherry picked from commit 1095cef9a14f916176a85639e35743339c320739) --- clients/cloud-setup/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/clients/cloud-setup/main.c b/clients/cloud-setup/main.c index 4e1a9a4e4e..b89943c61c 100644 --- a/clients/cloud-setup/main.c +++ b/clients/cloud-setup/main.c @@ -276,7 +276,6 @@ _nmc_skip_connection (NMConnection *connection) static gboolean _nmc_mangle_connection (NMDevice *device, NMConnection *connection, - gboolean is_single_nic, const NMCSProviderGetConfigIfaceData *config_data, gboolean *out_changed) { @@ -438,7 +437,6 @@ try_again: if (!_nmc_mangle_connection (device, applied_connection, - is_single_nic, config_data, &changed)) { _LOGD ("config device %s: device has no suitable applied connection. Skip", hwaddr); From 23c11af7f512b99edf2b4189bef3b800b02a2960 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:19:45 +0200 Subject: [PATCH 7/9] nmcs-http: add param to GET API to set custom HTTP headers https://bugzilla.redhat.com/show_bug.cgi?id=1821787 (cherry picked from commit 053bce438b231e5837164eb9e5ce9fa02c047b27) --- clients/cloud-setup/nm-http-client.c | 29 +++++++++++++++++++++++++ clients/cloud-setup/nm-http-client.h | 2 ++ clients/cloud-setup/nmcs-provider-ec2.c | 4 ++++ 3 files changed, 35 insertions(+) diff --git a/clients/cloud-setup/nm-http-client.c b/clients/cloud-setup/nm-http-client.c index bf42906271..946ed8ce93 100644 --- a/clients/cloud-setup/nm-http-client.c +++ b/clients/cloud-setup/nm-http-client.c @@ -119,6 +119,7 @@ typedef struct { CURL *ehandle; char *url; GString *recv_data; + struct curl_slist *headers; gssize max_data; gulong cancellable_id; } EHandleData; @@ -145,6 +146,8 @@ _ehandle_free (EHandleData *edata) if (edata->recv_data) g_string_free (edata->recv_data, TRUE); + if (edata->headers) + curl_slist_free_all (edata->headers); g_free (edata->url); nm_g_slice_free (edata); } @@ -260,12 +263,14 @@ nm_http_client_get (NMHttpClient *self, const char *url, int timeout_msec, gssize max_data, + const char *const *http_headers, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { NMHttpClientPrivate *priv; EHandleData *edata; + guint i; g_return_if_fail (NM_IS_HTTP_CLIENT (self)); g_return_if_fail (url); @@ -281,6 +286,7 @@ nm_http_client_get (NMHttpClient *self, .recv_data = g_string_sized_new (NM_MIN (max_data, 245)), .max_data = max_data, .url = g_strdup (url), + .headers = NULL, }; nmcs_wait_for_objects_register (edata->task); @@ -302,6 +308,23 @@ nm_http_client_get (NMHttpClient *self, curl_easy_setopt (edata->ehandle, CURLOPT_WRITEDATA, edata); curl_easy_setopt (edata->ehandle, CURLOPT_PRIVATE, edata); + if (http_headers) { + for (i = 0; http_headers[i]; ++i) { + struct curl_slist *tmp; + + tmp = curl_slist_append (edata->headers, + http_headers[i]); + if (!tmp) { + curl_slist_free_all (tmp); + _LOGE ("curl: curl_slist_append() failed adding %s", http_headers[i]); + continue; + } + edata->headers = tmp; + } + + curl_easy_setopt (edata->ehandle, CURLOPT_HTTPHEADER, edata->headers); + } + if (timeout_msec > 0) { edata->timeout_source = _source_attach (self, nm_g_timeout_source_new (timeout_msec, @@ -362,6 +385,7 @@ nm_http_client_get_finish (NMHttpClient *self, typedef struct { GTask *task; char *uri; + const char *const *http_headers; NMHttpClientPollGetCheckFcn check_fcn; gpointer check_user_data; GBytes *response_data; @@ -378,6 +402,7 @@ _poll_get_data_free (gpointer data) g_free (poll_get_data->uri); nm_clear_pointer (&poll_get_data->response_data, g_bytes_unref); + g_strfreev ((char **) poll_get_data->http_headers); nm_g_slice_free (poll_get_data); } @@ -397,6 +422,7 @@ _poll_get_probe_start_fcn (GCancellable *cancellable, poll_get_data->uri, poll_get_data->request_timeout_ms, poll_get_data->request_max_data, + poll_get_data->http_headers, cancellable, callback, user_data); @@ -476,6 +502,7 @@ nm_http_client_poll_get (NMHttpClient *self, gssize request_max_data, int poll_timeout_ms, int ratelimit_timeout_ms, + const char *const *http_headers, GCancellable *cancellable, NMHttpClientPollGetCheckFcn check_fcn, gpointer check_user_data, @@ -502,6 +529,7 @@ nm_http_client_poll_get (NMHttpClient *self, .check_fcn = check_fcn, .check_user_data = check_user_data, .response_code = -1, + .http_headers = NM_CAST_STRV_CC (g_strdupv ((char **) http_headers)), }; nmcs_wait_for_objects_register (poll_get_data->task); @@ -684,6 +712,7 @@ static void nm_http_client_init (NMHttpClient *self) { NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + priv->source_sockets_hashtable = g_hash_table_new_full (nm_direct_hash, NULL, NULL, diff --git a/clients/cloud-setup/nm-http-client.h b/clients/cloud-setup/nm-http-client.h index 86ee938ee6..ef7c984a9b 100644 --- a/clients/cloud-setup/nm-http-client.h +++ b/clients/cloud-setup/nm-http-client.h @@ -29,6 +29,7 @@ void nm_http_client_get (NMHttpClient *self, const char *uri, int timeout_msec, gssize max_data, + const char *const *http_headers, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); @@ -50,6 +51,7 @@ void nm_http_client_poll_get (NMHttpClient *self, gssize request_max_data, int poll_timeout_ms, int ratelimit_timeout_ms, + const char *const *http_headers, GCancellable *cancellable, NMHttpClientPollGetCheckFcn check_fcn, gpointer check_user_data, diff --git a/clients/cloud-setup/nmcs-provider-ec2.c b/clients/cloud-setup/nmcs-provider-ec2.c index 82ed094970..c8db31f97f 100644 --- a/clients/cloud-setup/nmcs-provider-ec2.c +++ b/clients/cloud-setup/nmcs-provider-ec2.c @@ -138,6 +138,7 @@ detect (NMCSProvider *provider, 256*1024, 7000, 1000, + NULL, g_task_get_cancellable (task), _detect_get_meta_data_check_cb, NULL, @@ -396,6 +397,7 @@ _get_config_metadata_ready_cb (GObject *source, 512*1024, 10000, 1000, + NULL, iface_data->cancellable, NULL, NULL, @@ -413,6 +415,7 @@ _get_config_metadata_ready_cb (GObject *source, 512*1024, 10000, 1000, + NULL, iface_data->cancellable, NULL, NULL, @@ -529,6 +532,7 @@ get_config (NMCSProvider *provider, 256 * 1024, 15000, 1000, + NULL, g_task_get_cancellable (get_config_data->task), _get_config_metadata_ready_check, metadata_data, From 8581038450cb8d73e2eafaa36e95385b1fed59ee Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:11:41 +0200 Subject: [PATCH 8/9] nmcs-main: support adding additional routes This allows a provider to only add additional routes to the applied profile https://bugzilla.redhat.com/show_bug.cgi?id=1821787 (cherry picked from commit 75a84677caab9161e3b347ddc20a4b387873d048) --- clients/cloud-setup/main.c | 125 ++++++++++++++++------------ clients/cloud-setup/nmcs-provider.c | 1 + clients/cloud-setup/nmcs-provider.h | 8 +- 3 files changed, 79 insertions(+), 55 deletions(-) diff --git a/clients/cloud-setup/main.c b/clients/cloud-setup/main.c index b89943c61c..ad3a47cbe0 100644 --- a/clients/cloud-setup/main.c +++ b/clients/cloud-setup/main.c @@ -280,16 +280,17 @@ _nmc_mangle_connection (NMDevice *device, gboolean *out_changed) { NMSettingIPConfig *s_ip; - gboolean addrs_changed; - gboolean routes_changed; - gboolean rules_changed; gsize i; in_addr_t gateway; gint64 rt_metric; guint32 rt_table; + NMIPRoute *route_entry; + gboolean addrs_changed = FALSE; + gboolean rules_changed = FALSE; + gboolean routes_changed = FALSE; gs_unref_ptrarray GPtrArray *addrs_new = NULL; gs_unref_ptrarray GPtrArray *rules_new = NULL; - nm_auto_unref_ip_route NMIPRoute *route_new = NULL; + gs_unref_ptrarray GPtrArray *routes_new = NULL; if (!nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_WIRED_SETTING_NAME)) return FALSE; @@ -298,62 +299,80 @@ _nmc_mangle_connection (NMDevice *device, if (!s_ip) return FALSE; - addrs_new = g_ptr_array_new_full (config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref); - for (i = 0; i < config_data->ipv4s_len; i++) { - NMIPAddress *entry; + addrs_new = g_ptr_array_new_full (config_data->ipv4s_len, + (GDestroyNotify) nm_ip_address_unref); + rules_new = g_ptr_array_new_full (config_data->ipv4s_len, + (GDestroyNotify) nm_ip_routing_rule_unref); + routes_new = g_ptr_array_new_full (config_data->iproutes_len + !!config_data->ipv4s_len, + (GDestroyNotify) nm_ip_route_unref); - entry = nm_ip_address_new_binary (AF_INET, - &config_data->ipv4s_arr[i], - config_data->cidr_prefix, - NULL); - if (entry) - g_ptr_array_add (addrs_new, entry); + if ( config_data->has_ipv4s + && config_data->has_cidr) { + for (i = 0; i < config_data->ipv4s_len; i++) { + NMIPAddress *entry; + + entry = nm_ip_address_new_binary (AF_INET, + &config_data->ipv4s_arr[i], + config_data->cidr_prefix, + NULL); + if (entry) + g_ptr_array_add (addrs_new, entry); + } + + gateway = nm_utils_ip4_address_clear_host_address (config_data->cidr_addr, config_data->cidr_prefix); + ((guint8 *) &gateway)[3] += 1; + + rt_metric = 10; + rt_table = 30400 + config_data->iface_idx; + + route_entry = nm_ip_route_new_binary (AF_INET, + &nm_ip_addr_zero, + 0, + &gateway, + rt_metric, + NULL); + nm_ip_route_set_attribute (route_entry, + NM_IP_ROUTE_ATTRIBUTE_TABLE, + g_variant_new_uint32 (rt_table)); + g_ptr_array_add (routes_new, route_entry); + + for (i = 0; i < config_data->ipv4s_len; i++) { + NMIPRoutingRule *entry; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + entry = nm_ip_routing_rule_new (AF_INET); + nm_ip_routing_rule_set_priority (entry, rt_table); + nm_ip_routing_rule_set_from (entry, + _nm_utils_inet4_ntop (config_data->ipv4s_arr[i], sbuf), + 32); + nm_ip_routing_rule_set_table (entry, rt_table); + + nm_assert (nm_ip_routing_rule_validate (entry, NULL)); + + g_ptr_array_add (rules_new, entry); + } } - gateway = nm_utils_ip4_address_clear_host_address (config_data->cidr_addr, config_data->cidr_prefix); - ((guint8 *) &gateway)[3] += 1; + for (i = 0; i < config_data->iproutes_len; ++i) + g_ptr_array_add (routes_new, config_data->iproutes_arr[i]); - rt_metric = 10; - rt_table = 30400 + config_data->iface_idx; - - route_new = nm_ip_route_new_binary (AF_INET, - &nm_ip_addr_zero, - 0, - &gateway, - rt_metric, - NULL); - nm_ip_route_set_attribute (route_new, - NM_IP_ROUTE_ATTRIBUTE_TABLE, - g_variant_new_uint32 (rt_table)); - - rules_new = g_ptr_array_new_full (config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref); - for (i = 0; i < config_data->ipv4s_len; i++) { - NMIPRoutingRule *entry; - char sbuf[NM_UTILS_INET_ADDRSTRLEN]; - - entry = nm_ip_routing_rule_new (AF_INET); - nm_ip_routing_rule_set_priority (entry, rt_table); - nm_ip_routing_rule_set_from (entry, - _nm_utils_inet4_ntop (config_data->ipv4s_arr[i], sbuf), - 32); - nm_ip_routing_rule_set_table (entry, rt_table); - - nm_assert (nm_ip_routing_rule_validate (entry, NULL)); - - g_ptr_array_add (rules_new, entry); + if (addrs_new->len) { + addrs_changed = nmcs_setting_ip_replace_ipv4_addresses (s_ip, + (NMIPAddress **) addrs_new->pdata, + addrs_new->len); } - addrs_changed = nmcs_setting_ip_replace_ipv4_addresses (s_ip, - (NMIPAddress **) addrs_new->pdata, - addrs_new->len); + if (routes_new->len) { + routes_changed = nmcs_setting_ip_replace_ipv4_routes (s_ip, + (NMIPRoute **) routes_new->pdata, + routes_new->len); + } - routes_changed = nmcs_setting_ip_replace_ipv4_routes (s_ip, - &route_new, - 1); - - rules_changed = nmcs_setting_ip_replace_ipv4_rules (s_ip, - (NMIPRoutingRule **) rules_new->pdata, - rules_new->len); + if (rules_new->len) { + rules_changed = nmcs_setting_ip_replace_ipv4_rules (s_ip, + (NMIPRoutingRule **) rules_new->pdata, + rules_new->len); + } NM_SET_OUT (out_changed, addrs_changed || routes_changed diff --git a/clients/cloud-setup/nmcs-provider.c b/clients/cloud-setup/nmcs-provider.c index 1f1b6e600d..bc21d8769e 100644 --- a/clients/cloud-setup/nmcs-provider.c +++ b/clients/cloud-setup/nmcs-provider.c @@ -114,6 +114,7 @@ _iface_data_free (gpointer data) NMCSProviderGetConfigIfaceData *iface_data = data; g_free (iface_data->ipv4s_arr); + g_free (iface_data->iproutes_arr); nm_g_slice_free (iface_data); } diff --git a/clients/cloud-setup/nmcs-provider.h b/clients/cloud-setup/nmcs-provider.h index e5a44da19f..e5950f930e 100644 --- a/clients/cloud-setup/nmcs-provider.h +++ b/clients/cloud-setup/nmcs-provider.h @@ -18,6 +18,9 @@ typedef struct { bool has_ipv4s:1; bool has_cidr:1; + NMIPRoute **iproutes_arr; + gsize iproutes_len; + /* TRUE, if the configuration was requested via hwaddrs argument to * nmcs_provider_get_config(). */ bool was_requested:1; @@ -29,8 +32,9 @@ nmcs_provider_get_config_iface_data_is_valid (const NMCSProviderGetConfigIfaceDa { return config_data && config_data->iface_idx >= 0 - && config_data->has_cidr - && config_data->has_ipv4s; + && ( ( config_data->has_ipv4s + && config_data->has_cidr) + || config_data->iproutes_len); } NMCSProviderGetConfigIfaceData *nmcs_provider_get_config_iface_data_new (gboolean was_requested); From 10abdedb1ac7cd25a9d0c17740b96efd47a264f9 Mon Sep 17 00:00:00 2001 From: Antonio Cardace Date: Thu, 18 Jun 2020 18:30:13 +0200 Subject: [PATCH 9/9] nmcs-gcp: add support for Google Cloud Platform load balancers This add a provider implementation for GCP that when detected fetches the ip addresses of configured internal load balancers. Once this information is fetched from the metadata server it instructs NetworkManager to add local routes for each found forwarded-ip. https://bugzilla.redhat.com/show_bug.cgi?id=1821787 (cherry picked from commit a2b699f40f29fb1d37cda8c9a10365229f55b0c8) --- Makefile.am | 2 + clients/cloud-setup/main.c | 2 + clients/cloud-setup/meson.build | 1 + clients/cloud-setup/nm-cloud-setup.service.in | 1 + clients/cloud-setup/nmcs-provider-gcp.c | 520 ++++++++++++++++++ clients/cloud-setup/nmcs-provider-gcp.h | 24 + 6 files changed, 550 insertions(+) create mode 100644 clients/cloud-setup/nmcs-provider-gcp.c create mode 100644 clients/cloud-setup/nmcs-provider-gcp.h diff --git a/Makefile.am b/Makefile.am index 285e07778f..bec61e0405 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4829,6 +4829,8 @@ clients_cloud_setup_nm_cloud_setup_SOURCES = \ clients/cloud-setup/nmcs-provider.h \ clients/cloud-setup/nmcs-provider-ec2.c \ clients/cloud-setup/nmcs-provider-ec2.h \ + clients/cloud-setup/nmcs-provider-gcp.c \ + clients/cloud-setup/nmcs-provider-gcp.h \ $(NULL) clients_cloud_setup_nm_cloud_setup_CPPFLAGS = \ diff --git a/clients/cloud-setup/main.c b/clients/cloud-setup/main.c index ad3a47cbe0..78260b9732 100644 --- a/clients/cloud-setup/main.c +++ b/clients/cloud-setup/main.c @@ -6,6 +6,7 @@ #include "nm-cloud-setup-utils.h" #include "nmcs-provider-ec2.h" +#include "nmcs-provider-gcp.h" #include "nm-libnm-core-intern/nm-libnm-core-utils.h" /*****************************************************************************/ @@ -84,6 +85,7 @@ _provider_detect (GCancellable *sigterm_cancellable) }; const GType gtypes[] = { NMCS_TYPE_PROVIDER_EC2, + NMCS_TYPE_PROVIDER_GCP, }; int i; gulong cancellable_signal_id; diff --git a/clients/cloud-setup/meson.build b/clients/cloud-setup/meson.build index d8f96539e0..805d46813b 100644 --- a/clients/cloud-setup/meson.build +++ b/clients/cloud-setup/meson.build @@ -28,6 +28,7 @@ sources = files( 'nm-cloud-setup-utils.c', 'nm-http-client.c', 'nmcs-provider-ec2.c', + 'nmcs-provider-gcp.c', 'nmcs-provider.c', ) diff --git a/clients/cloud-setup/nm-cloud-setup.service.in b/clients/cloud-setup/nm-cloud-setup.service.in index 69a1a29ccb..9866acd8b0 100644 --- a/clients/cloud-setup/nm-cloud-setup.service.in +++ b/clients/cloud-setup/nm-cloud-setup.service.in @@ -12,6 +12,7 @@ ExecStart=@libexecdir@/nm-cloud-setup # Opt-in by setting the right environment variable for # the provider. #Environment=NM_CLOUD_SETUP_EC2=yes +#Environment=NM_CLOUD_SETUP_GCP=yes CapabilityBoundingSet= LockPersonality=yes diff --git a/clients/cloud-setup/nmcs-provider-gcp.c b/clients/cloud-setup/nmcs-provider-gcp.c new file mode 100644 index 0000000000..ba4016dd15 --- /dev/null +++ b/clients/cloud-setup/nmcs-provider-gcp.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nmcs-provider-gcp.h" + +#include "nm-cloud-setup-utils.h" + +/*****************************************************************************/ + +#define HTTP_TIMEOUT_MS 3000 +#define HTTP_REQ_MAX_DATA 512*1024 +#define HTTP_POLL_TIMEOUT_MS 10000 +#define HTTP_RATE_LIMIT_MS 1000 + +#define NM_GCP_HOST "metadata.google.internal" +#define NM_GCP_BASE "http://" NM_GCP_HOST +#define NM_GCP_API_VERSION "/v1" +#define NM_GCP_METADATA_URL_BASE NM_GCP_BASE "/computeMetadata" NM_GCP_API_VERSION "/instance" +#define NM_GCP_METADATA_URL_NET "/network-interfaces/" + +#define NM_GCP_METADATA_HEADER "Metadata-Flavor: Google" + +#define _gcp_uri_concat(...) nmcs_utils_uri_build_concat (NM_GCP_METADATA_URL_BASE, __VA_ARGS__) +#define _gcp_uri_interfaces(...) _gcp_uri_concat (NM_GCP_METADATA_URL_NET, ##__VA_ARGS__) + +/*****************************************************************************/ + +struct _NMCSProviderGCP { + NMCSProvider parent; +}; + +struct _NMCSProviderGCPClass { + NMCSProviderClass parent; +}; + +G_DEFINE_TYPE (NMCSProviderGCP, nmcs_provider_gcp, NMCS_TYPE_PROVIDER); + +/*****************************************************************************/ + +static void +_detect_get_meta_data_done_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_object GTask *task = user_data; + gs_free_error GError *get_error = NULL; + gs_free_error GError *error = NULL; + gboolean success; + + success = nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + NULL, + &get_error); + + if (nm_utils_error_is_cancelled (get_error)) { + g_task_return_error (task, g_steal_pointer (&get_error)); + return; + } + + if (get_error) { + nm_utils_error_set (&error, + NM_UTILS_ERROR_UNKNOWN, + "failure to get GCP metadata: %s", + get_error->message); + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (!success) { + nm_utils_error_set (&error, + NM_UTILS_ERROR_UNKNOWN, + "failure to detect GCP metadata"); + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + g_task_return_boolean (task, TRUE); +} + +static void +detect (NMCSProvider *provider, + GTask *task) +{ + NMHttpClient *http_client; + gs_free char *uri = NULL; + + http_client = nmcs_provider_get_http_client (provider); + + nm_http_client_poll_get (http_client, + (uri = _gcp_uri_concat ("id")), + HTTP_TIMEOUT_MS, + 256*1024, + 7000, + 1000, + NM_MAKE_STRV (NM_GCP_METADATA_HEADER), + g_task_get_cancellable (task), + NULL, + NULL, + _detect_get_meta_data_done_cb, + task); +} + +/*****************************************************************************/ + +typedef struct { + NMCSProviderGetConfigTaskData *config_data; + guint n_ifaces_pending; + GError *error; + bool success:1; +} GCPData; + +typedef struct { + NMCSProviderGetConfigIfaceData *iface_get_config; + GCPData *gcp_data; + gssize iface_idx; + guint n_fips_pending; +} GCPIfaceData; + +static void +_get_config_maybe_task_return (GCPData *gcp_data, + GError *error_take) +{ + NMCSProviderGetConfigTaskData *config_data = gcp_data->config_data; + gs_free_error GError *gcp_error = NULL; + + if (error_take) { + nm_clear_error (&gcp_data->error); + gcp_data->error = error_take; + } + + if (gcp_data->n_ifaces_pending) + return; + + gcp_error = gcp_data->error; + + if (!gcp_data->success) { + nm_assert (gcp_error); + + if (nm_utils_error_is_cancelled (gcp_error)) + _LOGD ("get-config: cancelled"); + else + _LOGD ("get-config: failed: %s", gcp_error->message); + g_task_return_error (config_data->task, g_steal_pointer (&gcp_error)); + } else { + _LOGD ("get-config: success"); + g_task_return_pointer (config_data->task, + g_hash_table_ref (config_data->result_dict), + (GDestroyNotify) g_hash_table_unref); + } + + nm_g_slice_free (gcp_data); + g_object_unref (config_data->task); +} + +static void +_get_config_fip_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + NMCSProviderGetConfigIfaceData *iface_get_config; + gs_unref_bytes GBytes *response = NULL; + GCPIfaceData *iface_data = user_data; + gs_free_error GError *error = NULL; + const char *fip_str = NULL; + NMIPRoute **routes_arr; + NMIPRoute *route_new; + GCPData *gcp_data; + + gcp_data = iface_data->gcp_data; + + nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + &response, + &error); + + if (error) + goto iface_done; + + fip_str = g_bytes_get_data (response, NULL); + if (!nm_utils_ipaddr_valid (AF_INET, fip_str)) { + error = nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "forwarded-ip is not a valid ip address"); + goto iface_done; + } + + _LOGI ("GCP interface[%"G_GSSIZE_FORMAT"]: adding forwarded-ip %s", + iface_data->iface_idx, + fip_str); + + iface_get_config = iface_data->iface_get_config; + iface_get_config->iface_idx = iface_data->iface_idx; + routes_arr = iface_get_config->iproutes_arr; + + route_new = nm_ip_route_new (AF_INET, + fip_str, + 32, + NULL, + 100, + &error); + if (error) + goto iface_done; + + nm_ip_route_set_attribute (route_new, + NM_IP_ROUTE_ATTRIBUTE_TYPE, + g_variant_new_string ("local")); + routes_arr[iface_get_config->iproutes_len] = route_new; + ++iface_get_config->iproutes_len; + gcp_data->success = TRUE; + +iface_done: + --iface_data->n_fips_pending; + if (iface_data->n_fips_pending == 0) { + nm_g_slice_free (iface_data); + --gcp_data->n_ifaces_pending; + _get_config_maybe_task_return (gcp_data, g_steal_pointer (&error)); + } +} + +static void +_get_config_ips_list_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_ptrarray GPtrArray *uri_arr = NULL; + gs_unref_bytes GBytes *response = NULL; + GCPIfaceData *iface_data = user_data; + gs_free_error GError *error = NULL; + const char *response_str = NULL; + gsize response_len; + GCPData *gcp_data; + const char *line; + gsize line_len; + guint i; + + gcp_data = iface_data->gcp_data; + + nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + &response, + &error); + + if (error) + goto fips_error; + + + uri_arr = g_ptr_array_new_with_free_func (g_free); + response_str = g_bytes_get_data (response, &response_len); + + while (nm_utils_parse_next_line (&response_str, + &response_len, + &line, + &line_len)) { + nm_auto_free_gstring GString *gstr = NULL; + gint64 fip_index; + + gstr = g_string_new_len (line, line_len); + fip_index = _nm_utils_ascii_str_to_int64 (gstr->str, 10, 0, G_MAXINT64, -1); + + if (fip_index < 0) { + continue; + } + + g_string_printf (gstr, + "%"G_GSSIZE_FORMAT"/forwarded-ips/%"G_GINT64_FORMAT, + iface_data->iface_idx, + fip_index); + g_ptr_array_add (uri_arr, g_string_free (g_steal_pointer (&gstr), FALSE)); + } + + iface_data->n_fips_pending = uri_arr->len; + + _LOGI ("GCP interface[%"G_GSSIZE_FORMAT"]: found %u forwarded ips", + iface_data->iface_idx, + iface_data->n_fips_pending); + + if (iface_data->n_fips_pending == 0) { + error = nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "found no forwarded ip"); + goto fips_error; + } + + iface_data->iface_get_config->iproutes_arr = + g_new (NMIPRoute *, iface_data->n_fips_pending); + + for (i = 0; i < uri_arr->len; ++i) { + const char *str = uri_arr->pdata[i]; + gs_free const char *uri = NULL; + + nm_http_client_poll_get (NM_HTTP_CLIENT (source), + (uri = _gcp_uri_interfaces (str)), + HTTP_TIMEOUT_MS, + HTTP_REQ_MAX_DATA, + HTTP_POLL_TIMEOUT_MS, + HTTP_RATE_LIMIT_MS, + NM_MAKE_STRV (NM_GCP_METADATA_HEADER), + g_task_get_cancellable (gcp_data->config_data->task), + NULL, + NULL, + _get_config_fip_cb, + iface_data); + } + return; + +fips_error: + nm_g_slice_free (iface_data); + --gcp_data->n_ifaces_pending; + _get_config_maybe_task_return (gcp_data, g_steal_pointer (&error)); +} + +static void +_get_config_iface_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_bytes GBytes *response = NULL; + GCPIfaceData *iface_data = user_data; + gs_free_error GError *error = NULL; + gs_free const char *hwaddr = NULL; + gs_free const char *uri = NULL; + gs_free char *str = NULL; + GCPData *gcp_data; + + gcp_data = iface_data->gcp_data; + + nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + &response, + &error); + + if (error) + goto iface_error; + + hwaddr = nmcs_utils_hwaddr_normalize (g_bytes_get_data (response, NULL), -1); + iface_data->iface_get_config = g_hash_table_lookup (gcp_data->config_data->result_dict, + hwaddr); + if (!iface_data->iface_get_config) { + _LOGI ("GCP interface[%"G_GSSIZE_FORMAT"]: did not find a matching device", + iface_data->iface_idx); + error = nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "no matching hwaddr found for GCP interface"); + goto iface_error; + } + + _LOGI ("GCP interface[%"G_GSSIZE_FORMAT"]: found a matching device with hwaddr %s", + iface_data->iface_idx, + hwaddr); + + str = g_strdup_printf ("%"G_GSSIZE_FORMAT"/forwarded-ips/", + iface_data->iface_idx); + + nm_http_client_poll_get (NM_HTTP_CLIENT (source), + (uri = _gcp_uri_interfaces (str)), + HTTP_TIMEOUT_MS, + HTTP_REQ_MAX_DATA, + HTTP_POLL_TIMEOUT_MS, + HTTP_RATE_LIMIT_MS, + NM_MAKE_STRV (NM_GCP_METADATA_HEADER), + g_task_get_cancellable (gcp_data->config_data->task), + NULL, + NULL, + _get_config_ips_list_cb, + iface_data); + return; + +iface_error: + nm_g_slice_free (iface_data); + --gcp_data->n_ifaces_pending; + _get_config_maybe_task_return (gcp_data, g_steal_pointer (&error)); +} + +static void +_get_net_ifaces_list_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_ptrarray GPtrArray *ifaces_arr = NULL; + nm_auto_free_gstring GString *gstr = NULL; + gs_unref_bytes GBytes *response = NULL; + gs_free_error GError *error = NULL; + GCPData *gcp_data = user_data; + const char *response_str; + const char *token_start; + const char *token_end; + gsize response_len; + const char *line; + gsize line_len; + guint i; + + nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + &response, + &error); + + if (error) { + _get_config_maybe_task_return (gcp_data, g_steal_pointer (&error)); + return; + } + + response_str = g_bytes_get_data (response, &response_len); + ifaces_arr = g_ptr_array_new (); + gstr = g_string_new (NULL); + + while (nm_utils_parse_next_line (&response_str, + &response_len, + &line, + &line_len)) { + GCPIfaceData *iface_data; + gssize iface_idx; + + token_start = line; + token_end = memchr (token_start, '/', line_len); + + if (!token_end) + continue; + + g_string_truncate (gstr, 0); + g_string_append_len (gstr, token_start, token_end - token_start); + iface_idx = _nm_utils_ascii_str_to_int64 (gstr->str, 10, 0, G_MAXSSIZE, -1); + + if (iface_idx < 0) + continue; + + iface_data = g_slice_new (GCPIfaceData); + *iface_data = (GCPIfaceData) { + .iface_get_config = NULL, + .gcp_data = gcp_data, + .iface_idx = iface_idx, + .n_fips_pending = 0, + }; + g_ptr_array_add (ifaces_arr, iface_data); + } + + gcp_data->n_ifaces_pending = ifaces_arr->len; + _LOGI ("found GCP interfaces: %u", ifaces_arr->len); + + for (i = 0; i < ifaces_arr->len; ++i) { + GCPIfaceData *data = ifaces_arr->pdata[i]; + gs_free const char *uri = NULL; + + _LOGD ("GCP interface[%"G_GSSIZE_FORMAT"]: retrieving configuration", + data->iface_idx); + + g_string_printf (gstr, "%"G_GSSIZE_FORMAT"/mac", data->iface_idx); + + nm_http_client_poll_get (NM_HTTP_CLIENT (source), + (uri = _gcp_uri_interfaces (gstr->str)), + HTTP_TIMEOUT_MS, + HTTP_REQ_MAX_DATA, + HTTP_POLL_TIMEOUT_MS, + HTTP_RATE_LIMIT_MS, + NM_MAKE_STRV (NM_GCP_METADATA_HEADER), + g_task_get_cancellable (gcp_data->config_data->task), + NULL, + NULL, + _get_config_iface_cb, + data); + + } + + if (ifaces_arr->len == 0) { + error = nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "no GCP interfaces found"); + _get_config_maybe_task_return (gcp_data, g_steal_pointer (&error)); + } +} + + +static void +get_config (NMCSProvider *provider, + NMCSProviderGetConfigTaskData *get_config_data) +{ + gs_free const char *uri = NULL; + GCPData *gcp_data; + + gcp_data = g_slice_new (GCPData); + *gcp_data = (GCPData) { + .config_data = get_config_data, + .n_ifaces_pending = 0, + .error = NULL, + .success = FALSE, + + }; + + nm_http_client_poll_get (nmcs_provider_get_http_client (provider), + (uri = _gcp_uri_interfaces ()), + HTTP_TIMEOUT_MS, + HTTP_REQ_MAX_DATA, + HTTP_POLL_TIMEOUT_MS, + HTTP_RATE_LIMIT_MS, + NM_MAKE_STRV (NM_GCP_METADATA_HEADER), + g_task_get_cancellable (gcp_data->config_data->task), + NULL, + NULL, + _get_net_ifaces_list_cb, + gcp_data); +} + +/*****************************************************************************/ + +static void +nmcs_provider_gcp_init (NMCSProviderGCP *self) +{ +} + +static void +nmcs_provider_gcp_class_init (NMCSProviderGCPClass *klass) +{ + NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS (klass); + + provider_class->_name = "GCP"; + provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE ("NM_CLOUD_SETUP_GCP"); + provider_class->detect = detect; + provider_class->get_config = get_config; +} diff --git a/clients/cloud-setup/nmcs-provider-gcp.h b/clients/cloud-setup/nmcs-provider-gcp.h new file mode 100644 index 0000000000..b0d3ec7d02 --- /dev/null +++ b/clients/cloud-setup/nmcs-provider-gcp.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NMCS_PROVIDER_GCP_H__ +#define __NMCS_PROVIDER_GCP_H__ + +#include "nmcs-provider.h" + +/*****************************************************************************/ + +typedef struct _NMCSProviderGCP NMCSProviderGCP; +typedef struct _NMCSProviderGCPClass NMCSProviderGCPClass; + +#define NMCS_TYPE_PROVIDER_GCP (nmcs_provider_gcp_get_type ()) +#define NMCS_PROVIDER_GCP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMCS_TYPE_PROVIDER_GCP, NMCSProviderGCP)) +#define NMCS_PROVIDER_GCP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NMCS_TYPE_PROVIDER_GCP, NMCSProviderGCPClass)) +#define NMCS_IS_PROVIDER_GCP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMCS_TYPE_PROVIDER_GCP)) +#define NMCS_IS_PROVIDER_GCP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMCS_TYPE_PROVIDER_GCP)) +#define NMCS_PROVIDER_GCP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMCS_TYPE_PROVIDER_GCP, NMCSProviderGCPClass)) + +GType nmcs_provider_gcp_get_type (void); + +/*****************************************************************************/ + +#endif /* __NMCS_PROVIDER_GCP_H__ */