about summary refs log tree commit diff
path: root/src/nm-cloud-setup
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2021-09-27 13:36:06 +0200
committerSebastien Bacher <seb128@ubuntu.com>2021-09-27 13:36:06 +0200
commit2bfcbfb13b1672c072e4f902260d2122b3e3c122 (patch)
tree3469f17ea9af91f7ff169b890633bda68b0cf76e /src/nm-cloud-setup
parentcfb80376641fa49137b9996130352697e7f8b436 (diff)
New upstream version 1.32.12
Diffstat (limited to 'src/nm-cloud-setup')
-rw-r--r--src/nm-cloud-setup/main.c225
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.c32
-rw-r--r--src/nm-cloud-setup/nmcs-provider-azure.c21
-rw-r--r--src/nm-cloud-setup/nmcs-provider-ec2.c27
-rw-r--r--src/nm-cloud-setup/nmcs-provider-gcp.c20
-rw-r--r--src/nm-cloud-setup/nmcs-provider.c112
-rw-r--r--src/nm-cloud-setup/nmcs-provider.h44
7 files changed, 321 insertions, 160 deletions
diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c
index 8dc67851..916f41da 100644
--- a/src/nm-cloud-setup/main.c
+++ b/src/nm-cloud-setup/main.c
@@ -4,6 +4,8 @@
 
 #include "libnm-client-aux-extern/nm-libnm-aux.h"
 
+#include <linux/rtnetlink.h>
+
 #include "nm-cloud-setup-utils.h"
 #include "nmcs-provider-ec2.h"
 #include "nmcs-provider-gcp.h"
@@ -200,30 +202,30 @@ _nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
 /*****************************************************************************/
 
 typedef struct {
-    GMainLoop * main_loop;
-    GHashTable *config_dict;
+    GMainLoop *                  main_loop;
+    NMCSProviderGetConfigResult *result;
 } GetConfigData;
 
 static void
-_get_config_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+_get_config_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    GetConfigData *    data                    = user_data;
-    gs_unref_hashtable GHashTable *config_dict = NULL;
-    gs_free_error GError *error                = NULL;
+    GetConfigData *                              data                                = user_data;
+    nm_auto_free_nmcs_provider_get_config_result NMCSProviderGetConfigResult *result = NULL;
+    gs_free_error GError *error                                                      = NULL;
 
-    config_dict = nmcs_provider_get_config_finish(NMCS_PROVIDER(source), result, &error);
+    result = nmcs_provider_get_config_finish(NMCS_PROVIDER(source), res, &error);
 
-    if (!config_dict) {
+    if (!result) {
         if (!nm_utils_error_is_cancelled(error))
             _LOGI("failure to get meta data: %s", error->message);
     } else
         _LOGD("meta data received");
 
-    data->config_dict = g_steal_pointer(&config_dict);
+    data->result = g_steal_pointer(&result);
     g_main_loop_quit(data->main_loop);
 }
 
