summary refs log tree commit diff
path: root/src/nm-cloud-setup
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-cloud-setup')
-rwxr-xr-xsrc/nm-cloud-setup/90-nm-cloud-setup.sh16
-rw-r--r--src/nm-cloud-setup/main.c183
-rw-r--r--src/nm-cloud-setup/meson.build2
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup-utils.c252
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup-utils.h73
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup.service.in1
-rw-r--r--src/nm-cloud-setup/nm-http-client.c34
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.c24
-rw-r--r--src/nm-cloud-setup/nmcs-provider-azure.c6
-rw-r--r--src/nm-cloud-setup/nmcs-provider-ec2.c24
-rw-r--r--src/nm-cloud-setup/nmcs-provider-gcp.c29
-rw-r--r--src/nm-cloud-setup/nmcs-provider.c4
-rw-r--r--src/nm-cloud-setup/nmcs-provider.h7
13 files changed, 258 insertions, 397 deletions
diff --git a/src/nm-cloud-setup/90-nm-cloud-setup.sh b/src/nm-cloud-setup/90-nm-cloud-setup.sh
index 817e2555..ae55b725 100755
--- a/src/nm-cloud-setup/90-nm-cloud-setup.sh
+++ b/src/nm-cloud-setup/90-nm-cloud-setup.sh
@@ -1,9 +1,17 @@
 #!/bin/sh
 
 case "$2" in
-    up|dhcp4-change)
-        if systemctl -q is-enabled nm-cloud-setup.service ; then
-            exec systemctl --no-block restart nm-cloud-setup.service
-        fi
+    pre-up)
+	NO_BLOCK=
         ;;
+    dhcp4-change)
+	NO_BLOCK=--no-block
+        ;;
+    *)
+	exit 0
+	;;
 esac
+
+if systemctl -q is-enabled nm-cloud-setup.service ; then
+    exec systemctl $NO_BLOCK restart nm-cloud-setup.service
+fi
diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c
index 630f26ef..084b5f62 100644
--- a/src/nm-cloud-setup/main.c
+++ b/src/nm-cloud-setup/main.c
@@ -22,10 +22,12 @@ typedef struct {
 } SigTermData;
 
 typedef struct {
+    SigTermData  *sigterm_data;
     GMainLoop    *main_loop;
     GCancellable *cancellable;
     NMCSProvider *provider_result;
     guint         detect_count;
+    gboolean      any_provider_enabled;
 } ProviderDetectData;
 
 static void
@@ -33,38 +35,45 @@ _provider_detect_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     gs_unref_object NMCSProvider *provider = NMCS_PROVIDER(source);
     gs_free_error GError         *error    = NULL;
-    ProviderDetectData           *dd;
+    ProviderDetectData           *dd       = user_data;
     gboolean                      success;
 
+    nm_assert(dd->detect_count > 0);
+    dd->detect_count--;
+
     success = nmcs_provider_detect_finish(provider, result, &error);
 
     nm_assert(success != (!!error));
 
-    if (nm_utils_error_is_cancelled(error))
-        return;
-
-    dd = user_data;
-
-    nm_assert(dd->detect_count > 0);
-    dd->detect_count--;
-
+    if (nm_utils_error_is_cancelled(error)) {
+        _LOGD("provider %s detection cancelled", nmcs_provider_get_name(provider));
+        goto out;
+    }
     if (error) {
+        if (nm_g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_NOT_READY)) {
+            /* This error tells us, that the provider was not enabled in configuration. */
+        } else
+            dd->any_provider_enabled = TRUE;
         _LOGI("provider %s not detected: %s", nmcs_provider_get_name(provider), error->message);
-        if (dd->detect_count > 0) {
-            /* wait longer. */
-            return;
-        }
-
-        _LOGI("no provider detected");
-        goto done;
+        goto out;
     }
 
     _LOGI("provider %s detected", nmcs_provider_get_name(provider));
     dd->provider_result = g_steal_pointer(&provider);
-
-done:
     g_cancellable_cancel(dd->cancellable);
-    g_main_loop_quit(dd->main_loop);
+
+out:
+    if (dd->detect_count == 0) {
+        if (!dd->provider_result) {
+            NMLogLevel level = LOGL_INFO;
+
+            if (dd->any_provider_enabled && !dd->sigterm_data->signal_received)
+                level = LOGL_WARN;
+
+            _NMLOG(level, "no provider detected");
+        }
+        g_main_loop_quit(dd->main_loop);
+    }
 }
 
 static void
@@ -74,21 +83,21 @@ _provider_detect_sigterm_cb(GCancellable *source, gpointer user_data)
 
     g_cancellable_cancel(dd->cancellable);
     g_clear_object(&dd->provider_result);
-    dd->detect_count = 0;
-    g_main_loop_quit(dd->main_loop);
 }
 
 static NMCSProvider *
