diff --git a/docs/api/network-manager-docs.xml b/docs/api/network-manager-docs.xml
index caff6fa4fc..9916427399 100644
--- a/docs/api/network-manager-docs.xml
+++ b/docs/api/network-manager-docs.xml
@@ -183,6 +183,7 @@
+
diff --git a/docs/libnm/libnm-docs.xml b/docs/libnm/libnm-docs.xml
index a957f469c8..719d8998a1 100644
--- a/docs/libnm/libnm-docs.xml
+++ b/docs/libnm/libnm-docs.xml
@@ -378,6 +378,7 @@ print ("NetworkManager version " + client.get_version())]]>
+
diff --git a/introspection/meson.build b/introspection/meson.build
index 16bb5ed423..7703b46983 100644
--- a/introspection/meson.build
+++ b/introspection/meson.build
@@ -15,6 +15,7 @@ ifaces = [
'org.freedesktop.NetworkManager.Device.Bridge',
'org.freedesktop.NetworkManager.Device.Dummy',
'org.freedesktop.NetworkManager.Device.Generic',
+ 'org.freedesktop.NetworkManager.Device.Geneve',
'org.freedesktop.NetworkManager.Device.Hsr',
'org.freedesktop.NetworkManager.Device.IPTunnel',
'org.freedesktop.NetworkManager.Device.Infiniband',
diff --git a/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml b/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml
new file mode 100644
index 0000000000..8d24c5a578
--- /dev/null
+++ b/introspection/org.freedesktop.NetworkManager.Device.Geneve.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0bf3ca1c25..b66ca45263 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,6 +11,7 @@ src/core/devices/nm-device-bridge.c
src/core/devices/nm-device-dummy.c
src/core/devices/nm-device-ethernet-utils.c
src/core/devices/nm-device-ethernet.c
+src/core/devices/nm-device-geneve.c
src/core/devices/nm-device-infiniband.c
src/core/devices/nm-device-ip-tunnel.c
src/core/devices/nm-device-loopback.c
@@ -46,6 +47,7 @@ src/libnm-client-impl/nm-device-bt.c
src/libnm-client-impl/nm-device-dummy.c
src/libnm-client-impl/nm-device-ethernet.c
src/libnm-client-impl/nm-device-generic.c
+src/libnm-client-impl/nm-device-geneve.c
src/libnm-client-impl/nm-device-hsr.c
src/libnm-client-impl/nm-device-infiniband.c
src/libnm-client-impl/nm-device-ip-tunnel.c
diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index 1585836281..24755a5f00 100644
--- a/src/core/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -412,6 +412,7 @@ nm_device_factory_manager_load_factories(NMDeviceFactoryManagerFactoryFunc callb
_ADD_INTERNAL(nm_dummy_device_factory_get_type);
_ADD_INTERNAL(nm_ethernet_device_factory_get_type);
_ADD_INTERNAL(nm_generic_device_factory_get_type);
+ _ADD_INTERNAL(nm_geneve_device_factory_get_type);
_ADD_INTERNAL(nm_hsr_device_factory_get_type);
_ADD_INTERNAL(nm_infiniband_device_factory_get_type);
_ADD_INTERNAL(nm_ip_tunnel_device_factory_get_type);
diff --git a/src/core/devices/nm-device-geneve.c b/src/core/devices/nm-device-geneve.c
new file mode 100644
index 0000000000..0968a2fb9b
--- /dev/null
+++ b/src/core/devices/nm-device-geneve.c
@@ -0,0 +1,487 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-manager.h"
+#include "nm-device-geneve.h"
+
+#include "libnm-core-intern/nm-core-internal.h"
+#include "nm-act-request.h"
+#include "nm-device-private.h"
+#include "nm-setting-geneve.h"
+#include "libnm-platform/nm-platform.h"
+#include "nm-device-factory.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceGeneve
+#include "nm-device-logging.h"
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceGeneve,
+ PROP_ID,
+ PROP_REMOTE,
+ PROP_TOS,
+ PROP_TTL,
+ PROP_DF,
+ PROP_DST_PORT, );
+
+typedef struct {
+ NMPlatformLnkGeneve props;
+} NMDeviceGenevePrivate;
+
+struct _NMDeviceGeneve {
+ NMDevice parent;
+ NMDeviceGenevePrivate _priv;
+};
+
+struct _NMDeviceGeneveClass {
+ NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceGeneve, nm_device_geneve, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_GENEVE_GET_PRIVATE(self) \
+ _NM_GET_PRIVATE(self, NMDeviceGeneve, NM_IS_DEVICE_GENEVE, NMDevice)
+
+/*****************************************************************************/
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *dev)
+{
+ return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+update_properties(NMDevice *device)
+{
+ NMDeviceGeneve *self;
+ NMDeviceGenevePrivate *priv;
+ const NMPlatformLink *plink;
+ const NMPlatformLnkGeneve *props;
+ int ifindex;
+
+ g_return_if_fail(NM_IS_DEVICE_GENEVE(device));
+ self = NM_DEVICE_GENEVE(device);
+ priv = NM_DEVICE_GENEVE_GET_PRIVATE(self);
+
+ ifindex = nm_device_get_ifindex(device);
+ g_return_if_fail(ifindex > 0);
+ props = nm_platform_link_get_lnk_geneve(nm_device_get_platform(device), ifindex, &plink);
+
+ if (!props) {
+ _LOGW(LOGD_PLATFORM, "could not get GENEVE properties");
+ return;
+ }
+
+ g_object_freeze_notify((GObject *) device);
+
+#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
+
+#define CHECK_PROPERTY_CHANGED_IN6ADDR(field, prop) \
+ G_STMT_START \
+ { \
+ if (memcmp(&priv->props.field, &props->field, sizeof(props->field)) != 0) { \
+ priv->props.field = props->field; \
+ _notify(self, prop); \
+ } \
+ } \
+ G_STMT_END
+
+ CHECK_PROPERTY_CHANGED(id, PROP_ID);
+ CHECK_PROPERTY_CHANGED(remote, PROP_REMOTE);
+ CHECK_PROPERTY_CHANGED_IN6ADDR(remote6, PROP_REMOTE);
+ CHECK_PROPERTY_CHANGED(tos, PROP_TOS);
+ CHECK_PROPERTY_CHANGED(ttl, PROP_TTL);
+ CHECK_PROPERTY_CHANGED(df, PROP_DF);
+ CHECK_PROPERTY_CHANGED(dst_port, PROP_DST_PORT);
+
+ g_object_thaw_notify((GObject *) device);
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+ NM_DEVICE_CLASS(nm_device_geneve_parent_class)->link_changed(device, pllink);
+ update_properties(device);
+}
+
+static void
+unrealize_notify(NMDevice *device)
+{
+ NMDeviceGeneve *self = NM_DEVICE_GENEVE(device);
+ NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(self);
+ guint i;
+
+ NM_DEVICE_CLASS(nm_device_geneve_parent_class)->unrealize_notify(device);
+
+ memset(&priv->props, 0, sizeof(NMPlatformLnkGeneve));
+
+ for (i = 1; i < _PROPERTY_ENUMS_LAST; i++)
+ g_object_notify_by_pspec(G_OBJECT(self), obj_properties[i]);
+}
+
+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);
+ NMPlatformLnkGeneve props = {};
+ NMSettingGeneve *s_geneve;
+ const char *str;
+ int r;
+
+ s_geneve = nm_connection_get_setting_geneve(connection);
+ g_return_val_if_fail(s_geneve, FALSE);
+
+ props.id = nm_setting_geneve_get_id(s_geneve);
+
+ str = nm_setting_geneve_get_remote(s_geneve);
+ if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.remote)
+ && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.remote6)) {
+ return nm_assert_unreachable_val(FALSE);
+ }
+ props.tos = nm_setting_geneve_get_tos(s_geneve);
+ props.ttl = nm_setting_geneve_get_ttl(s_geneve);
+ props.df = nm_setting_geneve_get_df(s_geneve);
+ props.dst_port = nm_setting_geneve_get_destination_port(s_geneve);
+
+ r = nm_platform_link_geneve_add(nm_device_get_platform(device), iface, &props, out_plink);
+ if (r < 0) {
+ g_set_error(error,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_CREATION_FAILED,
+ "Failed to create geneve interface '%s' for '%s': %s",
+ iface,
+ nm_connection_get_id(connection),
+ nm_strerror(r));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6)
+{
+ NMIPAddr candidate_addr;
+ int addr_family;
+
+ if (!candidate)
+ return addr4 == 0u && IN6_IS_ADDR_UNSPECIFIED(addr6);
+
+ if (!nm_inet_parse_bin(AF_UNSPEC, candidate, &addr_family, &candidate_addr))
+ return FALSE;
+
+ if (!nm_ip_addr_equal(addr_family,
+ &candidate_addr,
+ NM_IS_IPv4(addr_family) ? (gpointer) &addr4 : addr6))
+ return FALSE;
+
+ if (NM_IS_IPv4(addr_family))
+ return IN6_IS_ADDR_UNSPECIFIED(addr6);
+ else
+ return addr4 == 0u;
+}
+
+static gboolean
+check_connection_compatible(NMDevice *device,
+ NMConnection *connection,
+ gboolean check_properties,
+ GError **error)
+{
+ NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(device);
+ NMSettingGeneve *s_geneve;
+
+ if (!NM_DEVICE_CLASS(nm_device_geneve_parent_class)
+ ->check_connection_compatible(device, connection, check_properties, error))
+ return FALSE;
+
+ if (check_properties && nm_device_is_real(device)) {
+ s_geneve = nm_connection_get_setting_geneve(connection);
+
+ if (priv->props.id != nm_setting_geneve_get_id(s_geneve)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve id mismatches");
+ return FALSE;
+ }
+
+ if (!address_matches(nm_setting_geneve_get_remote(s_geneve),
+ priv->props.remote,
+ &priv->props.remote6)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve remote address mismatches");
+ return FALSE;
+ }
+
+ if (priv->props.dst_port != nm_setting_geneve_get_destination_port(s_geneve)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve destination port mismatches");
+ return FALSE;
+ }
+
+ if (priv->props.tos != nm_setting_geneve_get_tos(s_geneve)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve TOS mismatches");
+ return FALSE;
+ }
+
+ if (priv->props.ttl != nm_setting_geneve_get_ttl(s_geneve)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve TTL mismatches");
+ return FALSE;
+ }
+
+ if (priv->props.df != nm_setting_geneve_get_df(s_geneve)) {
+ nm_utils_error_set_literal(error,
+ NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+ "geneve DF mismatches");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+complete_connection(NMDevice *device,
+ NMConnection *connection,
+ const char *specific_object,
+ NMConnection *const *existing_connections,
+ GError **error)
+{
+ NMSettingGeneve *s_geneve;
+
+ nm_utils_complete_generic(nm_device_get_platform(device),
+ connection,
+ NM_SETTING_GENEVE_SETTING_NAME,
+ existing_connections,
+ NULL,
+ _("Geneve connection"),
+ NULL,
+ NULL);
+
+ s_geneve = nm_connection_get_setting_geneve(connection);
+ if (!s_geneve) {
+ g_set_error_literal(error,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_INVALID_CONNECTION,
+ "A 'geneve' setting is required.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+update_connection(NMDevice *device, NMConnection *connection)
+{
+ NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(device);
+ NMSettingGeneve *s_geneve = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_GENEVE);
+ char sbuf[NM_INET_ADDRSTRLEN];
+
+ if (priv->props.id != nm_setting_geneve_get_id(s_geneve))
+ g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_ID, priv->props.id, NULL);
+
+ /* Handle remote (IPv4 or IPv6) */
+ if (priv->props.remote) {
+ g_object_set(s_geneve,
+ NM_SETTING_GENEVE_REMOTE,
+ nm_inet4_ntop(priv->props.remote, sbuf),
+ NULL);
+ } else if (memcmp(&priv->props.remote6, &in6addr_any, sizeof(in6addr_any))) {
+ g_object_set(s_geneve,
+ NM_SETTING_GENEVE_REMOTE,
+ nm_inet6_ntop(&priv->props.remote6, sbuf),
+ NULL);
+ }
+
+ if (priv->props.dst_port != nm_setting_geneve_get_destination_port(s_geneve))
+ g_object_set(G_OBJECT(s_geneve),
+ NM_SETTING_GENEVE_DESTINATION_PORT,
+ priv->props.dst_port,
+ NULL);
+
+ if (priv->props.tos != nm_setting_geneve_get_tos(s_geneve))
+ g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_TOS, priv->props.tos, NULL);
+
+ if (priv->props.ttl != nm_setting_geneve_get_ttl(s_geneve))
+ g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_TTL, priv->props.ttl, NULL);
+
+ if (priv->props.df != nm_setting_geneve_get_df(s_geneve))
+ g_object_set(G_OBJECT(s_geneve), NM_SETTING_GENEVE_DF, priv->props.df, NULL);
+}
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_value_set_uint(value, priv->props.id);
+ break;
+ case PROP_REMOTE:
+ if (priv->props.remote)
+ g_value_take_string(value, nm_inet4_ntop_dup(priv->props.remote));
+ else if (!IN6_IS_ADDR_UNSPECIFIED(&priv->props.remote6))
+ g_value_take_string(value, nm_inet6_ntop_dup(&priv->props.remote6));
+ break;
+ case PROP_TOS:
+ g_value_set_uchar(value, priv->props.tos);
+ break;
+ case PROP_TTL:
+ g_value_set_uchar(value, priv->props.ttl);
+ break;
+ case PROP_DF:
+ g_value_set_uint(value, priv->props.df);
+ break;
+ case PROP_DST_PORT:
+ g_value_set_uint(value, priv->props.dst_port);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_geneve_init(NMDeviceGeneve *self)
+{}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_geneve = {
+ .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+ NM_DBUS_INTERFACE_DEVICE_GENEVE,
+ .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Id", "u", NM_DEVICE_GENEVE_ID),
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Remote", "s", NM_DEVICE_GENEVE_REMOTE),
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tos", "y", NM_DEVICE_GENEVE_TOS),
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ttl", "y", NM_DEVICE_GENEVE_TTL),
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Df", "u", NM_DEVICE_GENEVE_DF),
+ NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DstPort",
+ "q",
+ NM_DEVICE_GENEVE_DST_PORT), ), ),
+};
+
+static void
+nm_device_geneve_class_init(NMDeviceGeneveClass *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_geneve);
+
+ device_class->connection_type_supported = NM_SETTING_GENEVE_SETTING_NAME;
+ device_class->connection_type_check_compatible = NM_SETTING_GENEVE_SETTING_NAME;
+ device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_GENEVE);
+
+ device_class->link_changed = link_changed;
+ device_class->unrealize_notify = unrealize_notify;
+ device_class->create_and_realize = create_and_realize;
+ device_class->check_connection_compatible = check_connection_compatible;
+ device_class->complete_connection = complete_connection;
+ device_class->get_generic_capabilities = get_generic_capabilities;
+ device_class->update_connection = update_connection;
+
+ obj_properties[PROP_ID] = g_param_spec_uint(NM_DEVICE_GENEVE_ID,
+ "",
+ "",
+ 0,
+ G_MAXUINT32,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_GENEVE_REMOTE,
+ "",
+ "",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_GENEVE_TOS,
+ "",
+ "",
+ 0,
+ 255,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_TTL] = g_param_spec_uchar(NM_DEVICE_GENEVE_TTL,
+ "",
+ "",
+ 0,
+ 255,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_DF] = g_param_spec_uint(NM_DEVICE_GENEVE_DF,
+ "",
+ "",
+ 0,
+ 2,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_DST_PORT] = g_param_spec_uint(NM_DEVICE_GENEVE_DST_PORT,
+ "",
+ "",
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_GENEVE_DEVICE_FACTORY (nm_geneve_device_factory_get_type())
+#define NM_GENEVE_DEVICE_FACTORY(obj) \
+ (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_GENEVE_DEVICE_FACTORY, NMGeneveDeviceFactory))
+
+static NMDevice *
+create_device(NMDeviceFactory *factory,
+ const char *iface,
+ const NMPlatformLink *plink,
+ NMConnection *connection,
+ gboolean *out_ignore)
+{
+ return g_object_new(NM_TYPE_DEVICE_GENEVE,
+ NM_DEVICE_IFACE,
+ iface,
+ NM_DEVICE_TYPE_DESC,
+ "Geneve",
+ NM_DEVICE_DEVICE_TYPE,
+ NM_DEVICE_TYPE_GENEVE,
+ NM_DEVICE_LINK_TYPE,
+ NM_LINK_TYPE_GENEVE,
+ NULL);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+ GENEVE,
+ Geneve,
+ geneve,
+ NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_GENEVE)
+ NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_GENEVE_SETTING_NAME),
+ factory_class->create_device = create_device;);
diff --git a/src/core/devices/nm-device-geneve.h b/src/core/devices/nm-device-geneve.h
new file mode 100644
index 0000000000..d0a44e9dbb
--- /dev/null
+++ b/src/core/devices/nm-device-geneve.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+
+#ifndef __NETWORKMANAGER_DEVICE_GENEVE_H__
+#define __NETWORKMANAGER_DEVICE_GENEVE_H__
+
+#include "nm-device.h"
+
+#define NM_TYPE_DEVICE_GENEVE (nm_device_geneve_get_type())
+#define NM_DEVICE_GENEVE(obj) \
+ (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneve))
+#define NM_DEVICE_GENEVE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass))
+#define NM_IS_DEVICE_GENEVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_GENEVE))
+#define NM_IS_DEVICE_GENEVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_GENEVE))
+#define NM_DEVICE_GENEVE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass))
+
+#define NM_DEVICE_GENEVE_ID "id"
+#define NM_DEVICE_GENEVE_REMOTE "remote"
+#define NM_DEVICE_GENEVE_TOS "tos"
+#define NM_DEVICE_GENEVE_TTL "ttl"
+#define NM_DEVICE_GENEVE_DF "df"
+#define NM_DEVICE_GENEVE_DST_PORT "dst-port"
+
+typedef struct _NMDeviceGeneve NMDeviceGeneve;
+typedef struct _NMDeviceGeneveClass NMDeviceGeneveClass;
+
+GType nm_device_geneve_get_type(void);
+
+#endif /* __NETWORKMANAGER_DEVICE_GENEVE_H__ */
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 434683d6b4..f3f09db18c 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -5962,7 +5962,6 @@ nm_device_get_route_metric_default(NMDeviceType device_type)
* in some aspects a VPN. */
case NM_DEVICE_TYPE_WIREGUARD:
return NM_VPN_ROUTE_METRIC_DEFAULT;
-
case NM_DEVICE_TYPE_ETHERNET:
case NM_DEVICE_TYPE_VETH:
return 100;
@@ -5996,6 +5995,8 @@ nm_device_get_route_metric_default(NMDeviceType device_type)
return 470;
case NM_DEVICE_TYPE_VXLAN:
return 500;
+ case NM_DEVICE_TYPE_GENEVE:
+ return 525;
case NM_DEVICE_TYPE_DUMMY:
return 550;
case NM_DEVICE_TYPE_WIFI:
@@ -19567,7 +19568,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN);
priv->type = g_value_get_uint(value);
nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN);
- nm_assert(priv->type <= NM_DEVICE_TYPE_IPVLAN);
+ nm_assert(priv->type <= NM_DEVICE_TYPE_GENEVE);
break;
case PROP_LINK_TYPE:
/* construct-only */
diff --git a/src/core/meson.build b/src/core/meson.build
index 26d27f96e2..4a3dfd8e11 100644
--- a/src/core/meson.build
+++ b/src/core/meson.build
@@ -111,6 +111,7 @@ libNetworkManager = static_library(
'devices/nm-device-ethernet-utils.c',
'devices/nm-device-factory.c',
'devices/nm-device-generic.c',
+ 'devices/nm-device-geneve.c',
'devices/nm-device-hsr.c',
'devices/nm-device-infiniband.c',
'devices/nm-device-ip-tunnel.c',
diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver
index 3d2bc79304..e9850ea6e5 100644
--- a/src/libnm-client-impl/libnm.ver
+++ b/src/libnm-client-impl/libnm.ver
@@ -2107,6 +2107,13 @@ global:
libnm_1_58_0 {
global:
nm_connection_get_setting_geneve;
+ nm_device_geneve_get_df;
+ nm_device_geneve_get_dst_port;
+ nm_device_geneve_get_id;
+ nm_device_geneve_get_remote;
+ nm_device_geneve_get_tos;
+ nm_device_geneve_get_ttl;
+ nm_device_geneve_get_type;
nm_ip_config_get_clat_address;
nm_ip_config_get_clat_pref64;
nm_setting_geneve_df_get_type;
diff --git a/src/libnm-client-impl/meson.build b/src/libnm-client-impl/meson.build
index b49366292f..3352ebfee0 100644
--- a/src/libnm-client-impl/meson.build
+++ b/src/libnm-client-impl/meson.build
@@ -16,6 +16,7 @@ libnm_client_impl_sources = files(
'nm-device-bt.c',
'nm-device-dummy.c',
'nm-device-ethernet.c',
+ 'nm-device-geneve.c',
'nm-device-generic.c',
'nm-device-hsr.c',
'nm-device-infiniband.c',
diff --git a/src/libnm-client-impl/nm-client.c b/src/libnm-client-impl/nm-client.c
index b81ac6e506..f13835269e 100644
--- a/src/libnm-client-impl/nm-client.c
+++ b/src/libnm-client-impl/nm-client.c
@@ -29,6 +29,7 @@
#include "nm-device-dummy.h"
#include "nm-device-ethernet.h"
#include "nm-device-generic.h"
+#include "nm-device-geneve.h"
#include "nm-device-hsr.h"
#include "nm-device-infiniband.h"
#include "nm-device-ip-tunnel.h"
diff --git a/src/libnm-client-impl/nm-device-geneve.c b/src/libnm-client-impl/nm-device-geneve.c
new file mode 100644
index 0000000000..415e978d16
--- /dev/null
+++ b/src/libnm-client-impl/nm-device-geneve.c
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Red Hat, Inc.
+ */
+
+#include "libnm-client-impl/nm-default-libnm.h"
+
+#include "nm-device-geneve.h"
+
+#include "nm-setting-connection.h"
+#include "nm-setting-geneve.h"
+#include "nm-utils.h"
+#include "nm-object-private.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_ID,
+ PROP_REMOTE,
+ PROP_TOS,
+ PROP_TTL,
+ PROP_DST_PORT,
+ PROP_DF, );
+
+typedef struct {
+ char *remote;
+ guint32 id;
+ gint32 ttl;
+ guint16 dst_port;
+ guint8 df;
+ guint8 tos;
+} NMDeviceGenevePrivate;
+
+struct _NMDeviceGeneve {
+ NMDevice parent;
+ NMDeviceGenevePrivate _priv;
+};
+
+struct _NMDeviceGeneveClass {
+ NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceGeneve, nm_device_geneve, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_GENEVE_GET_PRIVATE(self) \
+ _NM_GET_PRIVATE(self, NMDeviceGeneve, NM_IS_DEVICE_GENEVE, NMObject, NMDevice)
+
+/*****************************************************************************/
+
+/**
+ * nm_device_geneve_get_id:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the device's GENEVE ID.
+ *
+ * Since: 1.58
+ **/
+guint
+nm_device_geneve_get_id(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0);
+
+ return NM_DEVICE_GENEVE_GET_PRIVATE(device)->id;
+}
+
+/**
+ * nm_device_geneve_get_remote:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the IP address of the remote tunnel endpoint
+ *
+ * Since: 1.58
+ **/
+const char *
+nm_device_geneve_get_remote(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), NULL);
+
+ return _nml_coerce_property_str_not_empty(NM_DEVICE_GENEVE_GET_PRIVATE(device)->remote);
+}
+
+/**
+ * nm_device_geneve_get_dst_port:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the UDP destination port
+ *
+ * Since: 1.58
+ **/
+guint
+nm_device_geneve_get_dst_port(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0);
+
+ return NM_DEVICE_GENEVE_GET_PRIVATE(device)->dst_port;
+}
+
+/**
+ * nm_device_geneve_get_tos:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the TOS value to use in outgoing packets
+ *
+ * Since: 1.58
+ **/
+guint
+nm_device_geneve_get_tos(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0);
+
+ return NM_DEVICE_GENEVE_GET_PRIVATE(device)->tos;
+}
+
+/**
+ * nm_device_geneve_get_ttl:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the time-to-live value to use in outgoing packets
+ *
+ * Since: 1.58
+ **/
+guint
+nm_device_geneve_get_ttl(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0);
+
+ return NM_DEVICE_GENEVE_GET_PRIVATE(device)->ttl;
+}
+
+/**
+ * nm_device_geneve_get_df:
+ * @device: a #NMDeviceGeneve
+ *
+ * Returns: the Don't Fragment (DF) bit to set in outgoing packets
+ *
+ * Since: 1.58
+ **/
+guint
+nm_device_geneve_get_df(NMDeviceGeneve *device)
+{
+ g_return_val_if_fail(NM_IS_DEVICE_GENEVE(device), 0);
+
+ return NM_DEVICE_GENEVE_GET_PRIVATE(device)->df;
+}
+
+static gboolean
+connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+{
+ NMSettingGeneve *s_geneve;
+
+ if (!NM_DEVICE_CLASS(nm_device_geneve_parent_class)
+ ->connection_compatible(device, connection, error))
+ return FALSE;
+
+ if (!nm_connection_is_type(connection, NM_SETTING_GENEVE_SETTING_NAME)) {
+ g_set_error_literal(error,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+ _("The connection was not a GENEVE connection."));
+ return FALSE;
+ }
+
+ s_geneve = nm_connection_get_setting_geneve(connection);
+ if (nm_setting_geneve_get_id(s_geneve) != nm_device_geneve_get_id(NM_DEVICE_GENEVE(device))) {
+ g_set_error_literal(
+ error,
+ NM_DEVICE_ERROR,
+ NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+ _("The GENEVE identifiers of the device and the connection didn't match."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GType
+get_setting_type(NMDevice *device)
+{
+ return NM_TYPE_SETTING_GENEVE;
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ NMDeviceGeneve *device = NM_DEVICE_GENEVE(object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_value_set_uint(value, nm_device_geneve_get_id(device));
+ break;
+ case PROP_REMOTE:
+ g_value_set_string(value, nm_device_geneve_get_remote(device));
+ break;
+ case PROP_TOS:
+ g_value_set_uint(value, nm_device_geneve_get_tos(device));
+ break;
+ case PROP_TTL:
+ g_value_set_int(value, nm_device_geneve_get_ttl(device));
+ break;
+ case PROP_DST_PORT:
+ g_value_set_uint(value, nm_device_geneve_get_dst_port(device));
+ break;
+ case PROP_DF:
+ g_value_set_uint(value, nm_device_geneve_get_df(device));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nm_device_geneve_init(NMDeviceGeneve *device)
+{}
+
+static void
+finalize(GObject *object)
+{
+ NMDeviceGenevePrivate *priv = NM_DEVICE_GENEVE_GET_PRIVATE(object);
+
+ g_free(priv->remote);
+
+ G_OBJECT_CLASS(nm_device_geneve_parent_class)->finalize(object);
+}
+
+const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_geneve = NML_DBUS_META_IFACE_INIT_PROP(
+ NM_DBUS_INTERFACE_DEVICE_GENEVE,
+ nm_device_geneve_get_type,
+ NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_30,
+ NML_DBUS_META_IFACE_DBUS_PROPERTIES(
+ NML_DBUS_META_PROPERTY_INIT_Y("Df", PROP_DF, NMDeviceGeneve, _priv.df),
+ NML_DBUS_META_PROPERTY_INIT_Q("DstPort", PROP_DST_PORT, NMDeviceGeneve, _priv.dst_port),
+ NML_DBUS_META_PROPERTY_INIT_U("Id", PROP_ID, NMDeviceGeneve, _priv.id),
+ NML_DBUS_META_PROPERTY_INIT_S("Remote", PROP_REMOTE, NMDeviceGeneve, _priv.remote),
+ NML_DBUS_META_PROPERTY_INIT_Y("Tos", PROP_TOS, NMDeviceGeneve, _priv.tos),
+ NML_DBUS_META_PROPERTY_INIT_I("Ttl", PROP_TTL, NMDeviceGeneve, _priv.ttl), ), );
+
+static void
+nm_device_geneve_class_init(NMDeviceGeneveClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ NMObjectClass *nm_object_class = NM_OBJECT_CLASS(klass);
+ NMDeviceClass *device_class = NM_DEVICE_CLASS(klass);
+
+ object_class->get_property = get_property;
+ object_class->finalize = finalize;
+
+ _NM_OBJECT_CLASS_INIT_PRIV_PTR_DIRECT(nm_object_class, NMDeviceGeneve);
+
+ device_class->connection_compatible = connection_compatible;
+ device_class->get_setting_type = get_setting_type;
+
+ /**
+ * NMDeviceGeneve:id:
+ *
+ * The device's GENEVE ID.
+ *
+ * Since: 1.58
+ **/
+ obj_properties[PROP_ID] = g_param_spec_uint(NM_DEVICE_GENEVE_ID,
+ "",
+ "",
+ 0,
+ (1 << 24) - 1,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMDeviceGeneve:remote:
+ *
+ * The IP address of the remote tunnel endpoint.
+ *
+ * Since: 1.58
+ */
+ obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_GENEVE_REMOTE,
+ "",
+ "",
+ NULL,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMDeviceGeneve:tos:
+ *
+ * The TOS value to use in outgoing packets.
+ *
+ * Since: 1.58
+ */
+ obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_GENEVE_TOS,
+ "",
+ "",
+ 0,
+ 255,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMDeviceGeneve:ttl:
+ *
+ * The time-to-live value to use in outgoing packets.
+ *
+ * Since: 1.58
+ */
+ obj_properties[PROP_TTL] = g_param_spec_int(NM_DEVICE_GENEVE_TTL,
+ "",
+ "",
+ -1,
+ 255,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMDeviceGeneve:dst-port:
+ *
+ * The UDP destination port used to communicate with the remote GENEVE tunnel
+ * endpoint.
+ *
+ * Since: 1.58
+ */
+ obj_properties[PROP_DST_PORT] = g_param_spec_uint(NM_DEVICE_GENEVE_DST_PORT,
+ "",
+ "",
+ 0,
+ 65535,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * NMDeviceGeneve:df:
+ *
+ * The Don't Fragment (DF) bit to set in outgoing packets.
+ *
+ * Since: 1.58
+ */
+ obj_properties[PROP_DF] = g_param_spec_uchar(NM_DEVICE_GENEVE_DF,
+ "",
+ "",
+ 0,
+ 2,
+ 0,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ _nml_dbus_meta_class_init_with_properties(object_class, &_nml_dbus_meta_iface_nm_device_geneve);
+}
diff --git a/src/libnm-client-impl/nm-device.c b/src/libnm-client-impl/nm-device.c
index 1712efa5bb..9203fd6f13 100644
--- a/src/libnm-client-impl/nm-device.c
+++ b/src/libnm-client-impl/nm-device.c
@@ -302,6 +302,7 @@ coerce_type(NMDeviceType type)
case NM_DEVICE_TYPE_TUN:
case NM_DEVICE_TYPE_VETH:
case NM_DEVICE_TYPE_GENERIC:
+ case NM_DEVICE_TYPE_GENEVE:
case NM_DEVICE_TYPE_UNUSED1:
case NM_DEVICE_TYPE_UNUSED2:
case NM_DEVICE_TYPE_UNKNOWN:
@@ -1792,6 +1793,8 @@ get_type_name(NMDevice *device)
return _("MACVLAN");
case NM_DEVICE_TYPE_VXLAN:
return _("VXLAN");
+ case NM_DEVICE_TYPE_GENEVE:
+ return _("GENEVE");
case NM_DEVICE_TYPE_IP_TUNNEL:
return _("IPTunnel");
case NM_DEVICE_TYPE_TUN:
diff --git a/src/libnm-client-impl/nm-libnm-utils.c b/src/libnm-client-impl/nm-libnm-utils.c
index c2fa2addef..9f1b515c2e 100644
--- a/src/libnm-client-impl/nm-libnm-utils.c
+++ b/src/libnm-client-impl/nm-libnm-utils.c
@@ -789,6 +789,7 @@ const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[] = {
&_nml_dbus_meta_iface_nm_device_bridge,
&_nml_dbus_meta_iface_nm_device_dummy,
&_nml_dbus_meta_iface_nm_device_generic,
+ &_nml_dbus_meta_iface_nm_device_geneve,
&_nml_dbus_meta_iface_nm_device_hsr,
&_nml_dbus_meta_iface_nm_device_iptunnel,
&_nml_dbus_meta_iface_nm_device_infiniband,
diff --git a/src/libnm-client-impl/nm-libnm-utils.h b/src/libnm-client-impl/nm-libnm-utils.h
index 7dcf8c18dc..27b77b6009 100644
--- a/src/libnm-client-impl/nm-libnm-utils.h
+++ b/src/libnm-client-impl/nm-libnm-utils.h
@@ -579,7 +579,7 @@ struct _NMLDBusMetaIface {
NML_DBUS_META_IFACE_OBJ_PROPERTIES(), \
##__VA_ARGS__)
-extern const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[47];
+extern const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[48];
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_accesspoint;
@@ -593,6 +593,7 @@ extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bond;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bridge;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_dummy;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_generic;
+extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_geneve;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_hsr;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_infiniband;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_iptunnel;
diff --git a/src/libnm-client-public/NetworkManager.h b/src/libnm-client-public/NetworkManager.h
index 2d1c56521e..880bac6ed7 100644
--- a/src/libnm-client-public/NetworkManager.h
+++ b/src/libnm-client-public/NetworkManager.h
@@ -115,6 +115,7 @@
#include "nm-device-dummy.h"
#include "nm-device-ethernet.h"
#include "nm-device-generic.h"
+#include "nm-device-geneve.h"
#include "nm-device-hsr.h"
#include "nm-device-infiniband.h"
#include "nm-device-ip-tunnel.h"
diff --git a/src/libnm-client-public/meson.build b/src/libnm-client-public/meson.build
index b8ae9cce07..5aa6de2518 100644
--- a/src/libnm-client-public/meson.build
+++ b/src/libnm-client-public/meson.build
@@ -18,6 +18,7 @@ libnm_client_headers = files(
'nm-device-dummy.h',
'nm-device-ethernet.h',
'nm-device-generic.h',
+ 'nm-device-geneve.h',
'nm-device-hsr.h',
'nm-device-infiniband.h',
'nm-device-ip-tunnel.h',
diff --git a/src/libnm-client-public/nm-autoptr.h b/src/libnm-client-public/nm-autoptr.h
index f21f2970c8..12379fe1aa 100644
--- a/src/libnm-client-public/nm-autoptr.h
+++ b/src/libnm-client-public/nm-autoptr.h
@@ -41,6 +41,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceBt, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceDummy, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceEthernet, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceGeneric, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceGeneve, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceHsr, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceIPTunnel, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceInfiniband, g_object_unref)
diff --git a/src/libnm-client-public/nm-device-geneve.h b/src/libnm-client-public/nm-device-geneve.h
new file mode 100644
index 0000000000..5f7c92b30b
--- /dev/null
+++ b/src/libnm-client-public/nm-device-geneve.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#ifndef __NM_DEVICE_GENEVE_H__
+#define __NM_DEVICE_GENEVE_H__
+
+#if !defined(__NETWORKMANAGER_H_INSIDE__) && !defined(NETWORKMANAGER_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include "nm-device.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_DEVICE_GENEVE (nm_device_geneve_get_type())
+#define NM_DEVICE_GENEVE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneve))
+#define NM_DEVICE_GENEVE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass))
+#define NM_IS_DEVICE_GENEVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_GENEVE))
+#define NM_IS_DEVICE_GENEVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_GENEVE))
+#define NM_DEVICE_GENEVE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_GENEVE, NMDeviceGeneveClass))
+
+#define NM_DEVICE_GENEVE_ID "id"
+#define NM_DEVICE_GENEVE_REMOTE "remote"
+#define NM_DEVICE_GENEVE_TOS "tos"
+#define NM_DEVICE_GENEVE_TTL "ttl"
+#define NM_DEVICE_GENEVE_DST_PORT "dst-port"
+#define NM_DEVICE_GENEVE_DF "df"
+
+/**
+ * NMDeviceGeneve:
+ *
+ * Since: 1.58
+ */
+typedef struct _NMDeviceGeneve NMDeviceGeneve;
+typedef struct _NMDeviceGeneveClass NMDeviceGeneveClass;
+
+NM_AVAILABLE_IN_1_58
+GType nm_device_geneve_get_type(void);
+
+NM_AVAILABLE_IN_1_58
+guint nm_device_geneve_get_id(NMDeviceGeneve *device);
+NM_AVAILABLE_IN_1_58
+const char *nm_device_geneve_get_remote(NMDeviceGeneve *device);
+NM_AVAILABLE_IN_1_58
+guint nm_device_geneve_get_dst_port(NMDeviceGeneve *device);
+NM_AVAILABLE_IN_1_58
+guint nm_device_geneve_get_tos(NMDeviceGeneve *device);
+NM_AVAILABLE_IN_1_58
+guint nm_device_geneve_get_ttl(NMDeviceGeneve *device);
+NM_AVAILABLE_IN_1_58
+guint nm_device_geneve_get_df(NMDeviceGeneve *device);
+
+G_END_DECLS
+
+#endif /* __NM_DEVICE_GENEVE_H__ */
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index d7e19627df..9ce72b4145 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -3272,6 +3272,7 @@ nm_connection_is_virtual(NMConnection *connection)
NM_SETTING_BOND_SETTING_NAME,
NM_SETTING_BRIDGE_SETTING_NAME,
NM_SETTING_DUMMY_SETTING_NAME,
+ NM_SETTING_GENEVE_SETTING_NAME,
NM_SETTING_HSR_SETTING_NAME,
NM_SETTING_IP_TUNNEL_SETTING_NAME,
NM_SETTING_IPVLAN_SETTING_NAME,
diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h
index 42bff04ae0..8d34066f0e 100644
--- a/src/libnm-core-public/nm-dbus-interface.h
+++ b/src/libnm-core-public/nm-dbus-interface.h
@@ -36,6 +36,7 @@
#define NM_DBUS_INTERFACE_DEVICE_BRIDGE NM_DBUS_INTERFACE_DEVICE ".Bridge"
#define NM_DBUS_INTERFACE_DEVICE_DUMMY NM_DBUS_INTERFACE_DEVICE ".Dummy"
#define NM_DBUS_INTERFACE_DEVICE_GENERIC NM_DBUS_INTERFACE_DEVICE ".Generic"
+#define NM_DBUS_INTERFACE_DEVICE_GENEVE NM_DBUS_INTERFACE_DEVICE ".Geneve"
#define NM_DBUS_INTERFACE_DEVICE_GRE NM_DBUS_INTERFACE_DEVICE ".Gre"
#define NM_DBUS_INTERFACE_DEVICE_HSR NM_DBUS_INTERFACE_DEVICE ".Hsr"
#define NM_DBUS_INTERFACE_DEVICE_INFINIBAND NM_DBUS_INTERFACE_DEVICE ".Infiniband"
@@ -250,6 +251,7 @@ typedef enum {
* @NM_DEVICE_TYPE_LOOPBACK: a loopback interface. Since: 1.42.
* @NM_DEVICE_TYPE_HSR: A HSR/PRP device. Since: 1.46.
* @NM_DEVICE_TYPE_IPVLAN: A IPVLAN device. Since: 1.52.
+ * @NM_DEVICE_TYPE_GENEVE: A GENEVE device. Since: 1.58.
*
* #NMDeviceType values indicate the type of hardware represented by a
* device object.
@@ -290,6 +292,7 @@ typedef enum {
NM_DEVICE_TYPE_LOOPBACK = 32,
NM_DEVICE_TYPE_HSR = 33,
NM_DEVICE_TYPE_IPVLAN = 34,
+ NM_DEVICE_TYPE_GENEVE = 35,
} NMDeviceType;
/**
diff --git a/vapi/NM-1.0.metadata b/vapi/NM-1.0.metadata
index 6f1720ad7a..3084b7c289 100644
--- a/vapi/NM-1.0.metadata
+++ b/vapi/NM-1.0.metadata
@@ -115,6 +115,7 @@ DEVICE_BT_* parent="NM.DeviceBt" name="DEVICE
DEVICE_DUMMY_* parent="NM.DeviceDummy" name="DEVICE_DUMMY_(.+)"
DEVICE_ETHERNET_* parent="NM.DeviceEthernet" name="DEVICE_ETHERNET_(.+)"
DEVICE_GENERIC_* parent="NM.DeviceGeneric" name="DEVICE_GENERIC_(.+)"
+DEVICE_GENEVE_* parent="NM.DeviceGeneve" name="DEVICE_GENEVE_(.+)"
DEVICE_HSR_* parent="NM.DeviceHsr" name="DEVICE_HSR_(.+)"
DEVICE_INFINIBAND_* parent="NM.DeviceInfiniband" name="DEVICE_INFINIBAND_(.+)"
DEVICE_IP_TUNNEL_* parent="NM.DeviceIPTunnel" name="DEVICE_IP_TUNNEL_(.+)"