From 2b9e442013fad40d289982c4ac48f28f9801b12c Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 16 Oct 2014 20:09:38 -0500 Subject: [PATCH 1/5] libnm/libnm-util: add VPN 'persistent' property This property will indicate that the user wishes the VPN connection to stay active until explicitly disconnected, even across link changes or other interruptions. --- libnm-core/nm-setting-vpn.c | 40 +++++++++++++++++++++++++++ libnm-core/nm-setting-vpn.h | 2 ++ libnm-util/nm-setting-vpn.c | 40 +++++++++++++++++++++++++++ libnm-util/nm-setting-vpn.h | 2 ++ src/settings/plugins/keyfile/reader.c | 4 ++- 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/libnm-core/nm-setting-vpn.c b/libnm-core/nm-setting-vpn.c index 2e68ac9a67..4130412b65 100644 --- a/libnm-core/nm-setting-vpn.c +++ b/libnm-core/nm-setting-vpn.c @@ -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: * diff --git a/libnm-core/nm-setting-vpn.h b/libnm-core/nm-setting-vpn.h index aa4264fdd9..487549afaa 100644 --- a/libnm-core/nm-setting-vpn.h +++ b/libnm-core/nm-setting-vpn.h @@ -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, diff --git a/libnm-util/nm-setting-vpn.c b/libnm-util/nm-setting-vpn.c index 70f955b010..77c64ce48f 100644 --- a/libnm-util/nm-setting-vpn.c +++ b/libnm-util/nm-setting-vpn.c @@ -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: * diff --git a/libnm-util/nm-setting-vpn.h b/libnm-util/nm-setting-vpn.h index 3eb51ee9d6..fbc9fe34f0 100644 --- a/libnm-util/nm-setting-vpn.h +++ b/libnm-util/nm-setting-vpn.h @@ -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, diff --git a/src/settings/plugins/keyfile/reader.c b/src/settings/plugins/keyfile/reader.c index ac7c36ecf6..cbd6e49ab1 100644 --- a/src/settings/plugins/keyfile/reader.c +++ b/src/settings/plugins/keyfile/reader.c @@ -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)) { From a966a5e8b5c0b62975833b12bbb67d7e87f307ce Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 17 Oct 2014 09:16:37 -0500 Subject: [PATCH 2/5] vpn: allow plugins to re-enter the STARTING state to indicate reconnect If the VPN re-enters the STARTING state to indicate that it has disconnected from the VPN server and is trying to reconnect, change the VPN connection's state to indicate that connectivity may be limited. Wait for the VPN to provide updated IP configuration information from the reconnect to change back to the ACTIVATED state. --- src/vpn-manager/nm-vpn-connection.c | 32 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index 45ddb79bba..4c1f847617 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -85,6 +85,8 @@ typedef struct { guint dispatcher_id; NMVpnConnectionStateReason failure_reason; + NMVpnServiceState service_state; + DBusGProxy *proxy; GHashTable *connect_hash; guint connect_timeout; @@ -699,16 +701,18 @@ 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. */ @@ -722,6 +726,10 @@ plugin_state_changed (DBusGProxy *proxy, /* Reset the failure reason */ priv->failure_reason = NM_VPN_CONNECTION_STATE_REASON_UNKNOWN; } + } 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); } } @@ -895,12 +903,8 @@ nm_vpn_connection_config_maybe_complete (NMVpnConnection *connection, { NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); - if (priv->connect_timeout == 0) { - /* config_complete() was already called with an error; - * ignore further calls. - */ + if (priv->vpn_state < STATE_IP_CONFIG_GET || priv->vpn_state > STATE_ACTIVATED) return; - } if (success) { if ( (priv->has_ip4 && !priv->ip4_config) @@ -910,8 +914,10 @@ nm_vpn_connection_config_maybe_complete (NMVpnConnection *connection, } } - g_source_remove (priv->connect_timeout); - priv->connect_timeout = 0; + if (priv->connect_timeout == 0) { + g_source_remove (priv->connect_timeout); + priv->connect_timeout = 0; + } if (success) { print_vpn_config (connection); @@ -1210,6 +1216,7 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy, nm_connection_get_setting_ip4_config (priv->connection), vpn_routing_metric (connection)); + g_clear_object (&priv->ip4_config); priv->ip4_config = config; nm_ip4_config_export (config); g_object_notify (G_OBJECT (connection), NM_ACTIVE_CONNECTION_IP4_CONFIG); @@ -1356,6 +1363,7 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy, nm_connection_get_setting_ip6_config (priv->connection), vpn_routing_metric (connection)); + g_clear_object (&priv->ip6_config); priv->ip6_config = config; nm_ip6_config_export (config); g_object_notify (G_OBJECT (connection), NM_ACTIVE_CONNECTION_IP6_CONFIG); @@ -1751,7 +1759,9 @@ plugin_new_secrets_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self); GError *error = NULL; - if (!dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID)) { + if (dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID)) { + _set_vpn_state (self, STATE_CONNECT, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); + } else { nm_log_err (LOGD_VPN, "(%s/%s) sending new secrets to the plugin failed: %s %s", nm_connection_get_uuid (priv->connection), nm_connection_get_id (priv->connection), From 1f544d337c4ed5d686f114f4fa0b5314bfc55312 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 23 Oct 2014 13:07:20 -0500 Subject: [PATCH 3/5] vpn: allow plugins to indicate 'can-persist' capability The plugin can indicate that this connection can persist across link changes and other connectivity dropouts by passing this option back in the SetConfig() calls. --- libnm-core/nm-vpn-dbus-interface.h | 5 +++++ libnm-util/NetworkManagerVPN.h | 5 +++++ src/vpn-manager/nm-vpn-connection.c | 13 +++++++++++++ 3 files changed, 23 insertions(+) diff --git a/libnm-core/nm-vpn-dbus-interface.h b/libnm-core/nm-vpn-dbus-interface.h index ab294df7cd..c4099be2ad 100644 --- a/libnm-core/nm-vpn-dbus-interface.h +++ b/libnm-core/nm-vpn-dbus-interface.h @@ -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 ***/ diff --git a/libnm-util/NetworkManagerVPN.h b/libnm-util/NetworkManagerVPN.h index f316572a08..882d39739c 100644 --- a/libnm-util/NetworkManagerVPN.h +++ b/libnm-util/NetworkManagerVPN.h @@ -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 ***/ diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index 4c1f847617..7d3fb8d390 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -76,6 +76,8 @@ typedef enum { typedef struct { NMConnection *connection; + gboolean service_can_persist; + gboolean connection_can_persist; guint32 secrets_id; SecretsReq secrets_idx; @@ -945,6 +947,12 @@ process_generic_config (NMVpnConnection *connection, NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); GValue *val; + val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_CAN_PERSIST); + if (val && G_VALUE_HOLDS_BOOLEAN (val) && g_value_get_boolean (val)) { + /* Defaults to FALSE, so only let service indicate TRUE */ + priv->service_can_persist = TRUE; + } + g_clear_pointer (&priv->ip_iface, g_free); val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_CONFIG_TUNDEV); if (val) { @@ -1559,12 +1567,17 @@ void nm_vpn_connection_activate (NMVpnConnection *connection) { NMVpnConnectionPrivate *priv; + NMSettingVpn *s_vpn; DBusGConnection *bus; g_return_if_fail (NM_IS_VPN_CONNECTION (connection)); priv = NM_VPN_CONNECTION_GET_PRIVATE (connection); + s_vpn = nm_connection_get_setting_vpn (priv->connection); + g_assert (s_vpn); + priv->connection_can_persist = nm_setting_vpn_get_persistent (s_vpn); + _set_vpn_state (connection, STATE_PREPARE, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE); bus = nm_dbus_manager_get_connection (nm_dbus_manager_get ()); From b11798a196bc3cda29e0b0540bc19eef7c09a207 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 23 Oct 2014 17:04:49 -0500 Subject: [PATCH 4/5] vpn/core: move VPN gateway route between devices when routing changes --- src/nm-active-connection.c | 35 ++++++++-- src/nm-active-connection.h | 7 ++ src/nm-policy.c | 28 ++++++++ src/vpn-manager/nm-vpn-connection.c | 105 ++++++++++++++++++++++------ 4 files changed, 147 insertions(+), 28 deletions(-) diff --git a/src/nm-active-connection.c b/src/nm-active-connection.c index c3df3a5b79..73925659df 100644 --- a/src/nm-active-connection.c +++ b/src/nm-active-connection.c @@ -19,6 +19,7 @@ */ #include + #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; + + old_device = priv->device ? g_object_ref (priv->device) : NULL; + _device_cleanup (self); if (device) { - g_return_val_if_fail (priv->device == NULL, FALSE); - /* 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); diff --git a/src/nm-active-connection.h b/src/nm-active-connection.h index 9a6a54711c..710cfeed9f 100644 --- a/src/nm-active-connection.h +++ b/src/nm-active-connection.h @@ -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); diff --git a/src/nm-policy.c b/src/nm-policy.c index ddb85bd96b..ca2d340b59 100644 --- a/src/nm-policy.c +++ b/src/nm-policy.c @@ -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); diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index 7d3fb8d390..a6622f64f2 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -435,12 +435,27 @@ _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 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, @@ -839,41 +854,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) { @@ -892,6 +895,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)); @@ -1928,6 +1953,39 @@ plugin_interactive_secrets_required (DBusGProxy *proxy, /******************************************************************************/ +static void +device_changed (NMActiveConnection *active, + NMDevice *new_device, + NMDevice *old_device) +{ + NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (active); + + if (!_service_and_connection_can_persist (NM_VPN_CONNECTION (active))) + return; + if (priv->vpn_state < STATE_CONNECT || priv->vpn_state > STATE_ACTIVATED) + return; + + /* Route-based VPNs must update their routing and send a new IP config + * since all their routes need to be adjusted for new_device. + */ + if (priv->ip_ifindex <= 0) + return; + + /* Device changed underneath the VPN connection. Let the plugin figure + * out that connectivity is down and start its reconnect attempt if it + * needs to. + */ + if (old_device) { + nm_device_set_vpn4_config (old_device, NULL); + nm_device_set_vpn6_config (old_device, NULL); + } + + if (new_device) + apply_parent_device_config (NM_VPN_CONNECTION (active)); +} + +/******************************************************************************/ + static void nm_vpn_connection_init (NMVpnConnection *self) { @@ -2049,6 +2107,7 @@ nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class) object_class->dispose = dispose; object_class->finalize = finalize; active_class->device_state_changed = device_state_changed; + active_class->device_changed = device_changed; g_object_class_override_property (object_class, PROP_MASTER, NM_ACTIVE_CONNECTION_MASTER); From 6cbbb9c0bbdfb3784260fb644fd02ca518efdba5 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 26 Sep 2014 16:03:06 -0500 Subject: [PATCH 5/5] vpn: reconnect on service failures (bgo #349151) Attempt to reconnect the VPN on failures, except when the underlying device fails. https://bugzilla.gnome.org/show_bug.cgi?id=349151 --- src/nm-policy.c | 28 ++++++++++++++++++++++++++++ src/vpn-manager/nm-vpn-connection.c | 25 +++++++++++++++++++++++++ src/vpn-manager/nm-vpn-connection.h | 5 ++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/nm-policy.c b/src/nm-policy.c index ca2d340b59..e21df28a9c 100644 --- a/src/nm-policy.c +++ b/src/nm-policy.c @@ -1835,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, @@ -1859,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, @@ -1876,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); diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index a6622f64f2..a348ab7182 100644 --- a/src/vpn-manager/nm-vpn-connection.c +++ b/src/vpn-manager/nm-vpn-connection.c @@ -111,6 +111,7 @@ typedef struct { enum { VPN_STATE_CHANGED, INTERNAL_STATE_CHANGED, + INTERNAL_RETRY_AFTER_FAILURE, LAST_SIGNAL }; @@ -442,6 +443,13 @@ _service_and_connection_can_persist (NMVpnConnection *self) 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, @@ -736,12 +744,22 @@ plugin_state_changed (DBusGProxy *proxy, 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) { @@ -2148,6 +2166,13 @@ nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class) 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); + signals[INTERNAL_RETRY_AFTER_FAILURE] = + g_signal_new (NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + nm_dbus_manager_register_exported_type (nm_dbus_manager_get (), G_TYPE_FROM_CLASS (object_class), &dbus_glib_nm_vpn_connection_object_info); diff --git a/src/vpn-manager/nm-vpn-connection.h b/src/vpn-manager/nm-vpn-connection.h index 156f79f77c..74ea38a5db 100644 --- a/src/vpn-manager/nm-vpn-connection.h +++ b/src/vpn-manager/nm-vpn-connection.h @@ -42,7 +42,8 @@ /* Signals */ /* not exported: includes old reason code */ -#define NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED "internal-state-changed" +#define NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED "internal-state-changed" +#define NM_VPN_CONNECTION_INTERNAL_RETRY_AFTER_FAILURE "internal-retry-after-failure" typedef struct { @@ -62,6 +63,8 @@ typedef struct { NMVpnConnectionState new_state, NMVpnConnectionState old_state, NMVpnConnectionStateReason reason); + + void (*internal_failed_retry) (NMVpnConnection *connection); } NMVpnConnectionClass; GType nm_vpn_connection_get_type (void);