-_provider_detect(GCancellable *sigterm_cancellable)
+_provider_detect(SigTermData *sigterm_data)
 {
     nm_auto_unref_gmainloop GMainLoop *main_loop   = g_main_loop_new(NULL, FALSE);
     gs_unref_object GCancellable      *cancellable = g_cancellable_new();
     gs_unref_object NMHttpClient      *http_client = NULL;
     ProviderDetectData                 dd          = {
-                                 .cancellable     = cancellable,
-                                 .main_loop       = main_loop,
-                                 .detect_count    = 0,
-                                 .provider_result = NULL,
+                                 .sigterm_data         = sigterm_data,
+                                 .cancellable          = cancellable,
+                                 .main_loop            = main_loop,
+                                 .detect_count         = 0,
+                                 .provider_result      = NULL,
+                                 .any_provider_enabled = FALSE,
     };
     const GType gtypes[] = {
         NMCS_TYPE_PROVIDER_EC2,
@@ -99,7 +108,7 @@ _provider_detect(GCancellable *sigterm_cancellable)
     int    i;
     gulong cancellable_signal_id;
 
-    cancellable_signal_id = g_cancellable_connect(sigterm_cancellable,
+    cancellable_signal_id = g_cancellable_connect(sigterm_data->cancellable,
                                                   G_CALLBACK(_provider_detect_sigterm_cb),
                                                   &dd,
                                                   NULL);
@@ -123,12 +132,113 @@ _provider_detect(GCancellable *sigterm_cancellable)
         g_main_loop_run(main_loop);
 
 out:
-    nm_clear_g_signal_handler(sigterm_cancellable, &cancellable_signal_id);
+    nm_clear_g_signal_handler(sigterm_data->cancellable, &cancellable_signal_id);
     return dd.provider_result;
 }
 
 /*****************************************************************************/
 
+static NMUtilsNamedValue *
+_map_interfaces_parse(void)
+{
+    gs_free const char **split = NULL;
+    NMUtilsNamedValue   *map_interfaces;
+    const char          *env_var;
+    gsize                i;
+    gsize                j;
+    gsize                alloc_len;
+
+    env_var = g_getenv(NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES);
+
+    if (nm_str_is_empty(env_var))
+        return NULL;
+
+    split = nm_strsplit_set_full(env_var, ";", NM_STRSPLIT_SET_FLAGS_STRSTRIP);
+
+    alloc_len = NM_PTRARRAY_LEN(split) + 1u;
+
+    map_interfaces = g_new(NMUtilsNamedValue, alloc_len);
+
+    _LOGD("test: map interfaces via NM_CLOUD_SETUP_MAP_INTERFACES=\"%s\"", env_var);
+
+    for (i = 0, j = 0; split && split[i]; i++) {
+        NMUtilsNamedValue *m;
+        const char        *str = split[i];
+        char              *hwaddr;
+        const char        *s;
+
+        s = strchr(str, '=');
+        if (!s || str == s)
+            continue;
+
+        hwaddr = nmcs_utils_hwaddr_normalize(&s[1], -1);
+        if (!hwaddr)
+            continue;
+
+        nm_assert(j < alloc_len);
+        m = &map_interfaces[j++];
+
+        *m = (NMUtilsNamedValue){
+            .name      = g_strndup(str, s - str),
+            .value_str = hwaddr,
+        };
+
+        _LOGD("test:  map \"%s\" -> %s", m->name, m->value_str);
+    }
+
+    nm_assert(j < alloc_len);
+    map_interfaces[j++] = (NMUtilsNamedValue){
+        .name      = NULL,
+        .value_str = NULL,
+    };
+
+    return g_steal_pointer(&map_interfaces);
+}
+
+static const char *
+_device_get_hwaddr(NMDeviceEthernet *device)
+{
+    static const NMUtilsNamedValue *gl_map_interfaces_map = NULL;
+    static gsize                    gl_initialized        = 0;
+    const NMUtilsNamedValue        *map                   = NULL;
+
+    nm_assert(NM_IS_DEVICE_ETHERNET(device));
+
+    /* Network interfaces in cloud environments are identified by their permanent
+     * MAC address.
+     *
+     * For testing, we can set NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES
+     * to a ';' separate list of "$INTERFACE=$HWADDR", which means that we
+     * pretend that device with ip-interface "$INTERFACE" has the specified permanent
+     * MAC address. */
+
+    if (g_once_init_enter(&gl_initialized)) {
+        gl_map_interfaces_map = _map_interfaces_parse();
+        g_once_init_leave(&gl_initialized, 1);
+    }
+
+    map = gl_map_interfaces_map;
+    if (G_UNLIKELY(map)) {
+        const char *const iface = nm_device_get_iface(NM_DEVICE(device));
+
+        /* For testing, the device<->hwaddr is remapped and the actual permanent
+         * MAC address of the device ignored. This mapping is configured via
+         * NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES environment variable. */
+
+        if (!iface)
+            return NULL;
+
+        for (; map->name; map++) {
+            if (nm_streq(map->name, iface))
+                return map->value_str;
+        }
+
+        return NULL;
+    }
+
+    return nm_device_ethernet_get_permanent_hw_address(device);
+}
+
 static char **
 _nmc_get_hwaddrs(NMClient *nmc)
 {
@@ -151,7 +261,7 @@ _nmc_get_hwaddrs(NMClient *nmc)
         if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE)
             continue;
 
-        hwaddr = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
+        hwaddr = _device_get_hwaddr(NM_DEVICE_ETHERNET(device));
         if (!hwaddr)
             continue;
 
@@ -193,7 +303,7 @@ _nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
         if (!NM_IS_DEVICE_ETHERNET(device))
             continue;
 
-        hwaddr_dev = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
+        hwaddr_dev = _device_get_hwaddr(NM_DEVICE_ETHERNET(device));
         if (!hwaddr_dev)
             continue;
 
@@ -321,8 +431,9 @@ _nmc_mangle_connection(NMDevice                             *device,
     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);
+    routes_new =
+        g_ptr_array_new_full(nm_g_ptr_array_len(config_data->iproutes) + !!config_data->ipv4s_len,
+                             (GDestroyNotify) nm_ip_route_unref);
 
     if (remote_s_ip) {
         guint len;
@@ -428,8 +539,8 @@ _nmc_mangle_connection(NMDevice                             *device,
         }
     }
 
-    for (i = 0; i < config_data->iproutes_len; ++i)
-        g_ptr_array_add(routes_new, config_data->iproutes_arr[i]);
+    for (i = 0; i < nm_g_ptr_array_len(config_data->iproutes); i++)
+        g_ptr_array_add(routes_new, _nm_ip_route_ref(config_data->iproutes->pdata[i]));
 
     addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip,
                                                            (NMIPAddress **) addrs_new->pdata,
@@ -644,7 +755,7 @@ main(int argc, const char *const *argv)
     gs_free_error GError                                                     *error  = NULL;
     SigTermData                                                               sigterm_data;
 
-    _nm_logging_enabled_init(g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_LOG")));
+    _nm_logging_enabled_init(g_getenv(NMCS_ENV_NM_CLOUD_SETUP_LOG));
 
     _LOGD("nm-cloud-setup %s starting...", NM_DIST_VERSION);
 
@@ -662,7 +773,7 @@ main(int argc, const char *const *argv)
     };
     sigterm_source = nm_g_unix_signal_add_source(SIGTERM, sigterm_handler, &sigterm_data);
 
-    provider = _provider_detect(sigterm_cancellable);
+    provider = _provider_detect(&sigterm_data);
     if (!provider)
         goto done;
 
diff --git a/src/nm-cloud-setup/meson.build b/src/nm-cloud-setup/meson.build
index ea4ad113..b1269ec6 100644
--- a/src/nm-cloud-setup/meson.build
+++ b/src/nm-cloud-setup/meson.build
@@ -16,7 +16,7 @@ if install_systemdunitdir
 
   install_data(
     '90-nm-cloud-setup.sh',
-    install_dir: join_paths(nm_pkglibdir, 'dispatcher.d', 'no-wait.d'),
+    install_dir: join_paths(nm_pkglibdir, 'dispatcher.d', 'pre-up.d'),
   )
 
 endif
diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.c b/src/nm-cloud-setup/nm-cloud-setup-utils.c
index fb4883b7..75739c7c 100644
--- a/src/nm-cloud-setup/nm-cloud-setup-utils.c
+++ b/src/nm-cloud-setup/nm-cloud-setup-utils.c
@@ -166,258 +166,6 @@ nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int timeout_msec
 
 /*****************************************************************************/
 
-typedef struct {
-    GTask                      *task;
-    GSource                    *source_timeout;
-    GSource                    *source_next_poll;
-    GMainContext               *context;
-    GCancellable               *internal_cancellable;
-    NMCSUtilsPollProbeStartFcn  probe_start_fcn;
-    NMCSUtilsPollProbeFinishFcn probe_finish_fcn;
-    gpointer                    probe_user_data;
-    gulong                      cancellable_id;
-    gint64                      last_poll_start_ms;
-    int                         sleep_timeout_ms;
-    int                         ratelimit_timeout_ms;
-    bool                        completed : 1;
-} PollTaskData;
-
-static void
-_poll_task_data_free(gpointer data)
-{
-    PollTaskData *poll_task_data = data;
-
-    nm_assert(G_IS_TASK(poll_task_data->task));
-    nm_assert(!poll_task_data->source_next_poll);
-    nm_assert(!poll_task_data->source_timeout);
-    nm_assert(poll_task_data->cancellable_id == 0);
-
-    g_main_context_unref(poll_task_data->context);
-
-    nm_g_slice_free(poll_task_data);
-}
-
-static void
-_poll_return(PollTaskData *poll_task_data, GError *error_take)
-{
-    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
-    nm_clear_g_source_inst(&poll_task_data->source_timeout);
-    nm_clear_g_cancellable_disconnect(g_task_get_cancellable(poll_task_data->task),
-                                      &poll_task_data->cancellable_id);
-
-    nm_clear_g_cancellable(&poll_task_data->internal_cancellable);
-
-    if (error_take)
-        g_task_return_error(poll_task_data->task, g_steal_pointer(&error_take));
-    else
-        g_task_return_boolean(poll_task_data->task, TRUE);
-
-    g_object_unref(poll_task_data->task);
-}
-
-static gboolean _poll_start_cb(gpointer user_data);
-
-static void
-_poll_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
-{
-    PollTaskData                     *poll_task_data = user_data;
-    _nm_unused gs_unref_object GTask *task =
-        poll_task_data->task; /* balance ref from _poll_start_cb() */
-    gs_free_error GError *error = NULL;
-    gint64                now_ms;
-    gint64                wait_ms;
-    gboolean              is_finished;
-
-    is_finished =
-        poll_task_data->probe_finish_fcn(source, result, poll_task_data->probe_user_data, &error);
-
-    if (nm_utils_error_is_cancelled(error)) {
-        /* we already handle this differently. Nothing to do. */
-        return;
-    }
-
-    if (error || is_finished) {
-        _poll_return(poll_task_data, g_steal_pointer(&error));
-        return;
-    }
-
-    now_ms = nm_utils_get_monotonic_timestamp_msec();
-    if (poll_task_data->ratelimit_timeout_ms > 0)
-        wait_ms =
-            (poll_task_data->last_poll_start_ms + poll_task_data->ratelimit_timeout_ms) - now_ms;
-    else
-        wait_ms = 0;
-    if (poll_task_data->sleep_timeout_ms > 0)
-        wait_ms = MAX(wait_ms, poll_task_data->sleep_timeout_ms);
-
-    poll_task_data->source_next_poll =
-        nm_g_source_attach(nm_g_timeout_source_new(MAX(1, wait_ms),
-                                                   G_PRIORITY_DEFAULT,
-                                                   _poll_start_cb,
-                                                   poll_task_data,
-                                                   NULL),
-                           poll_task_data->context);
-}
-
-static gboolean
-_poll_start_cb(gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-
-    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
-
-    poll_task_data->last_poll_start_ms = nm_utils_get_monotonic_timestamp_msec();
-
-    g_object_ref(poll_task_data->task); /* balanced by _poll_done_cb() */
-
-    poll_task_data->probe_start_fcn(poll_task_data->internal_cancellable,
-                                    poll_task_data->probe_user_data,
-                                    _poll_done_cb,
-                                    poll_task_data);
-
-    return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-_poll_timeout_cb(gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-
-    _poll_return(poll_task_data, nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "timeout expired"));
-    return G_SOURCE_CONTINUE;
-}
-
-static void
-_poll_cancelled_cb(GObject *object, gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-    GError       *error          = NULL;
-
-    nm_clear_g_signal_handler(g_task_get_cancellable(poll_task_data->task),
-                              &poll_task_data->cancellable_id);
-    nm_utils_error_set_cancelled(&error, FALSE, NULL);
-    _poll_return(poll_task_data, error);
-}
-
-/**
- * nmcs_utils_poll:
- * @poll_timeout_ms: if >= 0, then this is the overall timeout for how long we poll.
- *   When this timeout expires, the request completes with failure (and error set).
- * @ratelimit_timeout_ms: if > 0, we ratelimit the starts from one prope_start_fcn
- *   call to the next.
- * @sleep_timeout_ms: if > 0, then we wait after a probe finished this timeout
- *   before the next. Together with @ratelimit_timeout_ms this determines how
- *   frequently we probe.
- * @probe_start_fcn: used to start a (asynchronous) probe. A probe must be completed
- *   by calling the provided callback. While a probe is in progress, we will not
- *   start another. This function is already invoked the first time synchronously,
- *   during nmcs_utils_poll().
- * @probe_finish_fcn: will be called from the callback of @probe_start_fcn. If the
- *   function returns %TRUE (polling done) or an error, polling stops. Otherwise,
- *   another poll will be started.
- * @probe_user_data: user_data for the probe functions.
- * @cancellable: cancellable for polling.
- * @callback: when polling completes.
- * @user_data: for @callback.
- *
- * This uses the current g_main_context_get_thread_default() for scheduling
- * actions.
- */
-void
-nmcs_utils_poll(int                         poll_timeout_ms,
-                int                         ratelimit_timeout_ms,
-                int                         sleep_timeout_ms,
-                NMCSUtilsPollProbeStartFcn  probe_start_fcn,
-                NMCSUtilsPollProbeFinishFcn probe_finish_fcn,
-                gpointer                    probe_user_data,
-                GCancellable               *cancellable,
-                GAsyncReadyCallback         callback,
-                gpointer                    user_data)
-{
-    PollTaskData *poll_task_data;
-
-    poll_task_data  = g_slice_new(PollTaskData);
-    *poll_task_data = (PollTaskData){
-        .task             = nm_g_task_new(NULL, cancellable, nmcs_utils_poll, callback, user_data),
-        .probe_start_fcn  = probe_start_fcn,
-        .probe_finish_fcn = probe_finish_fcn,
-        .probe_user_data  = probe_user_data,
-        .completed        = FALSE,
-        .context          = g_main_context_ref_thread_default(),
-        .sleep_timeout_ms = sleep_timeout_ms,
-        .ratelimit_timeout_ms = ratelimit_timeout_ms,
-        .internal_cancellable = g_cancellable_new(),
-    };
-
-    nmcs_wait_for_objects_register(poll_task_data->task);
-
-    g_task_set_task_data(poll_task_data->task, poll_task_data, _poll_task_data_free);
-
-    if (poll_timeout_ms >= 0) {
-        poll_task_data->source_timeout =
-            nm_g_source_attach(nm_g_timeout_source_new(poll_timeout_ms,
-                                                       G_PRIORITY_DEFAULT,
-                                                       _poll_timeout_cb,
-                                                       poll_task_data,
-                                                       NULL),
-                               poll_task_data->context);
-    }
-
-    poll_task_data->source_next_poll = nm_g_source_attach(
-        nm_g_idle_source_new(G_PRIORITY_DEFAULT_IDLE, _poll_start_cb, poll_task_data, NULL),
-        poll_task_data->context);
-
-    if (cancellable) {
-        gulong signal_id;
-
-        signal_id = g_cancellable_connect(cancellable,
-                                          G_CALLBACK(_poll_cancelled_cb),
-                                          poll_task_data,
-                                          NULL);
-        if (signal_id == 0) {
-            /* the request is already cancelled. Return. */
-            return;
-        }
-        poll_task_data->cancellable_id = signal_id;
-    }
-}
-
-/**
- * nmcs_utils_poll_finish:
- * @result: the GAsyncResult from the GAsyncReadyCallback callback.
- * @probe_user_data: the user data provided to nmcs_utils_poll().
- * @error: the failure code.
- *
- * Returns: %TRUE if the polling completed with success. In that case,
- *   the error won't be set.
- *   If the request was cancelled, this is indicated by @error and
- *   %FALSE will be returned.
- *   If the probe returned a failure, this returns %FALSE and the error
- *   provided by @probe_finish_fcn.
- *   If the request times out, this returns %FALSE with error set.
- *   Error is always set if (and only if) the function returns %FALSE.
- */
-gboolean
-nmcs_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error)
-{
-    GTask        *task;
-    PollTaskData *poll_task_data;
-
-    g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nmcs_utils_poll), FALSE);
-    g_return_val_if_fail(!error || !*error, FALSE);
-
-    task = G_TASK(result);
-
-    if (probe_user_data) {
-        poll_task_data = g_task_get_task_data(task);
-        NM_SET_OUT(probe_user_data, poll_task_data->probe_user_data);
-    }
-
-    return g_task_propagate_boolean(task, error);
-}
-
-/*****************************************************************************/
-
 char *
 nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len)
 {
diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.h b/src/nm-cloud-setup/nm-cloud-setup-utils.h
index fed0f4b6..4ca4634c 100644
--- a/src/nm-cloud-setup/nm-cloud-setup-utils.h
+++ b/src/nm-cloud-setup/nm-cloud-setup-utils.h
@@ -7,9 +7,20 @@
 
 /*****************************************************************************/
 
-/* mark names for  variables that can be used as configuration. Search
- * for NMCS_ENV_VARIABLE() to find all honored environment variables. */
-#define NMCS_ENV_VARIABLE(var) "" var ""
+/* Environment variables for configuring nm-cloud-setup */
+#define NMCS_ENV_NM_CLOUD_SETUP_ALIYUN "NM_CLOUD_SETUP_ALIYUN"
+#define NMCS_ENV_NM_CLOUD_SETUP_AZURE  "NM_CLOUD_SETUP_AZURE"
+#define NMCS_ENV_NM_CLOUD_SETUP_EC2    "NM_CLOUD_SETUP_EC2"
+#define NMCS_ENV_NM_CLOUD_SETUP_GCP    "NM_CLOUD_SETUP_GCP"
+#define NMCS_ENV_NM_CLOUD_SETUP_LOG    "NM_CLOUD_SETUP_LOG"
+
+/* Undocumented/internal environment variables for configuring nm-cloud-setup.
+ * These are mainly for testing/debugging. */
+#define NMCS_ENV_NM_CLOUD_SETUP_ALIYUN_HOST    "NM_CLOUD_SETUP_ALIYUN_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST     "NM_CLOUD_SETUP_AZURE_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST       "NM_CLOUD_SETUP_EC2_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST       "NM_CLOUD_SETUP_GCP_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES"
 
 /*****************************************************************************/
 
@@ -40,30 +51,6 @@ gboolean nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int tim
 
 /*****************************************************************************/
 
-typedef void (*NMCSUtilsPollProbeStartFcn)(GCancellable       *cancellable,
-                                           gpointer            probe_user_data,
-                                           GAsyncReadyCallback callback,
-                                           gpointer            user_data);
-
-typedef gboolean (*NMCSUtilsPollProbeFinishFcn)(GObject      *source,
-                                                GAsyncResult *result,
-                                                gpointer      probe_user_data,
-                                                GError      **error);
-
-void nmcs_utils_poll(int                         poll_timeout_ms,
-                     int                         ratelimit_timeout_ms,
-                     int                         sleep_timeout_ms,
-                     NMCSUtilsPollProbeStartFcn  probe_start_fcn,
-                     NMCSUtilsPollProbeFinishFcn probe_finish_fcn,
-                     gpointer                    probe_user_data,
-                     GCancellable               *cancellable,
-                     GAsyncReadyCallback         callback,
-                     gpointer                    user_data);
-
-gboolean nmcs_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error);
-
-/*****************************************************************************/
-
 char *nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len);
 
 static inline char *
