From 35779c6675728fa6f0fd0a21cefb904408509c23 Mon Sep 17 00:00:00 2001 From: Sebastien Bacher Date: Mon, 5 Jul 2021 20:35:03 +0200 Subject: New upstream version 1.32.2 --- src/nm-cloud-setup/main.c | 636 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 src/nm-cloud-setup/main.c (limited to 'src/nm-cloud-setup/main.c') diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c new file mode 100644 index 00000000..0667532b --- /dev/null +++ b/src/nm-cloud-setup/main.c @@ -0,0 +1,636 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-client-aux-extern/nm-default-client.h" + +#include "libnm-client-aux-extern/nm-libnm-aux.h" + +#include "nm-cloud-setup-utils.h" +#include "nmcs-provider-ec2.h" +#include "nmcs-provider-gcp.h" +#include "nmcs-provider-azure.h" +#include "libnm-core-aux-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)) + 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, + NMCS_TYPE_PROVIDER_GCP, + NMCS_TYPE_PROVIDER_AZURE, + }; + 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; +} + +/*****************************************************************************/ + +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)) + _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, + const NMCSProviderGetConfigIfaceData *config_data, + gboolean * out_changed) +{ + NMSettingIPConfig * s_ip; + NMActiveConnection *ac; + NMConnection * remote_connection; + NMSettingIPConfig * remote_s_ip = NULL; + gsize i; + in_addr_t gateway; + gint64 rt_metric; + guint32 rt_table; + NMIPRoute * route_entry; + gboolean addrs_changed = FALSE; + gboolean rules_changed = FALSE; + gboolean routes_changed = FALSE; + gs_unref_ptrarray GPtrArray *addrs_new = NULL; + gs_unref_ptrarray GPtrArray *rules_new = NULL; + gs_unref_ptrarray GPtrArray *routes_new = NULL; + + if (!nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_WIRED_SETTING_NAME)) + return FALSE; + + s_ip = nm_connection_get_setting_ip4_config(connection); + if (!s_ip) + return FALSE; + + if ((ac = nm_device_get_active_connection(device)) + && (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac)))) + remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection); + + addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref); + rules_new = + g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref); + routes_new = g_ptr_array_new_full(config_data->iproutes_len + !!config_data->ipv4s_len, + (GDestroyNotify) nm_ip_route_unref); + + if (remote_s_ip) { + guint len; + guint j; + + len = nm_setting_ip_config_get_num_addresses(remote_s_ip); + for (j = 0; j < len; j++) { + g_ptr_array_add(addrs_new, + nm_ip_address_dup(nm_setting_ip_config_get_address(remote_s_ip, j))); + } + + len = nm_setting_ip_config_get_num_routes(remote_s_ip); + for (j = 0; j < len; j++) { + g_ptr_array_add(routes_new, + nm_ip_route_dup(nm_setting_ip_config_get_route(remote_s_ip, j))); + } + + len = nm_setting_ip_config_get_num_routing_rules(remote_s_ip); + for (j = 0; j < len; j++) { + g_ptr_array_add( + rules_new, + nm_ip_routing_rule_ref(nm_setting_ip_config_get_routing_rule(remote_s_ip, j))); + } + } + + if (config_data->has_ipv4s && config_data->has_cidr) { + for (i = 0; i < config_data->ipv4s_len; i++) { + NMIPAddress *entry; + + entry = nm_ip_address_new_binary(AF_INET, + &config_data->ipv4s_arr[i], + config_data->cidr_prefix, + NULL); + if (entry) + g_ptr_array_add(addrs_new, entry); + } + + gateway = nm_utils_ip4_address_clear_host_address(config_data->cidr_addr, + config_data->cidr_prefix); + ((guint8 *) &gateway)[3] += 1; + + rt_metric = 10; + rt_table = 30400 + config_data->iface_idx; + + route_entry = + nm_ip_route_new_binary(AF_INET, &nm_ip_addr_zero, 0, &gateway, rt_metric, NULL); + nm_ip_route_set_attribute(route_entry, + NM_IP_ROUTE_ATTRIBUTE_TABLE, + g_variant_new_uint32(rt_table)); + g_ptr_array_add(routes_new, route_entry); + + for (i = 0; i < config_data->ipv4s_len; i++) { + NMIPRoutingRule *entry; + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + entry = nm_ip_routing_rule_new(AF_INET); + nm_ip_routing_rule_set_priority(entry, rt_table); + nm_ip_routing_rule_set_from(entry, + _nm_utils_inet4_ntop(config_data->ipv4s_arr[i], sbuf), + 32); + nm_ip_routing_rule_set_table(entry, rt_table); + + nm_assert(nm_ip_routing_rule_validate(entry, NULL)); + + g_ptr_array_add(rules_new, entry); + } + } + + for (i = 0; i < config_data->iproutes_len; ++i) + g_ptr_array_add(routes_new, config_data->iproutes_arr[i]); + + addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip, + (NMIPAddress **) addrs_new->pdata, + addrs_new->len); + + routes_changed = nmcs_setting_ip_replace_ipv4_routes(s_ip, + (NMIPRoute **) routes_new->pdata, + routes_new->len); + + rules_changed = nmcs_setting_ip_replace_ipv4_rules(s_ip, + (NMIPRoutingRule **) rules_new->pdata, + rules_new->len); + + 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)) + _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, 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)) { + _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; + gs_free_error GError *error = 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_client_new_waitsync(sigterm_cancellable, + &nmc, + &error, + NM_CLIENT_INSTANCE_FLAGS, + (guint) NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS, + NULL); + + nmcs_wait_for_objects_register(nmc); + nmcs_wait_for_objects_register(nm_client_get_context_busy_watcher(nmc)); + + if (error) { + if (!nm_utils_error_is_cancelled(error)) + _LOGI("failure to talk to NetworkManager: %s", error->message); + goto done; + } + + if (!nm_client_get_nm_running(nmc)) { + _LOGI("NetworkManager is not running"); + 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; +} -- cgit 1.3.0-6-gf8a5