diff --git a/NEWS b/NEWS index d2f5a31d4b..427a1afe9c 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * Support automatically adding routes to DNS servers via the ipv4.routed-dns and ipv6.routed-dns properties; when enabled, each name server is reached only via the device that specifies it. +* Support OCI in nm-cloud-setup ============================================= NetworkManager-1.50 diff --git a/man/nm-cloud-setup.xml b/man/nm-cloud-setup.xml index 7f9a7dbc52..b24de6b982 100644 --- a/man/nm-cloud-setup.xml +++ b/man/nm-cloud-setup.xml @@ -184,6 +184,10 @@ NM_CLOUD_SETUP_ALIYUN: boolean, whether Alibaba Cloud (Aliyun) support is enabled. Defaults to no. + + NM_CLOUD_SETUP_OCI: boolean, whether Oracle Cloud (OCI) support is enabled. Defaults + to no. + @@ -417,6 +421,34 @@ ln -s /etc/systemd/system/timers.target.wants/nm-cloud-setup.timer /usr/lib/syst + + Oracle Cloud (OCI) + + For OCI, the tools tries to fetch configuration from http://169.254.169.254/. Currently, it only + configures IPv4 and does nothing about IPv6. It will do the following. + + + + First fetch http://169.254.169.254/opc/v2/instance to determine whether the + expected API is present. This determines whether OCI environment is detected and whether to proceed + to configure the host using OCI meta data. + + + Fetch http://169.254.169.254/opc/v2/vnics to get the configuration + for all the VNICs, getting their MAC address, private IP address, gateway and subnet block. + + + Then nm-cloud-setup iterates over all interfaces for which it could fetch a 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. Also, there is only one interface and one IP address, the tool does nothing. + Then the tool configures the system like doing for AWS environment. That is, using source based policy routing + with the tables/rules 30200/30400. + + + + diff --git a/meson.build b/meson.build index f535cd3725..b4449b0134 100644 --- a/meson.build +++ b/meson.build @@ -800,6 +800,7 @@ endif enable_nm_cloud_setup = get_option('nm_cloud_setup') if enable_nm_cloud_setup assert(libcurl_dep.found(), 'nm-cloud-setup requires libcurl library. Use -Dnm_cloud_setup=false to disable it') + assert(jansson_dep.found(), 'nm-cloud-setup requires jansson library. Use -Dnm_cloud_setup=false to disable it') endif enable_docs = get_option('docs') diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c index 6f614a607a..1de890be58 100644 --- a/src/nm-cloud-setup/main.c +++ b/src/nm-cloud-setup/main.c @@ -11,6 +11,7 @@ #include "nmcs-provider-gcp.h" #include "nmcs-provider-azure.h" #include "nmcs-provider-aliyun.h" +#include "nmcs-provider-oci.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" /*****************************************************************************/ @@ -104,6 +105,7 @@ _provider_detect(SigTermData *sigterm_data) NMCS_TYPE_PROVIDER_GCP, NMCS_TYPE_PROVIDER_AZURE, NMCS_TYPE_PROVIDER_ALIYUN, + NMCS_TYPE_PROVIDER_OCI, }; int i; gulong cancellable_signal_id; diff --git a/src/nm-cloud-setup/meson.build b/src/nm-cloud-setup/meson.build index 872b3352b8..adb425ecb0 100644 --- a/src/nm-cloud-setup/meson.build +++ b/src/nm-cloud-setup/meson.build @@ -36,12 +36,14 @@ libnm_cloud_setup_core = static_library( 'nmcs-provider-gcp.c', 'nmcs-provider-azure.c', 'nmcs-provider-aliyun.c', + 'nmcs-provider-oci.c', 'nmcs-provider.c', ), dependencies: [ libnm_dep, glib_dep, libcurl_dep, + jansson_dep, ], ) diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.h b/src/nm-cloud-setup/nm-cloud-setup-utils.h index 434f7fcf0b..c4662842d2 100644 --- a/src/nm-cloud-setup/nm-cloud-setup-utils.h +++ b/src/nm-cloud-setup/nm-cloud-setup-utils.h @@ -12,6 +12,7 @@ #define NMCS_ENV_NM_CLOUD_SETUP_AZURE "NM_CLOUD_SETUP_AZURE" #define NMCS_ENV_NM_CLOUD_SETUP_EC2 "NM_CLOUD_SETUP_EC2" #define NMCS_ENV_NM_CLOUD_SETUP_GCP "NM_CLOUD_SETUP_GCP" +#define NMCS_ENV_NM_CLOUD_SETUP_OCI "NM_CLOUD_SETUP_OCI" #define NMCS_ENV_NM_CLOUD_SETUP_LOG "NM_CLOUD_SETUP_LOG" /* Undocumented/internal environment variables for configuring nm-cloud-setup. @@ -20,6 +21,7 @@ #define NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST "NM_CLOUD_SETUP_AZURE_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST "NM_CLOUD_SETUP_EC2_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST "NM_CLOUD_SETUP_GCP_HOST" +#define NMCS_ENV_NM_CLOUD_SETUP_OCI_HOST "NM_CLOUD_SETUP_OCI_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES" /*****************************************************************************/ diff --git a/src/nm-cloud-setup/nm-cloud-setup.service.in b/src/nm-cloud-setup/nm-cloud-setup.service.in index cb782b2219..455f0ee066 100644 --- a/src/nm-cloud-setup/nm-cloud-setup.service.in +++ b/src/nm-cloud-setup/nm-cloud-setup.service.in @@ -31,6 +31,7 @@ ExecStart=@libexecdir@/nm-cloud-setup #Environment=NM_CLOUD_SETUP_GCP=yes #Environment=NM_CLOUD_SETUP_AZURE=yes #Environment=NM_CLOUD_SETUP_ALIYUN=yes +#Environment=NM_CLOUD_SETUP_OCI=yes CapabilityBoundingSet= KeyringMode=private diff --git a/src/nm-cloud-setup/nmcs-provider-oci.c b/src/nm-cloud-setup/nmcs-provider-oci.c new file mode 100644 index 0000000000..324d88bd84 --- /dev/null +++ b/src/nm-cloud-setup/nmcs-provider-oci.c @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-client-aux-extern/nm-default-client.h" +#include "nmcs-provider-oci.h" +#include "nm-cloud-setup-utils.h" +#include "libnm-glib-aux/nm-jansson.h" + +/*****************************************************************************/ + +#define HTTP_TIMEOUT_MS 3000 + +#define NM_OCI_HEADER "Authorization:Bearer Oracle" +#define NM_OCI_HOST "169.254.169.254" +#define NM_OCI_BASE "http://" NM_OCI_HOST + +NMCS_DEFINE_HOST_BASE(_oci_base, NMCS_ENV_NM_CLOUD_SETUP_OCI_HOST, NM_OCI_BASE); + +#define _oci_uri_concat(...) nmcs_utils_uri_build_concat(_oci_base(), "opc/v2/", __VA_ARGS__) + +/*****************************************************************************/ + +struct _NMCSProviderOCI { + NMCSProvider parent; +}; + +struct _NMCSProviderOCIClass { + NMCSProviderClass parent; +}; + +G_DEFINE_TYPE(NMCSProviderOCI, nmcs_provider_oci, NMCS_TYPE_PROVIDER); + +/*****************************************************************************/ + +static void +_detect_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_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 OCI instance data: %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_req(http_client, + (uri = _oci_uri_concat("instance")), + HTTP_TIMEOUT_MS, + 256 * 1024, + 7000, + 1000, + NM_MAKE_STRV(NM_OCI_HEADER), + NULL, + g_task_get_cancellable(task), + NULL, + NULL, + _detect_done_cb, + task); +} + +/*****************************************************************************/ + +static void +_get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + NMCSProviderGetConfigTaskData *get_config_data; + NMCSProviderGetConfigIfaceData *config_iface_data; + gs_unref_bytes GBytes *response = NULL; + gs_free_error GError *error = NULL; + nm_auto_decref_json json_t *vnics = NULL; + size_t i; + + nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error); + + if (nm_utils_error_is_cancelled(error)) + return; + + get_config_data = user_data; + + if (error) + goto out; + + vnics = json_loads(g_bytes_get_data(response, NULL), JSON_REJECT_DUPLICATES, NULL); + if (!vnics || !json_is_array(vnics)) { + nm_utils_error_set(&error, + NM_UTILS_ERROR_UNKNOWN, + "get-config: JSON parse failure, can't configure VNICs"); + goto out; + } + + for (i = 0; i < json_array_size(vnics); i++) { + json_t *vnic, *field; + const char *vnic_id, *val; + gs_free char *mac = NULL; + in_addr_t addr; + int prefix; + + vnic = json_array_get(vnics, i); + if (!json_is_object(vnic)) { + _LOGW("get-config: JSON parse failure for VNIC at index %zu, ignoring VNIC", i); + continue; + } + + field = json_object_get(vnic, "vnicId"); + vnic_id = field && json_is_string(field) ? json_string_value(field) : ""; + + field = json_object_get(vnic, "macAddr"); + val = field && json_is_string(field) ? json_string_value(field) : NULL; + if (!val) { + _LOGW("get-config: missing or invalid 'macAddr' (VNIC %s idx=%zu), ignoring VNIC", + vnic_id, + i); + continue; + } + + mac = nmcs_utils_hwaddr_normalize(val, json_string_length(field)); + config_iface_data = nmcs_provider_get_config_iface_data_create(get_config_data, FALSE, mac); + config_iface_data->iface_idx = i; + + field = json_object_get(vnic, "privateIp"); + val = field && json_is_string(field) ? json_string_value(field) : NULL; + if (val && nm_inet_parse_bin(AF_INET, val, NULL, &addr)) { + config_iface_data->has_ipv4s = TRUE; + config_iface_data->ipv4s_len = 1; + config_iface_data->ipv4s_arr = g_new(in_addr_t, 1); + config_iface_data->ipv4s_arr[0] = addr; + } else { + _LOGW("get-config: missing or invalid 'privateIp' (VNIC %s idx=%zu)", vnic_id, i); + } + + field = json_object_get(vnic, "virtualRouterIp"); + val = field && json_is_string(field) ? json_string_value(field) : NULL; + if (val && nm_inet_parse_bin(AF_INET, val, NULL, &addr)) { + config_iface_data->has_gateway = TRUE; + config_iface_data->gateway = addr; + } else { + _LOGW("get-config: missing or invalid 'virtualRouterIp' (VNIC %s idx=%zu)", vnic_id, i); + } + + field = json_object_get(vnic, "subnetCidrBlock"); + val = field && json_is_string(field) ? json_string_value(field) : NULL; + if (val && nm_inet_parse_with_prefix_bin(AF_INET, val, NULL, &addr, &prefix)) { + config_iface_data->has_cidr = TRUE; + config_iface_data->cidr_addr = addr; + config_iface_data->cidr_prefix = prefix; + } else { + _LOGW("get-config: missing or invalid 'subnetCidrBlock' (VNIC %s idx=%zu)", vnic_id, i); + } + } + +out: + _nmcs_provider_get_config_task_maybe_return(get_config_data, g_steal_pointer(&error)); +} + +static void +get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data) +{ + gs_free const char *uri = NULL; + + nm_http_client_poll_req(nmcs_provider_get_http_client(provider), + (uri = _oci_uri_concat("vnics")), + HTTP_TIMEOUT_MS, + 256 * 1024, + 15000, + 1000, + NM_MAKE_STRV(NM_OCI_HEADER), + NULL, + get_config_data->intern_cancellable, + NULL, + NULL, + _get_config_done_cb, + get_config_data); +} + +/*****************************************************************************/ + +static void +nmcs_provider_oci_init(NMCSProviderOCI *self) +{} + +static void +dispose(GObject *object) +{ + G_OBJECT_CLASS(nmcs_provider_oci_parent_class)->dispose(object); +} + +static void +nmcs_provider_oci_class_init(NMCSProviderOCIClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass); + + object_class->dispose = dispose; + + provider_class->_name = "oci"; + provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_OCI; + provider_class->detect = detect; + provider_class->get_config = get_config; +} diff --git a/src/nm-cloud-setup/nmcs-provider-oci.h b/src/nm-cloud-setup/nmcs-provider-oci.h new file mode 100644 index 0000000000..8447bc1c5b --- /dev/null +++ b/src/nm-cloud-setup/nmcs-provider-oci.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NMCS_PROVIDER_OCI_H__ +#define __NMCS_PROVIDER_OCI_H__ + +#include "nmcs-provider.h" + +/*****************************************************************************/ + +typedef struct _NMCSProviderOCI NMCSProviderOCI; +typedef struct _NMCSProviderOCIClass NMCSProviderOCIClass; + +#define NMCS_TYPE_PROVIDER_OCI (nmcs_provider_oci_get_type()) +#define NMCS_PROVIDER_OCI(obj) \ + (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCI)) +#define NMCS_PROVIDER_OCI_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCIClass)) +#define NMCS_IS_PROVIDER_OCI(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMCS_TYPE_PROVIDER_OCI)) +#define NMCS_IS_PROVIDER_OCI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMCS_TYPE_PROVIDER_OCI)) +#define NMCS_PROVIDER_OCI_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCIClass)) + +GType nmcs_provider_oci_get_type(void); + +/*****************************************************************************/ + +#endif /* __NMCS_PROVIDER_OCI_H__ */