@@ -104,6 +91,38 @@ const char *nmcs_utils_parse_get_full_line(GBytes *mem, const char *needle);
 
 /*****************************************************************************/
 
+#define NMCS_DEFINE_HOST_BASE(base_fcn, nmcs_env_host, default_host)               \
+    static const char *base_fcn(void)                                              \
+    {                                                                              \
+        static const char *base_cached = NULL;                                     \
+        const char        *base;                                                   \
+                                                                                   \
+again:                                                                             \
+        base = g_atomic_pointer_get(&base_cached);                                 \
+        if (G_UNLIKELY(!base)) {                                                   \
+            /* The base URI can be set via environment variable. \
+             * This is mainly for testing, it's not usually supposed to be configured. \
+             * Consider this private API! */                 \
+            base = g_getenv("" nmcs_env_host "");                                  \
+            base = nmcs_utils_uri_complete_interned(base) ?: ("" default_host ""); \
+                                                                                   \
+            if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))  \
+                goto again;                                                        \
+                                                                                   \
+            if (!nm_streq(base, ("" default_host ""))) {                           \
+                _LOGD("test: mock %s=\"%s\" (default \"%s\")",                     \
+                      "" nmcs_env_host "",                                         \
+                      base,                                                        \
+                      "" default_host "");                                         \
+            }                                                                      \
+        }                                                                          \
+                                                                                   \
+        return base;                                                               \
+    }                                                                              \
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
+
+/*****************************************************************************/
+
 char *nmcs_utils_uri_build_concat_v(const char *base, const char **components, gsize n_components);
 
 #define nmcs_utils_uri_build_concat(base, ...) \
