cloud-setup: add tool for automatic IP configuration in cloud

This is a tool for automatically configuring networking in a cloud
environment.

Currently it only supports IPv4 on EC2, but it's intended for extending
to other cloud providers (Azure). See [1] and [2] for how to configure
secondary IP addresses on EC2. This is what the tool currently aims to
do (but in the future it might do more).

[1] https://aws.amazon.com/premiumsupport/knowledge-center/ec2-ubuntu-secondary-network-interface/

It is inspired by SuSE's cloud-netconfig ([1], [2]) and ec2-net-utils
package on Amazon Linux ([3], [4]).

[1] https://www.suse.com/c/multi-nic-cloud-netconfig-ec2-azure/
[2] https://github.com/SUSE-Enceladus/cloud-netconfig
[3] https://github.com/aws/ec2-net-utils
[4] https://github.com/lorengordon/ec2-net-utils.git

It is also intended to work without configuration. The main point is
that you boot an image with NetworkManager and nm-cloud-setup enabled,
and it just works.
This commit is contained in:
Thomas Haller 2019-11-12 15:54:22 +01:00
parent 2b6f5a305c
commit 69f048bf0c
24 changed files with 3586 additions and 5 deletions

2
.gitignore vendored
View file

@ -65,6 +65,8 @@ test-*.trs
/dispatcher/tests/test-dispatcher-envp
/clients/cli/nmcli
/clients/cloud-setup/nm-cloud-setup
/clients/cloud-setup/nm-cloud-setup.service
/clients/common/settings-docs.h
/clients/common/tests/test-clients-common
/clients/common/tests/test-libnm-core-aux

View file

@ -4636,6 +4636,87 @@ EXTRA_DIST += \
clients/tui/meson.build \
clients/tui/newt/meson.build
###############################################################################
# clients/nm-cloud-setup
###############################################################################
if BUILD_NM_CLOUD_SETUP
libexec_PROGRAMS += clients/cloud-setup/nm-cloud-setup
clients_cloud_setup_nm_cloud_setup_SOURCES = \
clients/cloud-setup/main.c \
clients/cloud-setup/nm-cloud-setup-utils.c \
clients/cloud-setup/nm-cloud-setup-utils.h \
clients/cloud-setup/nm-http-client.c \
clients/cloud-setup/nm-http-client.h \
clients/cloud-setup/nmcs-provider.c \
clients/cloud-setup/nmcs-provider.h \
clients/cloud-setup/nmcs-provider-ec2.c \
clients/cloud-setup/nmcs-provider-ec2.h \
$(NULL)
clients_cloud_setup_nm_cloud_setup_CPPFLAGS = \
$(clients_cppflags) \
-DG_LOG_DOMAIN=\""nm-cloud-setup"\" \
$(LIBCURL_CFLAGS) \
$(NULL)
clients_cloud_setup_nm_cloud_setup_LDFLAGS = \
-Wl,--version-script="$(srcdir)/linker-script-binary.ver" \
$(SANITIZER_EXEC_LDFLAGS) \
$(NULL)
clients_cloud_setup_nm_cloud_setup_LDADD = \
shared/nm-libnm-core-aux/libnm-libnm-core-aux.la \
shared/nm-libnm-core-intern/libnm-libnm-core-intern.la \
shared/nm-glib-aux/libnm-glib-aux.la \
shared/nm-std-aux/libnm-std-aux.la \
shared/libcsiphash.la \
libnm/libnm.la \
$(GLIB_LIBS) \
$(LIBCURL_LIBS) \
$(NULL)
$(clients_cloud_setup_nm_cloud_setup_OBJECTS): $(libnm_core_lib_h_pub_mkenums)
$(clients_cloud_setup_nm_cloud_setup_OBJECTS): $(libnm_lib_h_pub_mkenums)
if HAVE_SYSTEMD
systemdsystemunit_DATA += \
clients/cloud-setup/nm-cloud-setup.service \
clients/cloud-setup/nm-cloud-setup.timer \
$(NULL)
clients/cloud-setup/nm-cloud-setup.service: $(srcdir)/clients/cloud-setup/nm-cloud-setup.service.in
$(AM_V_GEN) $(data_edit) $< >$@
install-data-hook-cloud-setup: install-data-hook-dispatcher
$(INSTALL_SCRIPT) "$(srcdir)/clients/cloud-setup/90-nm-cloud-setup.sh" "$(DESTDIR)$(nmlibdir)/dispatcher.d/no-wait.d/"
ln -fs no-wait.d/90-nm-cloud-setup.sh "$(DESTDIR)$(nmlibdir)/dispatcher.d/90-nm-cloud-setup.sh"
install_data_hook += install-data-hook-cloud-setup
uninstall-hook-cloud-setup:
rm -f "$(DESTDIR)$(nmlibdir)/dispatcher.d/no-wait.d/90-nm-cloud-setup.sh"
rm -f "$(DESTDIR)$(nmlibdir)/dispatcher.d/90-nm-cloud-setup.sh"
uninstall_hook += uninstall-hook-cloud-setup
endif
EXTRA_DIST += \
clients/cloud-setup/90-nm-cloud-setup.sh \
clients/cloud-setup/meson.build \
clients/cloud-setup/nm-cloud-setup.service.in \
clients/cloud-setup/nm-cloud-setup.timer \
$(NULL)
CLEANFILES += \
clients/cloud-setup/nm-cloud-setup.service
endif
###############################################################################
# clients/tests
###############################################################################