-static GHashTable *
+static NMCSProviderGetConfigResult *
 _get_config(GCancellable *sigterm_cancellable, NMCSProvider *provider, NMClient *nmc)
 {
     nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new(NULL, FALSE);
@@ -243,7 +245,7 @@ _get_config(GCancellable *sigterm_cancellable, NMCSProvider *provider, NMClient
 
     g_main_loop_run(main_loop);
 
-    return data.config_dict;
+    return data.result;
 }
 
 /*****************************************************************************/
@@ -269,7 +271,9 @@ _nmc_skip_connection(NMConnection *connection)
 static gboolean
 _nmc_mangle_connection(NMDevice *                            device,
                        NMConnection *                        connection,
+                       const NMCSProviderGetConfigResult *   result,
                        const NMCSProviderGetConfigIfaceData *config_data,
+                       gboolean *                            out_skipped_single_addr,
                        gboolean *                            out_changed)
 {
     NMSettingIPConfig * s_ip;
@@ -277,10 +281,6 @@ _nmc_mangle_connection(NMDevice *                            device,
     NMConnection *      remote_connection;
     NMSettingIPConfig * remote_s_ip = NULL;
     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;
@@ -288,6 +288,9 @@ _nmc_mangle_connection(NMDevice *                            device,
     gs_unref_ptrarray GPtrArray *rules_new  = NULL;
     gs_unref_ptrarray GPtrArray *routes_new = NULL;
 
+    NM_SET_OUT(out_skipped_single_addr, FALSE);
+    NM_SET_OUT(out_changed, FALSE);
+
     if (!nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_WIRED_SETTING_NAME))
         return FALSE;
 
@@ -329,48 +332,84 @@ _nmc_mangle_connection(NMDevice *                            device,
         }
     }
 
-    if (config_data->has_ipv4s && config_data->has_cidr) {
+    if (result->num_valid_ifaces <= 1 && result->num_ipv4s <= 1) {
+        /* this setup only has one interface and one IPv4 address (or less).
+         * We don't need to configure policy routing in this case. */
+        NM_SET_OUT(out_skipped_single_addr, TRUE);
+    } else if (config_data->has_ipv4s && config_data->has_cidr) {
+        gs_unref_hashtable GHashTable *unique_subnets =
+            g_hash_table_new(nm_direct_hash, g_direct_equal);
+        NMIPAddress *    addr_entry;
+        NMIPRoute *      route_entry;
+        NMIPRoutingRule *rule_entry;
+        in_addr_t        gateway;
+        char             sbuf[NM_UTILS_INET_ADDRSTRLEN];
+
         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);
+            addr_entry = nm_ip_address_new_binary(AF_INET,
+                                                  &config_data->ipv4s_arr[i],
+                                                  config_data->cidr_prefix,
+                                                  NULL);
+            nm_assert(addr_entry);
+            g_ptr_array_add(addrs_new, addr_entry);
         }
+
         if (config_data->has_gateway && config_data->gateway) {
             gateway = config_data->gateway;
         } else {
             gateway = nm_utils_ip4_address_clear_host_address(config_data->cidr_addr,
                                                               config_data->cidr_prefix);
-            ((guint8 *) &gateway)[3] += 1;
+            if (config_data->cidr_prefix < 32)
+                ((guint8 *) &gateway)[3] += 1;
+        }
+
+        for (i = 0; i < config_data->ipv4s_len; i++) {
+            in_addr_t a = config_data->ipv4s_arr[i];
+
+            a = nm_utils_ip4_address_clear_host_address(a, config_data->cidr_prefix);
+
+            G_STATIC_ASSERT_EXPR(sizeof(gsize) >= sizeof(in_addr_t));
+            if (g_hash_table_add(unique_subnets, GSIZE_TO_POINTER(a))) {
+                route_entry =
+                    nm_ip_route_new_binary(AF_INET, &a, config_data->cidr_prefix, NULL, 10, NULL);
+                nm_ip_route_set_attribute(route_entry,
+                                          NM_IP_ROUTE_ATTRIBUTE_TABLE,
+                                          g_variant_new_uint32(30200 + config_data->iface_idx));
+                g_ptr_array_add(routes_new, route_entry);
+            }
+
+            rule_entry = nm_ip_routing_rule_new(AF_INET);
+            nm_ip_routing_rule_set_priority(rule_entry, 30200 + config_data->iface_idx);
+            nm_ip_routing_rule_set_from(rule_entry,
+                                        _nm_utils_inet4_ntop(config_data->ipv4s_arr[i], sbuf),
+                                        32);
+            nm_ip_routing_rule_set_table(rule_entry, 30200 + config_data->iface_idx);
+            nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
+            g_ptr_array_add(rules_new, rule_entry);
         }
-        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);
+        rule_entry = nm_ip_routing_rule_new(AF_INET);
+        nm_ip_routing_rule_set_priority(rule_entry, 30350);
+        nm_ip_routing_rule_set_table(rule_entry, RT_TABLE_MAIN);
+        nm_ip_routing_rule_set_suppress_prefixlength(rule_entry, 0);
+        nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
+        g_ptr_array_add(rules_new, rule_entry);
+
+        route_entry = nm_ip_route_new_binary(AF_INET, &nm_ip_addr_zero, 0, &gateway, 10, NULL);
         nm_ip_route_set_attribute(route_entry,
                                   NM_IP_ROUTE_ATTRIBUTE_TABLE,
-                                  g_variant_new_uint32(rt_table));
+                                  g_variant_new_uint32(30400 + config_data->iface_idx));
         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,
+            rule_entry = nm_ip_routing_rule_new(AF_INET);
+            nm_ip_routing_rule_set_priority(rule_entry, 30400 + config_data->iface_idx);
+            nm_ip_routing_rule_set_from(rule_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);
+            nm_ip_routing_rule_set_table(rule_entry, 30400 + config_data->iface_idx);
+            nm_assert(nm_ip_routing_rule_validate(rule_entry, NULL));
+            g_ptr_array_add(rules_new, rule_entry);
         }
     }
 
@@ -395,34 +434,20 @@ _nmc_mangle_connection(NMDevice *                            device,
 
 /*****************************************************************************/
 
-static guint
-_config_data_get_num_valid(GHashTable *config_dict)
-{
-    const NMCSProviderGetConfigIfaceData *config_data;
-    GHashTableIter                        h_iter;
-    guint                                 n = 0;
-
-    g_hash_table_iter_init(&h_iter, config_dict);
-    while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &config_data)) {
-        if (nmcs_provider_get_config_iface_data_is_valid(config_data))
-            n++;
-    }
-
-    return n;
-}
-
 static gboolean
