// SPDX-License-Identifier: LGPL-2.1+ #include "nm-default.h" #include "nm-libnm-aux/nm-libnm-aux.h" #include "nm-cloud-setup-utils.h" #include "nmcs-provider-ec2.h" #include "nmcs-provider-gcp.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)) 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, }; 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; 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; 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 ( 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]); if (addrs_new->len) { addrs_changed = nmcs_setting_ip_replace_ipv4_addresses (s_ip, (NMIPAddress **) addrs_new->pdata, addrs_new->len); } if (routes_new->len) { routes_changed = nmcs_setting_ip_replace_ipv4_routes (s_ip, (NMIPRoute **) routes_new->pdata, routes_new->len); } if (rules_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; }