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
link_changed (NMDevice *device, NMPlatformLink *info)
{
	NM_DEVICE_CLASS (nm_device_tun_parent_class)->link_changed (device, info);

	reload_tun_properties (NM_DEVICE_TUN (device));
}
static void
get_property (GObject *object, guint prop_id,
              GValue *value, GParamSpec *pspec)
{
	NMDeviceTun *self = NM_DEVICE_TUN (object);
	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);

	switch (prop_id) {
	case PROP_OWNER:
		g_value_set_int64 (value, priv->props.owner);
		break;
	case PROP_GROUP:
		g_value_set_int64 (value, priv->props.group);
		break;
	case PROP_MODE:
		g_value_set_string (value, priv->mode);
		break;
	case PROP_NO_PI:
		g_value_set_boolean (value, priv->props.no_pi);
		break;
	case PROP_VNET_HDR:
		g_value_set_boolean (value, priv->props.vnet_hdr);
		break;
	case PROP_MULTI_QUEUE:
		g_value_set_boolean (value, priv->props.multi_queue);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static NMActStageReturn
act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
{
	NMDeviceTun *self = NM_DEVICE_TUN (device);
	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
	NMSettingWired *s_wired;
	const char *cloned_mac;
	NMActStageReturn ret;

	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);

	ret = NM_DEVICE_CLASS (nm_device_tun_parent_class)->act_stage1_prepare (device, reason);
	if (ret != NM_ACT_STAGE_RETURN_SUCCESS)
		return ret;

	/* Nothing to do for TUN devices */
	if (g_strcmp0 (priv->mode, "tap"))
		return NM_ACT_STAGE_RETURN_SUCCESS;

	s_wired = (NMSettingWired *) nm_device_get_applied_setting (device, NM_TYPE_SETTING_WIRED);
	if (s_wired) {
		/* Set device MAC address if the connection wants to change it */
		cloned_mac = nm_setting_wired_get_cloned_mac_address (s_wired);
		nm_device_set_hw_addr (device, cloned_mac, "set", LOGD_DEVICE);
	}

	return NM_ACT_STAGE_RETURN_SUCCESS;
}
static void
setup (NMDevice *device, NMPlatformLink *plink)
{
	NMDeviceTun *self = NM_DEVICE_TUN (device);
	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);

	NM_DEVICE_CLASS (nm_device_tun_parent_class)->setup (device, plink);

	priv->mode = NULL;
	if (plink->type == NM_LINK_TYPE_TUN)
		priv->mode = "tun";
	else if (plink->type == NM_LINK_TYPE_TAP)
		priv->mode = "tap";
	else
		g_assert_not_reached ();
	g_object_notify (G_OBJECT (device), NM_DEVICE_TUN_MODE);

	reload_tun_properties (NM_DEVICE_TUN (device));
}
static void
update_connection (NMDevice *device, NMConnection *connection)
{
	NMDeviceTun *self = NM_DEVICE_TUN (device);
	NMSettingTun *s_tun = nm_connection_get_setting_tun (connection);
	NMPlatformTunProperties props;
	NMSettingTunMode mode;
	gint64 user, group;
	char *str;

	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_PLATFORM_GET, nm_device_get_ifindex (device), &props)) {
		_LOGW (LOGD_HW, "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, 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);

	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);
	}

	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 ((!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 void
unrealize_notify (NMDevice *device)
{
	NMDeviceTun *self = NM_DEVICE_TUN (device);
	NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE (self);
	GParamSpec **properties;
	guint n_properties, i;

	NM_DEVICE_CLASS (nm_device_tun_parent_class)->unrealize_notify (device);

	memset (&priv->props, 0, sizeof (NMPlatformTunProperties));

	properties = g_object_class_list_properties (G_OBJECT_GET_CLASS (self), &n_properties);
	for (i = 0; i < n_properties; i++)
		g_object_notify_by_pspec (G_OBJECT (self), properties[i]);
	g_free (properties);
}
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;

	s_tun = nm_connection_get_setting_tun (connection);
	if (!s_tun)
		return FALSE;

	if (nm_device_is_real (device)) {
		mode = tun_mode_from_string (priv->mode);
		if (mode != nm_setting_tun_get_mode (s_tun))
			return FALSE;

		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 (group != priv->props.group)
			return FALSE;
		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;
		if (nm_setting_tun_get_multi_queue (s_tun) != priv->props.multi_queue)
			return FALSE;
	}

	return TRUE;
}
static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
	NMDeviceTun *device = NM_DEVICE_TUN (object);

	switch (prop_id) {
	case PROP_HW_ADDRESS:
		g_value_set_string (value, nm_device_tun_get_hw_address (device));
		break;
	case PROP_MODE:
		g_value_set_string (value, nm_device_tun_get_mode (device));
		break;
	case PROP_OWNER:
		g_value_set_int64 (value, nm_device_tun_get_owner (device));
		break;
	case PROP_GROUP:
		g_value_set_int64 (value, nm_device_tun_get_group (device));
		break;
	case PROP_NO_PI:
		g_value_set_boolean (value, nm_device_tun_get_no_pi (device));
		break;
	case PROP_VNET_HDR:
		g_value_set_boolean (value, nm_device_tun_get_vnet_hdr (device));
		break;
	case PROP_MULTI_QUEUE:
		g_value_set_boolean (value, nm_device_tun_get_multi_queue (device));
		break;

	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static const char *
get_hw_address (NMDevice *device)
{
	return nm_device_tun_get_hw_address (NM_DEVICE_TUN (device));
}