summary refs log tree commit diff
path: root/clients/cloud-setup/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/cloud-setup/main.c')
-rw-r--r--clients/cloud-setup/main.c646
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;
+}