-_config_one(GCancellable *                        sigterm_cancellable,
-            NMClient *                            nmc,
-            gboolean                              is_single_nic,
-            const char *                          hwaddr,
-            const NMCSProviderGetConfigIfaceData *config_data)
+_config_one(GCancellable *                     sigterm_cancellable,
+            NMClient *                         nmc,
+            const NMCSProviderGetConfigResult *result,
+            guint                              idx)
 {
-    gs_unref_object NMDevice *device                 = NULL;
-    gs_unref_object NMConnection *applied_connection = NULL;
+    const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
+    const char *                          hwaddr      = config_data->hwaddr;
+    gs_unref_object NMDevice *device                  = NULL;
+    gs_unref_object NMConnection *applied_connection  = NULL;
     guint64                       applied_version_id;
     gs_free_error GError *error = NULL;
     gboolean              changed;
+    gboolean              skipped_single_addr;
     gboolean              version_id_changed;
     guint                 try_count;
     gboolean              any_changes = FALSE;
@@ -443,6 +468,14 @@ _config_one(GCancellable *                        sigterm_cancellable,
         return FALSE;
     }
 
+    if (config_data->iface_idx >= 100) {
+        /* since we use the iface_idx to select a table number, the range is limited from
+         * 0 to 99. Note that the providers are required to provide increasing numbers,
+         * so this means we bail out after the first 100 devices.  */
+        _LOGD("config device %s: skip because number of supported interfaces reached", hwaddr);
+        return FALSE;
+    }
+
     _LOGD("config device %s: configuring \"%s\" (%s)...",
           hwaddr,
           nm_device_get_iface(device) ?: "/unknown/",
@@ -471,16 +504,30 @@ try_again:
         return any_changes;
     }
 
-    if (!_nmc_mangle_connection(device, applied_connection, config_data, &changed)) {
+    if (!_nmc_mangle_connection(device,
+                                applied_connection,
+                                result,
+                                config_data,
+                                &skipped_single_addr,
+                                &changed)) {
         _LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
         return any_changes;
     }
 
     if (!changed) {
-        _LOGD("config device %s: device needs no update to applied connection \"%s\" (%s). Skip",
-              hwaddr,
-              nm_connection_get_id(applied_connection),
-              nm_connection_get_uuid(applied_connection));
+        if (skipped_single_addr) {
+            _LOGD("config device %s: device needs no update to applied connection \"%s\" (%s) "
+                  "because there are not multiple IP addresses. Skip",
+                  hwaddr,
+                  nm_connection_get_id(applied_connection),
+                  nm_connection_get_uuid(applied_connection));
+        } else {
+            _LOGD(
+                "config device %s: device needs no update to applied connection \"%s\" (%s). Skip",
+                hwaddr,
+                nm_connection_get_id(applied_connection),
+                nm_connection_get_uuid(applied_connection));
+        }
         return any_changes;
     }
 
@@ -489,7 +536,7 @@ try_again:
           nm_connection_get_id(applied_connection),
           nm_connection_get_uuid(applied_connection));
 
-    /* we are about to call Reapply(). If if that fails, it counts as if we changed something. */
+    /* we are about to call Reapply(). Even if that fails, it counts as if we changed something. */
     any_changes = TRUE;
 
     if (!nmcs_device_reapply(device,
@@ -525,19 +572,15 @@ try_again:
 }
 
 static gboolean
-_config_all(GCancellable *sigterm_cancellable, NMClient *nmc, GHashTable *config_dict)
+_config_all(GCancellable *                     sigterm_cancellable,
+            NMClient *                         nmc,
+            const NMCSProviderGetConfigResult *result)
 {
-    GHashTableIter                        h_iter;
-    const NMCSProviderGetConfigIfaceData *c_config_data;
-    const char *                          c_hwaddr;
-    gboolean                              is_single_nic;
-    gboolean                              any_changes = FALSE;
-
-    is_single_nic = (_config_data_get_num_valid(config_dict) <= 1);
+    gboolean any_changes = FALSE;
+    guint    i;
 
-    g_hash_table_iter_init(&h_iter, config_dict);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &c_hwaddr, (gpointer *) &c_config_data)) {
-        if (_config_one(sigterm_cancellable, nmc, is_single_nic, c_hwaddr, c_config_data))
+    for (i = 0; i < result->n_iface_datas; i++) {
+        if (_config_one(sigterm_cancellable, nmc, result, i))
             any_changes = TRUE;
     }
 
@@ -564,12 +607,12 @@ sigterm_handler(gpointer user_data)
 int
 main(int argc, const char *const *argv)
 {
-    gs_unref_object GCancellable *    sigterm_cancellable     = NULL;
-    nm_auto_destroy_and_unref_gsource GSource *sigterm_source = NULL;
-    gs_unref_object NMCSProvider *provider                    = NULL;
-    gs_unref_object NMClient *nmc                             = NULL;
-    gs_unref_hashtable GHashTable *config_dict                = NULL;
-    gs_free_error GError *error                               = NULL;
+    gs_unref_object GCancellable *    sigterm_cancellable                            = NULL;
+    nm_auto_destroy_and_unref_gsource GSource *sigterm_source                        = NULL;
+    gs_unref_object NMCSProvider *provider                                           = NULL;
+    gs_unref_object NMClient *                   nmc                                 = NULL;
+    nm_auto_free_nmcs_provider_get_config_result NMCSProviderGetConfigResult *result = NULL;
+    gs_free_error GError *error                                                      = NULL;
 
     _nm_logging_enabled_init(g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_LOG")));
 
@@ -614,17 +657,17 @@ main(int argc, const char *const *argv)
         goto done;
     }
 
-    config_dict = _get_config(sigterm_cancellable, provider, nmc);
-    if (!config_dict)
+    result = _get_config(sigterm_cancellable, provider, nmc);
+    if (!result)
         goto done;
 
-    if (_config_all(sigterm_cancellable, nmc, config_dict))
+    if (_config_all(sigterm_cancellable, nmc, result))
         _LOGI("some changes were applied for provider %s", nmcs_provider_get_name(provider));
     else
         _LOGD("no changes were applied for provider %s", nmcs_provider_get_name(provider));
 
 done:
-    nm_clear_pointer(&config_dict, g_hash_table_unref);
+    nm_clear_pointer(&result, nmcs_provider_get_config_result_free);
     g_clear_object(&nmc);
     g_clear_object(&provider);
 
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.c b/src/nm-cloud-setup/nmcs-provider-aliyun.c
index 01a4af0f..126980fd 100644
--- a/src/nm-cloud-setup/nmcs-provider-aliyun.c
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.c
@@ -134,7 +134,6 @@ _get_config_fetch_done_cb(NMHttpClient *         http_client,
                           GetConfigFetchDoneType fetch_type)
 {
     NMCSProviderGetConfigTaskData *get_config_data;
-    const char *                   hwaddr = NULL;
     gs_unref_bytes GBytes *response       = NULL;
     gs_free_error GError *          error = NULL;
     NMCSProviderGetConfigIfaceData *config_iface_data;
@@ -146,7 +145,7 @@ _get_config_fetch_done_cb(NMHttpClient *         http_client,
     gsize                           i;
     gsize                           len;
 
-    nm_utils_user_data_unpack(user_data, &get_config_data, &hwaddr);
+    nm_utils_user_data_unpack(user_data, &get_config_data, &config_iface_data);
 
     nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
 
@@ -156,8 +155,6 @@ _get_config_fetch_done_cb(NMHttpClient *         http_client,
     if (error)
         goto out;
 
-    config_iface_data = g_hash_table_lookup(get_config_data->result_dict, hwaddr);
-
     switch (fetch_type) {
     case GET_CONFIG_FETCH_DONE_TYPE_PRIVATE_IPV4S:
 
@@ -300,22 +297,21 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
         gs_free char *                  uri2 = NULL;
         gs_free char *                  uri3 = NULL;
         gs_free char *                  uri4 = NULL;
-        const char *                    hwaddr;
 
-        if (!g_hash_table_lookup_extended(get_config_data->result_dict,
-                                          v_hwaddr,
-                                          (gpointer *) &hwaddr,
-                                          (gpointer *) &config_iface_data)) {
+        config_iface_data = g_hash_table_lookup(get_config_data->result_dict, v_hwaddr);
+
+        if (!config_iface_data) {
             if (!get_config_data->any) {
                 _LOGD("get-config: skip fetching meta data for %s (%s)",
                       v_hwaddr,
                       v_mac_data->path);
                 continue;
             }
-            config_iface_data = nmcs_provider_get_config_iface_data_new(FALSE);
-            g_hash_table_insert(get_config_data->result_dict,
-                                (char *) (hwaddr = g_strdup(v_hwaddr)),
-                                config_iface_data);
+
+            config_iface_data =
+                nmcs_provider_get_config_iface_data_create(get_config_data->result_dict,
+                                                           FALSE,
+                                                           v_hwaddr);
         }
 
         nm_assert(config_iface_data->iface_idx == -1);
@@ -324,7 +320,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
 
         _LOGD("get-config: start fetching meta data for #%" G_GSSIZE_FORMAT ", %s (%s)",
               config_iface_data->iface_idx,
-              hwaddr,
+              config_iface_data->hwaddr,
               v_mac_data->path);
 
         get_config_data->n_pending++;
@@ -342,7 +338,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_vpc_cidr_block,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
 
         get_config_data->n_pending++;
         nm_http_client_poll_get(
@@ -359,7 +355,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_private_ipv4s,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
 
         get_config_data->n_pending++;
         nm_http_client_poll_get(
@@ -376,7 +372,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_netmask,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
 
         get_config_data->n_pending++;
         nm_http_client_poll_get(
@@ -393,7 +389,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_gateway,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
     }
 
     _nmcs_provider_get_config_task_maybe_return(get_config_data, NULL);
diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c
index 69785d64..b3f0c68b 100644
--- a/src/nm-cloud-setup/nmcs-provider-azure.c
+++ b/src/nm-cloud-setup/nmcs-provider-azure.c
@@ -97,7 +97,6 @@ typedef struct {
     gssize                          intern_iface_idx;
     gssize                          extern_iface_idx;
     guint                           n_iface_data_pending;
-    const char *                    hwaddr;
 } AzureIfaceData;
 
 static void
@@ -378,25 +377,24 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
         goto out_done;
     }
 
-    if (!g_hash_table_lookup_extended(get_config_data->result_dict,
-                                      v_hwaddr,
-                                      (gpointer *) &iface_data->hwaddr,
-                                      (gpointer *) &iface_data->iface_get_config)) {
+    iface_data->iface_get_config = g_hash_table_lookup(get_config_data->result_dict, v_hwaddr);
+
+    if (!iface_data->iface_get_config) {
         if (!get_config_data->any) {
             _LOGD("get-config: skip fetching meta data for %s (%" G_GSSIZE_FORMAT ")",
                   v_hwaddr,
                   iface_data->intern_iface_idx);
             goto out_done;
         }
-        iface_data->iface_get_config = nmcs_provider_get_config_iface_data_new(FALSE);
-        g_hash_table_insert(get_config_data->result_dict,
-                            (char *) (iface_data->hwaddr = g_steal_pointer(&v_hwaddr)),
-                            iface_data->iface_get_config);
+        iface_data->iface_get_config =
+            nmcs_provider_get_config_iface_data_create(get_config_data->result_dict,
+                                                       FALSE,
+                                                       v_hwaddr);
     } else {
         if (iface_data->iface_get_config->iface_idx >= 0) {
             _LOGI("interface[%" G_GSSIZE_FORMAT "]: duplicate MAC address %s returned",
                   iface_data->intern_iface_idx,
-                  iface_data->hwaddr);
+                  iface_data->iface_get_config->hwaddr);
             error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN,
                                        "duplicate MAC address for index %" G_GSSIZE_FORMAT,
                                        iface_data->intern_iface_idx);
@@ -408,7 +406,7 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     _LOGD("interface[%" G_GSSIZE_FORMAT "]: found a matching device with hwaddr %s",
           iface_data->intern_iface_idx,
-          iface_data->hwaddr);
+          iface_data->iface_get_config->hwaddr);
 
     nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/ipv4/ipAddress/", iface_data->intern_iface_idx);
 
@@ -488,7 +486,6 @@ _get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
             .intern_iface_idx     = intern_iface_idx,
             .extern_iface_idx     = extern_iface_idx_cnt++,
             .n_iface_data_pending = 0,
-            .hwaddr               = NULL,
         };
         g_ptr_array_add(ifaces_arr, iface_data);
     }
diff --git a/src/nm-cloud-setup/nmcs-provider-ec2.c b/src/nm-cloud-setup/nmcs-provider-ec2.c
index 6f83238d..9fe62518 100644
--- a/src/nm-cloud-setup/nmcs-provider-ec2.c
+++ b/src/nm-cloud-setup/nmcs-provider-ec2.c
@@ -122,14 +122,13 @@ _get_config_fetch_done_cb(NMHttpClient *http_client,
                           gboolean      is_local_ipv4)
 {
     NMCSProviderGetConfigTaskData *get_config_data;
-    const char *                   hwaddr = NULL;
     gs_unref_bytes GBytes *response       = NULL;
     gs_free_error GError *          error = NULL;
     NMCSProviderGetConfigIfaceData *config_iface_data;
     in_addr_t                       tmp_addr;
     int                             tmp_prefix;
 
-    nm_utils_user_data_unpack(user_data, &get_config_data, &hwaddr);
+    nm_utils_user_data_unpack(user_data, &get_config_data, &config_iface_data);
 
     nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
 
@@ -139,8 +138,6 @@ _get_config_fetch_done_cb(NMHttpClient *http_client,
     if (error)
         goto out;
 
-    config_iface_data = g_hash_table_lookup(get_config_data->result_dict, hwaddr);
-
     if (is_local_ipv4) {
         gs_free const char **s_addrs = NULL;
         gsize                i, len;
@@ -236,22 +233,20 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
         NMCSProviderGetConfigIfaceData *config_iface_data;
         gs_free char *                  uri1 = NULL;
         gs_free char *                  uri2 = NULL;
-        const char *                    hwaddr;
 
-        if (!g_hash_table_lookup_extended(get_config_data->result_dict,
-                                          v_hwaddr,
-                                          (gpointer *) &hwaddr,
-                                          (gpointer *) &config_iface_data)) {
+        config_iface_data = g_hash_table_lookup(get_config_data->result_dict, v_hwaddr);
+
+        if (!config_iface_data) {
             if (!get_config_data->any) {
                 _LOGD("get-config: skip fetching meta data for %s (%s)",
                       v_hwaddr,
                       v_mac_data->path);
                 continue;
             }
-            config_iface_data = nmcs_provider_get_config_iface_data_new(FALSE);
-            g_hash_table_insert(get_config_data->result_dict,
-                                (char *) (hwaddr = g_strdup(v_hwaddr)),
-                                config_iface_data);
+            config_iface_data =
+                nmcs_provider_get_config_iface_data_create(get_config_data->result_dict,
+                                                           FALSE,
+                                                           v_hwaddr);
         }
 
         nm_assert(config_iface_data->iface_idx == -1);
@@ -260,7 +255,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
 
         _LOGD("get-config: start fetching meta data for #%" G_GSSIZE_FORMAT ", %s (%s)",
               config_iface_data->iface_idx,
-              hwaddr,
+              config_iface_data->hwaddr,
               v_mac_data->path);
 
         get_config_data->n_pending++;
@@ -278,7 +273,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_subnet_ipv4_cidr_block,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
 
         get_config_data->n_pending++;
         nm_http_client_poll_get(
@@ -295,7 +290,7 @@ _get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer us
             NULL,
             NULL,
             _get_config_fetch_done_cb_local_ipv4s,
-            nm_utils_user_data_pack(get_config_data, hwaddr));
+            nm_utils_user_data_pack(get_config_data, config_iface_data));
     }
 
     _nmcs_provider_get_config_task_maybe_return(get_config_data, NULL);
diff --git a/src/nm-cloud-setup/nmcs-provider-gcp.c b/src/nm-cloud-setup/nmcs-provider-gcp.c
index eacfd5e2..60425ad9 100644
--- a/src/nm-cloud-setup/nmcs-provider-gcp.c
+++ b/src/nm-cloud-setup/nmcs-provider-gcp.c
@@ -247,7 +247,6 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     GCPIfaceData *         iface_data       = user_data;
     gs_free_error GError *         error    = NULL;
     gs_free char *                 v_hwaddr = NULL;
-    const char *                   hwaddr   = NULL;
     gs_free const char *           uri      = NULL;
     char                           sbuf[100];
     NMCSProviderGetConfigTaskData *get_config_data;
@@ -273,26 +272,25 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
         goto out_done;
     }
 
-    if (!g_hash_table_lookup_extended(get_config_data->result_dict,
-                                      v_hwaddr,
-                                      (gpointer *) &hwaddr,
-                                      (gpointer *) &iface_data->iface_get_config)) {
+    iface_data->iface_get_config = g_hash_table_lookup(get_config_data->result_dict, v_hwaddr);
+
+    if (!iface_data->iface_get_config) {
         if (!get_config_data->any) {
             _LOGD("get-config: skip fetching meta data for %s (%" G_GSSIZE_FORMAT ")",
                   v_hwaddr,
                   iface_data->intern_iface_idx);
             goto out_done;
         }
-        iface_data->iface_get_config = nmcs_provider_get_config_iface_data_new(FALSE);
-        g_hash_table_insert(get_config_data->result_dict,
-                            (char *) (hwaddr = g_steal_pointer(&v_hwaddr)),
-                            iface_data->iface_get_config);
+        iface_data->iface_get_config =
+            nmcs_provider_get_config_iface_data_create(get_config_data->result_dict,
+                                                       FALSE,
+                                                       v_hwaddr);
         is_requested = FALSE;
     } else {
         if (iface_data->iface_get_config->iface_idx >= 0) {
             _LOGI("GCP interface[%" G_GSSIZE_FORMAT "]: duplicate MAC address %s returned",
                   iface_data->intern_iface_idx,
-                  hwaddr);
+                  iface_data->iface_get_config->hwaddr);
             error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN,
                                        "duplicate MAC address for index %" G_GSSIZE_FORMAT,
                                        iface_data->intern_iface_idx);
@@ -306,7 +304,7 @@ _get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     _LOGI("GCP interface[%" G_GSSIZE_FORMAT "]: found a %sdevice with hwaddr %s",
           iface_data->intern_iface_idx,
           is_requested ? "requested " : "",
-          hwaddr);
+          iface_data->iface_get_config->hwaddr);
 
     nm_sprintf_buf(sbuf, "%" G_GSSIZE_FORMAT "/forwarded-ips/", iface_data->intern_iface_idx);
 
diff --git a/src/nm-cloud-setup/nmcs-provider.c b/src/nm-cloud-setup/nmcs-provider.c
index 678152aa..138e78d4 100644
--- a/src/nm-cloud-setup/nmcs-provider.c
+++ b/src/nm-cloud-setup/nmcs-provider.c
@@ -49,6 +49,90 @@ nmcs_provider_get_main_context(NMCSProvider *self)
 
     return nm_http_client_get_main_context(NMCS_PROVIDER_GET_PRIVATE(self)->http_client);
 }
+/*****************************************************************************/
+
+static int
+_result_new_sort_iface_data(gconstpointer pa, gconstpointer pb)
+{
+    const NMCSProviderGetConfigIfaceData *a = *((const NMCSProviderGetConfigIfaceData *const *) pa);
+    const NMCSProviderGetConfigIfaceData *b = *((const NMCSProviderGetConfigIfaceData *const *) pb);
+
+    /* negative iface_idx are sorted to the end. */
+    NM_CMP_DIRECT((a->iface_idx < 0), (b->iface_idx < 0));
+
+    NM_CMP_FIELD(a, b, iface_idx);
+    return 0;
+}
+
+static NMCSProviderGetConfigResult *
+nmcs_provider_get_config_result_new(GHashTable *iface_datas)
+{
+    const NMCSProviderGetConfigIfaceData *iface_data;
+    NMCSProviderGetConfigResult *         result;
+    GHashTableIter                        h_iter;
+    guint                                 num_valid_ifaces = 0;
+    guint                                 num_ipv4s        = 0;
+    GPtrArray *                           ptrarr;
+    guint                                 n_iface_datas;
+
+    n_iface_datas = g_hash_table_size(iface_datas);
+
+    ptrarr = g_ptr_array_sized_new(n_iface_datas + 1u);
+
+    g_hash_table_iter_init(&h_iter, iface_datas);
+    while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &iface_data)) {
+        if (nmcs_provider_get_config_iface_data_is_valid(iface_data)) {
+            num_valid_ifaces++;
+            num_ipv4s += iface_data->ipv4s_len;
+        }
+        g_ptr_array_add(ptrarr, (gpointer) iface_data);
+    }
+
+    g_ptr_array_sort(ptrarr, _result_new_sort_iface_data);
+
+    nm_assert(n_iface_datas == ptrarr->len);
+
+    g_ptr_array_add(ptrarr, NULL);
+
+    result  = g_new(NMCSProviderGetConfigResult, 1);
+    *result = (NMCSProviderGetConfigResult){
+        .iface_datas   = g_hash_table_ref(iface_datas),
+        .n_iface_datas = n_iface_datas,
+        .iface_datas_arr =
+            (const NMCSProviderGetConfigIfaceData **) g_ptr_array_free(ptrarr, FALSE),
+        .num_valid_ifaces = num_valid_ifaces,
+        .num_ipv4s        = num_ipv4s,
+    };
+
+#if NM_MORE_ASSERTS > 5
+    {
+        gsize iface_idx_expected = 0;
+        guint i;
+
+        for (i = 0; i < result->n_iface_datas; i++) {
+            if (result->iface_datas_arr[i]->iface_idx < 0) {
+                nm_assert(result->iface_datas_arr[i]->iface_idx == -1);
+                iface_idx_expected = -1;
+                continue;
+            }
+            nm_assert(result->iface_datas_arr[i]->iface_idx == iface_idx_expected);
+            iface_idx_expected++;
+        }
+    }
+#endif
+
+    return result;
+}
+
+void
+nmcs_provider_get_config_result_free(NMCSProviderGetConfigResult *result)
+{
+    if (result) {
+        nm_g_hash_table_unref(result->iface_datas);
+        g_free((gpointer) result->iface_datas_arr);
+        g_free(result);
+    }
+}
 
 /*****************************************************************************/
 
@@ -90,15 +174,25 @@ nmcs_provider_detect_finish(NMCSProvider *self, GAsyncResult *result, GError **e
 /*****************************************************************************/
 
 NMCSProviderGetConfigIfaceData *
-nmcs_provider_get_config_iface_data_new(gboolean was_requested)
+nmcs_provider_get_config_iface_data_create(GHashTable *iface_datas,
+                                           gboolean    was_requested,
+                                           const char *hwaddr)
 {
     NMCSProviderGetConfigIfaceData *iface_data;
 
+    nm_assert(hwaddr);
+
     iface_data  = g_slice_new(NMCSProviderGetConfigIfaceData);
     *iface_data = (NMCSProviderGetConfigIfaceData){
+        .hwaddr        = g_strdup(hwaddr),
         .iface_idx     = -1,
         .was_requested = was_requested,
     };
+
+    /* the has does not own the key (iface_datta->hwaddr), the lifetime of the
+     * key is associated with the iface_data instance. */
+    g_hash_table_replace(iface_datas, (char *) iface_data->hwaddr, iface_data);
+
     return iface_data;
 }
 
@@ -109,6 +203,7 @@ _iface_data_free(gpointer data)
 
     g_free(iface_data->ipv4s_arr);
     g_free(iface_data->iproutes_arr);
+    g_free((char *) iface_data->hwaddr);
 
     nm_g_slice_free(iface_data);
 }
@@ -137,8 +232,8 @@ _get_config_task_maybe_return(NMCSProviderGetConfigTaskData *get_config_data, GE
     } else {
         _LOGD("get-config: success");
         g_task_return_pointer(get_config_data->task,
-                              g_hash_table_ref(get_config_data->result_dict),
-                              (GDestroyNotify) g_hash_table_unref);
+                              nmcs_provider_get_config_result_new(get_config_data->result_dict),
+                              (GDestroyNotify) nmcs_provider_get_config_result_free);
     }
 
     nm_clear_g_signal_handler(g_task_get_cancellable(get_config_data->task),
@@ -187,16 +282,13 @@ nmcs_provider_get_config(NMCSProvider *      self,
     *get_config_data = (NMCSProviderGetConfigTaskData){
         .task = nm_g_task_new(self, cancellable, nmcs_provider_get_config, callback, user_data),
         .any  = any,
-        .result_dict = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, _iface_data_free),
+        .result_dict = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, _iface_data_free),
     };
 
     nmcs_wait_for_objects_register(get_config_data->task);
 
-    for (; hwaddrs && hwaddrs[0]; hwaddrs++) {
-        g_hash_table_insert(get_config_data->result_dict,
-                            g_strdup(hwaddrs[0]),
-                            nmcs_provider_get_config_iface_data_new(TRUE));
-    }
+    for (; hwaddrs && hwaddrs[0]; hwaddrs++)
+        nmcs_provider_get_config_iface_data_create(get_config_data->result_dict, TRUE, hwaddrs[0]);
 
     if (cancellable) {
         gulong cancelled_id;
@@ -217,7 +309,7 @@ nmcs_provider_get_config(NMCSProvider *      self,
     NMCS_PROVIDER_GET_CLASS(self)->get_config(self, get_config_data);
 }
 
-GHashTable *
+NMCSProviderGetConfigResult *
 nmcs_provider_get_config_finish(NMCSProvider *self, GAsyncResult *result, GError **error)
 {
     g_return_val_if_fail(NMCS_IS_PROVIDER(self), FALSE);
diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h
index 13212b8e..36632059 100644
--- a/src/nm-cloud-setup/nmcs-provider.h
+++ b/src/nm-cloud-setup/nmcs-provider.h
@@ -10,6 +10,10 @@
 /*****************************************************************************/
 
 typedef struct {
+    /* And it's exactly the same pointer that is also the key for the iface_datas
+     * dictionary. */
+    const char *hwaddr;
+
     in_addr_t *ipv4s_arr;
     gsize      ipv4s_len;
 
@@ -41,7 +45,43 @@ nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceDat
            && ((config_data->has_ipv4s && config_data->has_cidr) || config_data->iproutes_len);
 }
 
-NMCSProviderGetConfigIfaceData *nmcs_provider_get_config_iface_data_new(gboolean was_requested);
+NMCSProviderGetConfigIfaceData *nmcs_provider_get_config_iface_data_create(GHashTable *iface_datas,
+                                                                           gboolean was_requested,
+                                                                           const char *hwaddr);
+
+/*****************************************************************************/
+
+typedef struct {
+    /* A dictionary of (const char *) -> (NMCSProviderGetConfigIfaceData *).
+     * This is the per-interface result of get_config().
+     *
+     * The key is the same pointer as NMCSProviderGetConfigIfaceData's hwaddr. */
+    GHashTable *iface_datas;
+
+    /* The number of iface_datas that are nmcs_provider_get_config_iface_data_is_valid(). */
+    guint num_valid_ifaces;
+
+    /* the number of IPv4 addresses over all valid iface_datas. */
+    guint num_ipv4s;
+
+    guint n_iface_datas;
+
+    /* The sorted value of @iface_datas, sorted by iface_idx.
+     *
+     * Not found entries (iface_idx == -1) are sorted at the end. */
+    const NMCSProviderGetConfigIfaceData *const *iface_datas_arr;
+
+} NMCSProviderGetConfigResult;
+
+void nmcs_provider_get_config_result_free(NMCSProviderGetConfigResult *result);
+
+NM_AUTO_DEFINE_FCN0(NMCSProviderGetConfigResult *,
+                    _nm_auto_free_nmcs_provider_get_config_result,
+                    nmcs_provider_get_config_result_free);
+#define nm_auto_free_nmcs_provider_get_config_result \
+    nm_auto(_nm_auto_free_nmcs_provider_get_config_result)
+
+/*****************************************************************************/
 
 typedef struct {
     GTask *task;
@@ -124,7 +164,7 @@ void nmcs_provider_get_config(NMCSProvider *      provider,
                               GAsyncReadyCallback callback,
                               gpointer            user_data);
 
-GHashTable *
+NMCSProviderGetConfigResult *
 nmcs_provider_get_config_finish(NMCSProvider *provider, GAsyncResult *result, GError **error);
 
 #endif /* __NMCS_PROVIDER_H__ */