diff --git a/src/nm-cloud-setup/nm-cloud-setup.service.in b/src/nm-cloud-setup/nm-cloud-setup.service.in
index f4b0e263..a2b4581f 100644
--- a/src/nm-cloud-setup/nm-cloud-setup.service.in
+++ b/src/nm-cloud-setup/nm-cloud-setup.service.in
@@ -1,6 +1,7 @@
 [Unit]
 Description=Automatically configure NetworkManager in cloud
 Documentation=man:nm-cloud-setup(8)
+Before=network-online.target
 After=NetworkManager.service
 
 [Service]
diff --git a/src/nm-cloud-setup/nm-http-client.c b/src/nm-cloud-setup/nm-http-client.c
index db123f10..20ef6473 100644
--- a/src/nm-cloud-setup/nm-http-client.c
+++ b/src/nm-cloud-setup/nm-http-client.c
@@ -359,8 +359,8 @@ nm_http_client_req(NMHttpClient       *self,
  * nm_http_client_req_finish:
  * @self: the #NMHttpClient instance
  * @result: the #GAsyncResult which to complete.
- * @out_response_code: (allow-none) (out): the HTTP response code or -1 on other error.
- * @out_response_data: (allow-none) (transfer full): the HTTP response data, if any.
+ * @out_response_code: (out) (optional): the HTTP response code or -1 on other error.
+ * @out_response_data: (out) (optional) (nullable) (transfer full): the HTTP response data, if any.
  *   The GBytes buffer is guaranteed to have a trailing NUL character *after* the
  *   returned buffer size. That means, you can always trust that the buffer is NUL terminated
  *   and that there is one additional hidden byte after the data.
@@ -430,6 +430,12 @@ _poll_req_data_free(gpointer data)
 }
 
 static void
