about summary refs log tree commit diff
path: root/src/core/devices/nm-device-ipvlan.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/nm-device-ipvlan.c')
-rw-r--r--src/core/devices/nm-device-ipvlan.c467
1 files changed, 467 insertions, 0 deletions
diff --git a/src/core/devices/nm-device-ipvlan.c b/src/core/devices/nm-device-ipvlan.c
new file mode 100644
index 00000000..00a1b579
--- /dev/null
+++ b/src/core/devices/nm-device-ipvlan.c
@@ -0,0 +1,467 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ipvlan.h"
+
+#include <linux/if_link.h>
+
+#include "libnm-core-intern/nm-core-internal.h"
+#include "nm-device-private.h"
+#include "settings/nm-settings.h"
+#include "nm-act-request.h"
+#include "nm-manager.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "libnm-platform/nm-platform.h"
+#include "nm-device-factory.h"
+#include "nm-setting-ipvlan.h"
+#include "nm-setting-wired.h"
+#include "nm-active-connection.h"
+#include "nm-utils.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceIpvlan
+#include "nm-device-logging.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIpvlan, PROP_MODE, PROP_PRIVATE, PROP_VEPA, );
+
+typedef struct {
+    NMPlatformLnkIpvlan props;
+} NMDeviceIpvlanPrivate;
+
+struct _NMDeviceIpvlan {
+    NMDevice              parent;
+    NMDeviceIpvlanPrivate _priv;
+};
+
+struct _NMDeviceIpvlanClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceIpvlan, nm_device_ipvlan, NM_TYPE_DEVICE);
+
+#define NM_DEVICE_IPVLAN_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceIpvlan, NM_IS_DEVICE_IPVLAN, NMDevice)
+
+/*****************************************************************************/
+
+static int modes[][2] = {
+    {NM_SETTING_IPVLAN_MODE_L2, IPVLAN_MODE_L2},
+    {NM_SETTING_IPVLAN_MODE_L3, IPVLAN_MODE_L3},
+    {NM_SETTING_IPVLAN_MODE_L3S, IPVLAN_MODE_L3S},
+};
+
+static int
+setting_mode_to_platform(int mode)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS(modes); i++) {
+        if (modes[i][0] == mode)
+            return modes[i][1];
+    }
+
+    return -1;
+}
+
+static int
+platform_mode_to_setting(int mode)
+{
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS(modes); i++) {
+        if (modes[i][1] == mode)
+            return modes[i][0];
+    }
+
+    return 0;
+}
+
+static const char *
+platform_mode_to_string(guint mode)
+{
+    switch (mode) {
+    case IPVLAN_MODE_L2:
+        return "l2";
+    case IPVLAN_MODE_L3:
+        return "l3";
+    case IPVLAN_MODE_L3S:
+        return "l3s";
+    default:
+        return "unknown";
+    }
+}
+
+/*****************************************************************************/
+
+static void
+update_properties(NMDevice *device)
+{
+    NMDeviceIpvlan            *self   = NM_DEVICE_IPVLAN(device);
+    NMDeviceIpvlanPrivate     *priv   = NM_DEVICE_IPVLAN_GET_PRIVATE(self);
+    GObject                   *object = G_OBJECT(device);
+    const NMPlatformLnkIpvlan *props;
+    const NMPlatformLink      *plink;
+
+    props = nm_platform_link_get_lnk_ipvlan(nm_device_get_platform(device),
+                                            nm_device_get_ifindex(device),
+                                            &plink);
+
+    if (!props) {
+        _LOGW(LOGD_PLATFORM, "could not get IPVLAN properties");
+        return;
+    }
+
+    g_object_freeze_notify(object);
+
+    nm_device_parent_set_ifindex(device, plink->parent);
+
+#define CHECK_PROPERTY_CHANGED(field, prop)      \
+    G_STMT_START                                 \
+    {                                            \
+        if (priv->props.field != props->field) { \
+            priv->props.field = props->field;    \
+            _notify(self, prop);                 \
+        }                                        \
+    }                                            \
+    G_STMT_END
+
+    CHECK_PROPERTY_CHANGED(mode, PROP_MODE);
+    CHECK_PROPERTY_CHANGED(private_flag, PROP_PRIVATE);
+    CHECK_PROPERTY_CHANGED(vepa, PROP_VEPA);
+
+    g_object_thaw_notify(object);
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+    NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->link_changed(device, pllink);
+    update_properties(device);
+}
+
+static gboolean
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
+                   const NMPlatformLink **out_plink,
+                   GError               **error)
+{
+    const char         *iface = nm_device_get_iface(device);
+    NMSettingIpvlan    *s_ipvlan;
+    NMPlatformLnkIpvlan lnk = {};
+    int                 parent_ifindex;
+    int                 r;
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+    nm_assert(s_ipvlan);
+
+    if (!parent) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "IPVLAN device cannot be created without a parent interface");
+        return FALSE;
+    }
+
+    parent_ifindex = nm_device_get_ifindex(parent);
+    if (parent_ifindex <= 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "cannot retrieve ifindex of interface %s (%s)",
+                    nm_device_get_iface(parent),
+                    nm_device_get_type_desc(parent));
+        return FALSE;
+    }
+
+    if (setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)) < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_FAILED,
+                    "unsupported IPVLAN mode %u",
+                    nm_setting_ipvlan_get_mode(s_ipvlan));
+        return FALSE;
+    }
+    lnk.mode         = setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan));
+    lnk.private_flag = nm_setting_ipvlan_get_private(s_ipvlan);
+    lnk.vepa         = nm_setting_ipvlan_get_vepa(s_ipvlan);
+
+    r = nm_platform_link_ipvlan_add(nm_device_get_platform(device),
+                                    iface,
+                                    parent_ifindex,
+                                    &lnk,
+                                    out_plink);
+
+    if (r < 0) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Failed to create IPVLAN interface '%s' for '%s': %s",
+                    iface,
+                    nm_connection_get_id(connection),
+                    nm_strerror(r));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+    if (!nm_device_parent_get_device(device))
+        return FALSE;
+    return NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)->is_available(device, flags);
+}
+
+/*****************************************************************************/
+
+static gboolean
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(device);
+    NMSettingIpvlan       *s_ipvlan;
+    const char            *parent = NULL;
+
+    if (!NM_DEVICE_CLASS(nm_device_ipvlan_parent_class)
+             ->check_connection_compatible(device, connection, check_properties, error))
+        return FALSE;
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+
+    if (check_properties && nm_device_is_real(device)) {
+        if (setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)) != priv->props.mode) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN mode setting differs");
+            return FALSE;
+        }
+
+        if (nm_setting_ipvlan_get_private(s_ipvlan) != priv->props.private_flag) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN private flag setting differs");
+            return FALSE;
+        }
+        if (nm_setting_ipvlan_get_vepa(s_ipvlan) != priv->props.vepa) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IPVLAN VEPA flag setting differs");
+            return FALSE;
+        }
+
+        /* Check parent interface; could be an interface name or a UUID */
+        parent = nm_setting_ipvlan_get_parent(s_ipvlan);
+        if (parent) {
+            if (!nm_device_match_parent(device, parent)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "IPVLAN parent setting differs");
+                return FALSE;
+            }
+        } else {
+            /* Parent could be a MAC address in an NMSettingWired */
+            if (!nm_device_match_parent_hwaddr(device, connection, TRUE)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "IPVLAN parent mac setting differs");
+                return FALSE;
+            }
+        }
+    }
+    return TRUE;
+}
+
+static void
+update_connection(NMDevice *device, NMConnection *connection)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(device);
+    NMSettingIpvlan *s_ipvlan   = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_IPVLAN);
+
+    if (priv->props.mode != setting_mode_to_platform(nm_setting_ipvlan_get_mode(s_ipvlan)))
+        g_object_set(s_ipvlan,
+                     NM_SETTING_IPVLAN_MODE,
+                     platform_mode_to_setting(priv->props.mode),
+                     NULL);
+
+    if (priv->props.private_flag != nm_setting_ipvlan_get_private(s_ipvlan))
+        g_object_set(s_ipvlan, NM_SETTING_IPVLAN_PRIVATE, priv->props.private_flag, NULL);
+
+    if (priv->props.vepa != nm_setting_ipvlan_get_vepa(s_ipvlan))
+        g_object_set(s_ipvlan, NM_SETTING_IPVLAN_VEPA, priv->props.vepa, NULL);
+
+    g_object_set(
+        s_ipvlan,
+        NM_SETTING_IPVLAN_PARENT,
+        nm_device_parent_find_for_connection(device, nm_setting_ipvlan_get_parent(s_ipvlan)),
+        NULL);
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMDeviceIpvlanPrivate *priv = NM_DEVICE_IPVLAN_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MODE:
+        g_value_set_string(value, platform_mode_to_string(priv->props.mode));
+        break;
+    case PROP_PRIVATE:
+        g_value_set_boolean(value, priv->props.private_flag);
+        break;
+    case PROP_VEPA:
+        g_value_set_boolean(value, priv->props.vepa);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ipvlan_init(NMDeviceIpvlan *self)
+{}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ipvlan = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_IPVLAN,
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mode", "s", NM_DEVICE_IPVLAN_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Private",
+                                                           "b",
+                                                           NM_DEVICE_IPVLAN_PRIVATE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Vepa",
+                                                           "b",
+                                                           NM_DEVICE_IPVLAN_VEPA), ), ),
+};
+
+static void
+nm_device_ipvlan_class_init(NMDeviceIpvlanClass *klass)
+{
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
+
+    object_class->get_property = get_property;
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ipvlan);
+
+    device_class->connection_type_supported        = NM_SETTING_IPVLAN_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_IPVLAN_SETTING_NAME;
+    device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_IPVLAN);
+
+    device_class->check_connection_compatible = check_connection_compatible;
+    device_class->create_and_realize          = create_and_realize;
+    device_class->get_generic_capabilities    = get_generic_capabilities;
+    device_class->is_available                = is_available;
+    device_class->link_changed                = link_changed;
+    device_class->update_connection           = update_connection;
+
+    obj_properties[PROP_MODE] = g_param_spec_string(NM_DEVICE_IPVLAN_MODE,
+                                                    "",
+                                                    "",
+                                                    NULL,
+                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PRIVATE] = g_param_spec_boolean(NM_DEVICE_IPVLAN_PRIVATE,
+                                                        "",
+                                                        "",
+                                                        TRUE,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_VEPA] = g_param_spec_boolean(NM_DEVICE_IPVLAN_VEPA,
+                                                     "",
+                                                     "",
+                                                     TRUE,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_IPVLAN_DEVICE_FACTORY (nm_ipvlan_device_factory_get_type())
+#define NM_IPVLAN_DEVICE_FACTORY(obj) \
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IPVLAN_DEVICE_FACTORY, NMIpvlanDeviceFactory))
+
+static NMDevice *
+create_device(NMDeviceFactory      *factory,
+              const char           *iface,
+              const NMPlatformLink *plink,
+              NMConnection         *connection,
+              gboolean             *out_ignore)
+{
+    NMSettingIpvlan *s_ipvlan;
+
+    if (connection) {
+        s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+        nm_assert(s_ipvlan);
+    }
+
+    return g_object_new(NM_TYPE_DEVICE_IPVLAN,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "Ipvlan",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_IPVLAN,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_IPVLAN,
+                        NULL);
+}
+
+static const char *
+get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
+{
+    NMSettingIpvlan *s_ipvlan;
+    NMSettingWired  *s_wired;
+    const char      *parent = NULL;
+
+    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IPVLAN_SETTING_NAME), NULL);
+
+    s_ipvlan = _nm_connection_get_setting(connection, NM_TYPE_SETTING_IPVLAN);
+    if (s_ipvlan) {
+        parent = nm_setting_ipvlan_get_parent(s_ipvlan);
+        if (parent)
+            return parent;
+    }
+
+    /* Try the hardware address from the IPVLAN connection's hardware setting */
+    s_wired = nm_connection_get_setting_wired(connection);
+    if (s_wired)
+        return nm_setting_wired_get_mac_address(s_wired);
+    else
+        return NULL;
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+    IPVLAN,
+    Ipvlan,
+    ipvlan,
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_IPVLAN)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_IPVLAN_SETTING_NAME),
+    factory_class->create_device         = create_device;
+    factory_class->get_connection_parent = get_connection_parent;);