From f3404435a9fb24698dabdc80b52f52e9cc215730 Mon Sep 17 00:00:00 2001 From: Wen Liang Date: Sun, 6 Jun 2021 21:15:12 -0400 Subject: [PATCH] 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 --- Makefile.am | 2 + man/nm-cloud-setup.xml | 60 +++ src/nm-cloud-setup/main.c | 2 + src/nm-cloud-setup/meson.build | 1 + src/nm-cloud-setup/nm-cloud-setup.service.in | 1 + src/nm-cloud-setup/nmcs-provider-aliyun.c | 471 +++++++++++++++++++ src/nm-cloud-setup/nmcs-provider-aliyun.h | 28 ++ 7 files changed, 565 insertions(+) create mode 100644 src/nm-cloud-setup/nmcs-provider-aliyun.c create mode 100644 src/nm-cloud-setup/nmcs-provider-aliyun.h diff --git a/Makefile.am b/Makefile.am index 054a574ffa..0e72432039 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 = \ diff --git a/man/nm-cloud-setup.xml b/man/nm-cloud-setup.xml index 63fe8176f4..63a805eb6b 100644 --- a/man/nm-cloud-setup.xml +++ b/man/nm-cloud-setup.xml @@ -187,6 +187,10 @@ NM_CLOUD_SETUP_GCP: boolean, whether Google GCP support is enabled. Defaults to no. + + NM_CLOUD_SETUP_ALIYUN: boolean, whether Alibaba Cloud (Aliyun) support is enabled. Defaults + to no. + @@ -368,6 +372,62 @@ ln -s /etc/systemd/system/timers.target.wants/nm-cloud-setup.timer /usr/lib/syst + + Alibaba Cloud (Aliyun) + + For Aliyun, the tools tries to fetch configuration from http://100.100.100.200/. Currently, it only + configures IPv4 and does nothing about IPv6. It will do the following. + + + + First fetch http://100.100.100.200/2016-01-01/meta-data/ 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. + + + Fetch http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/ to get the list + of available interface. Interfaces are identified by their MAC address. + + + Then for each interface fetch http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/vpc-cidr-block + , http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/private-ipv4s and + http://100.100.100.200/2016-01-01/meta-data/network/interfaces/macs/$MAC/netmask. + Thereby we get a list of private IPv4 addresses, one CIDR subnet block and private IPv4 addresses prefix. + + + 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 org.freedesktop.nm-cloud-setup.skip=yes, + it is skipped. + Then, the tool will change the runtime configuration of the device. + + + Add static IPv4 addresses for all the configured addresses from private-ipv4s with + prefix length according to netmask. For example, + we might have here 2 IP addresses like "10.0.0.150/24,10.0.0.152/24". + + + Choose a route table 30400 + the index of the interface and + add a default route 0.0.0.0/0. The gateway + is the first IP address in the CIDR subnet block. For + example, we might get a route "0.0.0.0/0 10.0.0.1 10 table=30400". + + + Finally, add a policy routing rule for each address. For example + "priority 30400 from 10.0.0.150/32 table 30400, priority 30400 from 10.0.0.152/32 table 30400". + + + With above example, this roughly corresponds for interface eth0 to + 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". + 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. + + + + + 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 + +#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__ */