+_poll_reg_probe_register_object_fcn(GObject *object, gpointer user_data)
+{
+    nmcs_wait_for_objects_register(object);
+}
+
+static void
 _poll_req_probe_start_fcn(GCancellable       *cancellable,
                           gpointer            probe_user_data,
                           GAsyncReadyCallback callback,
@@ -508,13 +514,14 @@ _poll_req_probe_finish_fcn(GObject      *source,
 static void
 _poll_req_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    PollReqData          *poll_req_data = user_data;
+    PollReqData          *poll_req_data = NULL;
     gs_free_error GError *error         = NULL;
     gboolean              success;
 
-    success = nmcs_utils_poll_finish(result, NULL, &error);
+    success = nm_utils_poll_finish(result, (gpointer *) &poll_req_data, &error);
 
     nm_assert((!!success) == (!error));
+    nm_assert(poll_req_data);
 
     if (error)
         g_task_return_error(poll_req_data->task, g_steal_pointer(&error));
@@ -575,15 +582,16 @@ nm_http_client_poll_req(NMHttpClient               *self,
     context =
         nm_g_main_context_push_thread_default_if_necessary(nm_http_client_get_main_context(self));
 
-    nmcs_utils_poll(poll_timeout_ms,
-                    ratelimit_timeout_ms,
-                    0,
-                    _poll_req_probe_start_fcn,
-                    _poll_req_probe_finish_fcn,
-                    poll_req_data,
-                    cancellable,
-                    _poll_req_done_cb,
-                    poll_req_data);
+    nm_utils_poll(poll_timeout_ms,
+                  ratelimit_timeout_ms,
+                  0,
+                  _poll_reg_probe_register_object_fcn,
+                  _poll_req_probe_start_fcn,
+                  _poll_req_probe_finish_fcn,
+                  poll_req_data,
+                  cancellable,
+                  _poll_req_done_cb,
+                  NULL);
 }
 
 gboolean
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.c b/src/nm-cloud-setup/nmcs-provider-aliyun.c
index 7f51f431..20010a2c 100644
--- a/src/nm-cloud-setup/nmcs-provider-aliyun.c
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.c
@@ -18,27 +18,7 @@
 #define NM_ALIYUN_METADATA_URL_BASE /* $NM_ALIYUN_BASE/$NM_ALIYUN_API_VERSION */ \
     "/meta-data/network/interfaces/macs/"
 
-static const char *
-_aliyun_base(void)
-{
-    static const char *base_cached = NULL;
-    const char        *base;
-
-again:
-    base = g_atomic_pointer_get(&base_cached);
-    if (G_UNLIKELY(!base)) {
-        /* The base URI can be set via environment variable.
-         * This is mainly for testing, it's not usually supposed to be configured.
-         * Consider this private API! */
-        base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_ALIYUN_HOST"));
-        base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_ALIYUN_HOST);
-
-        if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))
-            goto again;
-    }
-
-    return base;
-}
+NMCS_DEFINE_HOST_BASE(_aliyun_base, NMCS_ENV_NM_CLOUD_SETUP_ALIYUN_HOST, NM_ALIYUN_HOST);
 
 #define _aliyun_uri_concat(...) nmcs_utils_uri_build_concat(_aliyun_base(), __VA_ARGS__)
 #define _aliyun_uri_interfaces(...) \
