NetworkManager/clients/cloud-setup/nmcs-provider-azure.c
Lubomir Rintel 0962b6e23d cloud-setup: make nm_http_client_req() accept a method argument
We'll need to be able to issue PUT calls.

Conflicts: variable alignments only, missing nmcs-provider-aliyun,
missing commit 494819bbbf ("cloud-setup: move common code for
get_config() to base class and improve cancellation")

(cherry picked from commit cd74d75002)
(cherry picked from commit eff4372045)
(cherry picked from commit aaf66e9174)
(cherry picked from commit 3d94f4fdf9)
(cherry picked from commit 181466c6da)
(cherry picked from commit 7243307bb8)
(cherry picked from commit 1aa88024cb)
(cherry picked from commit 59b5a8fdcb)
2023-09-18 11:50:27 +02:00

537 lines
18 KiB
C

/* SPDX-License-Identifier: LGPL-2.1+ */
#include "nm-default.h"
#include "nmcs-provider-azure.h"
#include "nm-cloud-setup-utils.h"
/*****************************************************************************/
#define HTTP_TIMEOUT_MS 3000
#define NM_AZURE_METADATA_HEADER "Metadata:true"
#define NM_AZURE_HOST "169.254.169.254"
#define NM_AZURE_BASE "http://" NM_AZURE_HOST
#define NM_AZURE_API_VERSION "?format=text&api-version=2017-04-02"
#define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \
"/metadata/instance/network/interface/"
#define _azure_uri_concat(...) \
nmcs_utils_uri_build_concat(NM_AZURE_BASE, __VA_ARGS__, NM_AZURE_API_VERSION)
#define _azure_uri_interfaces(...) _azure_uri_concat(NM_AZURE_METADATA_URL_BASE, ##__VA_ARGS__)
/*****************************************************************************/
struct _NMCSProviderAzure {
NMCSProvider parent;
};
struct _NMCSProviderAzureClass {
NMCSProviderClass parent;
};
G_DEFINE_TYPE(NMCSProviderAzure, nmcs_provider_azure, NMCS_TYPE_PROVIDER);
/*****************************************************************************/
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_req_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 Azure 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 azure 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_req(http_client,
(uri = _azure_uri_concat("/metadata/instance")),
HTTP_TIMEOUT_MS,
256 * 1024,
7000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(task),
NULL,
NULL,
_detect_get_meta_data_done_cb,
task);
}
/*****************************************************************************/
typedef struct {
NMCSProviderGetConfigTaskData *config_data;
guint n_ifaces_pending;
GError * error;
} AzureData;
typedef struct {
NMCSProviderGetConfigIfaceData *iface_get_config;
AzureData * azure_data;
gssize iface_idx;
guint n_ips_prefix_pending;
char * hwaddr;
} AzureIfaceData;
static void
_azure_iface_data_free(AzureIfaceData *iface_data)
{
g_free(iface_data->hwaddr);
nm_g_slice_free(iface_data);
}
static void
_get_config_maybe_task_return(AzureData *azure_data, GError *error_take)
{
NMCSProviderGetConfigTaskData *config_data = azure_data->config_data;
if (error_take) {
if (!azure_data->error)
azure_data->error = error_take;
else if (!nm_utils_error_is_cancelled(azure_data->error)
&& nm_utils_error_is_cancelled(error_take)) {
nm_clear_error(&azure_data->error);
azure_data->error = error_take;
} else
g_error_free(error_take);
}
if (azure_data->n_ifaces_pending > 0)
return;
if (azure_data->error) {
if (nm_utils_error_is_cancelled(azure_data->error))
_LOGD("get-config: cancelled");
else
_LOGD("get-config: failed: %s", azure_data->error->message);
g_task_return_error(config_data->task, g_steal_pointer(&azure_data->error));
} else {
_LOGD("get-config: success");
g_task_return_pointer(config_data->task,
g_hash_table_ref(config_data->result_dict),
(GDestroyNotify) g_hash_table_unref);
}
nm_g_slice_free(azure_data);
g_object_unref(config_data->task);
}
static void
_get_config_fetch_done_cb(NMHttpClient *http_client,
GAsyncResult *result,
gpointer user_data,
gboolean is_ipv4)
{
NMCSProviderGetConfigIfaceData *iface_get_config;
gs_unref_bytes GBytes *response = NULL;
AzureIfaceData * iface_data = user_data;
gs_free_error GError *error = NULL;
const char * fip_str = NULL;
AzureData * azure_data;
azure_data = iface_data->azure_data;
nm_http_client_poll_req_finish(http_client, result, NULL, &response, &error);
if (error)
goto done;
if (!error) {
in_addr_t tmp_addr;
int tmp_prefix;
fip_str = g_bytes_get_data(response, NULL);
iface_data->iface_get_config =
g_hash_table_lookup(azure_data->config_data->result_dict, iface_data->hwaddr);
iface_get_config = iface_data->iface_get_config;
iface_get_config->iface_idx = iface_data->iface_idx;
if (is_ipv4) {
if (!nm_utils_parse_inaddr_bin(AF_INET, fip_str, NULL, &tmp_addr)) {
error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN,
"ip is not a valid private ip address");
goto done;
}
_LOGD("interface[%" G_GSSIZE_FORMAT "]: adding private ip %s",
iface_data->iface_idx,
fip_str);
iface_get_config->ipv4s_arr[iface_get_config->ipv4s_len] = tmp_addr;
iface_get_config->has_ipv4s = TRUE;
iface_get_config->ipv4s_len++;
} else {
tmp_prefix = (_nm_utils_ascii_str_to_int64(fip_str, 10, 0, 32, -1));
if (tmp_prefix == -1) {
_LOGD("interface[%" G_GSSIZE_FORMAT "]: invalid prefix %d",
iface_data->iface_idx,
tmp_prefix);
goto done;
}
_LOGD("interface[%" G_GSSIZE_FORMAT "]: adding prefix %d",
iface_data->iface_idx,
tmp_prefix);
iface_get_config->cidr_prefix = tmp_prefix;
iface_get_config->has_cidr = TRUE;
}
}
done:
--iface_data->n_ips_prefix_pending;
if (iface_data->n_ips_prefix_pending == 0) {
_azure_iface_data_free(iface_data);
--azure_data->n_ifaces_pending;
_get_config_maybe_task_return(azure_data, g_steal_pointer(&error));
}
}
static void
_get_config_fetch_done_cb_private_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_done_cb_subnet_cidr_prefix(GObject * source,
GAsyncResult *result,
gpointer user_data)
{
_get_config_fetch_done_cb(NM_HTTP_CLIENT(source), result, user_data, FALSE);
}
static void
_get_config_ips_prefix_list_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
gs_unref_bytes GBytes *response = NULL;
AzureIfaceData * iface_data = user_data;
gs_free_error GError *error = NULL;
const char * response_str = NULL;
gsize response_len;
AzureData * azure_data;
const char * line;
gsize line_len;
azure_data = iface_data->azure_data;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
if (error)
goto done;
response_str = g_bytes_get_data(response, &response_len);
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
nm_assert(response_str[response_len] == 0);
nm_assert(!iface_data->iface_get_config->has_ipv4s);
nm_assert(!iface_data->iface_get_config->ipv4s_arr);
nm_assert(!iface_data->iface_get_config->has_cidr);
while (nm_utils_parse_next_line(&response_str, &response_len, &line, &line_len)) {
gint64 ips_prefix_idx;
if (line_len == 0)
continue;
/* Truncate the string. It's safe to do, because we own @response_data an it has an
* extra NULL character after the buffer. */
((char *) line)[line_len] = '\0';
if (line[line_len - 1] == '/')
((char *) line)[--line_len] = '\0';
ips_prefix_idx = _nm_utils_ascii_str_to_int64(line, 10, 0, G_MAXINT64, -1);
if (ips_prefix_idx < 0)
continue;
{
gs_free const char *uri = NULL;
char buf[100];
iface_data->n_ips_prefix_pending++;
nm_http_client_poll_req(
NM_HTTP_CLIENT(source),
(uri = _azure_uri_interfaces(nm_sprintf_buf(
buf,
"%" G_GSSIZE_FORMAT "/ipv4/ipAddress/%" G_GINT64_FORMAT "/privateIpAddress",
iface_data->iface_idx,
ips_prefix_idx))),
HTTP_TIMEOUT_MS,
512 * 1024,
10000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(azure_data->config_data->task),
NULL,
NULL,
_get_config_fetch_done_cb_private_ipv4s,
iface_data);
}
}
iface_data->iface_get_config->ipv4s_len = 0;
iface_data->iface_get_config->ipv4s_arr = g_new(in_addr_t, iface_data->n_ips_prefix_pending);
{
gs_free const char *uri = NULL;
char buf[30];
iface_data->n_ips_prefix_pending++;
nm_http_client_poll_req(
NM_HTTP_CLIENT(source),
(uri = _azure_uri_interfaces(
nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT, iface_data->iface_idx),
"/ipv4/subnet/0/prefix/")),
HTTP_TIMEOUT_MS,
512 * 1024,
10000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(azure_data->config_data->task),
NULL,
NULL,
_get_config_fetch_done_cb_subnet_cidr_prefix,
iface_data);
}
return;
done:
_azure_iface_data_free(iface_data);
--azure_data->n_ifaces_pending;
_get_config_maybe_task_return(azure_data, g_steal_pointer(&error));
}
static void
_get_config_iface_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
gs_unref_bytes GBytes *response = NULL;
AzureIfaceData * iface_data = user_data;
gs_free_error GError *error = NULL;
gs_free const char * uri = NULL;
char buf[100];
AzureData * azure_data;
azure_data = iface_data->azure_data;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
if (error)
goto done;
iface_data->hwaddr = nmcs_utils_hwaddr_normalize(g_bytes_get_data(response, NULL), -1);
if (!iface_data->hwaddr) {
goto done;
}
iface_data->iface_get_config =
g_hash_table_lookup(azure_data->config_data->result_dict, iface_data->hwaddr);
if (!iface_data->iface_get_config) {
if (!iface_data->azure_data->config_data->any) {
_LOGD("interface[%" G_GSSIZE_FORMAT "]: ignore hwaddr %s",
iface_data->iface_idx,
iface_data->hwaddr);
goto done;
}
iface_data->iface_get_config = nmcs_provider_get_config_iface_data_new(FALSE);
g_hash_table_insert(azure_data->config_data->result_dict,
g_strdup(iface_data->hwaddr),
iface_data->iface_get_config);
}
_LOGD("interface[%" G_GSSIZE_FORMAT "]: found a matching device with hwaddr %s",
iface_data->iface_idx,
iface_data->hwaddr);
nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/ipv4/ipAddress/", iface_data->iface_idx);
nm_http_client_poll_req(NM_HTTP_CLIENT(source),
(uri = _azure_uri_interfaces(buf)),
HTTP_TIMEOUT_MS,
512 * 1024,
10000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(azure_data->config_data->task),
NULL,
NULL,
_get_config_ips_prefix_list_cb,
iface_data);
return;
done:
nm_g_slice_free(iface_data);
--azure_data->n_ifaces_pending;
_get_config_maybe_task_return(azure_data, g_steal_pointer(&error));
}
static void
_get_net_ifaces_list_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
gs_unref_ptrarray GPtrArray *ifaces_arr = NULL;
gs_unref_bytes GBytes *response = NULL;
gs_free_error GError *error = NULL;
AzureData * azure_data = user_data;
const char * response_str;
gsize response_len;
const char * line;
gsize line_len;
guint i;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
if (error) {
_get_config_maybe_task_return(azure_data, g_steal_pointer(&error));
return;
}
response_str = g_bytes_get_data(response, &response_len);
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
nm_assert(response_str[response_len] == 0);
ifaces_arr = g_ptr_array_new();
while (nm_utils_parse_next_line(&response_str, &response_len, &line, &line_len)) {
AzureIfaceData *iface_data;
gssize iface_idx;
if (line_len == 0)
continue;
/* Truncate the string. It's safe to do, because we own @response_data an it has an
* extra NULL character after the buffer. */
((char *) line)[line_len] = '\0';
if (line[line_len - 1] == '/' && line_len != 0)
((char *) line)[--line_len] = '\0';
iface_idx = _nm_utils_ascii_str_to_int64(line, 10, 0, G_MAXSSIZE, -1);
if (iface_idx < 0)
continue;
iface_data = g_slice_new(AzureIfaceData);
*iface_data = (AzureIfaceData){
.iface_get_config = NULL,
.azure_data = azure_data,
.iface_idx = iface_idx,
.n_ips_prefix_pending = 0,
.hwaddr = NULL,
};
g_ptr_array_add(ifaces_arr, iface_data);
}
_LOGD("found azure interfaces: %u", ifaces_arr->len);
if (ifaces_arr->len == 0) {
error = nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "no Azure interfaces found");
_get_config_maybe_task_return(azure_data, g_steal_pointer(&error));
return;
}
for (i = 0; i < ifaces_arr->len; ++i) {
AzureIfaceData * data = ifaces_arr->pdata[i];
gs_free const char *uri = NULL;
char buf[100];
_LOGD("azure interface[%" G_GSSIZE_FORMAT "]: retrieving configuration", data->iface_idx);
nm_sprintf_buf(buf, "%" G_GSSIZE_FORMAT "/macAddress", data->iface_idx);
azure_data->n_ifaces_pending++;
nm_http_client_poll_req(NM_HTTP_CLIENT(source),
(uri = _azure_uri_interfaces(buf)),
HTTP_TIMEOUT_MS,
512 * 1024,
10000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(azure_data->config_data->task),
NULL,
NULL,
_get_config_iface_cb,
data);
}
}
static void
get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data)
{
gs_free const char *uri = NULL;
AzureData * azure_data;
azure_data = g_slice_new(AzureData);
*azure_data = (AzureData){
.config_data = get_config_data,
.n_ifaces_pending = 0,
};
nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
(uri = _azure_uri_interfaces()),
HTTP_TIMEOUT_MS,
256 * 1024,
15000,
1000,
NM_MAKE_STRV(NM_AZURE_METADATA_HEADER),
NULL,
g_task_get_cancellable(get_config_data->task),
NULL,
NULL,
_get_net_ifaces_list_cb,
azure_data);
}
/*****************************************************************************/
static void
nmcs_provider_azure_init(NMCSProviderAzure *self)
{}
static void
nmcs_provider_azure_class_init(NMCSProviderAzureClass *klass)
{
NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
provider_class->_name = "azure";
provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_AZURE");
provider_class->detect = detect;
provider_class->get_config = get_config;
}