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>
This commit is contained in:
Wen Liang 2021-06-06 21:15:12 -04:00 committed by Thomas Haller
parent 09daf5dd92
commit f3404435a9
No known key found for this signature in database
GPG Key ID: 29C2366E4DFC5728
7 changed files with 565 additions and 0 deletions

View File

@ -5137,6 +5137,8 @@ src_nm_cloud_setup_libnm_cloud_setup_core_a_SOURCES = \
src/nm-cloud-setup/nmcs-provider-gcp.h \
src/nm-cloud-setup/nmcs-provider-azure.c \
src/nm-cloud-setup/nmcs-provider-azure.h \
src/nm-cloud-setup/nmcs-provider-aliyun.c \
src/nm-cloud-setup/nmcs-provider-aliyun.h \
$(NULL)
src_nm_cloud_setup_libnm_cloud_setup_core_a_CPPFLAGS = \

View File

@ -187,6 +187,10 @@
<para><literal>NM_CLOUD_SETUP_GCP</literal>: boolean, whether Google GCP support is enabled. Defaults
to <literal>no</literal>.</para>
</listitem>
<listitem>
<para><literal>NM_CLOUD_SETUP_ALIYUN</literal>: boolean, whether Alibaba Cloud (Aliyun) support is enabled. Defaults
to <literal>no</literal>.</para>
</listitem>
</itemizedlist>
</refsect1>
@ -368,6 +372,62 @@ ln -s /etc/systemd/system/timers.target.wants/nm-cloud-setup.timer /usr/lib/syst
</itemizedlist>
</refsect2>
<refsect2>
<title>Alibaba Cloud (Aliyun)</title>
<para>For Aliyun, the tools tries to fetch configuration from <literal>http://100.100.100.200/</literal>. Currently, it only
configures IPv4 and does nothing about IPv6. It will do the following.</para>
<itemizedlist>
<listitem>
<para>First fetch <literal>http://100.100.100.200/2016-01-01/meta-data/</literal> to determine whether the
expected API is present. This determines whether Aliyun environment is detected and whether to proceed
to configure the host using Aliyun meta data.</para>
</listitem>
<listitem>
<para>Fetch <literal>http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/</literal> to get the list
of available interface. Interfaces are identified by their MAC address.</para>
</listitem>
<listitem>
<para>Then for each interface fetch <literal>http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/vpc-cidr-block</literal>
, <literal>http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/private-ipv4s</literal> and
<literal>http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/netmask</literal>.
Thereby we get a list of private IPv4 addresses, one CIDR subnet block and private IPv4 addresses prefix.</para>
</listitem>
<listitem>
<para>Then nm-cloud-setup iterates over all interfaces for which it could fetch IP configuration.
If no ethernet device for the respective MAC address is found, it is skipped.
Also, if the device is currently not activated in NetworkManager or if the currently
activated profile has a user-data <literal>org.freedesktop.nm-cloud-setup.skip=yes</literal>,
it is skipped.</para>
<para>Then, the tool will change the runtime configuration of the device.
<itemizedlist>
<listitem>
<para>Add static IPv4 addresses for all the configured addresses from <literal>private-ipv4s</literal> with
prefix length according to <literal>netmask</literal>. For example,
we might have here 2 IP addresses like <literal>"10.0.0.150/24,10.0.0.152/24"</literal>.</para>
</listitem>
<listitem>
<para>Choose a route table 30400 + the index of the interface and
add a default route <literal>0.0.0.0/0</literal>. The gateway
is the first IP address in the CIDR subnet block. For
example, we might get a route <literal>"0.0.0.0/0 10.0.0.1 10 table=30400"</literal>.</para>
</listitem>
<listitem>
<para>Finally, add a policy routing rule for each address. For example
<literal>"priority 30400 from 10.0.0.150/32 table 30400, priority 30400 from 10.0.0.152/32 table 30400"</literal>.</para>
</listitem>
</itemizedlist>
With above example, this roughly corresponds for interface <literal>eth0</literal> to
<command>nmcli device modify "eth0" ipv4.addresses "10.0.0.150/24,10.0.0.150/24" ipv4.routes "0.0.0.0/0 10.0.0.1 10 table=30400" ipv4.routing-rules "priority 30400 from 10.0.0.150/32 table 30400, priority 30400 from 10.0.0.152/32 table 30400"</command>.
Note that this replaces the previous addresses, routes and rules with the new information.
But also note that this only changes the run time configuration of the device. The
connection profile on disk is not affected.
</para>
</listitem>
</itemizedlist>
</refsect2>
</refsect1>
<refsect1>

View File

@ -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;

View File

@ -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: [

View File

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

View File

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

View File

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