@@ -558,7 +538,7 @@ nmcs_provider_aliyun_class_init(NMCSProviderAliyunClass *klass)
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
     provider_class->_name                 = "aliyun";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_ALIYUN");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_ALIYUN;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c
index 69946f5c..771c43d9 100644
--- a/src/nm-cloud-setup/nmcs-provider-azure.c
+++ b/src/nm-cloud-setup/nmcs-provider-azure.c
@@ -17,8 +17,10 @@
 #define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \
     "/metadata/instance/network/interface/"
 
+NMCS_DEFINE_HOST_BASE(_azure_base, NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST, NM_AZURE_BASE);
+
 #define _azure_uri_concat(...) \
-    nmcs_utils_uri_build_concat(NM_AZURE_BASE, __VA_ARGS__, NM_AZURE_API_VERSION)
+    nmcs_utils_uri_build_concat(_azure_base(), __VA_ARGS__, NM_AZURE_API_VERSION)
 #define _azure_uri_interfaces(...) _azure_uri_concat(NM_AZURE_METADATA_URL_BASE, ##__VA_ARGS__)
 
 /*****************************************************************************/
@@ -564,7 +566,7 @@ nmcs_provider_azure_class_init(NMCSProviderAzureClass *klass)
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
     provider_class->_name                 = "azure";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_AZURE");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_AZURE;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-ec2.c b/src/nm-cloud-setup/nmcs-provider-ec2.c