2
NEWS
View file

@ -29,6 +29,8 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* libnm: heavily internal rework NMClient. This slims down libnm and makes the
implementation more efficient. NMClient should work now well with a separate
GMainContext.
* nm-cloud-setup: add new tool for automatically configuring NetworkManager
in cloud. Currently only EC2 and IPv4 is supported.
=============================================
NetworkManager-1.20

View file

@ -0,0 +1,7 @@
#!/bin/sh
case "$2" in
up|dhcp4-change)
exec systemctl --no-block restart nm-cloud-setup.service
;;
esac

646
clients/cloud-setup/main.c Normal file
View file

@ -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 ("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;
}

View file

@ -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,
)

View file

@ -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_ms: 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_ms)
{
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_ms >= 0) {
timeout_source = nm_g_source_attach (nm_g_timeout_source_new (timeout_ms,
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;
}

View file

@ -0,0 +1,121 @@
// 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"
/*****************************************************************************/
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_ms);
/*****************************************************************************/
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__ */

View file

@ -0,0 +1,8 @@
[Unit]
Description=Automatically configure NetworkManager in cloud
[Service]
Type=oneshot
ExecStart=@libexecdir@/nm-cloud-setup
#Environment=NM_CLOUD_SETUP_LOG=TRACE

View file

@ -0,0 +1,9 @@
[Unit]
Description=Periodically run nm-cloud-setup
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target

View file

@ -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_ms,
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_ms >= 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_ms > 0) {
edata->timeout_source = _source_attach (self,
nm_g_timeout_source_new (timeout_ms,
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_ms, 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_ms >= 0) {
priv->mhandle_source_timeout = _source_attach (self,
nm_g_timeout_source_new (NM_MIN (timeout_ms, 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 ();
}

View file

@ -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_ms,
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__ */

View file

@ -0,0 +1,551 @@
// 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 only for testing, not really to be configurable! */
base = g_getenv ("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->detect = detect;
provider_class->get_config = get_config;
}

View file

@ -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__ */

View file

@ -0,0 +1,236 @@
// 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;
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);
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);
}

View file

@ -0,0 +1,107 @@
// 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;
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__ */

View file

@ -26,3 +26,7 @@ endif
if enable_nmtui
subdir('tui')
endif
if enable_nm_cloud_setup
subdir('cloud-setup')
endif

View file

@ -1014,6 +1014,17 @@ else
fi
AM_CONDITIONAL(BUILD_NMCLI, test "$build_nmcli" = yes)
AC_ARG_WITH(nm-cloud-setup,
AS_HELP_STRING([--with-nm-cloud-setup=yes|no], [Build nm-cloud-setup]))
if test "$with_nm_cloud_setup" != no; then
PKG_CHECK_MODULES(LIBCURL, [libcurl >= 7.24.0], [have_libcurl=yes], [have_libcurl=no])
if test "$have_libcurl" != "yes"; then
AC_MSG_ERROR(--with-nm-cloud-setup requires libcurl library.)
fi
with_nm_cloud_setup=yes
fi
AM_CONDITIONAL(BUILD_NM_CLOUD_SETUP, test "$with_nm_cloud_setup" == yes)
AC_ARG_WITH(nmtui,
AS_HELP_STRING([--with-nmtui=yes|no], [Build nmtui]))
if test "$with_nmtui" != no; then
@ -1303,6 +1314,7 @@ echo " libteamdctl: $enable_teamdctl"
echo " ovs: $enable_ovs"
echo " nmcli: $build_nmcli"
echo " nmtui: $build_nmtui"
echo " nm-cloud-setup: $with_nm_cloud_setup"
echo " iwd: $ac_with_iwd"
echo

View file

@ -42,6 +42,8 @@
%global systemd_units NetworkManager.service NetworkManager-wait-online.service NetworkManager-dispatcher.service
%global systemd_units_cloud_setup nm-cloud-setup.service nm-cloud-setup.timer
###############################################################################
%bcond_with meson
@ -53,6 +55,7 @@
%bcond_without ovs
%bcond_without ppp
%bcond_without nmtui
%bcond_without nm_cloud_setup
%bcond_without regen_docs
%bcond_with debug
%bcond_with test
@ -480,6 +483,19 @@ by nm-connection-editor and nm-applet in a non-graphical environment.
%endif
%if %{with nm_cloud_setup}
%package cloud-setup
Summary: Automatically configure NetworkManager in cloud
Group: System Environment/Base
Requires: %{name} = %{epoch}:%{version}-%{release}
Requires: %{name}-libnm%{?_isa} = %{epoch}:%{version}-%{release}
%description cloud-setup
Installs a nm-cloud-setup tool that can automatically configure
NetworkManager in cloud setups. Currently only EC2 is supported.
%endif
%prep
%autosetup -p1 -n NetworkManager-%{real_version}
@ -538,6 +554,11 @@ by nm-connection-editor and nm-applet in a non-graphical environment.
-Dnmtui=true \
%else
-Dnmtui=false \
%endif
%if %{with nm_cloud_setup}
-Dnm_cloud_setup=true \
%else
-Dnm_cloud_setup=false \
%endif
-Dvapi=true \
-Dintrospection=true \
@ -659,6 +680,11 @@ intltoolize --automake --copy --force
--with-nmtui=yes \
%else
--with-nmtui=no \
%endif
%if %{with nm_cloud_setup}
--with-nm-cloud-setup=yes \
%else
--with-nm-cloud-setup=no \
%endif
--enable-vala=yes \
--enable-introspection \
@ -801,6 +827,12 @@ else
fi
%if %{with nm_cloud_setup}
%post cloud-setup
%systemd_post %{systemd_units_cloud_setup}
%endif
%preun
if [ $1 -eq 0 ]; then
# Package removal, not upgrade
@ -814,6 +846,12 @@ fi
%systemd_preun NetworkManager-wait-online.service NetworkManager-dispatcher.service
%if %{with nm_cloud_setup}
%preun cloud-setup
%systemd_preun %{systemd_units_cloud_setup}
%endif
%postun
/usr/bin/udevadm control --reload-rules || :
/usr/bin/udevadm trigger --subsystem-match=net || :
@ -827,6 +865,12 @@ fi
%endif
%if %{with nm_cloud_setup}
%postun cloud-setup
%systemd_postun %{systemd_units_cloud_setup}
%endif
%files
%{dbus_sys_dir}/org.freedesktop.NetworkManager.conf
%{dbus_sys_dir}/nm-dispatcher.conf
@ -995,5 +1039,15 @@ fi
%endif
%if %{with nm_cloud_setup}
%files cloud-setup
%{_libexecdir}/nm-cloud-setup
%{systemd_dir}/nm-cloud-setup.service
%{systemd_dir}/nm-cloud-setup.timer
%{nmlibdir}/dispatcher.d/90-nm-cloud-setup.sh
%{nmlibdir}/dispatcher.d/no-wait.d/90-nm-cloud-setup.sh
%endif
%changelog
__CHANGELOG__

View file

@ -154,6 +154,7 @@ if [[ $NO_DIST != 1 ]]; then
--with-config-logging-backend-default=syslog \
--with-libaudit=yes-disabled-by-default \
--enable-polkit=yes \
--with-nm-cloud-setup=yes \
--with-config-dhcp-default=internal \
--with-config-dns-rc-manager-default=symlink \
|| die "Error autogen.sh"

View file

@ -661,9 +661,10 @@ if enable_libpsl
endif
config_h.set10('WITH_LIBPSL', enable_libpsl)
libcurl_dep = dependency('libcurl', version: '>= 7.24.0', required: false)
enable_concheck = get_option('concheck')
if enable_concheck
libcurl_dep = dependency('libcurl', version: '>= 7.24.0', required: false)
assert(libcurl_dep.found(), 'concheck requires libcurl library. Use -Dconcheck=false to disable it')
endif
config_h.set10('WITH_CONCHECK', enable_concheck)
@ -694,6 +695,11 @@ if enable_nmtui
assert(newt_dep.found(), 'You must have libnewt installed to build nmtui. Use -Dnmtui=false to disable it')
endif
enable_nm_cloud_setup = get_option('nm_cloud_setup')
if enable_nm_cloud_setup
assert(libcurl_dep.found(), 'nm-cloud-setup requires libcurl library. Use -Dnm_cloud_setup=false to disable it')
endif
more_asserts = get_option('more_asserts')
if more_asserts == 'no'
more_asserts = 0
@ -910,6 +916,8 @@ meson.add_install_script(
nm_sysconfdir,
enable_docs ? '1' : '0',
enable_ifcfg_rh ? '1' : '0',
enable_nm_cloud_setup ? '1' : '0',
install_systemdunitdir ? '1' : '0',
)
output = '\nSystem paths:\n'
@ -954,6 +962,7 @@ output += ' libteamdctl: ' + enable_teamdctl.to_string() + '\n'
output += ' ovs: ' + enable_ovs.to_string() + '\n'
output += ' nmcli: ' + enable_nmcli.to_string() + '\n'
output += ' nmtui: ' + enable_nmtui.to_string() + '\n'
output += ' nm-cloud-setup: ' + enable_nm_cloud_setup.to_string() + '\n'
output += '\nConfiguration_plugins (main.plugins=' + config_plugins_default + ')\n'
output += ' ifcfg-rh: ' + enable_ifcfg_rh.to_string() + '\n'
output += ' ifupdown: ' + enable_ifupdown.to_string() + '\n'

View file

@ -36,6 +36,7 @@ option('teamdctl', type: 'boolean', value: false, description: 'enable Teamd con
option('ovs', type: 'boolean', value: true, description: 'enable Open vSwitch support')
option('nmcli', type: 'boolean', value: true, description: 'Build nmcli')
option('nmtui', type: 'boolean', value: true, description: 'Build nmtui')
option('nm_cloud_setup', type: 'boolean', value: false, description: 'Build nm_cloud_setup')
option('bluez5_dun', type: 'boolean', value: false, description: 'enable Bluez5 DUN support')
option('ebpf', type: 'combo', choices : ['auto', 'true', 'false'], description: 'Enable eBPF support')

View file

@ -1,13 +1,15 @@
clients/cloud-setup/nm-cloud-setup.service.in
contrib/fedora/rpm/
data/NetworkManager.service.in
data/org.freedesktop.NetworkManager.policy.in
examples/python/NetworkManager.py
examples/python/systray/eggtrayicon.c
data/org.freedesktop.NetworkManager.policy.in
shared/nm-utils/nm-vpn-editor-plugin-call.h
shared/nm-utils/nm-vpn-plugin-utils.c
vpn-daemons/openvpn
vpn-daemons/pptp
vpn-daemons/vpnc
contrib/fedora/rpm/
shared/nm-utils/nm-vpn-editor-plugin-call.h
shared/nm-utils/nm-vpn-plugin-utils.c
# https://bugs.launchpad.net/intltool/+bug/1117944
sub/data/org.freedesktop.NetworkManager.policy.in

View file

@ -9,6 +9,8 @@ nm_mandir="$6"
nm_sysconfdir="$7"
enable_docs="$8"
enable_ifcfg_rh="$9"
enable_nm_cloud_setup="${10}"
install_systemdunitdir="${11}"
[ -n "$DESTDIR" ] && DESTDIR="${DESTDIR%%/}/"
@ -55,3 +57,8 @@ fi
if [ "$enable_ifcfg_rh" = 1 ]; then
mkdir -p "${DESTDIR}${nm_sysconfdir}/sysconfig/network-scripts"
fi
if [ "$enable_nm_cloud_setup" = 1 -a "$install_systemdunitdir" = 1 ]; then
ln -s 'no-wait.d/90-nm-cloud-setup.sh' "${DESTDIR}${nm_pkglibdir}/dispatcher.d/90-nm-cloud-setup.sh"
fi