about summary refs log tree commit diff
path: root/src/core/devices/nm-device-ip-tunnel.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/devices/nm-device-ip-tunnel.c
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/devices/nm-device-ip-tunnel.c')
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c1299
1 files changed, 1299 insertions, 0 deletions
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
new file mode 100644
index 00000000..da6afb3b
--- /dev/null
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -0,0 +1,1299 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-ip-tunnel.h"
+
+#include <netinet/in.h>
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+#include <linux/if_ether.h>
+
+#include "nm-device-private.h"
+#include "nm-manager.h"
+#include "platform/nm-platform.h"
+#include "nm-device-factory.h"
+#include "nm-core-internal.h"
+#include "settings/nm-settings.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceIPTunnel
+#include "nm-device-logging.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIPTunnel,
+                             PROP_MODE,
+                             PROP_LOCAL,
+                             PROP_REMOTE,
+                             PROP_TTL,
+                             PROP_TOS,
+                             PROP_PATH_MTU_DISCOVERY,
+                             PROP_INPUT_KEY,
+                             PROP_OUTPUT_KEY,
+                             PROP_ENCAPSULATION_LIMIT,
+                             PROP_FLOW_LABEL,
+                             PROP_FLAGS, );
+
+typedef struct {
+    NMIPTunnelMode  mode;
+    char *          local;
+    char *          remote;
+    guint8          ttl;
+    guint8          tos;
+    gboolean        path_mtu_discovery;
+    int             addr_family;
+    char *          input_key;
+    char *          output_key;
+    guint8          encap_limit;
+    guint32         flow_label;
+    NMIPTunnelFlags flags;
+} NMDeviceIPTunnelPrivate;
+
+struct _NMDeviceIPTunnel {
+    NMDevice                parent;
+    NMDeviceIPTunnelPrivate _priv;
+};
+
+struct _NMDeviceIPTunnelClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceIPTunnel, nm_device_ip_tunnel, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceIPTunnel, NM_IS_DEVICE_IP_TUNNEL, NMDevice)
+
+/*****************************************************************************/
+
+static guint32
+ip6tnl_flags_setting_to_plat(NMIPTunnelFlags flags)
+{
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT == IP6_TNL_F_IGN_ENCAP_LIMIT);
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS == IP6_TNL_F_USE_ORIG_TCLASS);
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL == IP6_TNL_F_USE_ORIG_FLOWLABEL);
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV == IP6_TNL_F_MIP6_DEV);
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY == IP6_TNL_F_RCV_DSCP_COPY);
+    G_STATIC_ASSERT(NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK == IP6_TNL_F_USE_ORIG_FWMARK);
+
+    /* NOTE: "accidentally", the numeric values correspond.
+     *       For flags added in the future, that might no longer
+     *       be the case. */
+    return flags & _NM_IP_TUNNEL_FLAG_ALL_IP6TNL;
+}
+
+static NMIPTunnelFlags
+ip6tnl_flags_plat_to_setting(guint32 flags)
+{
+    return flags & ((guint32) _NM_IP_TUNNEL_FLAG_ALL_IP6TNL);
+}
+
+/*****************************************************************************/
+
+static gboolean
+address_equal_pp(int addr_family, const char *a, const char *b)
+{
+    const NMIPAddr *addr_a = &nm_ip_addr_zero;
+    const NMIPAddr *addr_b = &nm_ip_addr_zero;
+    NMIPAddr        addr_a_val;
+    NMIPAddr        addr_b_val;
+
+    nm_assert_addr_family(addr_family);
+
+    if (a) {
+        if (!nm_utils_parse_inaddr_bin(addr_family, a, NULL, &addr_a_val))
+            nm_assert_not_reached();
+        addr_a = &addr_a_val;
+    }
+    if (b) {
+        if (!nm_utils_parse_inaddr_bin(addr_family, b, NULL, &addr_b_val))
+            nm_assert_not_reached();
+        addr_b = &addr_b_val;
+    }
+
+    return nm_ip_addr_equal(addr_family, addr_a, addr_b);
+}
+
+static gboolean
+address_set(int addr_family, char **p_addr, const NMIPAddr *addr_new)
+{
+    nm_assert_addr_family(addr_family);
+    nm_assert(p_addr);
+    nm_assert(!*p_addr || nm_utils_ipaddr_is_normalized(addr_family, *p_addr));
+
+    if (!addr_new || nm_ip_addr_is_null(addr_family, addr_new)) {
+        if (nm_clear_g_free(p_addr))
+            return TRUE;
+        return FALSE;
+    }
+
+    if (*p_addr) {
+        NMIPAddr addr_val;
+
+        if (!nm_utils_parse_inaddr_bin(addr_family, *p_addr, NULL, &addr_val))
+            nm_assert_not_reached();
+
+        if (nm_ip_addr_equal(addr_family, &addr_val, addr_new))
+            return FALSE;
+
+        g_free(*p_addr);
+    }
+
+    *p_addr = nm_utils_inet_ntop_dup(addr_family, addr_new);
+    return TRUE;
+}
+
+static void
+update_properties_from_ifindex(NMDevice *device, int ifindex)
+{
+    NMDeviceIPTunnel *       self           = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnelPrivate *priv           = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+    int                      parent_ifindex = 0;
+    NMIPAddr                 local          = NM_IP_ADDR_INIT;
+    NMIPAddr                 remote         = NM_IP_ADDR_INIT;
+    guint8                   ttl            = 0;
+    guint8                   tos            = 0;
+    guint8                   encap_limit    = 0;
+    gboolean                 pmtud          = FALSE;
+    guint32                  flow_label     = 0;
+    NMIPTunnelFlags          flags          = NM_IP_TUNNEL_FLAG_NONE;
+    char *                   key;
+
+    if (ifindex <= 0) {
+clear:
+        nm_device_parent_set_ifindex(device, 0);
+        if (priv->local) {
+            nm_clear_g_free(&priv->local);
+            _notify(self, PROP_LOCAL);
+        }
+        if (priv->remote) {
+            nm_clear_g_free(&priv->remote);
+            _notify(self, PROP_REMOTE);
+        }
+        if (priv->input_key) {
+            nm_clear_g_free(&priv->input_key);
+            _notify(self, PROP_INPUT_KEY);
+        }
+        if (priv->output_key) {
+            nm_clear_g_free(&priv->output_key);
+            _notify(self, PROP_OUTPUT_KEY);
+        }
+
+        goto out;
+    }
+
+    if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_GRE, NM_IP_TUNNEL_MODE_GRETAP)) {
+        const NMPlatformLnkGre *lnk;
+
+        if (priv->mode == NM_IP_TUNNEL_MODE_GRE)
+            lnk = nm_platform_link_get_lnk_gre(nm_device_get_platform(device), ifindex, NULL);
+        else
+            lnk = nm_platform_link_get_lnk_gretap(nm_device_get_platform(device), ifindex, NULL);
+        if (!lnk) {
+            _LOGW(LOGD_PLATFORM, "could not read %s properties", "gre");
+            goto clear;
+        }
+
+        parent_ifindex = lnk->parent_ifindex;
+        local.addr4    = lnk->local;
+        remote.addr4   = lnk->remote;
+        ttl            = lnk->ttl;
+        tos            = lnk->tos;
+        pmtud          = lnk->path_mtu_discovery;
+
+        if (NM_FLAGS_HAS(lnk->input_flags, NM_GRE_KEY)) {
+            key = g_strdup_printf("%u", lnk->input_key);
+            if (g_strcmp0(priv->input_key, key)) {
+                g_free(priv->input_key);
+                priv->input_key = key;
+                _notify(self, PROP_INPUT_KEY);
+            } else
+                g_free(key);
+        } else {
+            if (priv->input_key) {
+                nm_clear_g_free(&priv->input_key);
+                _notify(self, PROP_INPUT_KEY);
+            }
+        }
+
+        if (NM_FLAGS_HAS(lnk->output_flags, NM_GRE_KEY)) {
+            key = g_strdup_printf("%u", lnk->output_key);
+            if (g_strcmp0(priv->output_key, key)) {
+                g_free(priv->output_key);
+                priv->output_key = key;
+                _notify(self, PROP_OUTPUT_KEY);
+            } else
+                g_free(key);
+        } else {
+            if (priv->output_key) {
+                nm_clear_g_free(&priv->output_key);
+                _notify(self, PROP_OUTPUT_KEY);
+            }
+        }
+    } else if (priv->mode == NM_IP_TUNNEL_MODE_SIT) {
+        const NMPlatformLnkSit *lnk;
+
+        lnk = nm_platform_link_get_lnk_sit(nm_device_get_platform(device), ifindex, NULL);
+        if (!lnk) {
+            _LOGW(LOGD_PLATFORM, "could not read %s properties", "sit");
+            goto clear;
+        }
+
+        parent_ifindex = lnk->parent_ifindex;
+        local.addr4    = lnk->local;
+        remote.addr4   = lnk->remote;
+        ttl            = lnk->ttl;
+        tos            = lnk->tos;
+        pmtud          = lnk->path_mtu_discovery;
+    } else if (priv->mode == NM_IP_TUNNEL_MODE_IPIP) {
+        const NMPlatformLnkIpIp *lnk;
+
+        lnk = nm_platform_link_get_lnk_ipip(nm_device_get_platform(device), ifindex, NULL);
+        if (!lnk) {
+            _LOGW(LOGD_PLATFORM, "could not read %s properties", "ipip");
+            goto clear;
+        }
+
+        parent_ifindex = lnk->parent_ifindex;
+        local.addr4    = lnk->local;
+        remote.addr4   = lnk->remote;
+        ttl            = lnk->ttl;
+        tos            = lnk->tos;
+        pmtud          = lnk->path_mtu_discovery;
+    } else if (NM_IN_SET(priv->mode,
+                         NM_IP_TUNNEL_MODE_IPIP6,
+                         NM_IP_TUNNEL_MODE_IP6IP6,
+                         NM_IP_TUNNEL_MODE_IP6GRE,
+                         NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+        const NMPlatformLnkIp6Tnl *lnk;
+        NMPlatform *               plat = nm_device_get_platform(device);
+
+        if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRE)
+            lnk = nm_platform_link_get_lnk_ip6gre(plat, ifindex, NULL);
+        else if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRETAP)
+            lnk = nm_platform_link_get_lnk_ip6gretap(plat, ifindex, NULL);
+        else
+            lnk = nm_platform_link_get_lnk_ip6tnl(plat, ifindex, NULL);
+
+        if (!lnk) {
+            _LOGW(LOGD_PLATFORM, "could not read %s properties", "ip6tnl");
+            goto clear;
+        }
+
+        parent_ifindex = lnk->parent_ifindex;
+        local.addr6    = lnk->local;
+        remote.addr6   = lnk->remote;
+        ttl            = lnk->ttl;
+        tos            = lnk->tclass;
+        encap_limit    = lnk->encap_limit;
+        flow_label     = lnk->flow_label;
+        flags          = ip6tnl_flags_plat_to_setting(lnk->flags);
+
+        if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_IP6GRE, NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+            if (NM_FLAGS_HAS(lnk->input_flags, NM_GRE_KEY)) {
+                key = g_strdup_printf("%u", lnk->input_key);
+                if (g_strcmp0(priv->input_key, key)) {
+                    g_free(priv->input_key);
+                    priv->input_key = key;
+                    _notify(self, PROP_INPUT_KEY);
+                } else
+                    g_free(key);
+            } else {
+                if (priv->input_key) {
+                    nm_clear_g_free(&priv->input_key);
+                    _notify(self, PROP_INPUT_KEY);
+                }
+            }
+
+            if (NM_FLAGS_HAS(lnk->output_flags, NM_GRE_KEY)) {
+                key = g_strdup_printf("%u", lnk->output_key);
+                if (g_strcmp0(priv->output_key, key)) {
+                    g_free(priv->output_key);
+                    priv->output_key = key;
+                    _notify(self, PROP_OUTPUT_KEY);
+                } else
+                    g_free(key);
+            } else {
+                if (priv->output_key) {
+                    nm_clear_g_free(&priv->output_key);
+                    _notify(self, PROP_OUTPUT_KEY);
+                }
+            }
+        }
+    } else
+        g_return_if_reached();
+
+    nm_device_parent_set_ifindex(device, parent_ifindex);
+
+    if (address_set(priv->addr_family, &priv->local, &local))
+        _notify(self, PROP_LOCAL);
+    if (address_set(priv->addr_family, &priv->remote, &remote))
+        _notify(self, PROP_REMOTE);
+
+out:
+
+    if (priv->ttl != ttl) {
+        priv->ttl = ttl;
+        _notify(self, PROP_TTL);
+    }
+
+    if (priv->tos != tos) {
+        priv->tos = tos;
+        _notify(self, PROP_TOS);
+    }
+
+    if (priv->path_mtu_discovery != pmtud) {
+        priv->path_mtu_discovery = pmtud;
+        _notify(self, PROP_PATH_MTU_DISCOVERY);
+    }
+
+    if (priv->encap_limit != encap_limit) {
+        priv->encap_limit = encap_limit;
+        _notify(self, PROP_ENCAPSULATION_LIMIT);
+    }
+
+    if (priv->flow_label != flow_label) {
+        priv->flow_label = flow_label;
+        _notify(self, PROP_FLOW_LABEL);
+    }
+
+    if (priv->flags != flags) {
+        priv->flags = flags;
+        _notify(self, PROP_FLAGS);
+    }
+}
+
+static void
+update_properties(NMDevice *device)
+{
+    update_properties_from_ifindex(device, nm_device_get_ifindex(device));
+}
+
+static void
+link_changed(NMDevice *device, const NMPlatformLink *pllink)
+{
+    NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)->link_changed(device, pllink);
+    update_properties(device);
+}
+
+static gboolean
+complete_connection(NMDevice *           device,
+                    NMConnection *       connection,
+                    const char *         specific_object,
+                    NMConnection *const *existing_connections,
+                    GError **            error)
+{
+    NMSettingIPTunnel *s_ip_tunnel;
+
+    nm_utils_complete_generic(nm_device_get_platform(device),
+                              connection,
+                              NM_SETTING_IP_TUNNEL_SETTING_NAME,
+                              existing_connections,
+                              NULL,
+                              _("IP tunnel connection"),
+                              NULL,
+                              NULL,
+                              TRUE);
+
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+    if (!s_ip_tunnel) {
+        g_set_error_literal(error,
+                            NM_DEVICE_ERROR,
+                            NM_DEVICE_ERROR_INVALID_CONNECTION,
+                            "A 'tunnel' setting is required.");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void
+update_connection(NMDevice *device, NMConnection *connection)
+{
+    NMDeviceIPTunnel *       self        = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnelPrivate *priv        = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+    NMSettingIPTunnel *      s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+
+    if (!s_ip_tunnel) {
+        s_ip_tunnel = (NMSettingIPTunnel *) nm_setting_ip_tunnel_new();
+        nm_connection_add_setting(connection, (NMSetting *) s_ip_tunnel);
+    }
+
+    if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) != priv->mode)
+        g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_MODE, priv->mode, NULL);
+
+    g_object_set(
+        s_ip_tunnel,
+        NM_SETTING_IP_TUNNEL_PARENT,
+        nm_device_parent_find_for_connection(device, nm_setting_ip_tunnel_get_parent(s_ip_tunnel)),
+        NULL);
+
+    if (!address_equal_pp(priv->addr_family,
+                          nm_setting_ip_tunnel_get_local(s_ip_tunnel),
+                          priv->local))
+        g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_LOCAL, priv->local, NULL);
+
+    if (!address_equal_pp(priv->addr_family,
+                          nm_setting_ip_tunnel_get_remote(s_ip_tunnel),
+                          priv->remote))
+        g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_REMOTE, priv->remote, NULL);
+
+    if (nm_setting_ip_tunnel_get_ttl(s_ip_tunnel) != priv->ttl)
+        g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_TTL, priv->ttl, NULL);
+
+    if (nm_setting_ip_tunnel_get_tos(s_ip_tunnel) != priv->tos)
+        g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_TOS, priv->tos, NULL);
+
+    if (nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel) != priv->path_mtu_discovery) {
+        g_object_set(G_OBJECT(s_ip_tunnel),
+                     NM_SETTING_IP_TUNNEL_PATH_MTU_DISCOVERY,
+                     priv->path_mtu_discovery,
+                     NULL);
+    }
+
+    if (nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel) != priv->encap_limit) {
+        g_object_set(G_OBJECT(s_ip_tunnel),
+                     NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT,
+                     priv->encap_limit,
+                     NULL);
+    }
+
+    if (nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel) != priv->flow_label) {
+        g_object_set(G_OBJECT(s_ip_tunnel),
+                     NM_SETTING_IP_TUNNEL_FLOW_LABEL,
+                     priv->flow_label,
+                     NULL);
+    }
+
+    if (NM_IN_SET(priv->mode,
+                  NM_IP_TUNNEL_MODE_GRE,
+                  NM_IP_TUNNEL_MODE_GRETAP,
+                  NM_IP_TUNNEL_MODE_IP6GRE,
+                  NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+        if (g_strcmp0(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel), priv->input_key)) {
+            g_object_set(G_OBJECT(s_ip_tunnel),
+                         NM_SETTING_IP_TUNNEL_INPUT_KEY,
+                         priv->input_key,
+                         NULL);
+        }
+        if (g_strcmp0(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel), priv->output_key)) {
+            g_object_set(G_OBJECT(s_ip_tunnel),
+                         NM_SETTING_IP_TUNNEL_OUTPUT_KEY,
+                         priv->output_key,
+                         NULL);
+        }
+    }
+}
+
+static gboolean
+check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+{
+    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+    NMSettingIPTunnel *      s_ip_tunnel;
+    const char *             parent;
+
+    if (!NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)
+             ->check_connection_compatible(device, connection, error))
+        return FALSE;
+
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+
+    if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) != priv->mode) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                   "incompatible IP tunnel mode");
+        return FALSE;
+    }
+
+    if (nm_device_is_real(device)) {
+        /* Check parent interface; could be an interface name or a UUID */
+        parent = nm_setting_ip_tunnel_get_parent(s_ip_tunnel);
+        if (parent && !nm_device_match_parent(device, parent)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "IP tunnel parent mismatches");
+            return FALSE;
+        }
+
+        if (!address_equal_pp(priv->addr_family,
+                              nm_setting_ip_tunnel_get_local(s_ip_tunnel),
+                              priv->local)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "local IP tunnel address mismatches");
+            return FALSE;
+        }
+
+        if (!address_equal_pp(priv->addr_family,
+                              nm_setting_ip_tunnel_get_remote(s_ip_tunnel),
+                              priv->remote)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "remote IP tunnel address mismatches");
+            return FALSE;
+        }
+
+        if (nm_setting_ip_tunnel_get_ttl(s_ip_tunnel) != priv->ttl) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "TTL of IP tunnel mismatches");
+            return FALSE;
+        }
+
+        if (nm_setting_ip_tunnel_get_tos(s_ip_tunnel) != priv->tos) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "TOS of IP tunnel mismatches");
+            return FALSE;
+        }
+
+        if (priv->addr_family == AF_INET) {
+            if (nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel)
+                != priv->path_mtu_discovery) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "MTU discovery setting of IP tunnel mismatches");
+                return FALSE;
+            }
+        } else {
+            if (nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel) != priv->encap_limit) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "encapsulation limit of IP tunnel mismatches");
+                return FALSE;
+            }
+
+            if (nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel) != priv->flow_label) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "flow-label of IP tunnel mismatches");
+                return FALSE;
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+static NMIPTunnelMode
+platform_link_to_tunnel_mode(const NMPlatformLink *link)
+{
+    const NMPlatformLnkIp6Tnl *lnk;
+
+    switch (link->type) {
+    case NM_LINK_TYPE_GRE:
+        return NM_IP_TUNNEL_MODE_GRE;
+    case NM_LINK_TYPE_GRETAP:
+        return NM_IP_TUNNEL_MODE_GRETAP;
+    case NM_LINK_TYPE_IP6TNL:
+        lnk = nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, link->ifindex, NULL);
+        if (lnk) {
+            if (lnk->proto == IPPROTO_IPIP)
+                return NM_IP_TUNNEL_MODE_IPIP6;
+            if (lnk->proto == IPPROTO_IPV6)
+                return NM_IP_TUNNEL_MODE_IP6IP6;
+        }
+        return NM_IP_TUNNEL_MODE_UNKNOWN;
+    case NM_LINK_TYPE_IP6GRE:
+        return NM_IP_TUNNEL_MODE_IP6GRE;
+    case NM_LINK_TYPE_IP6GRETAP:
+        return NM_IP_TUNNEL_MODE_IP6GRETAP;
+    case NM_LINK_TYPE_IPIP:
+        return NM_IP_TUNNEL_MODE_IPIP;
+    case NM_LINK_TYPE_SIT:
+        return NM_IP_TUNNEL_MODE_SIT;
+    default:
+        g_return_val_if_reached(NM_IP_TUNNEL_MODE_UNKNOWN);
+    }
+}
+
+static NMLinkType
+tunnel_mode_to_link_type(NMIPTunnelMode tunnel_mode)
+{
+    switch (tunnel_mode) {
+    case NM_IP_TUNNEL_MODE_GRE:
+        return NM_LINK_TYPE_GRE;
+    case NM_IP_TUNNEL_MODE_GRETAP:
+        return NM_LINK_TYPE_GRETAP;
+    case NM_IP_TUNNEL_MODE_IPIP6:
+    case NM_IP_TUNNEL_MODE_IP6IP6:
+        return NM_LINK_TYPE_IP6TNL;
+    case NM_IP_TUNNEL_MODE_IP6GRE:
+        return NM_LINK_TYPE_IP6GRE;
+    case NM_IP_TUNNEL_MODE_IP6GRETAP:
+        return NM_LINK_TYPE_IP6GRETAP;
+    case NM_IP_TUNNEL_MODE_IPIP:
+        return NM_LINK_TYPE_IPIP;
+    case NM_IP_TUNNEL_MODE_SIT:
+        return NM_LINK_TYPE_SIT;
+    case NM_IP_TUNNEL_MODE_VTI:
+    case NM_IP_TUNNEL_MODE_VTI6:
+    case NM_IP_TUNNEL_MODE_ISATAP:
+        return NM_LINK_TYPE_UNKNOWN;
+    case NM_IP_TUNNEL_MODE_UNKNOWN:
+        break;
+    }
+    g_return_val_if_reached(NM_LINK_TYPE_UNKNOWN);
+}
+
+/*****************************************************************************/
+
+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);
+    NMSettingIPTunnel * s_ip_tunnel;
+    NMPlatformLnkGre    lnk_gre    = {};
+    NMPlatformLnkSit    lnk_sit    = {};
+    NMPlatformLnkIpIp   lnk_ipip   = {};
+    NMPlatformLnkIp6Tnl lnk_ip6tnl = {};
+    const char *        str;
+    gint64              val;
+    NMIPTunnelMode      mode;
+    int                 r;
+    gs_free char *      hwaddr = NULL;
+    guint8              mac_address[ETH_ALEN];
+    gboolean            mac_address_valid = FALSE;
+
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+    nm_assert(NM_IS_SETTING_IP_TUNNEL(s_ip_tunnel));
+
+    mode = nm_setting_ip_tunnel_get_mode(s_ip_tunnel);
+
+    if (_nm_ip_tunnel_mode_is_layer2(mode)
+        && nm_device_hw_addr_get_cloned(device, connection, FALSE, &hwaddr, NULL, NULL) && hwaddr) {
+        /* FIXME: we set the MAC address when creating the interface, while the
+         * NMDevice is still unrealized. As we afterwards realize the device, it
+         * forgets the parameters for the cloned MAC address, and in stage 1
+         * it might create a different MAC address. That should be fixed by
+         * better handling device realization. */
+        if (!nm_utils_hwaddr_aton(hwaddr, mac_address, ETH_ALEN)) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_FAILED,
+                        "Invalid hardware address '%s'",
+                        hwaddr);
+            g_return_val_if_reached(FALSE);
+        }
+
+        mac_address_valid = TRUE;
+    }
+
+    switch (mode) {
+    case NM_IP_TUNNEL_MODE_GRETAP:
+        lnk_gre.is_tap = TRUE;
+        /* fall-through */
+    case NM_IP_TUNNEL_MODE_GRE:
+        if (parent)
+            lnk_gre.parent_ifindex = nm_device_get_ifindex(parent);
+
+        str = nm_setting_ip_tunnel_get_local(s_ip_tunnel);
+        if (str)
+            inet_pton(AF_INET, str, &lnk_gre.local);
+
+        str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel);
+        g_assert(str);
+        inet_pton(AF_INET, str, &lnk_gre.remote);
+
+        lnk_gre.ttl                = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel);
+        lnk_gre.tos                = nm_setting_ip_tunnel_get_tos(s_ip_tunnel);
+        lnk_gre.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel);
+
+        val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel),
+                                           10,
+                                           0,
+                                           G_MAXUINT32,
+                                           -1);
+        if (val != -1) {
+            lnk_gre.input_key   = val;
+            lnk_gre.input_flags = NM_GRE_KEY;
+        }
+
+        val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel),
+                                           10,
+                                           0,
+                                           G_MAXUINT32,
+                                           -1);
+        if (val != -1) {
+            lnk_gre.output_key   = val;
+            lnk_gre.output_flags = NM_GRE_KEY;
+        }
+
+        r = nm_platform_link_gre_add(nm_device_get_platform(device),
+                                     iface,
+                                     mac_address_valid ? mac_address : NULL,
+                                     mac_address_valid ? ETH_ALEN : 0,
+                                     &lnk_gre,
+                                     out_plink);
+        if (r < 0) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_CREATION_FAILED,
+                        "Failed to create GRE interface '%s' for '%s': %s",
+                        iface,
+                        nm_connection_get_id(connection),
+                        nm_strerror(r));
+            return FALSE;
+        }
+        break;
+    case NM_IP_TUNNEL_MODE_SIT:
+        if (parent)
+            lnk_sit.parent_ifindex = nm_device_get_ifindex(parent);
+
+        str = nm_setting_ip_tunnel_get_local(s_ip_tunnel);
+        if (str)
+            inet_pton(AF_INET, str, &lnk_sit.local);
+
+        str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel);
+        g_assert(str);
+        inet_pton(AF_INET, str, &lnk_sit.remote);
+
+        lnk_sit.ttl                = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel);
+        lnk_sit.tos                = nm_setting_ip_tunnel_get_tos(s_ip_tunnel);
+        lnk_sit.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel);
+
+        r = nm_platform_link_sit_add(nm_device_get_platform(device), iface, &lnk_sit, out_plink);
+        if (r < 0) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_CREATION_FAILED,
+                        "Failed to create SIT interface '%s' for '%s': %s",
+                        iface,
+                        nm_connection_get_id(connection),
+                        nm_strerror(r));
+            return FALSE;
+        }
+        break;
+    case NM_IP_TUNNEL_MODE_IPIP:
+        if (parent)
+            lnk_ipip.parent_ifindex = nm_device_get_ifindex(parent);
+
+        str = nm_setting_ip_tunnel_get_local(s_ip_tunnel);
+        if (str)
+            inet_pton(AF_INET, str, &lnk_ipip.local);
+
+        str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel);
+        g_assert(str);
+        inet_pton(AF_INET, str, &lnk_ipip.remote);
+
+        lnk_ipip.ttl                = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel);
+        lnk_ipip.tos                = nm_setting_ip_tunnel_get_tos(s_ip_tunnel);
+        lnk_ipip.path_mtu_discovery = nm_setting_ip_tunnel_get_path_mtu_discovery(s_ip_tunnel);
+
+        r = nm_platform_link_ipip_add(nm_device_get_platform(device), iface, &lnk_ipip, out_plink);
+        if (r < 0) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_CREATION_FAILED,
+                        "Failed to create IPIP interface '%s' for '%s': %s",
+                        iface,
+                        nm_connection_get_id(connection),
+                        nm_strerror(r));
+            return FALSE;
+        }
+        break;
+    case NM_IP_TUNNEL_MODE_IPIP6:
+    case NM_IP_TUNNEL_MODE_IP6IP6:
+    case NM_IP_TUNNEL_MODE_IP6GRE:
+    case NM_IP_TUNNEL_MODE_IP6GRETAP:
+        if (parent)
+            lnk_ip6tnl.parent_ifindex = nm_device_get_ifindex(parent);
+
+        str = nm_setting_ip_tunnel_get_local(s_ip_tunnel);
+        if (str)
+            inet_pton(AF_INET6, str, &lnk_ip6tnl.local);
+
+        str = nm_setting_ip_tunnel_get_remote(s_ip_tunnel);
+        g_assert(str);
+        inet_pton(AF_INET6, str, &lnk_ip6tnl.remote);
+
+        lnk_ip6tnl.ttl         = nm_setting_ip_tunnel_get_ttl(s_ip_tunnel);
+        lnk_ip6tnl.tclass      = nm_setting_ip_tunnel_get_tos(s_ip_tunnel);
+        lnk_ip6tnl.encap_limit = nm_setting_ip_tunnel_get_encapsulation_limit(s_ip_tunnel);
+        lnk_ip6tnl.flow_label  = nm_setting_ip_tunnel_get_flow_label(s_ip_tunnel);
+        lnk_ip6tnl.flags =
+            ip6tnl_flags_setting_to_plat(nm_setting_ip_tunnel_get_flags(s_ip_tunnel));
+
+        if (NM_IN_SET(mode, NM_IP_TUNNEL_MODE_IP6GRE, NM_IP_TUNNEL_MODE_IP6GRETAP)) {
+            val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_input_key(s_ip_tunnel),
+                                               10,
+                                               0,
+                                               G_MAXUINT32,
+                                               -1);
+            if (val != -1) {
+                lnk_ip6tnl.input_key   = val;
+                lnk_ip6tnl.input_flags = NM_GRE_KEY;
+            }
+
+            val = _nm_utils_ascii_str_to_int64(nm_setting_ip_tunnel_get_output_key(s_ip_tunnel),
+                                               10,
+                                               0,
+                                               G_MAXUINT32,
+                                               -1);
+            if (val != -1) {
+                lnk_ip6tnl.output_key   = val;
+                lnk_ip6tnl.output_flags = NM_GRE_KEY;
+            }
+
+            lnk_ip6tnl.is_gre = TRUE;
+            lnk_ip6tnl.is_tap = (mode == NM_IP_TUNNEL_MODE_IP6GRETAP);
+
+            r = nm_platform_link_ip6gre_add(nm_device_get_platform(device),
+                                            iface,
+                                            mac_address_valid ? mac_address : NULL,
+                                            mac_address_valid ? ETH_ALEN : 0,
+                                            &lnk_ip6tnl,
+                                            out_plink);
+        } else {
+            lnk_ip6tnl.proto = nm_setting_ip_tunnel_get_mode(s_ip_tunnel) == NM_IP_TUNNEL_MODE_IPIP6
+                                   ? IPPROTO_IPIP
+                                   : IPPROTO_IPV6;
+            r                = nm_platform_link_ip6tnl_add(nm_device_get_platform(device),
+                                            iface,
+                                            &lnk_ip6tnl,
+                                            out_plink);
+        }
+        if (r < 0) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_CREATION_FAILED,
+                        "Failed to create IPv6 tunnel interface '%s' for '%s': %s",
+                        iface,
+                        nm_connection_get_id(connection),
+                        nm_strerror(r));
+            return FALSE;
+        }
+        break;
+    default:
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_CREATION_FAILED,
+                    "Failed to create IP tunnel interface '%s' for '%s': mode %d not supported",
+                    iface,
+                    nm_connection_get_id(connection),
+                    (int) nm_setting_ip_tunnel_get_mode(s_ip_tunnel));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static guint32
+get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
+{
+    return nm_device_get_configured_mtu_from_connection(device,
+                                                        NM_TYPE_SETTING_IP_TUNNEL,
+                                                        out_source);
+}
+
+static NMDeviceCapabilities
+get_generic_capabilities(NMDevice *device)
+{
+    return NM_DEVICE_CAP_IS_SOFTWARE;
+}
+
+static void
+unrealize_notify(NMDevice *device)
+{
+    NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)->unrealize_notify(device);
+
+    update_properties_from_ifindex(device, 0);
+}
+
+static gboolean
+can_reapply_change(NMDevice *  device,
+                   const char *setting_name,
+                   NMSetting * s_old,
+                   NMSetting * s_new,
+                   GHashTable *diffs,
+                   GError **   error)
+{
+    NMDeviceClass *device_class;
+
+    /* Only handle ip-tunnel setting here, delegate other settings to parent class */
+    if (nm_streq(setting_name, NM_SETTING_IP_TUNNEL_SETTING_NAME)) {
+        return nm_device_hash_check_invalid_keys(
+            diffs,
+            NM_SETTING_IP_TUNNEL_SETTING_NAME,
+            error,
+            NM_SETTING_IP_TUNNEL_MTU); /* reapplied with IP config */
+    }
+
+    device_class = NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class);
+    return device_class->can_reapply_change(device, setting_name, s_old, s_new, diffs, error);
+}
+
+static NMActStageReturn
+act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+
+    if (_nm_ip_tunnel_mode_is_layer2(priv->mode)
+        && !nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) {
+        *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MODE:
+        g_value_set_uint(value, priv->mode);
+        break;
+    case PROP_LOCAL:
+        g_value_set_string(value, priv->local);
+        break;
+    case PROP_REMOTE:
+        g_value_set_string(value, priv->remote);
+        break;
+    case PROP_TTL:
+        g_value_set_uchar(value, priv->ttl);
+        break;
+    case PROP_TOS:
+        g_value_set_uchar(value, priv->tos);
+        break;
+    case PROP_PATH_MTU_DISCOVERY:
+        g_value_set_boolean(value, priv->path_mtu_discovery);
+        break;
+    case PROP_INPUT_KEY:
+        g_value_set_string(value, priv->input_key);
+        break;
+    case PROP_OUTPUT_KEY:
+        g_value_set_string(value, priv->output_key);
+        break;
+    case PROP_ENCAPSULATION_LIMIT:
+        g_value_set_uchar(value, priv->encap_limit);
+        break;
+    case PROP_FLOW_LABEL:
+        g_value_set_uint(value, priv->flow_label);
+        break;
+    case PROP_FLAGS:
+        g_value_set_uint(value, priv->flags);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MODE:
+        priv->mode = g_value_get_uint(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_ip_tunnel_init(NMDeviceIPTunnel *self)
+{}
+
+static void
+constructed(GObject *object)
+{
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(object);
+
+    if (NM_IN_SET(priv->mode,
+                  NM_IP_TUNNEL_MODE_IPIP6,
+                  NM_IP_TUNNEL_MODE_IP6IP6,
+                  NM_IP_TUNNEL_MODE_IP6GRE,
+                  NM_IP_TUNNEL_MODE_IP6GRETAP))
+        priv->addr_family = AF_INET6;
+    else
+        priv->addr_family = AF_INET;
+
+    G_OBJECT_CLASS(nm_device_ip_tunnel_parent_class)->constructed(object);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(object);
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+
+    nm_clear_g_free(&priv->local);
+    nm_clear_g_free(&priv->remote);
+    nm_clear_g_free(&priv->input_key);
+    nm_clear_g_free(&priv->output_key);
+
+    G_OBJECT_CLASS(nm_device_ip_tunnel_parent_class)->dispose(object);
+}
+
+static const NMDBusInterfaceInfoExtended interface_info_device_ip_tunnel = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_IP_TUNNEL,
+        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mode", "u", NM_DEVICE_IP_TUNNEL_MODE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Parent", "o", NM_DEVICE_PARENT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Local",
+                                                             "s",
+                                                             NM_DEVICE_IP_TUNNEL_LOCAL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Remote",
+                                                             "s",
+                                                             NM_DEVICE_IP_TUNNEL_REMOTE),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ttl", "y", NM_DEVICE_IP_TUNNEL_TTL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Tos", "y", NM_DEVICE_IP_TUNNEL_TOS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
+                "PathMtuDiscovery",
+                "b",
+                NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("InputKey",
+                                                             "s",
+                                                             NM_DEVICE_IP_TUNNEL_INPUT_KEY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("OutputKey",
+                                                             "s",
+                                                             NM_DEVICE_IP_TUNNEL_OUTPUT_KEY),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L(
+                "EncapsulationLimit",
+                "y",
+                NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FlowLabel",
+                                                             "u",
+                                                             NM_DEVICE_IP_TUNNEL_FLOW_LABEL),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Flags",
+                                                             "u",
+                                                             NM_DEVICE_IP_TUNNEL_FLAGS), ), ),
+    .legacy_property_changed = TRUE,
+};
+
+static void
+nm_device_ip_tunnel_class_init(NMDeviceIPTunnelClass *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->constructed  = constructed;
+    object_class->dispose      = dispose;
+    object_class->get_property = get_property;
+    object_class->set_property = set_property;
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ip_tunnel);
+
+    device_class->connection_type_supported        = NM_SETTING_IP_TUNNEL_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_IP_TUNNEL_SETTING_NAME;
+    device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_GRE,
+                                                           NM_LINK_TYPE_GRETAP,
+                                                           NM_LINK_TYPE_IP6TNL,
+                                                           NM_LINK_TYPE_IP6GRE,
+                                                           NM_LINK_TYPE_IP6GRETAP,
+                                                           NM_LINK_TYPE_IPIP,
+                                                           NM_LINK_TYPE_SIT);
+
+    device_class->act_stage1_prepare          = act_stage1_prepare;
+    device_class->link_changed                = link_changed;
+    device_class->can_reapply_change          = can_reapply_change;
+    device_class->complete_connection         = complete_connection;
+    device_class->update_connection           = update_connection;
+    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->get_configured_mtu          = get_configured_mtu;
+    device_class->unrealize_notify            = unrealize_notify;
+
+    obj_properties[PROP_MODE] =
+        g_param_spec_uint(NM_DEVICE_IP_TUNNEL_MODE,
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_LOCAL] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_LOCAL,
+                                                     "",
+                                                     "",
+                                                     NULL,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_REMOTE] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_REMOTE,
+                                                      "",
+                                                      "",
+                                                      NULL,
+                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_TTL] = g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_TTL,
+                                                  "",
+                                                  "",
+                                                  0,
+                                                  255,
+                                                  0,
+                                                  G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_TOS] = g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_TOS,
+                                                  "",
+                                                  "",
+                                                  0,
+                                                  255,
+                                                  0,
+                                                  G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PATH_MTU_DISCOVERY] =
+        g_param_spec_boolean(NM_DEVICE_IP_TUNNEL_PATH_MTU_DISCOVERY,
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_INPUT_KEY] = g_param_spec_string(NM_DEVICE_IP_TUNNEL_INPUT_KEY,
+                                                         "",
+                                                         "",
+                                                         NULL,
+                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_OUTPUT_KEY] =
+        g_param_spec_string(NM_DEVICE_IP_TUNNEL_OUTPUT_KEY,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_ENCAPSULATION_LIMIT] =
+        g_param_spec_uchar(NM_DEVICE_IP_TUNNEL_ENCAPSULATION_LIMIT,
+                           "",
+                           "",
+                           0,
+                           255,
+                           0,
+                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_FLOW_LABEL] = g_param_spec_uint(NM_DEVICE_IP_TUNNEL_FLOW_LABEL,
+                                                        "",
+                                                        "",
+                                                        0,
+                                                        (1 << 20) - 1,
+                                                        0,
+                                                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_DEVICE_IP_TUNNEL_FLAGS,
+                                                   "",
+                                                   "",
+                                                   0,
+                                                   G_MAXUINT32,
+                                                   0,
+                                                   G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
+
+/*****************************************************************************/
+
+#define NM_TYPE_IP_TUNNEL_DEVICE_FACTORY (nm_ip_tunnel_device_factory_get_type())
+#define NM_IP_TUNNEL_DEVICE_FACTORY(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP_TUNNEL_DEVICE_FACTORY, NMIPTunnelDeviceFactory))
+
+static NMDevice *
+create_device(NMDeviceFactory *     factory,
+              const char *          iface,
+              const NMPlatformLink *plink,
+              NMConnection *        connection,
+              gboolean *            out_ignore)
+{
+    NMSettingIPTunnel *s_ip_tunnel;
+    NMIPTunnelMode     mode;
+    NMLinkType         link_type;
+
+    if (connection) {
+        s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+        mode        = nm_setting_ip_tunnel_get_mode(s_ip_tunnel);
+        link_type   = tunnel_mode_to_link_type(mode);
+    } else {
+        link_type = plink->type;
+        mode      = platform_link_to_tunnel_mode(plink);
+    }
+
+    if (mode == NM_IP_TUNNEL_MODE_UNKNOWN || link_type == NM_LINK_TYPE_UNKNOWN)
+        return NULL;
+
+    return g_object_new(NM_TYPE_DEVICE_IP_TUNNEL,
+                        NM_DEVICE_IFACE,
+                        iface,
+                        NM_DEVICE_TYPE_DESC,
+                        "IPTunnel",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_IP_TUNNEL,
+                        NM_DEVICE_LINK_TYPE,
+                        link_type,
+                        NM_DEVICE_IP_TUNNEL_MODE,
+                        mode,
+                        NULL);
+}
+
+static const char *
+get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
+{
+    NMSettingIPTunnel *s_ip_tunnel;
+
+    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME),
+                         NULL);
+
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+    g_assert(s_ip_tunnel);
+
+    return nm_setting_ip_tunnel_get_parent(s_ip_tunnel);
+}
+
+static char *
+get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
+{
+    const char *       ifname;
+    NMSettingIPTunnel *s_ip_tunnel;
+
+    g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME),
+                         NULL);
+
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
+    g_assert(s_ip_tunnel);
+
+    if (nm_setting_ip_tunnel_get_parent(s_ip_tunnel) && !parent_iface)
+        return NULL;
+
+    ifname = nm_connection_get_interface_name(connection);
+
+    return g_strdup(ifname);
+}
+
+NM_DEVICE_FACTORY_DEFINE_INTERNAL(
+    IP_TUNNEL,
+    IPTunnel,
+    ip_tunnel,
+    NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(NM_LINK_TYPE_GRE,
+                                         NM_LINK_TYPE_GRETAP,
+                                         NM_LINK_TYPE_SIT,
+                                         NM_LINK_TYPE_IPIP,
+                                         NM_LINK_TYPE_IP6TNL,
+                                         NM_LINK_TYPE_IP6GRE,
+                                         NM_LINK_TYPE_IP6GRETAP)
+        NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_IP_TUNNEL_SETTING_NAME),
+    factory_class->create_device         = create_device;
+    factory_class->get_connection_parent = get_connection_parent;
+    factory_class->get_connection_iface  = get_connection_iface;);