index 65a8f629..9cdc7a0c 100644
--- a/src/nm-cloud-setup/nmcs-provider-ec2.c
+++ b/src/nm-cloud-setup/nmcs-provider-ec2.c
@@ -21,27 +21,7 @@
 #define NM_EC2_TOKEN_TTL_HEADER "X-aws-ec2-metadata-token-ttl-seconds: 180"
 #define NM_EC2_TOKEN_HEADER     "X-aws-ec2-metadata-token: "
 
-static const char *
-_ec2_base(void)
-{
-    static const char *base_cached = NULL;
-    const char        *base;
-
-again:
-    base = g_atomic_pointer_get(&base_cached);
-    if (G_UNLIKELY(!base)) {
-        /* The base URI can be set via environment variable.
-         * This is mainly for testing, it's not usually supposed to be configured.
-         * Consider this private API! */
-        base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2_HOST"));
-        base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_EC2_BASE);
-
-        if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))
-            goto again;
-    }
-
-    return base;
-}
+NMCS_DEFINE_HOST_BASE(_ec2_base, NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST, NM_EC2_BASE);
 
 #define _ec2_uri_concat(...) nmcs_utils_uri_build_concat(_ec2_base(), __VA_ARGS__)
 #define _ec2_uri_interfaces(...) \
