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