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-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/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/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..e21df28a9c 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); @@ -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); 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)) { diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c index 45ddb79bba..a348ab7182 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; @@ -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); - 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 +959,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); @@ -939,6 +990,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) { @@ -1210,6 +1267,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 +1414,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); @@ -1551,12 +1610,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 ()); @@ -1751,7 +1815,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), @@ -1905,6 +1971,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) { @@ -2026,6 +2125,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); @@ -2066,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);