@@ -435,7 +415,7 @@ nmcs_provider_ec2_class_init(NMCSProviderEC2Class *klass)
     object_class->dispose = dispose;
 
     provider_class->_name                 = "ec2";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_EC2;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-gcp.c b/src/nm-cloud-setup/nmcs-provider-gcp.c
index ca354865..4d9ef965 100644
--- a/src/nm-cloud-setup/nmcs-provider-gcp.c
+++ b/src/nm-cloud-setup/nmcs-provider-gcp.c
@@ -13,15 +13,19 @@
 #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_HOST             "metadata.google.internal"
+#define NM_GCP_BASE             "http://" NM_GCP_HOST
+#define NM_GCP_API_VERSION      "/v1"
+#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__)
+NMCS_DEFINE_HOST_BASE(_gcp_base, NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST, NM_GCP_BASE);
+
+#define _gcp_uri_concat(...)                                                       \
+    nmcs_utils_uri_build_concat(_gcp_base(),                                       \
+                                "/computeMetadata" NM_GCP_API_VERSION "/instance", \
+                                __VA_ARGS__)
 #define _gcp_uri_interfaces(...) _gcp_uri_concat(NM_GCP_METADATA_URL_NET, ##__VA_ARGS__)
 
 /*****************************************************************************/
@@ -73,7 +77,7 @@ detect(NMCSProvider *provider, GTask *task)
     http_client = nmcs_provider_get_http_client(provider);
 
     nm_http_client_poll_req(http_client,
-                            (uri = _gcp_uri_concat("id")),
+                            (uri = _gcp_uri_concat("/id")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
@@ -112,7 +116,6 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     GCPIfaceData                   *iface_data = user_data;
     gs_free_error GError           *error      = NULL;
     gs_free char                   *ipaddr     = NULL;
-    NMIPRoute                     **routes_arr;
     NMIPRoute                      *route_new;
 
     nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
@@ -137,15 +140,14 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
           ipaddr);
 
     iface_get_config = iface_data->iface_get_config;
-    routes_arr       = iface_get_config->iproutes_arr;
 
     route_new = nm_ip_route_new(AF_INET, ipaddr, 32, NULL, 100, &error);
     if (error)
         goto out_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;
+
+    g_ptr_array_add(iface_get_config->iproutes, route_new);
 
 out_done:
     if (!error) {
@@ -215,7 +217,8 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
         goto out_error;
     }
 
-    iface_data->iface_get_config->iproutes_arr = g_new(NMIPRoute *, iface_data->n_fips_pending);
+    iface_data->iface_get_config->iproutes =
+        g_ptr_array_new_full(iface_data->n_fips_pending, (GDestroyNotify) nm_ip_route_unref);
 
     for (i = 0; i < uri_arr->len; ++i) {
         const char         *str = uri_arr->pdata[i];
@@ -459,7 +462,7 @@ 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->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_GCP;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider.c b/src/nm-cloud-setup/nmcs-provider.c
index fd9a61b8..5b4412b3 100644
--- a/src/nm-cloud-setup/nmcs-provider.c
+++ b/src/nm-cloud-setup/nmcs-provider.c
@@ -155,7 +155,7 @@ nmcs_provider_detect(NMCSProvider       *self,
     env = g_getenv(NMCS_PROVIDER_GET_CLASS(self)->_env_provider_enabled);
     if (!_nm_utils_ascii_str_to_bool(env, FALSE)) {
         g_task_return_error(task,
-                            nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "provider is disabled"));
+                            nm_utils_error_new(NM_UTILS_ERROR_NOT_READY, "provider is disabled"));
         return;
     }
 
@@ -216,7 +216,7 @@ _iface_data_free(gpointer data)
     NMCSProviderGetConfigIfaceData *iface_data = data;
 
     g_free(iface_data->ipv4s_arr);
-    g_free(iface_data->iproutes_arr);
+    nm_g_ptr_array_unref(iface_data->iproutes);
     g_free((char *) iface_data->hwaddr);
 
     nm_g_slice_free(iface_data);
diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h
index 09cdb414..9e5eeebe 100644
--- a/src/nm-cloud-setup/nmcs-provider.h
+++ b/src/nm-cloud-setup/nmcs-provider.h
@@ -34,8 +34,8 @@ typedef struct {
     bool      has_cidr : 1;
     bool      has_gateway : 1;
 
-    NMIPRoute **iproutes_arr;
-    gsize       iproutes_len;
+    /* Array of NMIPRoute (must own/free the entries). */
+    GPtrArray *iproutes;
 
     /* TRUE, if the configuration was requested via hwaddrs argument to
      * nmcs_provider_get_config(). */
@@ -59,7 +59,8 @@ static inline gboolean
 nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceData *config_data)
 {
     return config_data && config_data->iface_idx >= 0
-           && ((config_data->has_ipv4s && config_data->has_cidr) || config_data->iproutes_len);
+           && ((config_data->has_ipv4s && config_data->has_cidr)
+               || nm_g_ptr_array_len(config_data->iproutes) > 0);
 }
 
 /*****************************************************************************/