summary refs log tree commit diff
path: root/src/nm-cloud-setup
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-cloud-setup')
-rw-r--r--src/nm-cloud-setup/main.c388
-rw-r--r--src/nm-cloud-setup/nmcs-provider-oci.c77
-rw-r--r--src/nm-cloud-setup/nmcs-provider.c9
-rw-r--r--src/nm-cloud-setup/nmcs-provider.h6
4 files changed, 394 insertions, 86 deletions
diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c
index 44500ffa..9de93418 100644
--- a/src/nm-cloud-setup/main.c
+++ b/src/nm-cloud-setup/main.c
@@ -140,6 +140,8 @@ out:
 
 /*****************************************************************************/
 
+static const NMUtilsNamedValue *gl_interfaces_map = NULL;
+
 static NMUtilsNamedValue *
 _map_interfaces_parse(void)
 {
@@ -200,46 +202,38 @@ _map_interfaces_parse(void)
 static const char *
 _device_get_hwaddr(NMDevice *device)
 {
-    static const NMUtilsNamedValue *gl_map_interfaces_map = NULL;
-    static gsize                    gl_initialized        = 0;
-    const NMUtilsNamedValue        *map                   = NULL;
+    const NMUtilsNamedValue *map = NULL;
 
     nm_assert(NM_IS_DEVICE_ETHERNET(device) || NM_IS_DEVICE_MACVLAN(device)
               || NM_IS_DEVICE_VLAN(device));
 
-    /* Network interfaces in cloud environments are identified by their permanent
-     * MAC address.
-     *
-     * For testing, we can set NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES
-     * to a ';' separate list of "$INTERFACE=$HWADDR", which means that we
-     * pretend that device with ip-interface "$INTERFACE" has the specified permanent
-     * MAC address. */
-
-    if (g_once_init_enter(&gl_initialized)) {
-        gl_map_interfaces_map = _map_interfaces_parse();
-        g_once_init_leave(&gl_initialized, 1);
-    }
-
-    map = gl_map_interfaces_map;
-    if (G_UNLIKELY(map)) {
-        const char *const iface = nm_device_get_iface(NM_DEVICE(device));
-
-        /* For testing, the device<->hwaddr is remapped and the actual permanent
-         * MAC address of the device ignored. This mapping is configured via
-         * NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES environment variable. */
+    if (NM_IS_DEVICE_ETHERNET(device)) {
+        /* Ethernet interfaces in cloud environments are identified by their permanent
+         * MAC address.
+         *
+         * For testing, we can set NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES
+         * to a ';' separate list of "$INTERFACE=$HWADDR", which means that we
+         * pretend that device with ip-interface "$INTERFACE" has the specified permanent
+         * MAC address. */
+
+        map = gl_interfaces_map;
+        if (G_UNLIKELY(map)) {
+            const char *const iface = nm_device_get_iface(NM_DEVICE(device));
+
+            /* For testing, the device<->hwaddr is remapped and the actual permanent
+             * MAC address of the device ignored. This mapping is configured via
+             * NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES environment variable. */
+            if (!iface)
+                return NULL;
+
+            for (; map->name; map++) {
+                if (nm_streq(map->name, iface))
+                    return map->value_str;
+            }
 
-        if (!iface)
             return NULL;
-
-        for (; map->name; map++) {
-            if (nm_streq(map->name, iface))
-                return map->value_str;
         }
 
-        return NULL;
-    }
-
-    if (NM_IS_DEVICE_ETHERNET(device)) {
         return nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
     } else {
         return nm_device_get_hw_address(device);
@@ -295,7 +289,7 @@ _nmc_get_ethernet_hwaddrs(NMClient *nmc)
 }
 
 static NMDevice *
-_nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
+_nmc_get_device_by_hwaddr(NMClient *nmc, const GType type_device, const char *hwaddr)
 {
     const GPtrArray *devices;
     guint            i;
@@ -307,7 +301,7 @@ _nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
         const char   *hwaddr_dev;
         gs_free char *s = NULL;
 
-        if (!NM_IS_DEVICE_ETHERNET(device))
+        if (!G_TYPE_CHECK_INSTANCE_TYPE(device, type_device))
             continue;
 
         hwaddr_dev = _device_get_hwaddr(device);
@@ -427,13 +421,22 @@ _nmc_mangle_connection(NMDevice                             *device,
     NM_SET_OUT(out_skipped_single_addr, FALSE);
     NM_SET_OUT(out_changed, FALSE);
 
+    if (nm_streq(nm_connection_get_connection_type(connection), NM_SETTING_MACVLAN_SETTING_NAME)) {
+        /* The MACVLAN just sits in between, no L3 configuration on it */
+        return;
+    } else if (!nm_streq(nm_connection_get_connection_type(connection),
+                         NM_SETTING_VLAN_SETTING_NAME)) {
+        /* Preserve existing L3 configuration if not a VLAN */
+        if (device) {
+            if ((ac = nm_device_get_active_connection(device))
+                && (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
+                remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection);
+        }
+    }
+
     s_ip = nm_connection_get_setting_ip4_config(connection);
     nm_assert(NM_IS_SETTING_IP4_CONFIG(s_ip));
 
-    if ((ac = nm_device_get_active_connection(device))
-        && (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
-        remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection);
-
     addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref);
     rules_new =
         g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref);
@@ -566,54 +569,31 @@ _nmc_mangle_connection(NMDevice                             *device,
 /*****************************************************************************/
 
 static gboolean
-_config_one(SigTermData                       *sigterm_data,
-            NMClient                          *nmc,
-            const NMCSProviderGetConfigResult *result,
-            guint                              idx)
+_config_existing(SigTermData                          *sigterm_data,
+                 const NMCSProviderGetConfigIfaceData *config_data,
+                 NMClient                             *nmc,
+                 const NMCSProviderGetConfigResult    *result,
+                 const char                           *connection_type,
+                 NMDevice                             *device)
 {
-    const NMCSProviderGetConfigIfaceData *config_data        = result->iface_datas_arr[idx];
-    const char                           *hwaddr             = config_data->hwaddr;
-    gs_unref_object NMDevice             *device             = NULL;
-    gs_unref_object NMConnection         *applied_connection = NULL;
-    guint64                               applied_version_id;
-    gs_free_error GError                 *error = NULL;
-    gboolean                              changed;
-    gboolean                              skipped_single_addr;
-    gboolean                              version_id_changed;
-    guint                                 try_count;
-    gboolean                              any_changes = FALSE;
-    gboolean                              maybe_no_preserved_external_ip;
-
-    g_main_context_iteration(NULL, FALSE);
-
-    if (g_cancellable_is_cancelled(sigterm_data->cancellable))
-        return FALSE;
-
-    device = nm_g_object_ref(_nmc_get_device_by_hwaddr(nmc, hwaddr));
-    if (!device) {
-        _LOGD("config device %s: skip because device not found", hwaddr);
-        return FALSE;
-    }
-
-    if (!nmcs_provider_get_config_iface_data_is_valid(config_data)) {
-        _LOGD("config device %s: skip because meta data not successfully fetched", hwaddr);
-        return FALSE;
-    }
-
-    if (config_data->iface_idx >= 100) {
-        /* since we use the iface_idx to select a table number, the range is limited from
-         * 0 to 99. Note that the providers are required to provide increasing numbers,
-         * so this means we bail out after the first 100 devices.  */
-        _LOGD("config device %s: skip because number of supported interfaces reached", hwaddr);
-        return FALSE;
-    }
+    const char                   *hwaddr             = config_data->hwaddr;
+    gs_unref_object NMConnection *applied_connection = NULL;
+    guint64                       applied_version_id;
+    gs_free_error GError         *error = NULL;
+    gboolean                      changed;
+    gboolean                      skipped_single_addr;
+    gboolean                      version_id_changed;
+    guint                         try_count;
+    gboolean                      any_changes;
+    gboolean                      maybe_no_preserved_external_ip;
 
     _LOGD("config device %s: configuring \"%s\" (%s)...",
           hwaddr,
           nm_device_get_iface(device) ?: "/unknown/",
           nm_object_get_path(NM_OBJECT(device)));
 
-    try_count = 0;
+    try_count   = 0;
+    any_changes = FALSE;
 
 try_again:
     g_clear_object(&applied_connection);
@@ -638,7 +618,7 @@ try_again:
         return any_changes;
     }
 
-    if (_nmc_skip_connection_by_type(applied_connection, NM_SETTING_WIRED_SETTING_NAME)) {
+    if (_nmc_skip_connection_by_type(applied_connection, connection_type)) {
         _LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
         return any_changes;
     }
@@ -705,7 +685,7 @@ try_again:
                   nm_connection_get_uuid(applied_connection),
                   error->message);
         }
-        return any_changes;
+        return TRUE;
     }
 
     _LOGD("config device %s: connection \"%s\" (%s) reapplied",
@@ -713,17 +693,254 @@ try_again:
           nm_connection_get_id(applied_connection),
           nm_connection_get_uuid(applied_connection));
 
+    return TRUE;
+}
+
+static gboolean
+_config_ethernet(SigTermData                          *sigterm_data,
+                 const NMCSProviderGetConfigIfaceData *config_data,
+                 NMClient                             *nmc,
+                 const NMCSProviderGetConfigResult    *result)
+{
+    gs_unref_object NMDevice *device = NULL;
+
+    device = nm_g_object_ref(
+        _nmc_get_device_by_hwaddr(nmc, NM_TYPE_DEVICE_ETHERNET, config_data->hwaddr));
+    if (!device) {
+        _LOGD("config device %s: skip because device not found", config_data->hwaddr);
+        return FALSE;
+    }
+
+    return _config_existing(sigterm_data,
+                            config_data,
+                            nmc,
+                            result,
+                            NM_SETTING_WIRED_SETTING_NAME,
+                            device);
+}
+
+static gboolean
+_oci_new_vlan_dev(SigTermData                          *sigterm_data,
+                  const NMCSProviderGetConfigIfaceData *config_data,
+                  NMClient                             *nmc,
+                  const NMCSProviderGetConfigResult    *result,
+                  const char                           *connection_type,
+                  const char                           *parent_hwaddr)
+{
+    const char                         *hwaddr            = config_data->hwaddr;
+    gs_unref_object NMConnection       *connection        = NULL;
+    gs_unref_object NMActiveConnection *active_connection = NULL;
+    gs_free_error GError               *error             = NULL;
+    gs_free char                       *macvlan_name      = NULL;
+    gs_free char                       *connection_id     = NULL;
+    char                               *ifname            = NULL;
+    const char                         *macvlan_parent    = NULL;
+    const char                         *wired_mac_addr    = NULL;
+    const NMUtilsNamedValue            *map               = NULL;
+    const char                         *ip4_config_method;
+    NMSetting                          *s_user;
+
+    connection = nm_simple_connection_new();
+
+    macvlan_name  = g_strdup_printf("macvlan%ld", config_data->iface_idx);
+    connection_id = g_strdup_printf("%s%ld", connection_type, config_data->iface_idx);
+
+    wired_mac_addr = parent_hwaddr;
+    if (nm_streq(connection_type, NM_SETTING_MACVLAN_SETTING_NAME)) {
+        /* In NM-ci, use macvlan.parent instead of wired.mac-address for parent matching
+         * because we are faking the MAC addresses via NM_CLOUD_SETUP_MAP_INTERFACES.
+         * The daemon still needs the real MAC, not the mapped one, so it won't work. */
+        map = gl_interfaces_map;
+        if (G_UNLIKELY(map)) {
+            for (; map->name; map++) {
+                if (nm_streq(map->value_str, parent_hwaddr)) {
+                    macvlan_parent = map->name;
+                    wired_mac_addr = NULL;
+                    break;
+                }
+            }
+        }
+
+        nm_connection_add_setting(connection,
+                                  g_object_new(NM_TYPE_SETTING_MACVLAN,
+                                               NM_SETTING_MACVLAN_MODE,
+                                               NM_SETTING_MACVLAN_MODE_VEPA,
+                                               NM_SETTING_MACVLAN_PARENT,
+                                               macvlan_parent,
+                                               NULL));
+        nm_connection_add_setting(connection,
+                                  g_object_new(NM_TYPE_SETTING_IP6_CONFIG,
+                                               NM_SETTING_IP_CONFIG_METHOD,
+                                               NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                                               NULL));
+        ip4_config_method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+        ifname            = macvlan_name;
+    } else if (nm_streq(connection_type, NM_SETTING_VLAN_SETTING_NAME)) {
+        nm_connection_add_setting(connection,
+                                  g_object_new(NM_TYPE_SETTING_VLAN,
+                                               NM_SETTING_VLAN_PARENT,
+                                               macvlan_name,
+                                               NM_SETTING_VLAN_ID,
+                                               config_data->priv.oci.vlan_tag,
+                                               NULL));
+        ip4_config_method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
+    } else {
+        g_return_val_if_reached(FALSE);
+    }
+
+    nm_connection_add_setting(connection,
+                              g_object_new(NM_TYPE_SETTING_CONNECTION,
+                                           NM_SETTING_CONNECTION_ID,
+                                           connection_id,
+                                           NM_SETTING_CONNECTION_TYPE,
+                                           connection_type,
+                                           NM_SETTING_CONNECTION_INTERFACE_NAME,
+                                           ifname,
+                                           NULL));
+    nm_connection_add_setting(connection,
+                              g_object_new(NM_TYPE_SETTING_IP4_CONFIG,
+                                           NM_SETTING_IP_CONFIG_METHOD,
+                                           ip4_config_method,
+                                           NULL));
+
+    nm_connection_add_setting(connection,
+                              g_object_new(NM_TYPE_SETTING_WIRED,
+                                           NM_SETTING_WIRED_MAC_ADDRESS,
+                                           wired_mac_addr,
+                                           NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+                                           hwaddr,
+                                           NULL));
+
+    s_user = nm_setting_user_new();
+    nm_connection_add_setting(connection, s_user);
+    nm_setting_user_set_data(NM_SETTING_USER(s_user),
+                             "org.freedesktop.NetworkManager.origin",
+                             "nm-cloud-setup",
+                             NULL);
+
+    _nmc_mangle_connection(NULL, connection, result, config_data, NULL, NULL);
+
+    _LOGD("config device %s: creating %s connection for VLAN %d on %s...",
+          hwaddr,
+          ifname ?: connection_type,
+          config_data->priv.oci.vlan_tag,
+          parent_hwaddr);
+
+    active_connection = nmcs_add_and_activate(nmc, NULL, connection, &error);
+    if (active_connection == NULL) {
+        if (!nm_utils_error_is_cancelled(error)) {
+            _LOGD("config device %s: failure to activate connection: %s", hwaddr, error->message);
+        }
+        return FALSE;
+    }
+
+    _LOGD("config device %s: connection \"%s\" (%s) created",
+          hwaddr,
+          nm_active_connection_get_id(active_connection),
+          nm_active_connection_get_uuid(active_connection));
+
+    return TRUE;
+}
+
+static gboolean
+_oci_config_vnic_dev(SigTermData                          *sigterm_data,
+                     const NMCSProviderGetConfigIfaceData *config_data,
+                     NMClient                             *nmc,
+                     const NMCSProviderGetConfigResult    *result,
+                     const GType                           device_type,
+                     const char                           *connection_type,
+                     const char                           *parent_hwaddr)
+{
+    gs_unref_object NMDevice *device = NULL;
+
+    device = nm_g_object_ref(_nmc_get_device_by_hwaddr(nmc, device_type, config_data->hwaddr));
+    if (device) {
+        /* There is a device. Modify and reapply the currently applied connection. */
+        return _config_existing(sigterm_data, config_data, nmc, result, connection_type, device);
+    } else {
+        /* There is no device, but we're configuring a VLAN.
+         * We can just go ahead and create one with a new connection. */
+        return _oci_new_vlan_dev(sigterm_data,
+                                 config_data,
+                                 nmc,
+                                 result,
+                                 connection_type,
+                                 parent_hwaddr);
+    }
+}
+
+static gboolean
+_config_one(SigTermData                       *sigterm_data,
+            NMCSProvider                      *provider,
+            NMClient                          *nmc,
+            const NMCSProviderGetConfigResult *result,
+            guint                              idx)
+{
+    const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
+    gboolean                              any_changes;
+
+    g_main_context_iteration(NULL, FALSE);
+
+    if (g_cancellable_is_cancelled(sigterm_data->cancellable))
+        return FALSE;
+
+    if (!nmcs_provider_get_config_iface_data_is_valid(config_data)) {
+        _LOGD("config device %s: skip because meta data not successfully fetched",
+              config_data->hwaddr);
+        return FALSE;
+    }
+
+    if (config_data->iface_idx >= 100) {
+        /* since we use the iface_idx to select a table number, the range is limited from
+         * 0 to 99. Note that the providers are required to provide increasing numbers,
+         * so this means we bail out after the first 100 devices.  */
+        _LOGD("config device %s: skip because number of supported interfaces reached",
+              config_data->hwaddr);
+        return FALSE;
+    }
+
+    if (NMCS_IS_PROVIDER_OCI(provider) && config_data->priv.oci.vlan_tag != 0) {
+        if (config_data->priv.oci.parent_hwaddr == NULL) {
+            _LOGW("config device %s: has vlan id %d but no parent device",
+                  config_data->hwaddr,
+                  config_data->priv.oci.vlan_tag);
+            return FALSE;
+        }
+
+        /* MACVLAN first, because VLAN is on top of it. */
+        any_changes = _oci_config_vnic_dev(sigterm_data,
+                                           config_data,
+                                           nmc,
+                                           result,
+                                           NM_TYPE_DEVICE_MACVLAN,
+                                           NM_SETTING_MACVLAN_SETTING_NAME,
+                                           config_data->priv.oci.parent_hwaddr);
+        any_changes += _oci_config_vnic_dev(sigterm_data,
+                                            config_data,
+                                            nmc,
+                                            result,
+                                            NM_TYPE_DEVICE_VLAN,
+                                            NM_SETTING_VLAN_SETTING_NAME,
+                                            config_data->hwaddr);
+
+    } else {
+        any_changes = _config_ethernet(sigterm_data, config_data, nmc, result);
+    }
+
     return any_changes;
 }
 
 static gboolean
-_config_all(SigTermData *sigterm_data, NMClient *nmc, const NMCSProviderGetConfigResult *result)
+_config_all(SigTermData                       *sigterm_data,
+            NMCSProvider                      *provider,
+            NMClient                          *nmc,
+            const NMCSProviderGetConfigResult *result)
 {
     gboolean any_changes = FALSE;
     guint    i;
 
     for (i = 0; i < result->n_iface_datas; i++) {
-        if (_config_one(sigterm_data, nmc, result, i))
+        if (_config_one(sigterm_data, provider, nmc, result, i))
             any_changes = TRUE;
     }
 
@@ -804,11 +1021,14 @@ main(int argc, const char *const *argv)
         goto done;
     }
 
+    /* Initialize map used in test scenarios. */
+    gl_interfaces_map = _map_interfaces_parse();
+
     result = _get_config(sigterm_cancellable, provider, nmc);
     if (!result)
         goto done;
 
-    if (_config_all(&sigterm_data, nmc, result))
+    if (_config_all(&sigterm_data, provider, nmc, result))
         _LOGI("some changes were applied for provider %s", nmcs_provider_get_name(provider));
     else
         _LOGD("no changes were applied for provider %s", nmcs_provider_get_name(provider));
diff --git a/src/nm-cloud-setup/nmcs-provider-oci.c b/src/nm-cloud-setup/nmcs-provider-oci.c
index b0b0edf9..17cd997e 100644
--- a/src/nm-cloud-setup/nmcs-provider-oci.c
+++ b/src/nm-cloud-setup/nmcs-provider-oci.c
@@ -92,6 +92,9 @@ _get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     gs_unref_bytes GBytes          *response = NULL;
     gs_free_error GError           *error    = NULL;
     nm_auto_decref_json json_t     *vnics    = NULL;
+    gboolean                        is_baremetal;
+    gs_unref_ptrarray GPtrArray    *phys_nic_macs = NULL;
+    GHashTableIter                  h_iter;
     size_t                          i;
 
     nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
@@ -112,12 +115,24 @@ _get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
         goto out;
     }
 
+    if (json_array_size(vnics) > 0) {
+        is_baremetal = NULL != json_object_get(json_array_get(vnics, 0), "nicIndex");
+        _LOGI("get-config: detected %s instance", is_baremetal ? "baremetal" : "VM");
+    } else {
+        is_baremetal = FALSE;
+        _LOGI("get-config: empty VNICs metadata, cannot detect instance type");
+    }
+
+    if (is_baremetal)
+        phys_nic_macs = g_ptr_array_sized_new(16);
+
     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;
+        json_int_t    nic_index = -1, vlan_tag = -1;
 
         vnic = json_array_get(vnics, i);
         if (!json_is_object(vnic)) {
@@ -130,12 +145,28 @@ _get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
         field = json_object_get(vnic, "macAddr");
         val   = field && json_is_string(field) ? json_string_value(field) : NULL;
-        if (!val) {
+        mac   = val ? nmcs_utils_hwaddr_normalize(val, json_string_length(field)) : NULL;
+        if (!mac) {
             _VNIC_WARN("missing or invalid 'macAddr', ignoring VNIC");
             continue;
         }
 
-        mac               = nmcs_utils_hwaddr_normalize(val, json_string_length(field));
+        if (is_baremetal) {
+            field     = json_object_get(vnic, "nicIndex");
+            nic_index = field && json_is_integer(field) ? json_integer_value(field) : -1;
+            if (nic_index < 0 || nic_index >= 1024) { /* 1024 = random limit to prevent abuse*/
+                _VNIC_WARN("missing or invalid 'nicIndex', ignoring VNIC");
+                continue;
+            }
+
+            field    = json_object_get(vnic, "vlanTag");
+            vlan_tag = field && json_is_integer(field) ? json_integer_value(field) : -1;
+            if (vlan_tag < 0) {
+                _VNIC_WARN("missing or invalid 'vlanTag', ignoring VNIC");
+                continue;
+            }
+        }
+
         config_iface_data = nmcs_provider_get_config_iface_data_create(get_config_data, FALSE, mac);
         config_iface_data->iface_idx = i;
 
@@ -168,6 +199,48 @@ _get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
         } else {
             _VNIC_WARN("missing or invalid 'subnetCidrBlock'");
         }
+
+        if (is_baremetal) {
+            gboolean is_phys_nic = vlan_tag == 0;
+
+            /* In baremetal instances, configure VNICs' VLAN (physical NICs don't need it) */
+            if (is_phys_nic) {
+                config_iface_data->priv.oci.vlan_tag      = 0;
+                config_iface_data->priv.oci.parent_hwaddr = NULL;
+                if (nic_index >= phys_nic_macs->len)
+                    g_ptr_array_set_size(phys_nic_macs,
+                                         NM_MAX((guint) (nic_index + 1), phys_nic_macs->len * 2));
+                phys_nic_macs->pdata[nic_index] = (gpointer) config_iface_data->hwaddr;
+            } else {
+                /* We might not have all the physical NICs' MACs yet, save nicIndex for later */
+                config_iface_data->priv.oci.parent_hwaddr = GINT_TO_POINTER((int) nic_index);
+                config_iface_data->priv.oci.vlan_tag      = vlan_tag;
+            }
+        }
+    }
+
+    if (is_baremetal) {
+        g_hash_table_iter_init(&h_iter, get_config_data->result_dict);
+
+        /* Now that all the metadata is processed we should have all the physical NICs' MACs */
+        while (g_hash_table_iter_next(&h_iter, NULL, (gpointer *) &config_iface_data)) {
+            bool is_phys_nic = config_iface_data->priv.oci.vlan_tag == 0;
+            int  nic_index   = GPOINTER_TO_INT(config_iface_data->priv.oci.parent_hwaddr);
+
+            if (is_phys_nic)
+                continue;
+
+            if (nic_index >= phys_nic_macs->len || phys_nic_macs->pdata[nic_index] == NULL) {
+                _LOGW("get-config: physical NIC for nicIndex=%d not found, ignoring VNIC "
+                      "(VNIC macAddr=%s)",
+                      nic_index,
+                      config_iface_data->hwaddr);
+                g_hash_table_iter_remove(&h_iter);
+                continue;
+            }
+
+            config_iface_data->priv.oci.parent_hwaddr = g_strdup(phys_nic_macs->pdata[nic_index]);
+        }
     }
 
 out:
diff --git a/src/nm-cloud-setup/nmcs-provider.c b/src/nm-cloud-setup/nmcs-provider.c
index 0f06c4e2..251749ba 100644
--- a/src/nm-cloud-setup/nmcs-provider.c
+++ b/src/nm-cloud-setup/nmcs-provider.c
@@ -188,6 +188,7 @@ nmcs_provider_get_config_iface_data_create(NMCSProviderGetConfigTaskData *get_co
 
     iface_data  = g_slice_new(NMCSProviderGetConfigIfaceData);
     *iface_data = (NMCSProviderGetConfigIfaceData) {
+        .provider        = g_object_ref(get_config_data->self),
         .get_config_data = get_config_data,
         .hwaddr          = g_strdup(hwaddr),
         .iface_idx       = -1,
@@ -203,6 +204,11 @@ nmcs_provider_get_config_iface_data_create(NMCSProviderGetConfigTaskData *get_co
         iface_data->priv.aliyun = (typeof(iface_data->priv.aliyun)) {
             .has_primary_ip_address = FALSE,
         };
+    } else if (G_OBJECT_TYPE(get_config_data->self) == nmcs_provider_oci_get_type()) {
+        iface_data->priv.oci = (typeof(iface_data->priv.oci)) {
+            .vlan_tag      = 0,
+            .parent_hwaddr = NULL,
+        };
     }
 
     /* the has does not own the key (iface_datta->hwaddr), the lifetime of the
@@ -220,6 +226,9 @@ _iface_data_free(gpointer data)
     g_free(iface_data->ipv4s_arr);
     nm_g_ptr_array_unref(iface_data->iproutes);
     g_free((char *) iface_data->hwaddr);
+    if (G_OBJECT_TYPE(iface_data->provider) == nmcs_provider_oci_get_type())
+        g_free((char *) iface_data->priv.oci.parent_hwaddr);
+    g_clear_object(&iface_data->provider);
 
     nm_g_slice_free(iface_data);
 }
diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h
index 98e50ea3..95a591c6 100644
--- a/src/nm-cloud-setup/nmcs-provider.h
+++ b/src/nm-cloud-setup/nmcs-provider.h
@@ -17,6 +17,8 @@ typedef struct {
      * dictionary. */
     const char *hwaddr;
 
+    struct _NMCSProvider *provider;
+
     struct _NMCSProviderGetConfigTaskData *get_config_data;
 
     in_addr_t *ipv4s_arr;
@@ -51,6 +53,10 @@ typedef struct {
             bool      has_primary_ip_address : 1;
             bool      ipv4s_arr_ordered : 1;
         } aliyun;
+        struct {
+            guint32     vlan_tag; /* 0 if no VLAN is needed */
+            const char *parent_hwaddr;
+        } oci;
     } priv;
 
 } NMCSProviderGetConfigIfaceData;