diff options
| author | Michael Biebl <biebl@debian.org> | 2019-12-18 18:29:24 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2019-12-18 18:29:24 +0100 |
| commit | 28028b26b3371756811e95d894f709f4b1207c00 (patch) | |
| tree | 6fe7316fd743b51042db47601a8ef8814b3134ac /clients/cloud-setup | |
| parent | e22609983008e1a669196ad64ba3a59ae8c76e0d (diff) | |
New upstream version 1.22.0 upstream/1.22.0
Diffstat (limited to 'clients/cloud-setup')
| -rwxr-xr-x | clients/cloud-setup/90-nm-cloud-setup.sh | 9 | ||||
| -rw-r--r-- | clients/cloud-setup/main.c | 646 | ||||
| -rw-r--r-- | clients/cloud-setup/meson.build | 49 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-cloud-setup-utils.c | 835 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-cloud-setup-utils.h | 127 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-cloud-setup.service.in | 36 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-cloud-setup.timer | 9 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-http-client.c | 746 | ||||
| -rw-r--r-- | clients/cloud-setup/nm-http-client.h | 67 | ||||
| -rw-r--r-- | clients/cloud-setup/nmcs-provider-ec2.c | 555 | ||||
| -rw-r--r-- | clients/cloud-setup/nmcs-provider-ec2.h | 24 | ||||
| -rw-r--r-- | clients/cloud-setup/nmcs-provider.c | 245 | ||||
| -rw-r--r-- | clients/cloud-setup/nmcs-provider.h | 108 |
13 files changed, 3456 insertions, 0 deletions
diff --git a/clients/cloud-setup/90-nm-cloud-setup.sh b/clients/cloud-setup/90-nm-cloud-setup.sh new file mode 100755 index 00000000..817e2555 --- /dev/null +++ b/clients/cloud-setup/90-nm-cloud-setup.sh @@ -0,0 +1,9 @@ +#!/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 + ;; +esac diff --git a/clients/cloud-setup/main.c b/clients/cloud-setup/main.c new file mode 100644 index 00000000..8250945c --- /dev/null +++ b/clients/cloud-setup/main.c @@ -0,0 +1,646 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nm-cloud-setup-utils.h" + +#include "nmcs-provider-ec2.h" +#include "nm-libnm-core-intern/nm-libnm-core-utils.h" + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + GCancellable *cancellable; + NMCSProvider *provider_result; + guint detect_count; +} ProviderDetectData; + +static void +_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; + gboolean success; + + success = nmcs_provider_detect_finish (provider, result, &error); + + nm_assert (success != (!!error)); + + if (nm_utils_error_is_cancelled (error, FALSE)) + return; + + dd = user_data; + + nm_assert (dd->detect_count > 0); + dd->detect_count--; + + if (error) { + _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; + } + + _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); +} + +static void +_provider_detect_sigterm_cb (GCancellable *source, + gpointer user_data) +{ + ProviderDetectData *dd = 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) +{ + 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, + }; + const GType gtypes[] = { + NMCS_TYPE_PROVIDER_EC2, + }; + int i; + gulong cancellable_signal_id; + + cancellable_signal_id = g_cancellable_connect (sigterm_cancellable, + G_CALLBACK (_provider_detect_sigterm_cb), + &dd, + NULL); + if (!cancellable_signal_id) + goto out; + + http_client = nmcs_wait_for_objects_register (nm_http_client_new ()); + + for (i = 0; i < G_N_ELEMENTS (gtypes); i++) { + NMCSProvider *provider; + + provider = g_object_new (gtypes[i], + NMCS_PROVIDER_HTTP_CLIENT, http_client, + NULL); + nmcs_wait_for_objects_register (provider); + + _LOGD ("start detecting %s provider...", nmcs_provider_get_name (provider)); + dd.detect_count++; + nmcs_provider_detect (provider, + cancellable, + _provider_detect_cb, + &dd); + } + + if (dd.detect_count > 0) + g_main_loop_run (main_loop); + +out: + nm_clear_g_signal_handler (sigterm_cancellable, &cancellable_signal_id); + return dd.provider_result; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + NMClient *nmc; +} ClientCreateData; + +static void +_nmc_create_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + gs_unref_object NMClient *nmc = NULL; + ClientCreateData *data = user_data; + gs_free_error GError *error = NULL; + + nmc = nm_client_new_finish (result, &error); + if (!nmc) { + if (!nm_utils_error_is_cancelled (error, FALSE)) + _LOGI ("failure to talk to NetworkManager: %s", error->message); + goto out; + } + + if (!nm_client_get_nm_running (nmc)) { + _LOGI ("NetworkManager is not running"); + goto out; + } + + _LOGD ("NetworkManager is running"); + nmcs_wait_for_objects_register (nmc); + nmcs_wait_for_objects_register (nm_client_get_context_busy_watcher (nmc)); + + data->nmc = g_steal_pointer (&nmc); +out: + g_main_loop_quit (data->main_loop); +} + +static NMClient * +_nmc_create (GCancellable *sigterm_cancellable) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (NULL, FALSE); + ClientCreateData data = { + .main_loop = main_loop, + }; + + nm_client_new_async (sigterm_cancellable, _nmc_create_cb, &data); + + g_main_loop_run (main_loop); + + return data.nmc; +} + +/*****************************************************************************/ + +static char ** +_nmc_get_hwaddrs (NMClient *nmc) +{ + gs_unref_ptrarray GPtrArray *hwaddrs = NULL; + const GPtrArray *devices; + char **hwaddrs_v; + gs_free char *str = NULL; + guint i; + + devices = nm_client_get_devices (nmc); + + for (i = 0; i < devices->len; i++) { + NMDevice *device = devices->pdata[i]; + const char *hwaddr; + char *s; + + if (!NM_IS_DEVICE_ETHERNET (device)) + continue; + + if (nm_device_get_state (device) < NM_DEVICE_STATE_UNAVAILABLE) + continue; + + hwaddr = nm_device_ethernet_get_permanent_hw_address (NM_DEVICE_ETHERNET (device)); + if (!hwaddr) + continue; + + s = nmcs_utils_hwaddr_normalize (hwaddr, -1); + if (!s) + continue; + + if (!hwaddrs) + hwaddrs = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (hwaddrs, s); + } + + if (!hwaddrs) { + _LOGD ("found interfaces: none"); + return NULL; + } + + g_ptr_array_add (hwaddrs, NULL); + hwaddrs_v = (char **) g_ptr_array_free (g_steal_pointer (&hwaddrs), FALSE); + + _LOGD ("found interfaces: %s", (str = g_strjoinv (", ", hwaddrs_v))); + + return hwaddrs_v; +} + +static NMDevice * +_nmc_get_device_by_hwaddr (NMClient *nmc, + const char *hwaddr) +{ + const GPtrArray *devices; + guint i; + + devices = nm_client_get_devices (nmc); + + for (i = 0; i < devices->len; i++) { + NMDevice *device = devices->pdata[i]; + const char *hwaddr_dev; + gs_free char *s = NULL; + + if (!NM_IS_DEVICE_ETHERNET (device)) + continue; + + hwaddr_dev = nm_device_ethernet_get_permanent_hw_address (NM_DEVICE_ETHERNET (device)); + if (!hwaddr_dev) + continue; + + s = nmcs_utils_hwaddr_normalize (hwaddr_dev, -1); + if (s && nm_streq (s, hwaddr)) + return device; + } + + return NULL; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + GHashTable *config_dict; +} GetConfigData; + +static void +_get_config_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GetConfigData *data = user_data; + gs_unref_hashtable GHashTable *config_dict = NULL; + gs_free_error GError *error = NULL; + + config_dict = nmcs_provider_get_config_finish (NMCS_PROVIDER (source), result, &error); + + if (!config_dict) { + if (!nm_utils_error_is_cancelled (error, FALSE)) + _LOGI ("failure to get meta data: %s", error->message); + } else + _LOGD ("meta data received"); + + data->config_dict = g_steal_pointer (&config_dict); + g_main_loop_quit (data->main_loop); +} + +static GHashTable * +_get_config (GCancellable *sigterm_cancellable, + NMCSProvider *provider, + NMClient *nmc) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (NULL, FALSE); + GetConfigData data = { + .main_loop = main_loop, + }; + gs_strfreev char **hwaddrs = NULL; + + hwaddrs = _nmc_get_hwaddrs (nmc); + + nmcs_provider_get_config (provider, + TRUE, + (const char *const*) hwaddrs, + sigterm_cancellable, + _get_config_cb, + &data); + + g_main_loop_run (main_loop); + + return data.config_dict; +} + +/*****************************************************************************/ + +static gboolean +_nmc_skip_connection (NMConnection *connection) +{ + NMSettingUser *s_user; + const char *v; + + s_user = NM_SETTING_USER (nm_connection_get_setting (connection, NM_TYPE_SETTING_USER)); + if (!s_user) + return FALSE; + +#define USER_TAG_SKIP "org.freedesktop.nm-cloud-setup.skip" + + nm_assert (nm_setting_user_check_key (USER_TAG_SKIP, NULL)); + + v = nm_setting_user_get_data (s_user, USER_TAG_SKIP); + return _nm_utils_ascii_str_to_bool (v, FALSE); +} + +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; + gs_unref_ptrarray GPtrArray *addrs_new = NULL; + gs_unref_ptrarray GPtrArray *rules_new = NULL; + nm_auto_unref_ip_route NMIPRoute *route_new = NULL; + + if (!nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_WIRED_SETTING_NAME)) + return FALSE; + + s_ip = nm_connection_get_setting_ip4_config (connection); + 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; + + 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_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); + } + + 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); + + 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 + || rules_changed); + return TRUE; +} + +/*****************************************************************************/ + +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) +{ + 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 version_id_changed; + guint try_count; + gboolean any_changes = FALSE; + + g_main_context_iteration (NULL, FALSE); + + if (g_cancellable_is_cancelled (sigterm_cancellable)) + return FALSE; + + device = nm_g_object_ref (_nmc_get_device_by_hwaddr (nmc, hwaddr)); + if (!device) { + _LOGD ("config device %s: skip because device not found", hwaddr); + return FALSE; + } + + if (!nmcs_provider_get_config_iface_data_is_valid (config_data)) { + _LOGD ("config device %s: skip because meta data not successfully fetched", hwaddr); + return FALSE; + } + + _LOGD ("config device %s: configuring \"%s\" (%s)...", + hwaddr, + nm_device_get_iface (device) ?: "/unknown/", + nm_object_get_path (NM_OBJECT (device))); + + try_count = 0; + +try_again: + + applied_connection = nmcs_device_get_applied_connection (device, + sigterm_cancellable, + &applied_version_id, + &error); + if (!applied_connection) { + if (!nm_utils_error_is_cancelled (error, FALSE)) + _LOGD ("config device %s: device has no applied connection (%s). Skip", hwaddr, error->message); + return any_changes; + } + + if (_nmc_skip_connection (applied_connection)) { + _LOGD ("config device %s: skip applied connection due to user data %s", hwaddr, USER_TAG_SKIP); + return any_changes; + } + + 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); + 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)); + return any_changes; + } + + _LOGD ("config device %s: reapply connection \"%s\" (%s)", + hwaddr, + 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. */ + any_changes = TRUE; + + if (!nmcs_device_reapply (device, + sigterm_cancellable, + applied_connection, + applied_version_id, + &version_id_changed, + &error)) { + if ( version_id_changed + && try_count < 5) { + _LOGD ("config device %s: applied connection changed in the meantime. Retry...", + hwaddr); + g_clear_object (&applied_connection); + g_clear_error (&error); + try_count++; + goto try_again; + } + + if (!nm_utils_error_is_cancelled (error, FALSE)) { + _LOGD ("config device %s: failure to reapply connection \"%s\" (%s): %s", + hwaddr, + nm_connection_get_id (applied_connection), + nm_connection_get_uuid (applied_connection), + error->message); + } + return any_changes; + } + + _LOGD ("config device %s: connection \"%s\" (%s) reapplied", + hwaddr, + nm_connection_get_id (applied_connection), + nm_connection_get_uuid (applied_connection)); + + return any_changes; +} + +static gboolean +_config_all (GCancellable *sigterm_cancellable, + NMClient *nmc, + GHashTable *config_dict) +{ + 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); + + 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)) + any_changes = TRUE; + } + + return any_changes; +} + +/*****************************************************************************/ + +static gboolean +sigterm_handler (gpointer user_data) +{ + GCancellable *sigterm_cancellable = user_data; + + if (!g_cancellable_is_cancelled (sigterm_cancellable)) { + _LOGD ("SIGTERM received"); + g_cancellable_cancel (user_data); + } else + _LOGD ("SIGTERM received (again)"); + return G_SOURCE_CONTINUE; +} + +/*****************************************************************************/ + +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; + + _nm_logging_enabled_init (g_getenv (NMCS_ENV_VARIABLE ("NM_CLOUD_SETUP_LOG"))); + + _LOGD ("nm-cloud-setup %s starting...", NM_DIST_VERSION); + + if (argc != 1) { + g_printerr ("%s: no command line arguments supported\n", argv[0]); + return EXIT_FAILURE; + } + + sigterm_cancellable = g_cancellable_new (); + + sigterm_source = nm_g_source_attach (nm_g_unix_signal_source_new (SIGTERM, + G_PRIORITY_DEFAULT, + sigterm_handler, + sigterm_cancellable, + NULL), + NULL); + + provider = _provider_detect (sigterm_cancellable); + if (!provider) + goto done; + + nmc = _nmc_create (sigterm_cancellable); + if (!nmc) + goto done; + + config_dict = _get_config (sigterm_cancellable, provider, nmc); + if (!config_dict) + goto done; + + if (_config_all (sigterm_cancellable, nmc, config_dict)) + _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); + g_clear_object (&nmc); + g_clear_object (&provider); + + if (!nmcs_wait_for_objects_iterate_until_done (NULL, 2000)) { + _LOGE ("shutdown: timeout waiting to application to quit. This is a bug"); + nm_assert_not_reached (); + } + + nm_clear_g_source_inst (&sigterm_source); + g_clear_object (&sigterm_cancellable); + + return 0; +} diff --git a/clients/cloud-setup/meson.build b/clients/cloud-setup/meson.build new file mode 100644 index 00000000..e9cd970a --- /dev/null +++ b/clients/cloud-setup/meson.build @@ -0,0 +1,49 @@ +name = 'nm-cloud-setup' + +if install_systemdunitdir + + nm_cloud_setup_service = configure_file( + input: 'nm-cloud-setup.service.in', + output: '@BASENAME@', + install_dir: systemd_systemdsystemunitdir, + configuration: data_conf, + ) + + install_data( + 'nm-cloud-setup.timer', + install_dir: systemd_systemdsystemunitdir, + ) + + install_data( + '90-nm-cloud-setup.sh', + install_dir: join_paths(nm_pkglibdir, 'dispatcher.d', 'no-wait.d'), + ) + +endif + +sources = files( + 'main.c', + 'nm-cloud-setup-utils.c', + 'nm-http-client.c', + 'nmcs-provider-ec2.c', + 'nmcs-provider.c', +) + +deps = [ + libnmc_base_dep, + libnmc_dep, + libcurl_dep, +] + +executable( + name, + sources, + dependencies: deps, + c_args: clients_c_flags + + ['-DG_LOG_DOMAIN="@0@"'.format(name)], + link_with: libnm_systemd_logging_stub, + link_args: ldflags_linker_script_binary, + link_depends: linker_script_binary, + install: true, + install_dir: nm_libexecdir, +) diff --git a/clients/cloud-setup/nm-cloud-setup-utils.c b/clients/cloud-setup/nm-cloud-setup-utils.c new file mode 100644 index 00000000..50a59892 --- /dev/null +++ b/clients/cloud-setup/nm-cloud-setup-utils.c @@ -0,0 +1,835 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nm-cloud-setup-utils.h" + +#include "nm-glib-aux/nm-time-utils.h" +#include "nm-glib-aux/nm-logging-base.h" + +/*****************************************************************************/ + +volatile NMLogLevel _nm_logging_configured_level = LOGL_TRACE; + +void +_nm_logging_enabled_init (const char *level_str) +{ + NMLogLevel level; + + if (!_nm_log_parse_level (level_str, &level)) + level = LOGL_WARN; + else if (level == _LOGL_KEEP) + level = LOGL_WARN; + + _nm_logging_configured_level = level; +} + +void +_nm_log_impl_cs (NMLogLevel level, + const char *fmt, + ...) +{ + gs_free char *msg = NULL; + va_list ap; + const char *level_str; + gint64 ts; + + va_start (ap, fmt); + msg = g_strdup_vprintf (fmt, ap); + va_end (ap); + + switch (level) { + case LOGL_TRACE: level_str = "<trace>"; break; + case LOGL_DEBUG: level_str = "<debug>"; break; + case LOGL_INFO: level_str = "<info> "; break; + case LOGL_WARN: level_str = "<warn> "; break; + default: + nm_assert (level == LOGL_ERR); + level_str = "<error>"; + break; + } + + ts = nm_utils_clock_gettime_ns (CLOCK_BOOTTIME); + + g_print ("[%"G_GINT64_FORMAT".%05"G_GINT64_FORMAT"] %s %s\n", + ts / NM_UTILS_NS_PER_SECOND, + (ts / (NM_UTILS_NS_PER_SECOND / 10000)) % 10000, + level_str, + msg); +} + +void +_nm_utils_monotonic_timestamp_initialized (const struct timespec *tp, + gint64 offset_sec, + gboolean is_boottime) +{ +} + +/*****************************************************************************/ + +G_LOCK_DEFINE_STATIC (_wait_for_objects_lock); +static GSList *_wait_for_objects_list; +static GSList *_wait_for_objects_iterate_loops; + +static void +_wait_for_objects_maybe_quit_mainloops_with_lock (void) +{ + GSList *iter; + + if (!_wait_for_objects_list) { + for (iter = _wait_for_objects_iterate_loops; iter; iter = iter->next) + g_main_loop_quit (iter->data); + } +} + +static void +_wait_for_objects_weak_cb (gpointer data, + GObject *where_the_object_was) +{ + G_LOCK (_wait_for_objects_lock); + nm_assert (g_slist_find (_wait_for_objects_list, where_the_object_was)); + _wait_for_objects_list = g_slist_remove (_wait_for_objects_list, where_the_object_was); + _wait_for_objects_maybe_quit_mainloops_with_lock (); + G_UNLOCK (_wait_for_objects_lock); +} + +/** + * nmcs_wait_for_objects_register: + * @target: a #GObject to wait for. + * + * Registers @target as a pointer to wait during shutdown. Using + * nmcs_wait_for_objects_iterate_until_done() we keep waiting until + * @target gets destroyed, which means that it gets completely unreferenced. + */ +gpointer +nmcs_wait_for_objects_register (gpointer target) +{ + g_return_val_if_fail (G_IS_OBJECT (target), NULL); + + G_LOCK (_wait_for_objects_lock); + _wait_for_objects_list = g_slist_prepend (_wait_for_objects_list, target); + G_UNLOCK (_wait_for_objects_lock); + + g_object_weak_ref (target, + _wait_for_objects_weak_cb, + NULL); + return target; +} + +typedef struct { + GMainLoop *loop; + gboolean got_timeout; +} WaitForObjectsData; + +static gboolean +_wait_for_objects_iterate_until_done_timeout_cb (gpointer user_data) +{ + WaitForObjectsData *data = user_data; + + data->got_timeout = TRUE; + g_main_loop_quit (data->loop); + return G_SOURCE_CONTINUE; +} + +static gboolean +_wait_for_objects_iterate_until_done_idle_cb (gpointer user_data) +{ + /* This avoids a race where: + * + * - we check whether there are objects to wait for. + * - the last object to wait for gets removed (issuing g_main_loop_quit()). + * - we run the mainloop (and missed our signal). + * + * It's really a missing feature of GMainLoop where the "is-running" flag is always set to + * TRUE by g_main_loop_run(). That means, you cannot catch a g_main_loop_quit() in a race + * free way while not iterating the loop. + * + * Avoid this, by checking once again after we start running the mainloop. + */ + + G_LOCK (_wait_for_objects_lock); + _wait_for_objects_maybe_quit_mainloops_with_lock (); + G_UNLOCK (_wait_for_objects_lock); + return G_SOURCE_REMOVE; +} + +/** + * nmcs_wait_for_objects_iterate_until_done: + * @context: the #GMainContext to iterate. + * @timeout_msec: timeout or -1 for no timeout. + * + * Iterates the provided @context until all objects that we wait for + * are destroyed. + * + * The purpose of this is to cleanup all objects that we have on exit. That + * is especially because objects have asynchronous operations pending that + * should be cancelled and properly completed during exit. + * + * Returns: %FALSE on timeout or %TRUE if all objects destroyed before timeout. + */ +gboolean +nmcs_wait_for_objects_iterate_until_done (GMainContext *context, + int timeout_msec) +{ + nm_auto_unref_gmainloop GMainLoop *loop = g_main_loop_new (context, FALSE); + nm_auto_destroy_and_unref_gsource GSource *timeout_source = NULL; + WaitForObjectsData data; + gboolean has_more_objects; + + G_LOCK (_wait_for_objects_lock); + if (!_wait_for_objects_list) { + G_UNLOCK (_wait_for_objects_lock); + return TRUE; + } + _wait_for_objects_iterate_loops = g_slist_prepend (_wait_for_objects_iterate_loops, loop); + G_UNLOCK (_wait_for_objects_lock); + + data = (WaitForObjectsData) { + .loop = loop, + .got_timeout = FALSE, + }; + + if (timeout_msec >= 0) { + timeout_source = nm_g_source_attach (nm_g_timeout_source_new (timeout_msec, + G_PRIORITY_DEFAULT, + _wait_for_objects_iterate_until_done_timeout_cb, + &data, + NULL), + context); + } + + has_more_objects = TRUE; + while ( has_more_objects + && !data.got_timeout) { + nm_auto_destroy_and_unref_gsource GSource *idle_source = NULL; + + idle_source = nm_g_source_attach (nm_g_idle_source_new (G_PRIORITY_DEFAULT, + _wait_for_objects_iterate_until_done_idle_cb, + &data, + NULL), + context); + + g_main_loop_run (loop); + + G_LOCK (_wait_for_objects_lock); + has_more_objects = (!!_wait_for_objects_list); + if ( data.got_timeout + || !has_more_objects) + _wait_for_objects_iterate_loops = g_slist_remove (_wait_for_objects_iterate_loops, loop); + G_UNLOCK (_wait_for_objects_lock); + } + + return !data.got_timeout; +} + +/*****************************************************************************/ + +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, + gboolean success, + 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, success); + + 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, FALSE)) { + /* we already handle this differently. Nothing to do. */ + return; + } + + if ( error + || is_finished) { + _poll_return (poll_task_data, TRUE, g_steal_pointer (&error)); + return; + } + + now_ms = nm_utils_get_monotonic_timestamp_ms (); + 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_ms (); + + 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, FALSE, NULL); + return G_SOURCE_CONTINUE; +} + +static void +_poll_cancelled_cb (GObject *object, gpointer user_data) +{ + PollTaskData *poll_task_data = user_data; + GError *error = NULL; + + _LOGD (">> poll cancelled"); + 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, FALSE, 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 (but no 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 (asynchrnous) 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 sleep_timeout_ms, + int ratelimit_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, + _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 without error set. + */ +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) +{ + gs_free char *hwaddr_clone = NULL; + guint8 buf[ETH_ALEN]; + + nm_assert (len >= -1); + + if (len < 0) { + if (!hwaddr) + return NULL; + } else { + if (len == 0) + return NULL; + nm_assert (hwaddr); + hwaddr = nm_strndup_a (300, hwaddr, len, &hwaddr_clone); + } + + if (!nm_utils_hwaddr_aton (hwaddr, buf, sizeof (buf))) + return NULL; + + return nm_utils_hwaddr_ntoa (buf, sizeof (buf)); +} + +/*****************************************************************************/ + +const char * +nmcs_utils_parse_memmem (GBytes *mem, const char *needle) +{ + const char *mem_data; + gsize mem_size; + + g_return_val_if_fail (mem, NULL); + g_return_val_if_fail (needle, NULL); + + mem_data = g_bytes_get_data (mem, &mem_size); + return memmem (mem_data, mem_size, needle, strlen (needle)); +} + +const char * +nmcs_utils_parse_get_full_line (GBytes *mem, const char *needle) +{ + const char *mem_data; + gsize mem_size; + gsize c; + gsize l; + + const char *line; + + line = nmcs_utils_parse_memmem (mem, needle); + if (!line) + return NULL; + + mem_data = g_bytes_get_data (mem, &mem_size); + + if ( line != mem_data + && line[-1] != '\n') { + /* the line must be preceeded either by the begin of the data or + * by a newline. */ + return NULL; + } + + c = mem_size - (line - mem_data); + l = strlen (needle); + + if ( c != l + && line[l] != '\n') { + /* the end of the needle must be either a newline or the end of the buffer. */ + return NULL; + } + + return line; +} + +/*****************************************************************************/ + +char * +nmcs_utils_uri_build_concat_v (const char *base, + const char **components, + gsize n_components) +{ + GString *uri; + + nm_assert (base); + nm_assert (base[0]); + nm_assert (!NM_STR_HAS_SUFFIX (base, "/")); + + uri = g_string_sized_new (100); + + g_string_append (uri, base); + + if ( n_components > 0 + && components[0] + && components[0][0] == '/') { + /* the first component starts with a slash. We allow that, and don't add a duplicate + * slash. Otherwise, we add a separator after base. + * + * We only do that for the first component. */ + } else + g_string_append_c (uri, '/'); + + while (n_components > 0) { + if (!components[0]) { + /* we allow NULL, to indicate nothing to append*/ + } else + g_string_append (uri, components[0]); + components++; + n_components--; + } + + return g_string_free (uri, FALSE); +} + +/*****************************************************************************/ + +gboolean +nmcs_setting_ip_replace_ipv4_addresses (NMSettingIPConfig *s_ip, + NMIPAddress **entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_addresses (s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPAddress *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_address_cmp_full (entry, + nm_setting_ip_config_get_address (s_ip, i_next), + NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS) == 0) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_address (s_ip, --num); + any_changes = TRUE; + } + + if (!nm_setting_ip_config_add_address (s_ip, entry)) + continue; + + i_next++; + } + if (any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_address (s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +gboolean +nmcs_setting_ip_replace_ipv4_routes (NMSettingIPConfig *s_ip, + NMIPRoute **entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_routes (s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPRoute *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_route_equal_full (entry, + nm_setting_ip_config_get_route (s_ip, i_next), + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_route (s_ip, --num); + any_changes = TRUE; + } + + if (!nm_setting_ip_config_add_route (s_ip, entry)) + continue; + + i_next++; + } + if (!any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_route (s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +gboolean +nmcs_setting_ip_replace_ipv4_rules (NMSettingIPConfig *s_ip, + NMIPRoutingRule **entries_arr, + guint entries_len) +{ + gboolean any_changes = FALSE; + guint i_next; + guint num; + guint i; + + num = nm_setting_ip_config_get_num_routing_rules (s_ip); + + i_next = 0; + + for (i = 0; i < entries_len; i++) { + NMIPRoutingRule *entry = entries_arr[i]; + + if (!any_changes) { + if (i_next < num) { + if (nm_ip_routing_rule_cmp (entry, + nm_setting_ip_config_get_routing_rule (s_ip, i_next)) == 0) { + i_next++; + continue; + } + } + while (i_next < num) + nm_setting_ip_config_remove_routing_rule (s_ip, --num); + any_changes = TRUE; + } + + nm_setting_ip_config_add_routing_rule (s_ip, entry); + i_next++; + } + if (!any_changes) { + while (i_next < num) { + nm_setting_ip_config_remove_routing_rule (s_ip, --num); + any_changes = TRUE; + } + } + + return any_changes; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + NMConnection *connection; + GError *error; + guint64 version_id; +} DeviceGetAppliedConnectionData; + +static void +_nmcs_device_get_applied_connection_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + DeviceGetAppliedConnectionData *data = user_data; + + data->connection = nm_device_get_applied_connection_finish (NM_DEVICE (source), + result, + &data->version_id, + &data->error); + g_main_loop_quit (data->main_loop); +} + +NMConnection * +nmcs_device_get_applied_connection (NMDevice *device, + GCancellable *cancellable, + guint64 *version_id, + GError **error) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (NULL, FALSE); + DeviceGetAppliedConnectionData data = { + .main_loop = main_loop, + }; + + nm_device_get_applied_connection_async (device, + 0, + cancellable, + _nmcs_device_get_applied_connection_cb, + &data); + + g_main_loop_run (main_loop); + + if (data.error) + g_propagate_error (error, data.error); + NM_SET_OUT (version_id, data.version_id); + return data.connection; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *main_loop; + GError *error; +} DeviceReapplyData; + +static void +_nmcs_device_reapply_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + DeviceReapplyData *data = user_data; + + nm_device_reapply_finish (NM_DEVICE (source), + result, + &data->error); + g_main_loop_quit (data->main_loop); +} + +gboolean +nmcs_device_reapply (NMDevice *device, + GCancellable *sigterm_cancellable, + NMConnection *connection, + guint64 version_id, + gboolean *out_version_id_changed, + GError **error) +{ + nm_auto_unref_gmainloop GMainLoop *main_loop = g_main_loop_new (NULL, FALSE); + DeviceReapplyData data = { + .main_loop = main_loop, + }; + + nm_device_reapply_async (device, + connection, + version_id, + 0, + sigterm_cancellable, + _nmcs_device_reapply_cb, + &data); + + g_main_loop_run (main_loop); + + if (data.error) { + NM_SET_OUT (out_version_id_changed, g_error_matches (data.error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_VERSION_ID_MISMATCH)); + g_propagate_error (error, data.error); + return FALSE; + } + + NM_SET_OUT (out_version_id_changed, FALSE); + return TRUE; +} diff --git a/clients/cloud-setup/nm-cloud-setup-utils.h b/clients/cloud-setup/nm-cloud-setup-utils.h new file mode 100644 index 00000000..7c3e267a --- /dev/null +++ b/clients/cloud-setup/nm-cloud-setup-utils.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_CLOUD_SETUP_UTILS_H__ +#define __NM_CLOUD_SETUP_UTILS_H__ + +#include "nm-glib-aux/nm-logging-fwd.h" + +/*****************************************************************************/ + +/* 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"" + +/*****************************************************************************/ + +extern volatile NMLogLevel _nm_logging_configured_level; + +static inline gboolean +nm_logging_enabled (NMLogLevel level) +{ + return level >= _nm_logging_configured_level; +} + +void _nm_logging_enabled_init (const char *level_str); + +void _nm_log_impl_cs (NMLogLevel level, + const char *fmt, + ...) _nm_printf (2, 3); + +#define _nm_log(level, ...) \ + _nm_log_impl_cs ((level), __VA_ARGS__); + +#define _NMLOG(level, ...) \ + G_STMT_START { \ + const NMLogLevel _level = (level); \ + \ + if (nm_logging_enabled (_level)) { \ + _nm_log (_level, __VA_ARGS__); \ + } \ + } G_STMT_END + +/*****************************************************************************/ + +#ifndef NM_DIST_VERSION +#define NM_DIST_VERSION VERSION +#endif + +/*****************************************************************************/ + +gpointer nmcs_wait_for_objects_register (gpointer target); + +gboolean nmcs_wait_for_objects_iterate_until_done (GMainContext *context, + int timeout_msec); + +/*****************************************************************************/ + +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); + +/*****************************************************************************/ + +const char *nmcs_utils_parse_memmem (GBytes *mem, const char *needle); + +const char *nmcs_utils_parse_get_full_line (GBytes *mem, const char *needle); + +/*****************************************************************************/ + +char *nmcs_utils_uri_build_concat_v (const char *base, + const char **components, + gsize n_components); + +#define nmcs_utils_uri_build_concat(base, ...) nmcs_utils_uri_build_concat_v (base, ((const char *[]) { __VA_ARGS__ }), NM_NARG (__VA_ARGS__)) + +/*****************************************************************************/ + +gboolean nmcs_setting_ip_replace_ipv4_addresses (NMSettingIPConfig *s_ip, + NMIPAddress **entries_arr, + guint entries_len); + +gboolean nmcs_setting_ip_replace_ipv4_routes (NMSettingIPConfig *s_ip, + NMIPRoute **entries_arr, + guint entries_len); + +gboolean nmcs_setting_ip_replace_ipv4_rules (NMSettingIPConfig *s_ip, + NMIPRoutingRule **entries_arr, + guint entries_len); + +/*****************************************************************************/ + +NMConnection *nmcs_device_get_applied_connection (NMDevice *device, + GCancellable *cancellable, + guint64 *version_id, + GError **error); + +gboolean nmcs_device_reapply (NMDevice *device, + GCancellable *sigterm_cancellable, + NMConnection *connection, + guint64 version_id, + gboolean *out_version_id_changed, + GError **error); + +#endif /* __NM_CLOUD_SETUP_UTILS_H__ */ diff --git a/clients/cloud-setup/nm-cloud-setup.service.in b/clients/cloud-setup/nm-cloud-setup.service.in new file mode 100644 index 00000000..69a1a29c --- /dev/null +++ b/clients/cloud-setup/nm-cloud-setup.service.in @@ -0,0 +1,36 @@ +[Unit] +Description=Automatically configure NetworkManager in cloud +After=NetworkManager.service + +[Service] +Type=oneshot +ExecStart=@libexecdir@/nm-cloud-setup + +#Environment=NM_CLOUD_SETUP_LOG=TRACE + +# Cloud providers are disabled by default. You need to +# Opt-in by setting the right environment variable for +# the provider. +#Environment=NM_CLOUD_SETUP_EC2=yes + +CapabilityBoundingSet= +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectSystem=strict +RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallFilter=@system-service + +[Install] +WantedBy=NetworkManager.service diff --git a/clients/cloud-setup/nm-cloud-setup.timer b/clients/cloud-setup/nm-cloud-setup.timer new file mode 100644 index 00000000..fd1722a6 --- /dev/null +++ b/clients/cloud-setup/nm-cloud-setup.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Periodically run nm-cloud-setup + +[Timer] +OnBootSec=5min +OnUnitActiveSec=5min + +[Install] +WantedBy=timers.target diff --git a/clients/cloud-setup/nm-http-client.c b/clients/cloud-setup/nm-http-client.c new file mode 100644 index 00000000..94331095 --- /dev/null +++ b/clients/cloud-setup/nm-http-client.c @@ -0,0 +1,746 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nm-http-client.h" + +#include <curl/curl.h> +#include <glib-unix.h> + +#include "nm-cloud-setup-utils.h" + +#define NM_CURL_DEBUG 0 + +/*****************************************************************************/ + +typedef struct { + GMainContext *context; + CURLM *mhandle; + GSource *mhandle_source_timeout; + GSource *mhandle_source_socket; +} NMHttpClientPrivate; + +struct _NMHttpClient { + GObject parent; + NMHttpClientPrivate _priv; +}; + +struct _NMHttpClientClass { + GObjectClass parent; +}; + +G_DEFINE_TYPE (NMHttpClient, nm_http_client, G_TYPE_OBJECT); + +#define NM_HTTP_CLIENT_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMHttpClient, NM_IS_HTTP_CLIENT) + +/*****************************************************************************/ + +#define _NMLOG2(level, edata, ...) \ + G_STMT_START { \ + EHandleData *_edata = (edata); \ + \ + _NMLOG (level, \ + "http-request["NM_HASH_OBFUSCATE_PTR_FMT", \"%s\"]: " \ + _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR (_edata), \ + (_edata)->url \ + _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ + } G_STMT_END + +/*****************************************************************************/ + +G_LOCK_DEFINE_STATIC (_my_curl_initalized_lock); +static bool _my_curl_initialized = FALSE; + +__attribute__((destructor)) +static void +_my_curl_global_cleanup (void) +{ + G_LOCK (_my_curl_initalized_lock); + if (_my_curl_initialized) { + _my_curl_initialized = FALSE; + curl_global_cleanup (); + } + G_UNLOCK (_my_curl_initalized_lock); +} + +static void +nm_http_client_curl_global_init (void) +{ + G_LOCK (_my_curl_initalized_lock); + if (!_my_curl_initialized) { + _my_curl_initialized = TRUE; + if (curl_global_init (CURL_GLOBAL_ALL) != CURLE_OK) { + /* Even if this fails, we are partly initialized. WTF. */ + _LOGE ("curl: curl_global_init() failed!"); + } + } + G_UNLOCK (_my_curl_initalized_lock); +} + +/*****************************************************************************/ + +GMainContext * +nm_http_client_get_main_context (NMHttpClient *self) +{ + g_return_val_if_fail (NM_IS_HTTP_CLIENT (self), NULL); + + return NM_HTTP_CLIENT_GET_PRIVATE (self)->context; +} + +/*****************************************************************************/ + +static GSource * +_source_attach (NMHttpClient *self, + GSource *source) +{ + return nm_g_source_attach (source, NM_HTTP_CLIENT_GET_PRIVATE (self)->context); +} + +/*****************************************************************************/ + +typedef struct { + long response_code; + GBytes *response_data; +} GetResult; + +static void +_get_result_free (gpointer data) +{ + GetResult *get_result = data; + + g_bytes_unref (get_result->response_data); + nm_g_slice_free (get_result); +} + +typedef struct { + GTask *task; + GSource *timeout_source; + CURLcode ehandle_result; + CURL *ehandle; + char *url; + GString *recv_data; + gssize max_data; + gulong cancellable_id; +} EHandleData; + +static void +_ehandle_free_ehandle (EHandleData *edata) +{ + if (edata->ehandle) { + NMHttpClient *self = g_task_get_source_object (edata->task); + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + curl_multi_remove_handle (priv->mhandle, edata->ehandle); + curl_easy_cleanup (g_steal_pointer (&edata->ehandle)); + } +} + +static void +_ehandle_free (EHandleData *edata) +{ + nm_assert (!edata->ehandle); + nm_assert (!edata->timeout_source); + + g_object_unref (edata->task); + + if (edata->recv_data) + g_string_free (edata->recv_data, TRUE); + g_free (edata->url); + nm_g_slice_free (edata); +} + +static void +_ehandle_complete (EHandleData *edata, + GError *error_take) +{ + GetResult *get_result; + gs_free char *str_tmp_1 = NULL; + long response_code = -1; + + 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); + + if (error_take) { + if (nm_utils_error_is_cancelled (error_take, FALSE)) + _LOG2T (edata, "cancelled"); + else + _LOG2D (edata, "failed with %s", error_take->message); + } else if (edata->ehandle_result != CURLE_OK) { + _LOG2D (edata, "failed with curl error \"%s\"", curl_easy_strerror (edata->ehandle_result)); + nm_utils_error_set (&error_take, + NM_UTILS_ERROR_UNKNOWN, + "failed with curl error \"%s\"", + curl_easy_strerror (edata->ehandle_result)); + } + + if (error_take) { + _ehandle_free_ehandle (edata); + g_task_return_error (edata->task, error_take); + _ehandle_free (edata); + return; + } + + if (curl_easy_getinfo (edata->ehandle, + CURLINFO_RESPONSE_CODE, + &response_code) != CURLE_OK) + _LOG2E (edata, "failed to get response code from curl easy handle"); + + _LOG2D (edata, "success getting %"G_GSIZE_FORMAT" bytes (response code %ld)", + edata->recv_data->len, + response_code); + + _LOG2T (edata, "received %"G_GSIZE_FORMAT" bytes: [[%s]]", + edata->recv_data->len, + nm_utils_buf_utf8safe_escape (edata->recv_data->str, edata->recv_data->len, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, &str_tmp_1)); + + _ehandle_free_ehandle (edata); + + get_result = g_slice_new (GetResult); + *get_result = (GetResult) { + .response_code = response_code, + /* This ensures that response_data is always NUL terminated. This is an important guarantee + * that NMHttpClient makes. */ + .response_data = g_string_free_to_bytes (g_steal_pointer (&edata->recv_data)), + }; + + g_task_return_pointer (edata->task, get_result, _get_result_free); + + _ehandle_free (edata); +} + +/*****************************************************************************/ + +static size_t +_get_writefunction_cb (char *ptr, size_t size, size_t nmemb, void *user_data) +{ + EHandleData *edata = user_data; + gsize nconsume; + + /* size should always be 1, but still. Multiply them to be sure. */ + nmemb *= size; + + if (edata->max_data >= 0) { + nm_assert (edata->recv_data->len <= edata->max_data); + nconsume = (((gsize) edata->max_data) - edata->recv_data->len); + if (nconsume > nmemb) + nconsume = nmemb; + } else + nconsume = nmemb; + + g_string_append_len (edata->recv_data, ptr, nconsume); + return nconsume; +} + +static gboolean +_get_timeout_cb (gpointer user_data) +{ + _ehandle_complete (user_data, + g_error_new_literal (NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "HTTP request timed out")); + return G_SOURCE_REMOVE; +} + +static void +_get_cancelled_cb (GObject *object, gpointer user_data) +{ + EHandleData *edata = user_data; + GError *error = NULL; + + nm_clear_g_signal_handler (g_task_get_cancellable (edata->task), + &edata->cancellable_id); + nm_utils_error_set_cancelled (&error, FALSE, NULL); + _ehandle_complete (edata, error); +} + +void +nm_http_client_get (NMHttpClient *self, + const char *url, + int timeout_msec, + gssize max_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMHttpClientPrivate *priv; + EHandleData *edata; + + g_return_if_fail (NM_IS_HTTP_CLIENT (self)); + g_return_if_fail (url); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (timeout_msec >= 0); + g_return_if_fail (max_data >= -1); + + priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + edata = g_slice_new (EHandleData); + *edata = (EHandleData) { + .task = nm_g_task_new (self, cancellable, nm_http_client_get, callback, user_data), + .recv_data = g_string_sized_new (NM_MIN (max_data, 245)), + .max_data = max_data, + .url = g_strdup (url), + }; + + nmcs_wait_for_objects_register (edata->task); + + _LOG2D (edata, "start get ..."); + + edata->ehandle = curl_easy_init (); + if (!edata->ehandle) { + _ehandle_complete (edata, + g_error_new_literal (NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "HTTP request failed to create curl handle")); + return; + } + + curl_easy_setopt (edata->ehandle, CURLOPT_URL, url); + + curl_easy_setopt (edata->ehandle, CURLOPT_WRITEFUNCTION, _get_writefunction_cb); + curl_easy_setopt (edata->ehandle, CURLOPT_WRITEDATA, edata); + curl_easy_setopt (edata->ehandle, CURLOPT_PRIVATE, edata); + + if (timeout_msec > 0) { + edata->timeout_source = _source_attach (self, + nm_g_timeout_source_new (timeout_msec, + G_PRIORITY_DEFAULT, + _get_timeout_cb, + edata, + NULL)); + } + + curl_multi_add_handle (priv->mhandle, edata->ehandle); + + if (cancellable) { + gulong signal_id; + + signal_id = g_cancellable_connect (cancellable, + G_CALLBACK (_get_cancelled_cb), + edata, + NULL); + if (signal_id == 0) { + /* the request is already cancelled. Return. */ + return; + } + edata->cancellable_id = signal_id; + } +} + +gboolean +nm_http_client_get_finish (NMHttpClient *self, + GAsyncResult *result, + long *out_response_code, + GBytes **out_response_data, + GError **error) +{ + GetResult *get_result; + + g_return_val_if_fail (NM_IS_HTTP_CLIENT (self), FALSE); + g_return_val_if_fail (nm_g_task_is_valid (result, self, nm_http_client_get), FALSE); + + get_result = g_task_propagate_pointer (G_TASK (result), error); + if (!get_result) { + NM_SET_OUT (out_response_code, -1); + NM_SET_OUT (out_response_data, NULL); + return FALSE; + } + + NM_SET_OUT (out_response_code, get_result->response_code); + + /* response_data is binary, but is also guaranteed to be NUL terminated! */ + NM_SET_OUT (out_response_data, g_steal_pointer (&get_result->response_data)); + + _get_result_free (get_result); + + return TRUE; +} + +/*****************************************************************************/ + +typedef struct { + GTask *task; + char *uri; + NMHttpClientPollGetCheckFcn check_fcn; + gpointer check_user_data; + GBytes *response_data; + gsize request_max_data; + long response_code; + int request_timeout_ms; +} PollGetData; + +static void +_poll_get_data_free (gpointer data) +{ + PollGetData *poll_get_data = data; + + g_free (poll_get_data->uri); + + nm_clear_pointer (&poll_get_data->response_data, g_bytes_unref); + + nm_g_slice_free (poll_get_data); +} + +static void +_poll_get_probe_start_fcn (GCancellable *cancellable, + gpointer probe_user_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + PollGetData *poll_get_data = probe_user_data; + + /* balanced by _poll_get_probe_finish_fcn() */ + g_object_ref (poll_get_data->task); + + nm_http_client_get (g_task_get_source_object (poll_get_data->task), + poll_get_data->uri, + poll_get_data->request_timeout_ms, + poll_get_data->request_max_data, + cancellable, + callback, + user_data); +} + +static gboolean +_poll_get_probe_finish_fcn (GObject *source, + GAsyncResult *result, + gpointer probe_user_data, + GError **error) +{ + PollGetData *poll_get_data = probe_user_data; + _nm_unused gs_unref_object GTask *task = poll_get_data->task; /* balance ref from _poll_get_probe_start_fcn() */ + gboolean success; + gs_free_error GError *local_error = NULL; + long response_code; + gs_unref_bytes GBytes *response_data = NULL; + + success = nm_http_client_get_finish (g_task_get_source_object (poll_get_data->task), + result, + &response_code, + &response_data, + &local_error); + + if (!success) { + if (nm_utils_error_is_cancelled (local_error, FALSE)) { + g_propagate_error (error, g_steal_pointer (&local_error)); + return TRUE; + } + return FALSE; + } + + if (poll_get_data->check_fcn) { + success = poll_get_data->check_fcn (response_code, + response_data, + poll_get_data->check_user_data, + &local_error); + } else + success = (response_code == 200); + + if (local_error) { + g_propagate_error (error, g_steal_pointer (&local_error)); + return TRUE; + } + + if (!success) + return FALSE; + + poll_get_data->response_code = response_code; + poll_get_data->response_data = g_steal_pointer (&response_data); + return TRUE; +} + +static void +_poll_get_done_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + PollGetData *poll_get_data = user_data; + gs_free_error GError *error = NULL; + gboolean success; + + success = nmcs_utils_poll_finish (result, NULL, &error); + + if (error) + g_task_return_error (poll_get_data->task, g_steal_pointer (&error)); + else + g_task_return_boolean (poll_get_data->task, success); + + g_object_unref (poll_get_data->task); +} + +void +nm_http_client_poll_get (NMHttpClient *self, + const char *uri, + int request_timeout_ms, + gssize request_max_data, + int poll_timeout_ms, + int ratelimit_timeout_ms, + GCancellable *cancellable, + NMHttpClientPollGetCheckFcn check_fcn, + gpointer check_user_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + nm_auto_pop_gmaincontext GMainContext *context = NULL; + PollGetData *poll_get_data; + + g_return_if_fail (NM_IS_HTTP_CLIENT (self)); + g_return_if_fail (uri && uri[0]); + g_return_if_fail (request_timeout_ms >= -1); + g_return_if_fail (request_max_data >= -1); + g_return_if_fail (poll_timeout_ms >= -1); + g_return_if_fail (ratelimit_timeout_ms >= -1); + g_return_if_fail (!cancellable || G_CANCELLABLE (cancellable)); + + poll_get_data = g_slice_new (PollGetData); + *poll_get_data = (PollGetData) { + .task = nm_g_task_new (self, cancellable, nm_http_client_poll_get, callback, user_data), + .uri = g_strdup (uri), + .request_timeout_ms = request_timeout_ms, + .request_max_data = request_max_data, + .check_fcn = check_fcn, + .check_user_data = check_user_data, + .response_code = -1, + }; + + nmcs_wait_for_objects_register (poll_get_data->task); + + g_task_set_task_data (poll_get_data->task, + poll_get_data, + _poll_get_data_free); + + 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_get_probe_start_fcn, + _poll_get_probe_finish_fcn, + poll_get_data, + cancellable, + _poll_get_done_cb, + poll_get_data); +} + +gboolean +nm_http_client_poll_get_finish (NMHttpClient *self, + GAsyncResult *result, + long *out_response_code, + GBytes **out_response_data, + GError **error) +{ + PollGetData *poll_get_data; + GTask *task; + gboolean success; + gs_free_error GError *local_error = NULL; + + g_return_val_if_fail (NM_HTTP_CLIENT (self), FALSE); + g_return_val_if_fail (nm_g_task_is_valid (result, self, nm_http_client_poll_get), FALSE); + + task = G_TASK (result); + + success = g_task_propagate_boolean (task, &local_error); + if ( local_error + || !success) { + if (local_error) + g_propagate_error (error, g_steal_pointer (&local_error)); + NM_SET_OUT (out_response_code, -1); + NM_SET_OUT (out_response_data, NULL); + return FALSE; + } + + poll_get_data = g_task_get_task_data (task); + + NM_SET_OUT (out_response_code, poll_get_data->response_code); + NM_SET_OUT (out_response_data, g_steal_pointer (&poll_get_data->response_data)); + + return TRUE; +} + +/*****************************************************************************/ + +static void +_mhandle_action (NMHttpClient *self, + int sockfd, + int ev_bitmask) +{ + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + EHandleData *edata; + CURLMsg *msg; + CURLcode eret; + int m_left; + CURLMcode ret; + int running_handles; + + ret = curl_multi_socket_action (priv->mhandle, sockfd, ev_bitmask, &running_handles); + if (ret != CURLM_OK) { + _LOGE ("curl: curl_multi_socket_action() failed: (%d) %s", ret, curl_multi_strerror (ret)); + /* really unexpected. Not clear how to handle this. */ + } + + while ((msg = curl_multi_info_read (priv->mhandle, &m_left))) { + + if (msg->msg != CURLMSG_DONE) + continue; + + eret = curl_easy_getinfo (msg->easy_handle, CURLINFO_PRIVATE, (char **) &edata); + + nm_assert (eret == CURLE_OK); + nm_assert (edata); + + edata->ehandle_result = msg->data.result; + _ehandle_complete (edata, NULL); + } +} + +static gboolean +_mhandle_socket_cb (int fd, + GIOCondition condition, + gpointer user_data) +{ + int ev_bitmask = 0; + + if (condition & G_IO_IN) + ev_bitmask |= CURL_CSELECT_IN; + if (condition & G_IO_OUT) + ev_bitmask |= CURL_CSELECT_OUT; + if (condition & G_IO_ERR) + ev_bitmask |= CURL_CSELECT_ERR; + + _mhandle_action (user_data, fd, ev_bitmask); + return G_SOURCE_CONTINUE; +} + +static int +_mhandle_socketfunction_cb (CURL *e_handle, curl_socket_t fd, int what, void *user_data, void *socketp) +{ + 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); + + if (what != CURL_POLL_REMOVE) { + GIOCondition condition = 0; + + if (what == CURL_POLL_IN) + condition = G_IO_IN; + else if (what == CURL_POLL_OUT) + condition = G_IO_OUT; + else if (what == CURL_POLL_INOUT) + condition = G_IO_IN | G_IO_OUT; + else + condition = 0; + + if (condition) { + priv->mhandle_source_socket = g_unix_fd_source_new (fd, condition); + g_source_set_callback (priv->mhandle_source_socket, G_SOURCE_FUNC (_mhandle_socket_cb), self, NULL); + g_source_attach (priv->mhandle_source_socket, priv->context); + } + } + + return CURLM_OK; +} + +static gboolean +_mhandle_timeout_cb (gpointer user_data) +{ + _mhandle_action (user_data, CURL_SOCKET_TIMEOUT, 0); + return G_SOURCE_CONTINUE; +} + +static int +_mhandle_timerfunction_cb (CURLM *multi, long timeout_msec, void *user_data) +{ + NMHttpClient *self = user_data; + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + nm_clear_pointer (&priv->mhandle_source_timeout, nm_g_source_destroy_and_unref); + if (timeout_msec >= 0) { + priv->mhandle_source_timeout = _source_attach (self, + nm_g_timeout_source_new (NM_MIN (timeout_msec, G_MAXINT), + G_PRIORITY_DEFAULT, + _mhandle_timeout_cb, + self, + NULL)); + } + return 0; +} + +/*****************************************************************************/ + +static void +nm_http_client_init (NMHttpClient *self) +{ +} + +static void +constructed (GObject *object) +{ + NMHttpClient *self = NM_HTTP_CLIENT (object); + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + priv->context = g_main_context_ref_thread_default (); + + priv->mhandle = curl_multi_init (); + if (!priv->mhandle) + _LOGE ("curl: failed to create multi-handle"); + else { + curl_multi_setopt (priv->mhandle, CURLMOPT_SOCKETFUNCTION, _mhandle_socketfunction_cb); + curl_multi_setopt (priv->mhandle, CURLMOPT_SOCKETDATA, self); + curl_multi_setopt (priv->mhandle, CURLMOPT_TIMERFUNCTION, _mhandle_timerfunction_cb); + curl_multi_setopt (priv->mhandle, CURLMOPT_TIMERDATA, self); + if (NM_CURL_DEBUG) + curl_multi_setopt (priv->mhandle, CURLOPT_VERBOSE, 1); + } + + G_OBJECT_CLASS (nm_http_client_parent_class)->constructed (object); +} + +NMHttpClient * +nm_http_client_new (void) +{ + return g_object_new (NM_TYPE_HTTP_CLIENT, NULL); +} + +static void +dispose (GObject *object) +{ + NMHttpClient *self = NM_HTTP_CLIENT (object); + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + nm_clear_pointer (&priv->mhandle, curl_multi_cleanup); + + 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); +} + +static void +finalize (GObject *object) +{ + NMHttpClient *self = NM_HTTP_CLIENT (object); + NMHttpClientPrivate *priv = NM_HTTP_CLIENT_GET_PRIVATE (self); + + G_OBJECT_CLASS (nm_http_client_parent_class)->finalize (object); + + g_main_context_unref (priv->context); + + curl_global_cleanup (); +} + +static void +nm_http_client_class_init (NMHttpClientClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + nm_http_client_curl_global_init (); +} diff --git a/clients/cloud-setup/nm-http-client.h b/clients/cloud-setup/nm-http-client.h new file mode 100644 index 00000000..86ee938e --- /dev/null +++ b/clients/cloud-setup/nm-http-client.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NM_HTTP_CLIENT_C__ +#define __NM_HTTP_CLIENT_C__ + +/*****************************************************************************/ + +typedef struct _NMHttpClient NMHttpClient; +typedef struct _NMHttpClientClass NMHttpClientClass; + +#define NM_TYPE_HTTP_CLIENT (nm_http_client_get_type ()) +#define NM_HTTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_HTTP_CLIENT, NMHttpClient)) +#define NM_HTTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_HTTP_CLIENT, NMHttpClientClass)) +#define NM_IS_HTTP_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_HTTP_CLIENT)) +#define NM_IS_HTTP_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_HTTP_CLIENT)) +#define NM_HTTP_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_HTTP_CLIENT, NMHttpClientClass)) + +GType nm_http_client_get_type (void); + +NMHttpClient *nm_http_client_new (void); + +/*****************************************************************************/ + +GMainContext *nm_http_client_get_main_context (NMHttpClient *self); + +/*****************************************************************************/ + +void nm_http_client_get (NMHttpClient *self, + const char *uri, + int timeout_msec, + gssize max_data, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean nm_http_client_get_finish (NMHttpClient *self, + GAsyncResult *result, + long *out_response_code, + GBytes **out_response_data, + GError **error); + +typedef gboolean (*NMHttpClientPollGetCheckFcn) (long response_code, + GBytes *response_data, + gpointer check_user_data, + GError **error); + +void nm_http_client_poll_get (NMHttpClient *self, + const char *uri, + int request_timeout_ms, + gssize request_max_data, + int poll_timeout_ms, + int ratelimit_timeout_ms, + GCancellable *cancellable, + NMHttpClientPollGetCheckFcn check_fcn, + gpointer check_user_data, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean nm_http_client_poll_get_finish (NMHttpClient *self, + GAsyncResult *result, + long *out_response_code, + GBytes **out_response_data, + GError **error); + +/*****************************************************************************/ + +#endif /* __NM_HTTP_CLIENT_C__ */ diff --git a/clients/cloud-setup/nmcs-provider-ec2.c b/clients/cloud-setup/nmcs-provider-ec2.c new file mode 100644 index 00000000..b6ac5a4c --- /dev/null +++ b/clients/cloud-setup/nmcs-provider-ec2.c @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nmcs-provider-ec2.h" + +#include "nm-cloud-setup-utils.h" + +/*****************************************************************************/ + +#define HTTP_TIMEOUT_MS 3000 + +#define NM_EC2_HOST "169.254.169.254" +#define NM_EC2_BASE "http://" NM_EC2_HOST +#define NM_EC2_API_VERSION "2018-09-24" +#define NM_EC2_METADATA_URL_BASE /* $NM_EC2_BASE/$NM_EC2_API_VERSION */ "/meta-data/network/interfaces/macs/" + +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")); + + if ( base + && base[0] + && !strchr (base, '/')) { + if ( NM_STR_HAS_PREFIX (base, "http://") + || NM_STR_HAS_PREFIX (base, "https://")) + base = g_intern_string (base); + else { + gs_free char *s = NULL; + + s = g_strconcat ("http://", base, NULL); + base = g_intern_string (s); + } + } + if (!base) + base = NM_EC2_BASE; + + nm_assert (!NM_STR_HAS_SUFFIX (base, "/")); + + if (!g_atomic_pointer_compare_and_exchange (&base_cached, NULL, base)) + goto again; + } + + return base; +} + +#define _ec2_uri_concat(...) nmcs_utils_uri_build_concat (_ec2_base (), __VA_ARGS__) +#define _ec2_uri_interfaces(...) _ec2_uri_concat (NM_EC2_API_VERSION, NM_EC2_METADATA_URL_BASE, ##__VA_ARGS__) + +/*****************************************************************************/ + +struct _NMCSProviderEC2 { + NMCSProvider parent; +}; + +struct _NMCSProviderEC2Class { + NMCSProviderClass parent; +}; + +G_DEFINE_TYPE (NMCSProviderEC2, nmcs_provider_ec2, NMCS_TYPE_PROVIDER); + +/*****************************************************************************/ + +static gboolean +_detect_get_meta_data_check_cb (long response_code, + GBytes *response_data, + gpointer check_user_data, + GError **error) +{ + return response_code == 200 + && nmcs_utils_parse_get_full_line (response_data, "ami-id"); +} + +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, FALSE)) { + 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 EC2 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 EC2 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 = _ec2_uri_concat ("latest/meta-data/")), + HTTP_TIMEOUT_MS, + 256*1024, + 7000, + 1000, + g_task_get_cancellable (task), + _detect_get_meta_data_check_cb, + NULL, + _detect_get_meta_data_done_cb, + task); +} + +/*****************************************************************************/ + +typedef struct { + NMCSProviderGetConfigTaskData *get_config_data; + GCancellable *cancellable; + gulong cancelled_id; + guint n_pending; +} GetConfigIfaceData; + +static void +_get_config_task_return (GetConfigIfaceData *iface_data, + GError *error_take) +{ + NMCSProviderGetConfigTaskData *get_config_data = iface_data->get_config_data; + + nm_clear_g_cancellable_disconnect (g_task_get_cancellable (get_config_data->task), + &iface_data->cancelled_id); + + nm_clear_g_cancellable (&iface_data->cancellable); + + nm_g_slice_free (iface_data); + + if (error_take) { + if (nm_utils_error_is_cancelled (error_take, FALSE)) + _LOGD ("get-config: cancelled"); + else + _LOGD ("get-config: failed: %s", error_take->message); + g_task_return_error (get_config_data->task, error_take); + } 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); + } + + g_object_unref (get_config_data->task); +} + +static void +_get_config_fetch_done_cb (NMHttpClient *http_client, + GAsyncResult *result, + gpointer user_data, + gboolean is_local_ipv4) +{ + GetConfigIfaceData *iface_data; + NMCSProviderGetConfigTaskData *get_config_data; + const char *hwaddr = NULL; + gs_unref_bytes GBytes *response_data = NULL; + gs_free_error GError *error = NULL; + gboolean success; + NMCSProviderGetConfigIfaceData *config_iface_data; + + nm_utils_user_data_unpack (user_data, &iface_data, &hwaddr); + + success = nm_http_client_poll_get_finish (http_client, + result, + NULL, + &response_data, + &error); + if (nm_utils_error_is_cancelled (error, FALSE)) + return; + + get_config_data = iface_data->get_config_data; + + config_iface_data = g_hash_table_lookup (get_config_data->result_dict, hwaddr); + + if (success) { + in_addr_t tmp_addr; + int tmp_prefix; + + if (is_local_ipv4) { + gs_free const char **s_addrs = NULL; + gsize i, len; + + s_addrs = nm_utils_strsplit_set_full (g_bytes_get_data (response_data, NULL), "\n", NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP); + len = NM_PTRARRAY_LEN (s_addrs); + + nm_assert (!config_iface_data->has_ipv4s); + nm_assert (!config_iface_data->ipv4s_arr); + config_iface_data->has_ipv4s = TRUE; + config_iface_data->ipv4s_len = 0; + if (len > 0) { + config_iface_data->ipv4s_arr = g_new (in_addr_t, len); + + for (i = 0; i < len; i++) { + if (nm_utils_parse_inaddr_bin (AF_INET, + s_addrs[i], + NULL, + &tmp_addr)) + config_iface_data->ipv4s_arr[config_iface_data->ipv4s_len++] = tmp_addr; + } + } + } else { + if (nm_utils_parse_inaddr_prefix_bin (AF_INET, + g_bytes_get_data (response_data, NULL), + NULL, + &tmp_addr, + &tmp_prefix)) { + nm_assert (!config_iface_data->has_cidr); + config_iface_data->has_cidr = TRUE; + config_iface_data->cidr_prefix = tmp_prefix; + config_iface_data->cidr_addr = tmp_addr; + } + } + } + + if (--iface_data->n_pending > 0) + return; + + _get_config_task_return (iface_data, NULL); +} + +static void +_get_config_fetch_done_cb_subnet_ipv4_cidr_block (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + _get_config_fetch_done_cb (NM_HTTP_CLIENT (source), result, user_data, FALSE); +} + +static void +_get_config_fetch_done_cb_local_ipv4s (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + _get_config_fetch_done_cb (NM_HTTP_CLIENT (source), result, user_data, TRUE); +} + +static void +_get_config_fetch_cancelled_cb (GObject *object, gpointer user_data) +{ + GetConfigIfaceData *iface_data = user_data; + + if (iface_data->cancelled_id == 0) + return; + + nm_clear_g_signal_handler (g_task_get_cancellable (iface_data->get_config_data->task), + &iface_data->cancelled_id); + _get_config_task_return (iface_data, + nm_utils_error_new_cancelled (FALSE, NULL)); +} + +typedef struct { + NMCSProviderGetConfigTaskData *get_config_data; + GHashTable *response_parsed; +} GetConfigMetadataData; + +typedef struct { + gssize iface_idx; + char path[0]; +} GetConfigMetadataMac; + +static void +_get_config_metadata_ready_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GetConfigMetadataData *metadata_data = user_data; + GetConfigIfaceData *iface_data; + NMCSProviderGetConfigTaskData *get_config_data = metadata_data->get_config_data; + gs_unref_hashtable GHashTable *response_parsed = g_steal_pointer (&metadata_data->response_parsed); + gs_free_error GError *error = NULL; + GCancellable *cancellable; + GetConfigMetadataMac *v_mac_data; + const char *v_hwaddr; + GHashTableIter h_iter; + NMHttpClient *http_client; + + nm_g_slice_free (metadata_data); + + nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source), + result, + NULL, + NULL, + &error); + + iface_data = g_slice_new (GetConfigIfaceData); + *iface_data = (GetConfigIfaceData) { + .get_config_data = get_config_data, + .n_pending = 0, + }; + + if (nm_utils_error_is_cancelled (error, FALSE)) { + _get_config_task_return (iface_data, g_steal_pointer (&error)); + return; + } + + /* We ignore errors. Only if we got no response at all, it's a problem. + * Otherwise, we proceed with whatever we could fetch. */ + if (!response_parsed) { + _get_config_task_return (iface_data, + nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN, + "meta data for interfaces not found")); + return; + } + + cancellable = g_task_get_cancellable (get_config_data->task); + if (cancellable) { + gulong cancelled_id; + + cancelled_id = g_cancellable_connect (cancellable, + G_CALLBACK (_get_config_fetch_cancelled_cb), + iface_data, + NULL); + if (cancelled_id == 0) { + _get_config_task_return (iface_data, + nm_utils_error_new_cancelled (FALSE, NULL)); + return; + } + + iface_data->cancelled_id = cancelled_id; + } + + iface_data->cancellable = g_cancellable_new (); + + http_client = nmcs_provider_get_http_client (g_task_get_source_object (get_config_data->task)); + + g_hash_table_iter_init (&h_iter, response_parsed); + while (g_hash_table_iter_next (&h_iter, (gpointer *) &v_hwaddr, (gpointer *) &v_mac_data)) { + 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)) { + 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); + } + + nm_assert (config_iface_data->iface_idx == -1); + config_iface_data->iface_idx = v_mac_data->iface_idx; + + _LOGD ("get-config: start fetching meta data for #%"G_GSSIZE_FORMAT", %s (%s)", config_iface_data->iface_idx, hwaddr, v_mac_data->path); + + iface_data->n_pending++; + nm_http_client_poll_get (http_client, + (uri1 = _ec2_uri_interfaces (v_mac_data->path, + NM_STR_HAS_SUFFIX (v_mac_data->path, "/") + ? "" + : "/", + "subnet-ipv4-cidr-block")), + HTTP_TIMEOUT_MS, + 512*1024, + 10000, + 1000, + iface_data->cancellable, + NULL, + NULL, + _get_config_fetch_done_cb_subnet_ipv4_cidr_block, + nm_utils_user_data_pack (iface_data, hwaddr)); + + iface_data->n_pending++; + nm_http_client_poll_get (http_client, + (uri2 = _ec2_uri_interfaces (v_mac_data->path, + NM_STR_HAS_SUFFIX (v_mac_data->path, "/") + ? "" + : "/", + "local-ipv4s")), + HTTP_TIMEOUT_MS, + 512*1024, + 10000, + 1000, + iface_data->cancellable, + NULL, + NULL, + _get_config_fetch_done_cb_local_ipv4s, + nm_utils_user_data_pack (iface_data, hwaddr)); + } + + if (iface_data->n_pending == 0) + _get_config_task_return (iface_data, NULL); +} + +static gboolean +_get_config_metadata_ready_check (long response_code, + GBytes *response_data, + gpointer check_user_data, + GError **error) +{ + GetConfigMetadataData *metadata_data = check_user_data; + gs_unref_hashtable GHashTable *response_parsed = NULL; + const guint8 *r_data; + gsize r_len; + GHashTableIter h_iter; + gboolean has_all; + const char *c_hwaddr; + gssize iface_idx_counter = 0; + + if ( response_code != 200 + || !response_data) { + /* we wait longer. */ + return FALSE; + } + + r_data = g_bytes_get_data (response_data, &r_len); + + while (r_len > 0) { + const guint8 *p_eol; + const char *p_start; + gsize p_start_l; + gsize p_start_l_2; + char *hwaddr; + GetConfigMetadataMac *mac_data; + + p_start = (const char *) r_data; + + p_eol = memchr (r_data, '\n', r_len); + if (p_eol) { + p_start_l = (p_eol - r_data); + r_len -= p_start_l + 1; + r_data = &p_eol[1]; + } else { + p_start_l = r_len; + r_data = &r_data[r_len]; + r_len = 0; + } + + if (p_start_l == 0) + continue; + + p_start_l_2 = p_start_l; + if (p_start[p_start_l_2 - 1] == '/') { + /* trim the trailing "/". */ + p_start_l_2--; + } + + hwaddr = nmcs_utils_hwaddr_normalize (p_start, p_start_l_2); + if (!hwaddr) + continue; + + if (!response_parsed) + response_parsed = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free); + + mac_data = g_malloc (sizeof (GetConfigMetadataData) + 1 + p_start_l); + mac_data->iface_idx = iface_idx_counter++; + memcpy (mac_data->path, p_start, p_start_l); + mac_data->path[p_start_l] = '\0'; + + g_hash_table_insert (response_parsed, hwaddr, mac_data); + } + + has_all = TRUE; + g_hash_table_iter_init (&h_iter, metadata_data->get_config_data->result_dict); + while (g_hash_table_iter_next (&h_iter, (gpointer *) &c_hwaddr, NULL)) { + if ( !response_parsed + || !g_hash_table_contains (response_parsed, c_hwaddr)) { + has_all = FALSE; + break; + } + } + + nm_clear_pointer (&metadata_data->response_parsed, g_hash_table_unref); + metadata_data->response_parsed = g_steal_pointer (&response_parsed); + return has_all; +} + +static void +get_config (NMCSProvider *provider, + NMCSProviderGetConfigTaskData *get_config_data) +{ + gs_free char *uri = NULL; + GetConfigMetadataData *metadata_data; + + metadata_data = g_slice_new (GetConfigMetadataData); + *metadata_data = (GetConfigMetadataData) { + .get_config_data = get_config_data, + }; + + /* First we fetch the "macs/". If the caller requested some particular + * MAC addresses, then we poll until we see them. They might not yet be + * around from the start... + */ + nm_http_client_poll_get (nmcs_provider_get_http_client (provider), + (uri = _ec2_uri_interfaces ()), + HTTP_TIMEOUT_MS, + 256 * 1024, + 15000, + 1000, + g_task_get_cancellable (get_config_data->task), + _get_config_metadata_ready_check, + metadata_data, + _get_config_metadata_ready_cb, + metadata_data); +} + +/*****************************************************************************/ + +static void +nmcs_provider_ec2_init (NMCSProviderEC2 *self) +{ +} + +static void +nmcs_provider_ec2_class_init (NMCSProviderEC2Class *klass) +{ + NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS (klass); + + provider_class->_name = "ec2"; + provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE ("NM_CLOUD_SETUP_EC2"); + provider_class->detect = detect; + provider_class->get_config = get_config; +} diff --git a/clients/cloud-setup/nmcs-provider-ec2.h b/clients/cloud-setup/nmcs-provider-ec2.h new file mode 100644 index 00000000..763c3af8 --- /dev/null +++ b/clients/cloud-setup/nmcs-provider-ec2.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NMCS_PROVIDER_EC2_H__ +#define __NMCS_PROVIDER_EC2_H__ + +#include "nmcs-provider.h" + +/*****************************************************************************/ + +typedef struct _NMCSProviderEC2 NMCSProviderEC2; +typedef struct _NMCSProviderEC2Class NMCSProviderEC2Class; + +#define NMCS_TYPE_PROVIDER_EC2 (nmcs_provider_ec2_get_type ()) +#define NMCS_PROVIDER_EC2(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMCS_TYPE_PROVIDER_EC2, NMCSProviderEC2)) +#define NMCS_PROVIDER_EC2_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NMCS_TYPE_PROVIDER_EC2, NMCSProviderEC2Class)) +#define NMCS_IS_PROVIDER_EC2(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMCS_TYPE_PROVIDER_EC2)) +#define NMCS_IS_PROVIDER_EC2_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMCS_TYPE_PROVIDER_EC2)) +#define NMCS_PROVIDER_EC2_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMCS_TYPE_PROVIDER_EC2, NMCSProviderEC2Class)) + +GType nmcs_provider_ec2_get_type (void); + +/*****************************************************************************/ + +#endif /* __NMCS_PROVIDER_EC2_H__ */ diff --git a/clients/cloud-setup/nmcs-provider.c b/clients/cloud-setup/nmcs-provider.c new file mode 100644 index 00000000..1f1b6e60 --- /dev/null +++ b/clients/cloud-setup/nmcs-provider.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#include "nm-default.h" + +#include "nmcs-provider.h" + +#include "nm-cloud-setup-utils.h" + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE ( + PROP_HTTP_CLIENT, +); + +typedef struct _NMCSProviderPrivate { + NMHttpClient *http_client; +} NMCSProviderPrivate; + +G_DEFINE_TYPE (NMCSProvider, nmcs_provider, G_TYPE_OBJECT); + +#define NMCS_PROVIDER_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMCSProvider, NMCS_IS_PROVIDER) + +/*****************************************************************************/ + +const char * +nmcs_provider_get_name (NMCSProvider *self) +{ + NMCSProviderClass *klass; + + g_return_val_if_fail (NMCS_IS_PROVIDER (self), NULL); + + klass = NMCS_PROVIDER_GET_CLASS (self); + nm_assert (klass->_name); + return klass->_name; +} + +/*****************************************************************************/ + +NMHttpClient * +nmcs_provider_get_http_client (NMCSProvider *self) +{ + g_return_val_if_fail (NMCS_IS_PROVIDER (self), NULL); + + return NMCS_PROVIDER_GET_PRIVATE (self)->http_client; +} + +GMainContext * +nmcs_provider_get_main_context (NMCSProvider *self) +{ + g_return_val_if_fail (NMCS_IS_PROVIDER (self), NULL); + + return nm_http_client_get_main_context (NMCS_PROVIDER_GET_PRIVATE (self)->http_client); +} + +/*****************************************************************************/ + +void +nmcs_provider_detect (NMCSProvider *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + gs_unref_object GTask *task = NULL; + const char *env; + + g_return_if_fail (NMCS_IS_PROVIDER (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = nm_g_task_new (self, cancellable, nmcs_provider_detect, callback, user_data); + + nmcs_wait_for_objects_register (task); + + 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")); + return; + } + + NMCS_PROVIDER_GET_CLASS (self)->detect (self, + g_steal_pointer (&task)); +} + +gboolean +nmcs_provider_detect_finish (NMCSProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (NMCS_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (nm_g_task_is_valid (result, self, nmcs_provider_detect), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +/*****************************************************************************/ + +NMCSProviderGetConfigIfaceData * +nmcs_provider_get_config_iface_data_new (gboolean was_requested) +{ + NMCSProviderGetConfigIfaceData *iface_data; + + iface_data = g_slice_new (NMCSProviderGetConfigIfaceData); + *iface_data = (NMCSProviderGetConfigIfaceData) { + .iface_idx = -1, + .was_requested = was_requested, + }; + return iface_data; +} + +static void +_iface_data_free (gpointer data) +{ + NMCSProviderGetConfigIfaceData *iface_data = data; + + g_free (iface_data->ipv4s_arr); + + nm_g_slice_free (iface_data); +} + +static void +_get_config_data_free (gpointer data) +{ + NMCSProviderGetConfigTaskData *get_config_data = data; + + if (get_config_data->extra_destroy) + get_config_data->extra_destroy (get_config_data->extra_data); + + nm_clear_pointer (&get_config_data->result_dict, g_hash_table_unref); + + nm_g_slice_free (get_config_data); +} + +void +nmcs_provider_get_config (NMCSProvider *self, + gboolean any, + const char *const*hwaddrs, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + NMCSProviderGetConfigTaskData *get_config_data; + + g_return_if_fail (NMCS_IS_PROVIDER (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + get_config_data = g_slice_new (NMCSProviderGetConfigTaskData); + *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), + }; + + g_task_set_task_data (get_config_data->task, get_config_data, _get_config_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)); + } + + _LOGD ("get-config: starting"); + + NMCS_PROVIDER_GET_CLASS (self)->get_config (self, get_config_data); +} + +GHashTable * +nmcs_provider_get_config_finish (NMCSProvider *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (NMCS_IS_PROVIDER (self), FALSE); + g_return_val_if_fail (nm_g_task_is_valid (result, self, nmcs_provider_get_config), FALSE); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/*****************************************************************************/ + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + NMCSProviderPrivate *priv = NMCS_PROVIDER_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_HTTP_CLIENT: + priv->http_client = g_value_dup_object (value); + g_return_if_fail (NM_IS_HTTP_CLIENT (priv->http_client)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nmcs_provider_init (NMCSProvider *self) +{ + NMCSProviderPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NMCS_TYPE_PROVIDER, NMCSProviderPrivate); + + self->_priv = priv; +} + +static void +dispose (GObject *object) +{ + NMCSProvider *self = NMCS_PROVIDER (object); + NMCSProviderPrivate *priv = NMCS_PROVIDER_GET_PRIVATE (self); + + g_clear_object (&priv->http_client); + + G_OBJECT_CLASS (nmcs_provider_parent_class)->dispose (object); +} + +static void +nmcs_provider_class_init (NMCSProviderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (NMCSProviderPrivate)); + + object_class->set_property = set_property; + object_class->dispose = dispose; + + obj_properties[PROP_HTTP_CLIENT] = + g_param_spec_object (NMCS_PROVIDER_HTTP_CLIENT, "", "", + NM_TYPE_HTTP_CLIENT, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/clients/cloud-setup/nmcs-provider.h b/clients/cloud-setup/nmcs-provider.h new file mode 100644 index 00000000..e5a44da1 --- /dev/null +++ b/clients/cloud-setup/nmcs-provider.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: LGPL-2.1+ + +#ifndef __NMCS_PROVIDER_H__ +#define __NMCS_PROVIDER_H__ + +/*****************************************************************************/ + +#include "nm-http-client.h" + +/*****************************************************************************/ + +typedef struct { + in_addr_t *ipv4s_arr; + gsize ipv4s_len; + gssize iface_idx; + in_addr_t cidr_addr; + guint8 cidr_prefix; + bool has_ipv4s:1; + bool has_cidr:1; + + /* TRUE, if the configuration was requested via hwaddrs argument to + * nmcs_provider_get_config(). */ + bool was_requested:1; + +} NMCSProviderGetConfigIfaceData; + +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_cidr + && config_data->has_ipv4s; +} + +NMCSProviderGetConfigIfaceData *nmcs_provider_get_config_iface_data_new (gboolean was_requested); + +typedef struct { + GTask *task; + GHashTable *result_dict; + gpointer extra_data; + GDestroyNotify extra_destroy; + bool any:1; +} NMCSProviderGetConfigTaskData; + +#define NMCS_TYPE_PROVIDER (nmcs_provider_get_type ()) +#define NMCS_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NMCS_TYPE_PROVIDER, NMCSProvider)) +#define NMCS_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NMCS_TYPE_PROVIDER, NMCSProviderClass)) +#define NMCS_IS_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NMCS_TYPE_PROVIDER)) +#define NMCS_IS_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NMCS_TYPE_PROVIDER)) +#define NMCS_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NMCS_TYPE_PROVIDER, NMCSProviderClass)) + +#define NMCS_PROVIDER_HTTP_CLIENT "http-client" + +struct _NMCSProviderPrivate; + +typedef struct { + GObject parent; + struct _NMCSProviderPrivate *_priv; +} NMCSProvider; + +typedef struct { + GObjectClass parent; + const char *_name; + const char *_env_provider_enabled; + + void (*detect) (NMCSProvider *self, + GTask *task); + + void (*get_config) (NMCSProvider *self, + NMCSProviderGetConfigTaskData *get_config_data); + +} NMCSProviderClass; + +GType nmcs_provider_get_type (void); + +/*****************************************************************************/ + +const char *nmcs_provider_get_name (NMCSProvider *provider); + +NMHttpClient *nmcs_provider_get_http_client (NMCSProvider *provider); +GMainContext *nmcs_provider_get_main_context (NMCSProvider *provider); + +/*****************************************************************************/ + +void nmcs_provider_detect (NMCSProvider *provider, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean nmcs_provider_detect_finish (NMCSProvider *provider, + GAsyncResult *result, + GError **error); + +/*****************************************************************************/ + +void nmcs_provider_get_config (NMCSProvider *provider, + gboolean any, + const char *const*hwaddrs, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GHashTable *nmcs_provider_get_config_finish (NMCSProvider *provider, + GAsyncResult *result, + GError **error); + +#endif /* __NMCS_PROVIDER_H__ */ |