mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-07 20:40:21 +01:00
If we call g_cancellable_connect() on a GCancellable that is already cancelled, then the callback is invoked synchronously. We need to handle that. However, we can slightly simplify the code. There is no change in behavior, but we can always let the cancelled callback return the result.
525 lines
19 KiB
C
525 lines
19 KiB
C
/* 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 mainly for testing, it's not usually supposed to be configured.
|
|
* Consider this private API! */
|
|
base = g_getenv(NMCS_ENV_VARIABLE("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;
|
|
|
|
nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
|
|
|
|
if (nm_utils_error_is_cancelled(get_error)) {
|
|
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;
|
|
}
|
|
|
|
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,
|
|
NULL,
|
|
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;
|
|
GError * error;
|
|
GCancellable * cancellable;
|
|
gulong cancelled_id;
|
|
guint n_pending;
|
|
} GetConfigIfaceData;
|
|
|
|
static void
|
|
_get_config_task_maybe_return(GetConfigIfaceData *iface_data, GError *error_take)
|
|
{
|
|
NMCSProviderGetConfigTaskData *get_config_data = iface_data->get_config_data;
|
|
|
|
if (error_take) {
|
|
if (!iface_data->error)
|
|
iface_data->error = error_take;
|
|
else if (!nm_utils_error_is_cancelled(iface_data->error)
|
|
&& nm_utils_error_is_cancelled(error_take)) {
|
|
nm_clear_error(&iface_data->error);
|
|
iface_data->error = error_take;
|
|
} else
|
|
g_error_free(error_take);
|
|
|
|
nm_clear_g_cancellable(&iface_data->cancellable);
|
|
}
|
|
|
|
if (iface_data->n_pending > 0)
|
|
return;
|
|
|
|
nm_clear_g_cancellable_disconnect(g_task_get_cancellable(get_config_data->task),
|
|
&iface_data->cancelled_id);
|
|
|
|
nm_clear_g_cancellable(&iface_data->cancellable);
|
|
|
|
if (iface_data->error) {
|
|
if (nm_utils_error_is_cancelled(iface_data->error))
|
|
_LOGD("get-config: cancelled");
|
|
else
|
|
_LOGD("get-config: failed: %s", iface_data->error->message);
|
|
g_task_return_error(get_config_data->task, g_steal_pointer(&iface_data->error));
|
|
} 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);
|
|
}
|
|
|
|
nm_g_slice_free(iface_data);
|
|
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;
|
|
const char * hwaddr = NULL;
|
|
gs_unref_bytes GBytes *response_data = NULL;
|
|
gs_free_error GError *error = NULL;
|
|
|
|
nm_utils_user_data_unpack(user_data, &iface_data, &hwaddr);
|
|
|
|
nm_http_client_poll_get_finish(http_client, result, NULL, &response_data, &error);
|
|
|
|
if (!error) {
|
|
NMCSProviderGetConfigIfaceData *config_iface_data;
|
|
in_addr_t tmp_addr;
|
|
int tmp_prefix;
|
|
|
|
config_iface_data = g_hash_table_lookup(iface_data->get_config_data->result_dict, hwaddr);
|
|
|
|
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 nm_utils_error_is_cancelled(error), then our internal iface_data->cancellable
|
|
* was cancelled, because the overall request failed. From point of view of the
|
|
* caller, this does not mean that a cancellation happened. It also means, our
|
|
* request overall is already about to fail. */
|
|
nm_assert(!nm_utils_error_is_cancelled(error) || iface_data->error);
|
|
|
|
iface_data->n_pending--;
|
|
_get_config_task_maybe_return(iface_data,
|
|
nm_utils_error_is_cancelled(error) ? NULL
|
|
: g_steal_pointer(&error));
|
|
}
|
|
|
|
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;
|
|
|
|
nm_clear_g_signal_handler(g_task_get_cancellable(iface_data->get_config_data->task),
|
|
&iface_data->cancelled_id);
|
|
_get_config_task_maybe_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)) {
|
|
_get_config_task_maybe_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_maybe_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) {
|
|
/* the callback was already invoked synchronously and the task already returned. */
|
|
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,
|
|
NULL,
|
|
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,
|
|
NULL,
|
|
iface_data->cancellable,
|
|
NULL,
|
|
NULL,
|
|
_get_config_fetch_done_cb_local_ipv4s,
|
|
nm_utils_user_data_pack(iface_data, hwaddr));
|
|
}
|
|
|
|
_get_config_task_maybe_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;
|
|
const char * cur_line;
|
|
gsize r_len;
|
|
gsize cur_line_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);
|
|
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
|
|
nm_assert(r_data[r_len] == 0);
|
|
|
|
while (nm_utils_parse_next_line((const char **) &r_data, &r_len, &cur_line, &cur_line_len)) {
|
|
GetConfigMetadataMac *mac_data;
|
|
char * hwaddr;
|
|
|
|
if (cur_line_len == 0)
|
|
continue;
|
|
|
|
/* Truncate the string. It's safe to do, because we own @response_data an it has an
|
|
* extra NUL character after the buffer. */
|
|
((char *) cur_line)[cur_line_len] = '\0';
|
|
|
|
hwaddr = nmcs_utils_hwaddr_normalize(
|
|
cur_line,
|
|
cur_line[cur_line_len - 1u] == '/' ? (gssize)(cur_line_len - 1u) : -1);
|
|
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(GetConfigMetadataMac) + 1u + cur_line_len);
|
|
mac_data->iface_idx = iface_idx_counter++;
|
|
memcpy(mac_data->path, cur_line, cur_line_len + 1u);
|
|
|
|
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,
|
|
NULL,
|
|
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->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2");
|
|
provider_class->detect = detect;
|
|
provider_class->get_config = get_config;
|
|
}
|