summary refs log tree commit diff
path: root/src/devices/nm-device-tun.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/nm-device-tun.c')
-rw-r--r--src/devices/nm-device-tun.c355
1 files changed, 176 insertions, 179 deletions
diff --git a/src/devices/nm-device-tun.c b/src/devices/nm-device-tun.c
index c3ce4b73..a7d7c0bf 100644
--- a/src/devices/nm-device-tun.c
+++ b/src/devices/nm-device-tun.c
@@ -25,7 +25,6 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
-#include <linux/if_tun.h>
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
@@ -35,6 +34,8 @@
 #include "nm-setting-tun.h"
 #include "nm-core-internal.h"
 
+#include "introspection/org.freedesktop.NetworkManager.Device.Tun.h"
+
 #include "nm-device-logging.h"
 _LOG_DECLARE_SELF(NMDeviceTun);
 
@@ -50,7 +51,8 @@ NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceTun,
 );
 
 typedef struct {
-	NMPlatformLnkTun props;
+	NMPlatformTunProperties props;
+	const char *mode;
 } NMDeviceTunPrivate;
 
 struct _NMDeviceTun {
@@ -69,62 +71,48 @@ G_DEFINE_TYPE (NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void
-update_properties_from_struct (NMDeviceTun *self,
-                               const NMPlatformLnkTun *props)
-{
-	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	const NMPlatformLnkTun props0 = { };
-
-	if (!props) {
-		/* allow passing %NULL to reset all properties. */
-		props = &props0;
-	}
-
-	g_object_freeze_notify (G_OBJECT (self));
-
-#define CHECK_PROPERTY_CHANGED_VALID(field, prop) \
-	G_STMT_START { \
-		if (   priv->props.field != props->field \
-		    || priv->props.field##_valid != props->field##_valid) { \
-			priv->props.field##_valid = props->field##_valid; \
-			priv->props.field = props->field; \
-			_notify (self, prop); \
-		} \
-	} G_STMT_END
-
-#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_VALID (owner, PROP_OWNER);
-	CHECK_PROPERTY_CHANGED_VALID (group, PROP_GROUP);
-	CHECK_PROPERTY_CHANGED (type, PROP_MODE);
-	CHECK_PROPERTY_CHANGED (pi, PROP_NO_PI);
-	CHECK_PROPERTY_CHANGED (vnet_hdr, PROP_VNET_HDR);
-	CHECK_PROPERTY_CHANGED (multi_queue, PROP_MULTI_QUEUE);
-
-	g_object_thaw_notify (G_OBJECT (self));
-}
-
-static void
 update_properties (NMDeviceTun *self)
 {
-	NMPlatformLnkTun props_storage;
-	const NMPlatformLnkTun *props = NULL;
+	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
+	GObject *object = G_OBJECT (self);
+	NMPlatformTunProperties props;
 	int ifindex;
 
 	ifindex = nm_device_get_ifindex (NM_DEVICE (self));
-	if (   ifindex > 0
-	    && nm_platform_link_tun_get_properties (nm_device_get_platform (NM_DEVICE (self)),
-	                                            ifindex,
-	                                            &props_storage))
-		props = &props_storage;
-
-	update_properties_from_struct (self, props);
+	if (ifindex > 0) {
+		if (!nm_platform_link_tun_get_properties (nm_device_get_platform (NM_DEVICE (self)), ifindex, &props)) {
+			_LOGD (LOGD_DEVICE, "tun-properties: cannot loading tun properties from platform for ifindex %d", ifindex);
+			ifindex = 0;
+		} else if (g_strcmp0 (priv->mode, props.mode) != 0) {
+			/* if the mode differs, we ignore what we loaded. A NMDeviceTun cannot
+			 * change the mode after construction. */
+			_LOGD (LOGD_DEVICE, "tun-properties: loading tun properties yielded tun-mode %s%s%s, but %s%s%s expected (ifindex %d)",
+			       NM_PRINT_FMT_QUOTE_STRING (props.mode),
+			       NM_PRINT_FMT_QUOTE_STRING (priv->mode),
+			       ifindex);
+			ifindex = 0;
+		}
+	} else
+		_LOGD (LOGD_DEVICE, "tun-properties: ignore loading properties due to missing ifindex");
+	if (ifindex <= 0)
+		memset (&props, 0, sizeof (props));
+
+	g_object_freeze_notify (object);
+
+	if (priv->props.owner != props.owner)
+		_notify (self, PROP_OWNER);
+	if (priv->props.group != props.group)
+		_notify (self, PROP_GROUP);
+	if (priv->props.no_pi != props.no_pi)
+		_notify (self, PROP_NO_PI);
+	if (priv->props.vnet_hdr != props.vnet_hdr)
+		_notify (self, PROP_VNET_HDR);
+	if (priv->props.multi_queue != props.multi_queue)
+		_notify (self, PROP_MULTI_QUEUE);
+
+	memcpy (&priv->props, &props, sizeof (NMPlatformTunProperties));
+
+	g_object_thaw_notify (object);
 }
 
 static NMDeviceCapabilities
@@ -145,7 +133,7 @@ static gboolean
 complete_connection (NMDevice *device,
                      NMConnection *connection,
                      const char *specific_object,
-                     NMConnection *const*existing_connections,
+                     const GSList *existing_connections,
                      GError **error)
 {
 	NMSettingTun *s_tun;
@@ -169,57 +157,61 @@ complete_connection (NMDevice *device,
 	return TRUE;
 }
 
+static int
+tun_mode_from_string (const char *string)
+{
+	if (!g_strcmp0 (string, "tap"))
+		return NM_SETTING_TUN_MODE_TAP;
+	else
+		return NM_SETTING_TUN_MODE_TUN;
+}
+
 static void
 update_connection (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
-	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	NMSettingTun *s_tun;
+	NMSettingTun *s_tun = nm_connection_get_setting_tun (connection);
+	NMPlatformTunProperties props;
 	NMSettingTunMode mode;
-	char s_buf[100];
-	const char *str;
-
-	/* Note: since we read tun properties from sysctl for older kernels,
-	 *       we don't get proper change notifications. Make sure that all our
-	 *       tun properties are up to date at this point. We should not do this,
-	 *       if we would entirely rely on netlink events. */
-	update_properties (NM_DEVICE_TUN (device));
+	gint64 user, group;
+	char *str;
 
-	switch (priv->props.type) {
-	case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; break;
-	case IFF_TAP: mode = NM_SETTING_TUN_MODE_TAP; break;
-	default:
-		/* Huh? */
-		return;
-	}
-
-	s_tun = nm_connection_get_setting_tun (connection);
 	if (!s_tun) {
 		s_tun = (NMSettingTun *) nm_setting_tun_new ();
 		nm_connection_add_setting (connection, (NMSetting *) s_tun);
 	}
 
+	if (!nm_platform_link_tun_get_properties (nm_device_get_platform (device), nm_device_get_ifindex (device), &props)) {
+		_LOGW (LOGD_PLATFORM, "failed to get TUN interface info while updating connection.");
+		return;
+	}
+
+	mode = tun_mode_from_string (props.mode);
+
 	if (mode != nm_setting_tun_get_mode (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MODE, (guint) mode, NULL);
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MODE, mode, NULL);
+
+	user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
+	group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
 
-	str = priv->props.owner_valid
-	      ? nm_sprintf_buf (s_buf, "%" G_GINT32_FORMAT, priv->props.owner)
-	      : NULL;
-	if (!nm_streq0 (str, nm_setting_tun_get_owner (s_tun)))
+	if (props.owner != user) {
+		str = props.owner >= 0 ? g_strdup_printf ("%" G_GINT32_FORMAT, (gint32) props.owner) : NULL;
 		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_OWNER, str, NULL);
+		g_free (str);
+	}
 
-	str = priv->props.group_valid
-	      ? nm_sprintf_buf (s_buf, "%" G_GINT32_FORMAT, priv->props.group)
-	      : NULL;
-	if (!nm_streq0 (str, nm_setting_tun_get_group (s_tun)))
+	if (props.group != group) {
+		str = props.group >= 0 ? g_strdup_printf ("%" G_GINT32_FORMAT, (gint32) props.group) : NULL;
 		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_GROUP, str, NULL);
+		g_free (str);
+	}
 
-	if (priv->props.pi != nm_setting_tun_get_pi (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_PI, (gboolean) priv->props.pi, NULL);
-	if (priv->props.vnet_hdr != nm_setting_tun_get_vnet_hdr (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_VNET_HDR, (gboolean) priv->props.vnet_hdr, NULL);
-	if (priv->props.multi_queue != nm_setting_tun_get_multi_queue (s_tun))
-		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MULTI_QUEUE, (gboolean) priv->props.multi_queue, NULL);
+	if ((!props.no_pi) != nm_setting_tun_get_pi (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_PI, !props.no_pi, NULL);
+	if (props.vnet_hdr != nm_setting_tun_get_vnet_hdr (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_VNET_HDR, props.vnet_hdr, NULL);
+	if (props.multi_queue != nm_setting_tun_get_multi_queue (s_tun))
+		g_object_set (G_OBJECT (s_tun), NM_SETTING_TUN_MULTI_QUEUE, props.multi_queue, NULL);
 }
 
 static gboolean
@@ -230,42 +222,23 @@ create_and_realize (NMDevice *device,
                     GError **error)
 {
 	const char *iface = nm_device_get_iface (device);
-	NMPlatformLnkTun props = { };
 	NMPlatformError plerr;
 	NMSettingTun *s_tun;
-	gint64 owner, group;
+	gint64 user, group;
 
 	s_tun = nm_connection_get_setting_tun (connection);
-	g_return_val_if_fail (s_tun, FALSE);
-
-	switch (nm_setting_tun_get_mode (s_tun)) {
-	case NM_SETTING_TUN_MODE_TAP: props.type = IFF_TAP; break;
-	case NM_SETTING_TUN_MODE_TUN: props.type = IFF_TUN; break;
-	default:
-		g_return_val_if_reached (FALSE);
-	}
+	g_assert (s_tun);
 
-	owner = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
-	if (owner != -1) {
-		props.owner_valid = TRUE;
-		props.owner = owner;
-	}
+	user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
 	group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
-	if (group != -1) {
-		props.group_valid = TRUE;
-		props.group = group;
-	}
 
-	props.pi = nm_setting_tun_get_pi (s_tun);
-	props.vnet_hdr = nm_setting_tun_get_vnet_hdr (s_tun);
-	props.multi_queue = nm_setting_tun_get_multi_queue (s_tun);
-	props.persist = TRUE;
-
-	plerr = nm_platform_link_tun_add (nm_device_get_platform (device),
-	                                  iface,
-	                                  &props,
-	                                  out_plink,
-	                                  NULL);
+	plerr = nm_platform_link_tun_add (nm_device_get_platform (device), iface,
+	                                  nm_setting_tun_get_mode (s_tun) == NM_SETTING_TUN_MODE_TAP,
+	                                  user, group,
+	                                  nm_setting_tun_get_pi (s_tun),
+	                                  nm_setting_tun_get_vnet_hdr (s_tun),
+	                                  nm_setting_tun_get_multi_queue (s_tun),
+	                                  out_plink);
 	if (plerr != NM_PLATFORM_ERROR_SUCCESS) {
 		g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
 		             "Failed to create TUN/TAP interface '%s' for '%s': %s",
@@ -279,22 +252,13 @@ create_and_realize (NMDevice *device,
 }
 
 static gboolean
-_same_og (const char *str, gboolean og_valid, guint32 og_num)
-{
-	gint64 v;
-
-	v = _nm_utils_ascii_str_to_int64 (str, 10, 0, G_MAXINT32, -1);
-	return    (!og_valid && (           v  == (gint64) -1))
-	       || ( og_valid && (((guint32) v) == og_num     ));
-}
-
-static gboolean
 check_connection_compatible (NMDevice *device, NMConnection *connection)
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (device);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
 	NMSettingTunMode mode;
 	NMSettingTun *s_tun;
+	gint64 user, group;
 
 	if (!NM_DEVICE_CLASS (nm_device_tun_parent_class)->check_connection_compatible (device, connection))
 		return FALSE;
@@ -304,21 +268,18 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 		return FALSE;
 
 	if (nm_device_is_real (device)) {
-		switch (priv->props.type) {
-		case IFF_TUN: mode = NM_SETTING_TUN_MODE_TUN; break;
-		case IFF_TAP: mode = NM_SETTING_TUN_MODE_TAP; break;
-		default:
-			/* Huh? */
-			return FALSE;
-		}
-
+		mode = tun_mode_from_string (priv->mode);
 		if (mode != nm_setting_tun_get_mode (s_tun))
 			return FALSE;
-		if (!_same_og (nm_setting_tun_get_owner (s_tun), priv->props.owner_valid, priv->props.owner))
+
+		user = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_owner (s_tun), 10, 0, G_MAXINT32, -1);
+		group = _nm_utils_ascii_str_to_int64 (nm_setting_tun_get_group (s_tun), 10, 0, G_MAXINT32, -1);
+
+		if (user != priv->props.owner)
 			return FALSE;
-		if (!_same_og (nm_setting_tun_get_group (s_tun), priv->props.group_valid, priv->props.group))
+		if (group != priv->props.group)
 			return FALSE;
-		if (nm_setting_tun_get_pi (s_tun) != priv->props.pi)
+		if (nm_setting_tun_get_pi (s_tun) == priv->props.no_pi)
 			return FALSE;
 		if (nm_setting_tun_get_vnet_hdr (s_tun) != priv->props.vnet_hdr)
 			return FALSE;
@@ -341,7 +302,7 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 		return ret;
 
 	/* Nothing to do for TUN devices */
-	if (priv->props.type == IFF_TUN)
+	if (g_strcmp0 (priv->mode, "tap"))
 		return NM_ACT_STAGE_RETURN_SUCCESS;
 
 	if (!nm_device_hw_addr_set_cloned (device, nm_device_get_applied_connection (device), FALSE))
@@ -353,8 +314,16 @@ act_stage1_prepare (NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 unrealize_notify (NMDevice *device)
 {
+	NMDeviceTun *self = NM_DEVICE_TUN (device);
+	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
+	guint i;
+
 	NM_DEVICE_CLASS (nm_device_tun_parent_class)->unrealize_notify (device);
-	update_properties_from_struct (NM_DEVICE_TUN (device), NULL);
+
+	memset (&priv->props, 0, sizeof (NMPlatformTunProperties));
+
+	for (i = 1; i < _PROPERTY_ENUMS_LAST; i++)
+		g_object_notify_by_pspec ((GObject *) self, obj_properties[i]);
 }
 
 /*****************************************************************************/
@@ -365,25 +334,19 @@ get_property (GObject *object, guint prop_id,
 {
 	NMDeviceTun *self = NM_DEVICE_TUN (object);
 	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
-	const char *s;
 
 	switch (prop_id) {
 	case PROP_OWNER:
-		g_value_set_int64 (value, priv->props.owner_valid ? (gint64) priv->props.owner : (gint64) -1);
+		g_value_set_int64 (value, priv->props.owner);
 		break;
 	case PROP_GROUP:
-		g_value_set_int64 (value, priv->props.group_valid ? (gint64) priv->props.group : (gint64) -1);
+		g_value_set_int64 (value, priv->props.group);
 		break;
 	case PROP_MODE:
-		switch (priv->props.type) {
-		case IFF_TUN: s = "tun"; break;
-		case IFF_TAP: s = "tap"; break;
-		default:      s = NULL;  break;
-		}
-		g_value_set_static_string (value, s);
+		g_value_set_string (value, priv->mode);
 		break;
 	case PROP_NO_PI:
-		g_value_set_boolean (value, !priv->props.pi);
+		g_value_set_boolean (value, priv->props.no_pi);
 		break;
 	case PROP_VNET_HDR:
 		g_value_set_boolean (value, priv->props.vnet_hdr);
@@ -397,6 +360,33 @@ get_property (GObject *object, guint prop_id,
 	}
 }
 
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+	NMDeviceTun *self = NM_DEVICE_TUN (object);
+	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
+	const char *str;
+
+	switch (prop_id) {
+	case PROP_MODE:
+		/* construct-only */
+		str = g_value_get_string (value);
+
+		/* mode is G_PARAM_STATIC_STRINGS */
+		if (g_strcmp0 (str, "tun") == 0)
+			priv->mode = "tun";
+		else if (g_strcmp0 (str, "tap") == 0)
+			priv->mode = "tap";
+		else
+			g_return_if_fail (FALSE);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
 /*****************************************************************************/
 
 static void
@@ -404,37 +394,16 @@ nm_device_tun_init (NMDeviceTun *self)
 {
 }
 
-static const NMDBusInterfaceInfoExtended interface_info_device_tun = {
-	.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
-		NM_DBUS_INTERFACE_DEVICE_TUN,
-		.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 ("Owner",      "x",  NM_DEVICE_TUN_OWNER),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Group",      "x",  NM_DEVICE_TUN_GROUP),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Mode",       "s",  NM_DEVICE_TUN_MODE),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("NoPi",       "b",  NM_DEVICE_TUN_NO_PI),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("VnetHdr",    "b",  NM_DEVICE_TUN_VNET_HDR),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("MultiQueue", "b",  NM_DEVICE_TUN_MULTI_QUEUE),
-			NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("HwAddress",  "s",  NM_DEVICE_HW_ADDRESS),
-		),
-	),
-	.legacy_property_changed = TRUE,
-};
-
 static void
 nm_device_tun_class_init (NMDeviceTunClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-	NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
 	NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
 
-	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_TUN)
+	NM_DEVICE_CLASS_DECLARE_TYPES (klass, NULL, NM_LINK_TYPE_TUN, NM_LINK_TYPE_TAP)
 
 	object_class->get_property = get_property;
-
-	dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_tun);
+	object_class->set_property = set_property;
 
 	device_class->connection_type = NM_SETTING_TUN_SETTING_NAME;
 	device_class->link_changed = link_changed;
@@ -459,8 +428,9 @@ nm_device_tun_class_init (NMDeviceTunClass *klass)
 
 	obj_properties[PROP_MODE] =
 	     g_param_spec_string (NM_DEVICE_TUN_MODE, "", "",
-	                          NULL,
-	                          G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+	                          "tun",
+	                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+	                          G_PARAM_STATIC_STRINGS);
 
 	obj_properties[PROP_NO_PI] =
 	     g_param_spec_boolean (NM_DEVICE_TUN_NO_PI, "", "",
@@ -478,6 +448,10 @@ nm_device_tun_class_init (NMDeviceTunClass *klass)
 	                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
 	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+	nm_exported_object_class_add_interface (NM_EXPORTED_OBJECT_CLASS (klass),
+	                                        NMDBUS_TYPE_DEVICE_TUN_SKELETON,
+	                                        NULL);
 }
 
 
@@ -493,19 +467,42 @@ create_device (NMDeviceFactory *factory,
                NMConnection *connection,
                gboolean *out_ignore)
 {
-	g_return_val_if_fail (!plink || plink->type == NM_LINK_TYPE_TUN, NULL);
-	g_return_val_if_fail (!connection || nm_streq0 (nm_connection_get_connection_type (connection), NM_SETTING_TUN_SETTING_NAME), NULL);
+	NMSettingTun *s_tun;
+	NMLinkType link_type = NM_LINK_TYPE_UNKNOWN;
+	const char *mode;
+
+	if (plink) {
+		link_type = plink->type;
+	} else if (connection) {
+		s_tun = nm_connection_get_setting_tun (connection);
+		if (!s_tun)
+			return NULL;
+		switch (nm_setting_tun_get_mode (s_tun)) {
+		case NM_SETTING_TUN_MODE_TUN:
+			link_type = NM_LINK_TYPE_TUN;
+			break;
+		case NM_SETTING_TUN_MODE_TAP:
+			link_type = NM_LINK_TYPE_TAP;
+			break;
+		case NM_SETTING_TUN_MODE_UNKNOWN:
+			g_return_val_if_reached (NULL);
+		}
+	}
+
+	g_return_val_if_fail (link_type != NM_LINK_TYPE_UNKNOWN, NULL);
+	mode = link_type == NM_LINK_TYPE_TUN ? "tun" : "tap";
 
 	return (NMDevice *) g_object_new (NM_TYPE_DEVICE_TUN,
 	                                  NM_DEVICE_IFACE, iface,
 	                                  NM_DEVICE_TYPE_DESC, "Tun",
 	                                  NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_TUN,
-	                                  NM_DEVICE_LINK_TYPE, (guint) NM_LINK_TYPE_TUN,
+	                                  NM_DEVICE_LINK_TYPE, link_type,
+	                                  NM_DEVICE_TUN_MODE, mode,
 	                                  NULL);
 }
 
 NM_DEVICE_FACTORY_DEFINE_INTERNAL (TUN, Tun, tun,
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_TUN)
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_TUN, NM_LINK_TYPE_TAP)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_TUN_SETTING_NAME),
 	factory_class->create_device = create_device;
 );