summary refs log tree commit diff
path: root/clients/cloud-setup
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2020-06-29 22:15:58 +0200
committerMichael Biebl <biebl@debian.org>2020-06-29 22:15:58 +0200
commit10ae7d8cd706062742d0cdb1803d49909aef9e06 (patch)
treecb89e8b475cec18f22b1abfe45f1d63da7e3f440 /clients/cloud-setup
parenta54ac63bbf9b2c71026ac9028a8ffaf186cf3c82 (diff)
New upstream version 1.25.91 upstream/1.25.91
Diffstat (limited to 'clients/cloud-setup')
-rw-r--r--clients/cloud-setup/main.c131
-rw-r--r--clients/cloud-setup/meson.build1
-rw-r--r--clients/cloud-setup/nm-cloud-setup-utils.c3
-rw-r--r--clients/cloud-setup/nm-cloud-setup.service.in1
-rw-r--r--clients/cloud-setup/nm-http-client.c63
-rw-r--r--clients/cloud-setup/nm-http-client.h2
-rw-r--r--clients/cloud-setup/nmcs-provider-ec2.c4
-rw-r--r--clients/cloud-setup/nmcs-provider-gcp.c520
-rw-r--r--clients/cloud-setup/nmcs-provider-gcp.h24
-rw-r--r--clients/cloud-setup/nmcs-provider.c1
-rw-r--r--clients/cloud-setup/nmcs-provider.h8
11 files changed, 687 insertions, 71 deletions
diff --git a/clients/cloud-setup/main.c b/clients/cloud-setup/main.c
index 4e1a9a4e..78260b97 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;
@@ -276,21 +278,21 @@ _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)
 {
 	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;
@@ -299,62 +301,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);
+
+	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);
+		}
 
-		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;
-
-	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);
-	}
+	for (i = 0; i < config_data->iproutes_len; ++i)
+		g_ptr_array_add (routes_new, config_data->iproutes_arr[i]);
 
-	addrs_changed = nmcs_setting_ip_replace_ipv4_addresses (s_ip,
-	                                                        (NMIPAddress **) addrs_new->pdata,
-	                                                        addrs_new->len);
+	if (addrs_new->len) {
+		addrs_changed = nmcs_setting_ip_replace_ipv4_addresses (s_ip,
+		                                                        (NMIPAddress **) addrs_new->pdata,
+		                                                        addrs_new->len);
+	}
 
-	routes_changed = nmcs_setting_ip_replace_ipv4_routes (s_ip,
-	                                                      &route_new,
-	                                                      1);
+	if (routes_new->len) {
+		routes_changed = nmcs_setting_ip_replace_ipv4_routes (s_ip,
+		                                                      (NMIPRoute **) routes_new->pdata,
+		                                                      routes_new->len);
+	}
 
-	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
@@ -438,7 +458,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);
diff --git a/clients/cloud-setup/meson.build b/clients/cloud-setup/meson.build
index d8f96539..805d4681 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-utils.c b/clients/cloud-setup/nm-cloud-setup-utils.c
index 13c9566a..a32003cb 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;
 }
 
diff --git a/clients/cloud-setup/nm-cloud-setup.service.in b/clients/cloud-setup/nm-cloud-setup.service.in
index 69a1a29c..9866acd8 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/nm-http-client.c b/clients/cloud-setup/nm-http-client.c
index 94f3fe2b..946ed8ce 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 {
@@ -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);
 }
@@ -159,8 +162,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))
@@ -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);
@@ -615,12 +643,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 +664,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);
 		}
 	}
 
@@ -652,7 +685,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
@@ -678,6 +711,12 @@ _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 +753,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);
 }
diff --git a/clients/cloud-setup/nm-http-client.h b/clients/cloud-setup/nm-http-client.h
index 86ee938e..ef7c984a 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 82ed0949..c8db31f9 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,
diff --git a/clients/cloud-setup/nmcs-provider-gcp.c b/clients/cloud-setup/nmcs-provider-gcp.c
new file mode 100644
index 00000000..ba4016dd
--- /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 00000000..b0d3ec7d
--- /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__ */
diff --git a/clients/cloud-setup/nmcs-provider.c b/clients/cloud-setup/nmcs-provider.c
index 1f1b6e60..bc21d876 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 e5a44da1..e5950f93 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);