Commit b0626324 authored by Dan Williams's avatar Dan Williams
Browse files

merge: automatic VPN reconnect support (bgo #349151)

parents 4b2935b9 6cbbb9c0
......@@ -57,6 +57,11 @@ typedef struct {
*/
char *user_name;
/* Whether the VPN stays up across link changes, until the user
* explicitly disconnects it.
*/
gboolean persistent;
/* The hash table is created at setting object
* init time and should not be replaced. It is
* a char * -> char * mapping, and both the key
......@@ -80,6 +85,7 @@ enum {
PROP_0,
PROP_SERVICE_TYPE,
PROP_USER_NAME,
PROP_PERSISTENT,
PROP_DATA,
PROP_SECRETS,
......@@ -130,6 +136,20 @@ nm_setting_vpn_get_user_name (NMSettingVpn *setting)
return NM_SETTING_VPN_GET_PRIVATE (setting)->user_name;
}
/**
* nm_setting_vpn_get_persistent:
* @setting: the #NMSettingVpn
*
* Returns: the #NMSettingVpn:persistent property of the setting
**/
gboolean
nm_setting_vpn_get_persistent (NMSettingVpn *setting)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
return NM_SETTING_VPN_GET_PRIVATE (setting)->persistent;
}
/**
* nm_setting_vpn_get_num_data_items:
* @setting: the #NMSettingVpn
......@@ -721,6 +741,9 @@ set_property (GObject *object, guint prop_id,
g_free (priv->user_name);
priv->user_name = g_value_dup_string (value);
break;
case PROP_PERSISTENT:
priv->persistent = g_value_get_boolean (value);
break;
case PROP_DATA:
g_hash_table_unref (priv->data);
priv->data = _nm_utils_copy_strdict (g_value_get_boxed (value));
......@@ -749,6 +772,9 @@ get_property (GObject *object, guint prop_id,
case PROP_USER_NAME:
g_value_set_string (value, nm_setting_vpn_get_user_name (setting));
break;
case PROP_PERSISTENT:
g_value_set_boolean (value, priv->persistent);
break;
case PROP_DATA:
g_value_take_boxed (value, _nm_utils_copy_strdict (priv->data));
break;
......@@ -814,6 +840,20 @@ nm_setting_vpn_class_init (NMSettingVpnClass *setting_class)
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingVpn:persistent:
*
* If the VPN service supports persistence, and this property is %TRUE,
* the VPN will attempt to stay connected across link changes and outages,
* until explicitly disconnected.
**/
g_object_class_install_property
(object_class, PROP_PERSISTENT,
g_param_spec_boolean (NM_SETTING_VPN_PERSISTENT, "", "",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingVpn:data:
*
......
......@@ -42,6 +42,7 @@ G_BEGIN_DECLS
#define NM_SETTING_VPN_SERVICE_TYPE "service-type"
#define NM_SETTING_VPN_USER_NAME "user-name"
#define NM_SETTING_VPN_PERSISTENT "persistent"
#define NM_SETTING_VPN_DATA "data"
#define NM_SETTING_VPN_SECRETS "secrets"
......@@ -70,6 +71,7 @@ GType nm_setting_vpn_get_type (void);
NMSetting *nm_setting_vpn_new (void);
const char *nm_setting_vpn_get_service_type (NMSettingVpn *setting);
const char *nm_setting_vpn_get_user_name (NMSettingVpn *setting);
gboolean nm_setting_vpn_get_persistent (NMSettingVpn *setting);
guint32 nm_setting_vpn_get_num_data_items (NMSettingVpn *setting);
void nm_setting_vpn_add_data_item (NMSettingVpn *setting,
......
......@@ -206,6 +206,11 @@ typedef enum {
/* boolean: Has IP6 configuration? */
#define NM_VPN_PLUGIN_CONFIG_HAS_IP6 "has-ip6"
/* boolean: If %TRUE the VPN plugin can persist/reconnect the connection over
* link changes and VPN server dropouts.
*/
#define NM_VPN_PLUGIN_CAN_PERSIST "can-persist"
/*** Ip4Config ***/
......
......@@ -206,6 +206,11 @@ typedef enum {
/* boolean: Has IP6 configuration? */
#define NM_VPN_PLUGIN_CONFIG_HAS_IP6 "has-ip6"
/* boolean: If %TRUE the VPN plugin can persist/reconnect the connection over
* link changes and VPN server dropouts.
*/
#define NM_VPN_PLUGIN_CAN_PERSIST "can-persist"
/*** Ip4Config ***/
......
......@@ -81,6 +81,11 @@ typedef struct {
*/
char *user_name;
/* Whether the VPN stays up across link changes, until the user
* explicitly disconnects it.
*/
gboolean persistent;
/* The hash table is created at setting object
* init time and should not be replaced. It is
* a char * -> char * mapping, and both the key
......@@ -104,6 +109,7 @@ enum {
PROP_0,
PROP_SERVICE_TYPE,
PROP_USER_NAME,
PROP_PERSISTENT,
PROP_DATA,
PROP_SECRETS,
......@@ -154,6 +160,20 @@ nm_setting_vpn_get_user_name (NMSettingVPN *setting)
return NM_SETTING_VPN_GET_PRIVATE (setting)->user_name;
}
/**
* nm_setting_vpn_get_persistent:
* @setting: the #NMSettingVPN
*
* Returns: the #NMSettingVPN:persistent property of the setting
**/
gboolean
nm_setting_vpn_get_persistent (NMSettingVPN *setting)
{
g_return_val_if_fail (NM_IS_SETTING_VPN (setting), FALSE);
return NM_SETTING_VPN_GET_PRIVATE (setting)->persistent;
}
/**
* nm_setting_vpn_get_num_data_items:
* @setting: the #NMSettingVPN
......@@ -744,6 +764,9 @@ set_property (GObject *object, guint prop_id,
g_free (priv->user_name);
priv->user_name = g_value_dup_string (value);
break;
case PROP_PERSISTENT:
priv->persistent = g_value_get_boolean (value);
break;
case PROP_DATA:
/* Must make a deep copy of the hash table here... */
g_hash_table_remove_all (priv->data);
......@@ -778,6 +801,9 @@ get_property (GObject *object, guint prop_id,
case PROP_USER_NAME:
g_value_set_string (value, nm_setting_vpn_get_user_name (setting));
break;
case PROP_PERSISTENT:
g_value_set_boolean (value, priv->persistent);
break;
case PROP_DATA:
g_value_set_boxed (value, priv->data);
break;
......@@ -843,6 +869,20 @@ nm_setting_vpn_class_init (NMSettingVPNClass *setting_class)
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingVPN:persistent:
*
* If the VPN service supports persistence, and this property is %TRUE,
* the VPN will attempt to stay connected across link changes and outages,
* until explicitly disconnected.
**/
g_object_class_install_property
(object_class, PROP_PERSISTENT,
g_param_spec_boolean (NM_SETTING_VPN_PERSISTENT, "", "",
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* NMSettingVPN:data:
*
......
......@@ -54,6 +54,7 @@ GQuark nm_setting_vpn_error_quark (void);
#define NM_SETTING_VPN_SERVICE_TYPE "service-type"
#define NM_SETTING_VPN_USER_NAME "user-name"
#define NM_SETTING_VPN_PERSISTENT "persistent"
#define NM_SETTING_VPN_DATA "data"
#define NM_SETTING_VPN_SECRETS "secrets"
......@@ -85,6 +86,7 @@ GType nm_setting_vpn_get_type (void);
NMSetting *nm_setting_vpn_new (void);
const char *nm_setting_vpn_get_service_type (NMSettingVPN *setting);
const char *nm_setting_vpn_get_user_name (NMSettingVPN *setting);
gboolean nm_setting_vpn_get_persistent (NMSettingVPN *setting);
guint32 nm_setting_vpn_get_num_data_items (NMSettingVPN *setting);
void nm_setting_vpn_add_data_item (NMSettingVPN *setting,
......
......@@ -19,6 +19,7 @@
*/
#include <glib.h>
#include "nm-types.h"
#include "nm-active-connection.h"
#include "nm-dbus-interface.h"
......@@ -30,7 +31,7 @@
#include "nm-auth-utils.h"
#include "nm-auth-subject.h"
#include "NetworkManagerUtils.h"
#include "gsystem-local-alloc.h"
#include "nm-active-connection-glue.h"
/* Base class for anything implementing the Connection.Active D-Bus interface */
......@@ -94,6 +95,12 @@ enum {
LAST_PROP
};
enum {
DEVICE_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void check_master_ready (NMActiveConnection *self);
static void _device_cleanup (NMActiveConnection *self);
......@@ -395,20 +402,23 @@ gboolean
nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
{
NMActiveConnectionPrivate *priv;
gs_unref_object NMDevice *old_device = NULL;
g_return_val_if_fail (NM_IS_ACTIVE_CONNECTION (self), FALSE);
g_return_val_if_fail (!device || NM_IS_DEVICE (device), FALSE);
priv = NM_ACTIVE_CONNECTION_GET_PRIVATE (self);
if (device == priv->device)
return TRUE;
if (device) {
g_return_val_if_fail (priv->device == NULL, FALSE);
old_device = priv->device ? g_object_ref (priv->device) : NULL;
_device_cleanup (self);
if (device) {
/* Device obviously can't be its own master */
g_return_val_if_fail (!priv->master || device != nm_active_connection_get_device (priv->master), FALSE);
priv->device = g_object_ref (device);
g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_INT_DEVICE);
g_signal_connect (device, "state-changed",
G_CALLBACK (device_state_changed), self);
......@@ -419,7 +429,14 @@ nm_active_connection_set_device (NMActiveConnection *self, NMDevice *device)
priv->pending_activation_id = g_strdup_printf ("activation::%p", (void *)self);
nm_device_add_pending_action (device, priv->pending_activation_id, TRUE);
}
}
} else
priv->device = NULL;
g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_INT_DEVICE);
g_signal_emit (self, signals[DEVICE_CHANGED], 0, priv->device, old_device);
g_object_notify (G_OBJECT (self), NM_ACTIVE_CONNECTION_DEVICES);
return TRUE;
}
......@@ -1014,6 +1031,14 @@ nm_active_connection_class_init (NMActiveConnectionClass *ac_class)
FALSE, G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
signals[DEVICE_CHANGED] =
g_signal_new (NM_ACTIVE_CONNECTION_DEVICE_CHANGED,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (NMActiveConnectionClass, device_changed),
NULL, NULL, NULL,
G_TYPE_NONE, 2, NM_TYPE_DEVICE, NM_TYPE_DEVICE);
nm_dbus_manager_register_exported_type (nm_dbus_manager_get (),
G_TYPE_FROM_CLASS (ac_class),
&dbus_glib_nm_active_connection_object_info);
......
......@@ -56,6 +56,9 @@
#define NM_ACTIVE_CONNECTION_INT_MASTER "int-master"
#define NM_ACTIVE_CONNECTION_INT_MASTER_READY "int-master-ready"
/* Internal signals*/
#define NM_ACTIVE_CONNECTION_DEVICE_CHANGED "device-changed"
struct _NMActiveConnection {
GObject parent;
};
......@@ -71,6 +74,10 @@ typedef struct {
NMDeviceState new_state,
NMDeviceState old_state);
void (*master_failed) (NMActiveConnection *connection);
void (*device_changed) (NMActiveConnection *connection,
NMDevice *new_device,
NMDevice *old_device);
} NMActiveConnectionClass;
GType nm_active_connection_get_type (void);
......
......@@ -658,6 +658,20 @@ update_ip4_routing (NMPolicy *policy, gboolean force_update)
gw_addr = nm_ip4_config_get_gateway (ip4_config);
if (best) {
const GSList *connections, *iter;
connections = nm_manager_get_active_connections (priv->manager);
for (iter = connections; iter; iter = g_slist_next (iter)) {
NMActiveConnection *active = iter->data;
if ( NM_IS_VPN_CONNECTION (active)
&& nm_vpn_connection_get_ip4_config (NM_VPN_CONNECTION (active))
&& !nm_active_connection_get_device (active))
nm_active_connection_set_device (active, best);
}
}
if (vpn) {
NMDevice *parent = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
int parent_ifindex = nm_device_get_ip_ifindex (parent);
......@@ -861,6 +875,20 @@ update_ip6_routing (NMPolicy *policy, gboolean force_update)
if (!gw_addr)
gw_addr = &in6addr_any;
if (best) {
const GSList *connections, *iter;
connections = nm_manager_get_active_connections (priv->manager);
for (iter = connections; iter; iter = g_slist_next (iter)) {
NMActiveConnection *active = iter->data;
if ( NM_IS_VPN_CONNECTION (active)
&& nm_vpn_connection_get_ip6_config (NM_VPN_CONNECTION (active))
&& !nm_active_connection_get_device (active))
nm_active_connection_set_device (active, best);
}
}
if (vpn) {
NMDevice *parent = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
int parent_ifindex = nm_device_get_ip_ifindex (parent);
......@@ -1807,6 +1835,28 @@ vpn_connection_state_changed (NMVpnConnection *vpn,
}
}
static void
vpn_connection_retry_after_failure (NMVpnConnection *vpn, NMPolicy *policy)
{
NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE (policy);
NMActiveConnection *ac = NM_ACTIVE_CONNECTION (vpn);
NMConnection *connection = nm_active_connection_get_connection (ac);
GError *error = NULL;
/* Attempt to reconnect VPN connections that failed after being connected */
if (!nm_manager_activate_connection (priv->manager,
connection,
NULL,
NULL,
nm_active_connection_get_subject (ac),
&error)) {
nm_log_warn (LOGD_DEVICE, "VPN '%s' reconnect failed: %s",
nm_connection_get_id (connection),
error->message ? error->message : "unknown");
g_clear_error (&error);
}
}
static void
active_connection_state_changed (NMActiveConnection *active,
GParamSpec *pspec,
......@@ -1831,6 +1881,9 @@ active_connection_added (NMManager *manager,
g_signal_connect (active, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
G_CALLBACK (vpn_connection_state_changed),
policy);
g_signal_connect (active, NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE,
G_CALLBACK (vpn_connection_retry_after_failure),
policy);
}
g_signal_connect (active, "notify::" NM_ACTIVE_CONNECTION_STATE,
......@@ -1848,6 +1901,9 @@ active_connection_removed (NMManager *manager,
g_signal_handlers_disconnect_by_func (active,
vpn_connection_state_changed,
policy);
g_signal_handlers_disconnect_by_func (active,
vpn_connection_retry_after_failure,
policy);
g_signal_handlers_disconnect_by_func (active,
active_connection_state_changed,
policy);
......
......@@ -35,6 +35,7 @@
#include "reader.h"
#include "common.h"
#include "utils.h"
#include "nm-core-internal.h"
/* Some setting properties also contain setting names, such as
* NMSettingConnection's 'type' property (which specifies the base type of the
......@@ -616,7 +617,8 @@ read_hash_of_string (GKeyFile *file, NMSetting *setting, const char *key)
continue;
if (NM_IS_SETTING_VPN (setting)) {
if (strcmp (*iter, NM_SETTING_VPN_SERVICE_TYPE) && strcmp (*iter, NM_SETTING_VPN_USER_NAME))
/* Add any item that's not a class property to the data hash */
if (!g_object_class_find_property (G_OBJECT_GET_CLASS (setting), *iter))
nm_setting_vpn_add_data_item (NM_SETTING_VPN (setting), *iter, value);
}
if (NM_IS_SETTING_BOND (setting)) {
......
......@@ -76,6 +76,8 @@ typedef enum {
typedef struct {
NMConnection *connection;
gboolean service_can_persist;
gboolean connection_can_persist;
guint32 secrets_id;
SecretsReq secrets_idx;
......@@ -85,6 +87,8 @@ typedef struct {
guint dispatcher_id;
NMVpnConnectionStateReason failure_reason;
NMVpnServiceState service_state;
DBusGProxy *proxy;
GHashTable *connect_hash;
guint connect_timeout;
......@@ -107,6 +111,7 @@ typedef struct {
enum {
VPN_STATE_CHANGED,
INTERNAL_STATE_CHANGED,
INTERNAL_RETRY_AFTER_FAILURE,
LAST_SIGNAL
};
......@@ -431,12 +436,34 @@ _set_vpn_state (NMVpnConnection *connection,
g_object_unref (parent_dev);
}
static gboolean
_service_and_connection_can_persist (NMVpnConnection *self)
{
return NM_VPN_CONNECTION_GET_PRIVATE (self)->connection_can_persist &&
NM_VPN_CONNECTION_GET_PRIVATE (self)->service_can_persist;
}
static gboolean
_connection_only_can_persist (NMVpnConnection *self)
{
return NM_VPN_CONNECTION_GET_PRIVATE (self)->connection_can_persist &&
!NM_VPN_CONNECTION_GET_PRIVATE (self)->service_can_persist;
}
static void
device_state_changed (NMActiveConnection *active,
NMDevice *device,
NMDeviceState new_state,
NMDeviceState old_state)
{
if (_service_and_connection_can_persist (NM_VPN_CONNECTION (active))) {
if (new_state <= NM_DEVICE_STATE_DISCONNECTED ||
new_state == NM_DEVICE_STATE_FAILED) {
nm_active_connection_set_device (active, NULL);
}
return;
}
if (new_state <= NM_DEVICE_STATE_DISCONNECTED) {
_set_vpn_state (NM_VPN_CONNECTION (active),
STATE_DISCONNECTED,
......@@ -699,29 +726,45 @@ vpn_reason_to_string (NMVpnConnectionStateReason reason)
static void
plugin_state_changed (DBusGProxy *proxy,
NMVpnServiceState state,
NMVpnServiceState new_service_state,
gpointer user_data)
{
NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
NMVpnServiceState old_service_state = priv->service_state;
nm_log_info (LOGD_VPN, "VPN plugin state changed: %s (%d)",
vpn_service_state_to_string (state), state);
vpn_service_state_to_string (new_service_state), new_service_state);
priv->service_state = new_service_state;
if (state == NM_VPN_SERVICE_STATE_STOPPED) {
if (new_service_state == NM_VPN_SERVICE_STATE_STOPPED) {
/* Clear connection secrets to ensure secrets get requested each time the
* connection is activated.
*/
nm_connection_clear_secrets (priv->connection);
if ((priv->vpn_state >= STATE_WAITING) && (priv->vpn_state <= STATE_ACTIVATED)) {
VpnState old_state = priv->vpn_state;
nm_log_info (LOGD_VPN, "VPN plugin state change reason: %s (%d)",
vpn_reason_to_string (priv->failure_reason), priv->failure_reason);
_set_vpn_state (connection, STATE_FAILED, priv->failure_reason, FALSE);
/* Reset the failure reason */
priv->failure_reason = NM_VPN_CONNECTION_STATE_REASON_UNKNOWN;
/* If the connection failed, the service cannot persist, but the
* connection can persist, ask listeners to re-activate the connection.
*/
if ( old_state == STATE_ACTIVATED
&& priv->vpn_state == STATE_FAILED
&& _connection_only_can_persist (connection))
g_signal_emit (connection, signals[INTERNAL_RETRY_AFTER_FAILURE], 0);
}
} else if (new_service_state == NM_VPN_SERVICE_STATE_STARTING &&
old_service_state == NM_VPN_SERVICE_STATE_STARTED) {
/* The VPN service got disconnected and is attempting to reconnect */
_set_vpn_state (connection, STATE_CONNECT, NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, FALSE);
}
}
......@@ -829,41 +872,29 @@ print_vpn_config (NMVpnConnection *connection)
}
}
static gboolean
nm_vpn_connection_apply_config (NMVpnConnection *connection)
static void
apply_parent_device_config (NMVpnConnection *connection)
{
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (connection));
NMIP4Config *vpn4_parent_config = NULL;
NMIP6Config *vpn6_parent_config = NULL;
if (priv->ip_ifindex > 0) {
nm_platform_link_set_up (priv->ip_ifindex);
if (priv->ip4_config)
vpn4_parent_config = nm_ip4_config_new ();
if (priv->ip6_config)
vpn6_parent_config = nm_ip6_config_new ();
if (priv->ip4_config) {
if (!nm_ip4_config_commit (priv->ip4_config, priv->ip_ifindex))
return FALSE;
}
if (priv->ip6_config) {
if (!nm_ip6_config_commit (priv->ip6_config, priv->ip_ifindex))
return FALSE;
}
if (priv->ip4_config)
vpn4_parent_config = nm_ip4_config_new ();
if (priv->ip6_config)
vpn6_parent_config = nm_ip6_config_new ();
} else {
if (priv->ip_ifindex <= 0) {
/* If the VPN didn't return a network interface, it is a route-based
* VPN (like kernel IPSec) and all IP addressing and routing should
* be done on the parent interface instead.
*/
if (priv->ip4_config)
vpn4_parent_config = g_object_ref (priv->ip4_config);
if (priv->ip6_config)
vpn6_parent_config = g_object_ref (priv->ip6_config);
if (vpn4_parent_config)
nm_ip4_config_merge (vpn4_parent_config, priv->ip4_config);
if (vpn6_parent_config)
nm_ip6_config_merge (vpn6_parent_config, priv->ip6_config);
}
if (vpn4_parent_config) {
......@@ -882,6 +913,28 @@ nm_vpn_connection_apply_config (NMVpnConnection *connection)
nm_device_set_vpn6_config (parent_dev, vpn6_parent_config);
g_object_unref (vpn6_parent_config);
}
}
static gboolean
nm_vpn_connection_apply_config (NMVpnConnection *connection)
{
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
if (priv->ip_ifindex > 0) {
nm_platform_link_set_up (priv->ip_ifindex);
if (priv->ip4_config) {
if (!nm_ip4_config_commit (priv->ip4_config, priv->ip_ifindex))
return FALSE;
}
if (priv->ip6_config) {
if (!nm_ip6_config_commit (priv->ip6_config, priv->ip_ifindex))
return FALSE;
}
}
apply_parent_device_config (connection);
nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) complete.",
nm_connection_get_id (priv->connection));
......@@ -895,12 +948,8 @@ nm_vpn_connection_config_maybe_complete (NMVpnConnection *connection,
{
NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);