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/main.c | |
| parent | e22609983008e1a669196ad64ba3a59ae8c76e0d (diff) | |
New upstream version 1.22.0 upstream/1.22.0
Diffstat (limited to 'clients/cloud-setup/main.c')
| -rw-r--r-- | clients/cloud-setup/main.c | 646 |
1 files changed, 646 insertions, 0 deletions
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; +} |