summaryrefslogtreecommitdiff
path: root/src/nm-cloud-setup
diff options
context:
space:
mode:
authorWen Liang <liangwen12year@gmail.com>2021-06-06 21:15:12 -0400
committerThomas Haller <thaller@redhat.com>2021-07-19 17:41:52 +0200
commitf3404435a9fb24698dabdc80b52f52e9cc215730 (patch)
treee4b3b43e33c48bcd62974aafc25c91ced96bd69e /src/nm-cloud-setup
parent09daf5dd9220f5715397725c9c77b2ec9f571895 (diff)
downloadNetworkManager-f3404435a9fb24698dabdc80b52f52e9cc215730.tar.gz
cloud-setup: configure secondary ip in Aliyun cloud
This is a tool for automatically configuring networking in Aliyun cloud environment. This add a provider implementation for Aliyun that when detected fetches the private ip addressess and the subnet prefix of IPv4 CIDR block. Once this information is fetched from the metadata server, it instructs NetworkManager to add private ip addressess and subnet prefix for each interface detected. It is inspired by SuSE's cloud-netconfig ([1], [2]) and Aliyun Instance Metadata [3]. [1] https://www.suse.com/c/multi-nic-cloud-netconfig-ec2-azure/ [2] https://github.com/SUSE-Enceladus/cloud-netconfig [3] https://www.alibabacloud.com/help/doc-detail/49122.htm 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. https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/885 Signed-off-by: Wen Liang <liangwen12year@gmail.com>
Diffstat (limited to 'src/nm-cloud-setup')
-rw-r--r--src/nm-cloud-setup/main.c2
-rw-r--r--src/nm-cloud-setup/meson.build1
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup.service.in1
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.c471
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.h28
5 files changed, 503 insertions, 0 deletions
diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c
index 0667532bc1..dc87ac42e0 100644
--- a/src/nm-cloud-setup/main.c
+++ b/src/nm-cloud-setup/main.c
@@ -8,6 +8,7 @@
#include "nmcs-provider-ec2.h"
#include "nmcs-provider-gcp.h"
#include "nmcs-provider-azure.h"
+#include "nmcs-provider-aliyun.h"
#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
/*****************************************************************************/
@@ -85,6 +86,7 @@ _provider_detect(GCancellable *sigterm_cancellable)
NMCS_TYPE_PROVIDER_EC2,
NMCS_TYPE_PROVIDER_GCP,
NMCS_TYPE_PROVIDER_AZURE,
+ NMCS_TYPE_PROVIDER_ALIYUN,
};
int i;
gulong cancellable_signal_id;
diff --git a/src/nm-cloud-setup/meson.build b/src/nm-cloud-setup/meson.build
index 624a1043f4..ea4ad1131b 100644
--- a/src/nm-cloud-setup/meson.build
+++ b/src/nm-cloud-setup/meson.build
@@ -29,6 +29,7 @@ libnm_cloud_setup_core = static_library(
'nmcs-provider-ec2.c',
'nmcs-provider-gcp.c',
'nmcs-provider-azure.c',
+ 'nmcs-provider-aliyun.c',
'nmcs-provider.c',
),
dependencies: [
diff --git a/src/nm-cloud-setup/nm-cloud-setup.service.in b/src/nm-cloud-setup/nm-cloud-setup.service.in
index 809f707da1..f4b0e2638f 100644
--- a/src/nm-cloud-setup/nm-cloud-setup.service.in
+++ b/src/nm-cloud-setup/nm-cloud-setup.service.in
@@ -18,6 +18,7 @@ ExecStart=@libexecdir@/nm-cloud-setup
#Environment=NM_CLOUD_SETUP_EC2=yes
#Environment=NM_CLOUD_SETUP_GCP=yes
#Environment=NM_CLOUD_SETUP_AZURE=yes
+#Environment=NM_CLOUD_SETUP_ALIYUN=yes
CapabilityBoundingSet=
LockPersonality=yes
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.c b/src/nm-cloud-setup/nmcs-provider-aliyun.c
new file mode 100644
index 0000000000..b430b452d0
--- /dev/null
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.c
@@ -0,0 +1,471 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmcs-provider-aliyun.h"
+
+#include <arpa/inet.h>
+
+#include "nm-cloud-setup-utils.h"
+
+/*****************************************************************************/
+
+#define HTTP_TIMEOUT_MS 3000
+
+#define NM_ALIYUN_HOST "100.100.100.200"
+#define NM_ALIYUN_BASE "http://" NM_ALIYUN_HOST
+#define NM_ALIYUN_API_VERSION "2016-01-01"
+#define NM_ALIYUN_METADATA_URL_BASE /* $NM_ALIYUN_BASE/$NM_ALIYUN_API_VERSION */ \
+ "/meta-data/network/interfaces/macs/"
+
+static const char *
+_aliyun_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_ALIYUN_HOST"));
+
+ if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))
+ goto again;
+ }
+ base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_ALIYUN_BASE);
+ return base;
+}
+
+#define _aliyun_uri_concat(...) nmcs_utils_uri_build_concat(_aliyun_base(), __VA_ARGS__)
+#define _aliyun_uri_interfaces(...) \
+ _aliyun_uri_concat(NM_ALIYUN_API_VERSION, NM_ALIYUN_METADATA_URL_BASE, ##__VA_ARGS__)
+
+/*****************************************************************************/
+
+struct _NMCSProviderAliyun {
+ NMCSProvider parent;
+};
+
+struct _NMCSProviderAliyunClass {
+ NMCSProviderClass parent;
+};
+
+G_DEFINE_TYPE(NMCSProviderAliyun, nmcs_provider_aliyun, NMCS_TYPE_PROVIDER);
+
+/*****************************************************************************/
+
+static void
+filter_chars(char *str, const char *chars)
+{
+ gsize i;
+ gsize j;
+
+ for (i = 0, j = 0; str[i]; i++) {
+ if (!strchr(chars, str[i]))
+ str[j++] = str[i];
+ }
+ str[j] = '\0';
+}
+
+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 ALIYUN 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 = _aliyun_uri_concat(NM_ALIYUN_API_VERSION "/meta-data/")),
+ HTTP_TIMEOUT_MS,
+ 256 * 1024,
+ 7000,
+ 1000,
+ NULL,
+ g_task_get_cancellable(task),
+ NULL,
+ NULL,
+ _detect_get_meta_data_done_cb,
+ task);
+}
+
+/*****************************************************************************/
+
+typedef enum {
+ GET_CONFIG_FETCH_DONE_TYPE_SUBNET_VPC_CIDR_BLOCK,
+ GET_CONFIG_FETCH_DONE_TYPE_PRIVATE_IPV4S,
+ GET_CONFIG_FETCH_DONE_TYPE_NETMASK
+} GetConfigFetchDoneType;
+
+static void
+_get_config_fetch_done_cb(NMHttpClient * http_client,
+ GAsyncResult * result,
+ gpointer user_data,
+ GetConfigFetchDoneType fetch_type)
+{
+ NMCSProviderGetConfigTaskData *get_config_data;
+ const char * hwaddr = NULL;
+ gs_unref_bytes GBytes *response = NULL;
+ gs_free_error GError * error = NULL;
+ NMCSProviderGetConfigIfaceData *config_iface_data;
+ in_addr_t tmp_addr;
+ int tmp_prefix;
+ in_addr_t netmask_bin;
+ gs_free const char ** s_addrs = NULL;
+ gsize i;
+ gsize len;
+
+ nm_utils_user_data_unpack(user_data, &get_config_data, &hwaddr);
+
+ nm_http_client_poll_get_finish(http_client, result, NULL, &response, &error);
+
+ if (nm_utils_error_is_cancelled(error))
+ return;
+
+ if (error)
+ goto out;
+
+ config_iface_data = g_hash_table_lookup(get_config_data->result_dict, hwaddr);
+
+ switch (fetch_type) {
+ case GET_CONFIG_FETCH_DONE_TYPE_PRIVATE_IPV4S:
+
+ s_addrs = nm_utils_strsplit_set_full(g_bytes_get_data(response, NULL),
+ ",",
+ 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++) {
+ filter_chars((char *) s_addrs[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;
+ }
+ }
+ }
+ break;
+
+ case GET_CONFIG_FETCH_DONE_TYPE_SUBNET_VPC_CIDR_BLOCK:
+
+ if (nm_utils_parse_inaddr_prefix_bin(AF_INET,
+ g_bytes_get_data(response, NULL),
+ NULL,
+ &tmp_addr,
+ &tmp_prefix)) {
+ nm_assert(!config_iface_data->has_cidr);
+ config_iface_data->has_cidr = TRUE;
+ config_iface_data->cidr_addr = tmp_addr;
+ }
+ break;
+
+ case GET_CONFIG_FETCH_DONE_TYPE_NETMASK:
+
+ if (nm_utils_parse_inaddr_bin(AF_INET,
+ g_bytes_get_data(response, NULL),
+ NULL,
+ &netmask_bin)) {
+ config_iface_data->cidr_prefix = nm_utils_ip4_netmask_to_prefix(netmask_bin);
+ };
+ break;
+ }
+
+out:
+ get_config_data->n_pending--;
+ _nmcs_provider_get_config_task_maybe_return(get_config_data, g_steal_pointer(&error));
+}
+
+static void
+_get_config_fetch_done_cb_vpc_cidr_block(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ _get_config_fetch_done_cb(NM_HTTP_CLIENT(source),
+ result,
+ user_data,
+ GET_CONFIG_FETCH_DONE_TYPE_SUBNET_VPC_CIDR_BLOCK);
+}
+
+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,
+ GET_CONFIG_FETCH_DONE_TYPE_PRIVATE_IPV4S);
+}
+
+static void
+_get_config_fetch_done_cb_netmask(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ _get_config_fetch_done_cb(NM_HTTP_CLIENT(source),
+ result,
+ user_data,
+ GET_CONFIG_FETCH_DONE_TYPE_NETMASK);
+}
+
+typedef struct {
+ gssize iface_idx;
+ char path[0];
+} GetConfigMetadataMac;
+
+static void
+_get_config_metadata_ready_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ NMCSProviderGetConfigTaskData *get_config_data;
+ gs_unref_hashtable GHashTable *response_parsed = NULL;
+ gs_free_error GError *error = NULL;
+ GetConfigMetadataMac *v_mac_data;
+ const char * v_hwaddr;
+ GHashTableIter h_iter;
+ NMHttpClient * http_client;
+
+ nm_http_client_poll_get_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &error);
+
+ if (nm_utils_error_is_cancelled(error))
+ return;
+
+ get_config_data = user_data;
+
+ response_parsed = g_steal_pointer(&get_config_data->extra_data);
+ get_config_data->extra_data_destroy = NULL;
+
+ /* 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) {
+ _nmcs_provider_get_config_task_maybe_return(
+ get_config_data,
+ nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "meta data for interfaces not found"));
+ return;
+ }
+
+ 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;
+ gs_free char * uri3 = 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);
+
+ get_config_data->n_pending++;
+ nm_http_client_poll_get(
+ http_client,
+ (uri1 = _aliyun_uri_interfaces(v_mac_data->path,
+ NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
+ "vpc-cidr-block")),
+ HTTP_TIMEOUT_MS,
+ 512 * 1024,
+ 10000,
+ 1000,
+ NULL,
+ get_config_data->intern_cancellable,
+ NULL,
+ NULL,
+ _get_config_fetch_done_cb_vpc_cidr_block,
+ nm_utils_user_data_pack(get_config_data, hwaddr));
+
+ get_config_data->n_pending++;
+ nm_http_client_poll_get(
+ http_client,
+ (uri2 = _aliyun_uri_interfaces(v_mac_data->path,
+ NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
+ "private-ipv4s")),
+ HTTP_TIMEOUT_MS,
+ 512 * 1024,
+ 10000,
+ 1000,
+ NULL,
+ get_config_data->intern_cancellable,
+ NULL,
+ NULL,
+ _get_config_fetch_done_cb_private_ipv4s,
+ nm_utils_user_data_pack(get_config_data, hwaddr));
+
+ get_config_data->n_pending++;
+ nm_http_client_poll_get(
+ http_client,
+ (uri3 = _aliyun_uri_interfaces(v_mac_data->path,
+ NM_STR_HAS_SUFFIX(v_mac_data->path, "/") ? "" : "/",
+ "netmask")),
+ HTTP_TIMEOUT_MS,
+ 512 * 1024,
+ 10000,
+ 1000,
+ NULL,
+ get_config_data->intern_cancellable,
+ NULL,
+ NULL,
+ _get_config_fetch_done_cb_netmask,
+ nm_utils_user_data_pack(get_config_data, hwaddr));
+ }
+
+ _nmcs_provider_get_config_task_maybe_return(get_config_data, NULL);
+}
+
+static gboolean
+_get_config_metadata_ready_check(long response_code,
+ GBytes * response,
+ gpointer check_user_data,
+ GError **error)
+{
+ NMCSProviderGetConfigTaskData *get_config_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) {
+ /* we wait longer. */
+ return FALSE;
+ }
+
+ r_data = g_bytes_get_data(response, &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 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);
+
+ /* here we will ignore duplicate responses. */
+ g_hash_table_insert(response_parsed, hwaddr, mac_data);
+ }
+
+ has_all = TRUE;
+ g_hash_table_iter_init(&h_iter, 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(&get_config_data->extra_data, g_hash_table_unref);
+ if (response_parsed) {
+ get_config_data->extra_data = g_steal_pointer(&response_parsed);
+ get_config_data->extra_data_destroy = (GDestroyNotify) g_hash_table_unref;
+ }
+ return has_all;
+}
+
+static void
+get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data)
+{
+ gs_free char *uri = NULL;
+
+ /* 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 = _aliyun_uri_interfaces()),
+ HTTP_TIMEOUT_MS,
+ 256 * 1024,
+ 15000,
+ 1000,
+ NULL,
+ get_config_data->intern_cancellable,
+ _get_config_metadata_ready_check,
+ get_config_data,
+ _get_config_metadata_ready_cb,
+ get_config_data);
+}
+
+/*****************************************************************************/
+
+static void
+nmcs_provider_aliyun_init(NMCSProviderAliyun *self)
+{}
+
+static void
+nmcs_provider_aliyun_class_init(NMCSProviderAliyunClass *klass)
+{
+ NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
+
+ provider_class->_name = "aliyun";
+ provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_ALIYUN");
+ provider_class->detect = detect;
+ provider_class->get_config = get_config;
+}
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.h b/src/nm-cloud-setup/nmcs-provider-aliyun.h
new file mode 100644
index 0000000000..6e733a118b
--- /dev/null
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef __NMCS_PROVIDER_ALIYUN_H__
+#define __NMCS_PROVIDER_ALIYUN_H__
+
+#include "nmcs-provider.h"
+
+/*****************************************************************************/
+
+typedef struct _NMCSProviderAliyun NMCSProviderAliyun;
+typedef struct _NMCSProviderAliyunClass NMCSProviderAliyunClass;
+
+#define NMCS_TYPE_PROVIDER_ALIYUN (nmcs_provider_aliyun_get_type())
+#define NMCS_PROVIDER_ALIYUN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), NMCS_TYPE_PROVIDER_ALIYUN, NMCSProviderAliyun))
+#define NMCS_PROVIDER_ALIYUN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), NMCS_TYPE_PROVIDER_ALIYUN, NMCSProviderAliyunClass))
+#define NMCS_IS_PROVIDER_ALIYUN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMCS_TYPE_PROVIDER_ALIYUN))
+#define NMCS_IS_PROVIDER_ALIYUN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), NMCS_TYPE_PROVIDER_ALIYUN))
+#define NMCS_PROVIDER_ALIYUN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), NMCS_TYPE_PROVIDER_ALIYUN, NMCSProviderAliyunClass))
+
+GType nmcs_provider_aliyun_get_type(void);
+
+/*****************************************************************************/
+
+#endif /* __NMCS_PROVIDER_ALIYUN_H__ */