summary refs log tree commit diff
path: root/src/vpn-manager/nm-vpn-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vpn-manager/nm-vpn-connection.c')
-rw-r--r--src/vpn-manager/nm-vpn-connection.c550
1 files changed, 356 insertions, 194 deletions
diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c
index d66dcfdb..cb07c7f9 100644
--- a/src/vpn-manager/nm-vpn-connection.c
+++ b/src/vpn-manager/nm-vpn-connection.c
@@ -19,6 +19,8 @@
  * Copyright (C) 2006 - 2008 Novell, Inc.
  */
 
+#include "config.h"
+
 #include <glib.h>
 #include <string.h>
 #include <sys/socket.h>
@@ -27,16 +29,12 @@
 #include <errno.h>
 #include <stdlib.h>
 
-#include "NetworkManager.h"
-#include "NetworkManagerVPN.h"
 #include "nm-vpn-connection.h"
-#include "nm-setting-connection.h"
-#include "nm-setting-vpn.h"
-#include "nm-setting-ip4-config.h"
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
 #include "nm-dbus-manager.h"
 #include "nm-platform.h"
 #include "nm-logging.h"
-#include "nm-utils.h"
 #include "nm-active-connection.h"
 #include "nm-dbus-glib-types.h"
 #include "NetworkManagerUtils.h"
@@ -44,10 +42,12 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-dispatcher.h"
 #include "nm-agent-manager.h"
+#include "nm-core-internal.h"
+#include "nm-default-route-manager.h"
 
 #include "nm-vpn-connection-glue.h"
 
-G_DEFINE_TYPE (NMVPNConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION)
+G_DEFINE_TYPE (NMVpnConnection, nm_vpn_connection, NM_TYPE_ACTIVE_CONNECTION)
 
 typedef enum {
 	/* Only system secrets */
@@ -62,7 +62,7 @@ typedef enum {
 	SECRETS_REQ_LAST
 } SecretsReq;
 
-/* Internal VPN states, private to NMVPNConnection */
+/* Internal VPN states, private to NMVpnConnection */
 typedef enum {
 	STATE_UNKNOWN = 0,
 	STATE_WAITING,
@@ -79,6 +79,8 @@ typedef enum {
 
 typedef struct {
 	NMConnection *connection;
+	gboolean service_can_persist;
+	gboolean connection_can_persist;
 
 	guint32 secrets_id;
 	SecretsReq secrets_idx;
@@ -86,7 +88,9 @@ typedef struct {
 
 	VpnState vpn_state;
 	guint dispatcher_id;
-	NMVPNConnectionStateReason failure_reason;
+	NMVpnConnectionStateReason failure_reason;
+
+	NMVpnServiceState service_state;
 
 	DBusGProxy *proxy;
 	GHashTable *connect_hash;
@@ -103,13 +107,14 @@ typedef struct {
 	int ip_ifindex;
 	char *banner;
 	guint32 mtu;
-} NMVPNConnectionPrivate;
+} NMVpnConnectionPrivate;
 
-#define NM_VPN_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_CONNECTION, NMVPNConnectionPrivate))
+#define NM_VPN_CONNECTION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_CONNECTION, NMVpnConnectionPrivate))
 
 enum {
 	VPN_STATE_CHANGED,
 	INTERNAL_STATE_CHANGED,
+	INTERNAL_RETRY_AFTER_FAILURE,
 
 	LAST_SIGNAL
 };
@@ -127,7 +132,7 @@ enum {
 	LAST_PROP
 };
 
-static void get_secrets (NMVPNConnection *self,
+static void get_secrets (NMVpnConnection *self,
                          SecretsReq secrets_idx,
                          const char **hints);
 
@@ -136,14 +141,14 @@ static void plugin_interactive_secrets_required (DBusGProxy *proxy,
                                                  const char **secrets,
                                                  gpointer user_data);
 
-static void _set_vpn_state (NMVPNConnection *connection,
+static void _set_vpn_state (NMVpnConnection *connection,
                             VpnState vpn_state,
-                            NMVPNConnectionStateReason reason,
+                            NMVpnConnectionStateReason reason,
                             gboolean quitting);
 
 /*********************************************************************/
 
-static NMVPNConnectionState
+static NMVpnConnectionState
 _state_to_nm_vpn_state (VpnState state)
 {
 	switch (state) {
@@ -173,7 +178,7 @@ _state_to_nm_vpn_state (VpnState state)
 	case STATE_FAILED:
 		return NM_VPN_CONNECTION_STATE_FAILED;
 	default:
-		return STATE_UNKNOWN;
+		return NM_VPN_CONNECTION_STATE_UNKNOWN;
 	}
 }
 
@@ -203,9 +208,9 @@ _state_to_ac_state (VpnState vpn_state)
 }
 
 static void
-call_plugin_disconnect (NMVPNConnection *self)
+call_plugin_disconnect (NMVpnConnection *self)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	GError *error = NULL;
 
 	if (priv->proxy) {
@@ -222,9 +227,9 @@ call_plugin_disconnect (NMVPNConnection *self)
 }
 
 static void
-vpn_cleanup (NMVPNConnection *connection, NMDevice *parent_dev)
+vpn_cleanup (NMVpnConnection *connection, NMDevice *parent_dev)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 
 	if (priv->ip_ifindex) {
 		nm_platform_link_set_down (priv->ip_ifindex);
@@ -252,8 +257,8 @@ vpn_cleanup (NMVPNConnection *connection, NMDevice *parent_dev)
 static void
 dispatcher_pre_down_done (guint call_id, gpointer user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	priv->dispatcher_id = 0;
 	_set_vpn_state (self, STATE_DISCONNECTED, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
@@ -262,17 +267,17 @@ dispatcher_pre_down_done (guint call_id, gpointer user_data)
 static void
 dispatcher_pre_up_done (guint call_id, gpointer user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	priv->dispatcher_id = 0;
 	_set_vpn_state (self, STATE_ACTIVATED, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
 }
 
 static void
-dispatcher_cleanup (NMVPNConnection *self)
+dispatcher_cleanup (NMVpnConnection *self)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	if (priv->dispatcher_id) {
 		nm_dispatcher_call_cancel (priv->dispatcher_id);
@@ -281,14 +286,14 @@ dispatcher_cleanup (NMVPNConnection *self)
 }
 
 static void
-_set_vpn_state (NMVPNConnection *connection,
+_set_vpn_state (NMVpnConnection *connection,
                 VpnState vpn_state,
-                NMVPNConnectionStateReason reason,
+                NMVpnConnectionStateReason reason,
                 gboolean quitting)
 {
-	NMVPNConnectionPrivate *priv;
+	NMVpnConnectionPrivate *priv;
 	VpnState old_vpn_state;
-	NMVPNConnectionState new_external_state, old_external_state;
+	NMVpnConnectionState new_external_state, old_external_state;
 	NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (connection));
 
 	g_return_if_fail (NM_IS_VPN_CONNECTION (connection));
@@ -319,6 +324,9 @@ _set_vpn_state (NMVPNConnection *connection,
 
 	dispatcher_cleanup (connection);
 
+	nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), connection);
+	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), connection);
+
 	/* The connection gets destroyed by the VPN manager when it enters the
 	 * disconnected/failed state, but we need to keep it around for a bit
 	 * to send out signals and handle the dispatcher.  So ref it.
@@ -434,12 +442,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,
@@ -463,6 +493,7 @@ add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32
 	NMIP4Config *parent_config;
 	guint32 parent_gw;
 	NMPlatformIP4Route route;
+	guint32 route_metric;
 
 	g_return_if_fail (NM_IS_IP4_CONFIG (config));
 	g_return_if_fail (NM_IS_DEVICE (parent_device));
@@ -478,6 +509,8 @@ add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32
 	if (!parent_gw)
 		return;
 
+	route_metric = nm_device_get_ip4_route_metric (parent_device);
+
 	memset (&route, 0, sizeof (route));
 	route.network = vpn_gw;
 	route.plen = 32;
@@ -490,8 +523,8 @@ add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32
 	if (nm_ip4_config_destination_is_direct (parent_config, vpn_gw, 32))
 		route.gateway = 0;
 
-	route.source = NM_PLATFORM_SOURCE_VPN;
-	route.metric = nm_device_get_priority (parent_device);
+	route.source = NM_IP_CONFIG_SOURCE_VPN;
+	route.metric = route_metric;
 	nm_ip4_config_add_route (config, &route);
 
 	/* Ensure there's a route to the parent device's gateway through the
@@ -502,8 +535,8 @@ add_ip4_vpn_gateway_route (NMIP4Config *config, NMDevice *parent_device, guint32
 	memset (&route, 0, sizeof (route));
 	route.network = parent_gw;
 	route.plen = 32;
-	route.source = NM_PLATFORM_SOURCE_VPN;
-	route.metric = nm_device_get_priority (parent_device);
+	route.source = NM_IP_CONFIG_SOURCE_VPN;
+	route.metric = route_metric;
 
 	nm_ip4_config_add_route (config, &route);
 }
@@ -516,6 +549,7 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 	NMIP6Config *parent_config;
 	const struct in6_addr *parent_gw;
 	NMPlatformIP6Route route;
+	guint32 route_metric;
 
 	g_return_if_fail (NM_IS_IP6_CONFIG (config));
 	g_return_if_fail (NM_IS_DEVICE (parent_device));
@@ -527,6 +561,8 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 	if (!parent_gw)
 		return;
 
+	route_metric = nm_device_get_ip6_route_metric (parent_device);
+
 	memset (&route, 0, sizeof (route));
 	route.network = *vpn_gw;
 	route.plen = 128;
@@ -539,8 +575,8 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 	if (nm_ip6_config_destination_is_direct (parent_config, vpn_gw, 128))
 		route.gateway = in6addr_any;
 
-	route.source = NM_PLATFORM_SOURCE_VPN;
-	route.metric = nm_device_get_priority (parent_device);
+	route.source = NM_IP_CONFIG_SOURCE_VPN;
+	route.metric = route_metric;
 	nm_ip6_config_add_route (config, &route);
 
 	/* Ensure there's a route to the parent device's gateway through the
@@ -551,13 +587,13 @@ add_ip6_vpn_gateway_route (NMIP6Config *config,
 	memset (&route, 0, sizeof (route));
 	route.network = *parent_gw;
 	route.plen = 128;
-	route.source = NM_PLATFORM_SOURCE_VPN;
-	route.metric = nm_device_get_priority (parent_device);
+	route.source = NM_IP_CONFIG_SOURCE_VPN;
+	route.metric = route_metric;
 
 	nm_ip6_config_add_route (config, &route);
 }
 
-NMVPNConnection *
+NMVpnConnection *
 nm_vpn_connection_new (NMConnection *connection,
                        NMDevice *parent_device,
                        const char *specific_object,
@@ -566,7 +602,7 @@ nm_vpn_connection_new (NMConnection *connection,
 	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 	g_return_val_if_fail (NM_IS_DEVICE (parent_device), NULL);
 
-	return (NMVPNConnection *) g_object_new (NM_TYPE_VPN_CONNECTION,
+	return (NMVpnConnection *) g_object_new (NM_TYPE_VPN_CONNECTION,
 	                                         NM_ACTIVE_CONNECTION_INT_CONNECTION, connection,
 	                                         NM_ACTIVE_CONNECTION_INT_DEVICE, parent_device,
 	                                         NM_ACTIVE_CONNECTION_SPECIFIC_OBJECT, specific_object,
@@ -576,17 +612,17 @@ nm_vpn_connection_new (NMConnection *connection,
 }
 
 static const char *
-nm_vpn_connection_get_service (NMVPNConnection *connection)
+nm_vpn_connection_get_service (NMVpnConnection *connection)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
-	NMSettingVPN *s_vpn;
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMSettingVpn *s_vpn;
 
 	s_vpn = nm_connection_get_setting_vpn (priv->connection);
 	return nm_setting_vpn_get_service_type (s_vpn);
 }
 
 static const char *
-vpn_plugin_failure_to_string (NMVPNPluginFailure failure)
+vpn_plugin_failure_to_string (NMVpnPluginFailure failure)
 {
 	switch (failure) {
 	case NM_VPN_PLUGIN_FAILURE_LOGIN_FAILED:
@@ -603,10 +639,10 @@ vpn_plugin_failure_to_string (NMVPNPluginFailure failure)
 
 static void
 plugin_failed (DBusGProxy *proxy,
-               NMVPNPluginFailure plugin_failure,
+               NMVpnPluginFailure plugin_failure,
                gpointer user_data)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (user_data);
 
 	nm_log_warn (LOGD_VPN, "VPN plugin failed: %s (%d)",
 	             vpn_plugin_failure_to_string (plugin_failure), plugin_failure);
@@ -620,11 +656,12 @@ plugin_failed (DBusGProxy *proxy,
 		break;
 	default:
 		priv->failure_reason = NM_VPN_CONNECTION_STATE_REASON_UNKNOWN;
+		break;
 	}
 }
 
 static const char *
-vpn_service_state_to_string (NMVPNServiceState state)
+vpn_service_state_to_string (NMVpnServiceState state)
 {
 	switch (state) {
 	case NM_VPN_SERVICE_STATE_INIT:
@@ -662,13 +699,13 @@ static const char *state_table[] = {
 static const char *
 vpn_state_to_string (VpnState state)
 {
-	if (state >= 0 && state < G_N_ELEMENTS (state_table))
+	if ((gsize) state < G_N_ELEMENTS (state_table))
 		return state_table[state];
 	return "unknown";
 }
 
 static const char *
-vpn_reason_to_string (NMVPNConnectionStateReason reason)
+vpn_reason_to_string (NMVpnConnectionStateReason reason)
 {
 	switch (reason) {
 	case NM_VPN_CONNECTION_STATE_REASON_NONE:
@@ -701,36 +738,52 @@ 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);
+	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);
 	}
 }
 
 static void
-print_vpn_config (NMVPNConnection *connection)
+print_vpn_config (NMVpnConnection *connection)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 	const NMPlatformIP4Address *address4;
 	const NMPlatformIP6Address *address6;
 	char *dns_domain = NULL;
@@ -831,41 +884,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);
+	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) {
@@ -884,6 +925,32 @@ 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,
+			                           nm_vpn_connection_get_ip4_route_metric (connection)))
+				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_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), connection);
+	nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), connection);
 
 	nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) complete.",
 	             nm_connection_get_id (priv->connection));
@@ -892,17 +959,13 @@ nm_vpn_connection_apply_config (NMVPNConnection *connection)
 }
 
 static void
-nm_vpn_connection_config_maybe_complete (NMVPNConnection *connection,
+nm_vpn_connection_config_maybe_complete (NMVpnConnection *connection,
                                          gboolean         success)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (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)
@@ -912,8 +975,10 @@ nm_vpn_connection_config_maybe_complete (NMVPNConnection *connection,
 		}
 	}
 
-	g_source_remove (priv->connect_timeout);
-	priv->connect_timeout = 0;
+	if (priv->connect_timeout) {
+		g_source_remove (priv->connect_timeout);
+		priv->connect_timeout = 0;
+	}
 
 	if (success) {
 		print_vpn_config (connection);
@@ -935,12 +1000,18 @@ nm_vpn_connection_config_maybe_complete (NMVPNConnection *connection,
                           nm_connection_get_id (priv->connection), property)
 
 static gboolean
-process_generic_config (NMVPNConnection *connection,
+process_generic_config (NMVpnConnection *connection,
                         GHashTable *config_hash)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (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) {
@@ -1014,8 +1085,8 @@ nm_vpn_connection_config_get (DBusGProxy *proxy,
                               GHashTable *config_hash,
                               gpointer user_data)
 {
-	NMVPNConnection *connection = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 	GValue *val;
 
 	nm_log_info (LOGD_VPN, "VPN connection '%s' (IP Config Get) reply received.",
@@ -1049,18 +1120,34 @@ nm_vpn_connection_config_get (DBusGProxy *proxy,
 	g_clear_object (&priv->ip6_config);
 }
 
-static guint
-vpn_routing_metric (NMVPNConnection *connection)
+guint32
+nm_vpn_connection_get_ip4_route_metric (NMVpnConnection *connection)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 
-	if (priv->ip_ifindex)
-		return NM_PLATFORM_ROUTE_METRIC_DEFAULT;
-	else {
-		NMDevice *parent_dev = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (connection));
+	if (priv->connection) {
+		gint64 route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip4_config (priv->connection));
 
-		return nm_device_get_priority (parent_dev);
+		if (route_metric >= 0)
+			return route_metric;
 	}
+
+	return NM_VPN_ROUTE_METRIC_DEFAULT;
+}
+
+guint32
+nm_vpn_connection_get_ip6_route_metric (NMVpnConnection *connection)
+{
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+
+	if (priv->connection) {
+		gint64 route_metric = nm_setting_ip_config_get_route_metric (nm_connection_get_setting_ip6_config (priv->connection));
+
+		if (route_metric >= 0)
+			return route_metric;
+	}
+
+	return NM_VPN_ROUTE_METRIC_DEFAULT;
 }
 
 static void
@@ -1068,12 +1155,13 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
                                   GHashTable *config_hash,
                                   gpointer user_data)
 {
-	NMVPNConnection *connection = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 	NMPlatformIP4Address address;
 	NMIP4Config *config;
 	GValue *val;
 	int i;
+	guint32 route_metric;
 
 	if (priv->vpn_state == STATE_CONNECT)
 		_set_vpn_state (connection, STATE_IP_CONFIG_GET, NM_VPN_CONNECTION_STATE_REASON_NONE, FALSE);
@@ -1126,7 +1214,7 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 		address.plen = g_value_get_uint (val);
 
 	if (address.address && address.plen) {
-		address.source = NM_PLATFORM_SOURCE_VPN;
+		address.source = NM_IP_CONFIG_SOURCE_VPN;
 		nm_ip4_config_add_address (config, &address);
 	} else {
 		nm_log_err (LOGD_VPN, "invalid IP4 config received!");
@@ -1156,7 +1244,7 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 		nm_ip4_config_set_mss (config, g_value_get_uint (val));
 
 	if (priv->mtu)
-		nm_ip4_config_set_mtu (config, priv->mtu);
+		nm_ip4_config_set_mtu (config, priv->mtu, NM_IP_CONFIG_SOURCE_VPN);
 
 	val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_IP4_CONFIG_DOMAIN);
 	if (val)
@@ -1171,6 +1259,8 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 			nm_ip4_config_add_domain (config, *domain);
 	}
 
+	route_metric = nm_vpn_connection_get_ip4_route_metric (connection);
+
 	val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_IP4_CONFIG_ROUTES);
 	if (val) {
 		GSList *routes;
@@ -1178,15 +1268,15 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 
 		routes = nm_utils_ip4_routes_from_gvalue (val);
 		for (iter = routes; iter; iter = iter->next) {
-			NMIP4Route *item = iter->data;
+			NMIPRoute *item = iter->data;
 			NMPlatformIP4Route route;
 
 			memset (&route, 0, sizeof (route));
-			route.network = nm_ip4_route_get_dest (item);
-			route.plen = nm_ip4_route_get_prefix (item);
-			route.gateway = nm_ip4_route_get_next_hop (item);
-			route.source = NM_PLATFORM_SOURCE_VPN;
-			route.metric = vpn_routing_metric (connection);
+			nm_ip_route_get_dest_binary (item, &route.network);
+			route.plen = nm_ip_route_get_prefix (item);
+			nm_ip_route_get_next_hop_binary (item, &route.gateway);
+			route.source = NM_IP_CONFIG_SOURCE_VPN;
+			route.metric = route_metric;
 
 			/* Ignore host routes to the VPN gateway since NM adds one itself
 			 * below.  Since NM knows more about the routing situation than
@@ -1200,7 +1290,7 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 			nm_ip4_config_add_route (config, &route);
 		}
 
-		g_slist_free_full (routes, (GDestroyNotify) nm_ip4_route_unref);
+		g_slist_free_full (routes, (GDestroyNotify) nm_ip_route_unref);
 	}
 
 	val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT);
@@ -1210,8 +1300,9 @@ nm_vpn_connection_ip4_config_get (DBusGProxy *proxy,
 	/* Merge in user overrides from the NMConnection's IPv4 setting */
 	nm_ip4_config_merge_setting (config,
 	                             nm_connection_get_setting_ip4_config (priv->connection),
-	                             vpn_routing_metric (connection));
+	                             route_metric);
 
+	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);
@@ -1223,12 +1314,13 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
                                   GHashTable *config_hash,
                                   gpointer user_data)
 {
-	NMVPNConnection *connection = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 	NMPlatformIP6Address address;
 	NMIP6Config *config;
 	GValue *val;
 	int i;
+	guint32 route_metric;
 
 	nm_log_info (LOGD_VPN, "VPN connection '%s' (IP6 Config Get) reply received.",
 	             nm_connection_get_id (priv->connection));
@@ -1280,7 +1372,7 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 		address.plen = g_value_get_uint (val);
 
 	if (!IN6_IS_ADDR_UNSPECIFIED (&address.address) && address.plen) {
-		address.source = NM_PLATFORM_SOURCE_VPN;
+		address.source = NM_IP_CONFIG_SOURCE_VPN;
 		nm_ip6_config_add_address (config, &address);
 	} else {
 		nm_log_err (LOGD_VPN, "invalid IP6 config received!");
@@ -1317,6 +1409,8 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 			nm_ip6_config_add_domain (config, *domain);
 	}
 
+	route_metric = nm_vpn_connection_get_ip6_route_metric (connection);
+
 	val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_IP6_CONFIG_ROUTES);
 	if (val) {
 		GSList *routes;
@@ -1324,15 +1418,15 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 
 		routes = nm_utils_ip6_routes_from_gvalue (val);
 		for (iter = routes; iter; iter = iter->next) {
-			NMIP6Route *item = iter->data;
+			NMIPRoute *item = iter->data;
 			NMPlatformIP6Route route;
 
 			memset (&route, 0, sizeof (route));
-			route.network = *nm_ip6_route_get_dest (item);
-			route.plen = nm_ip6_route_get_prefix (item);
-			route.gateway = *nm_ip6_route_get_next_hop (item);
-			route.source = NM_PLATFORM_SOURCE_VPN;
-			route.metric = vpn_routing_metric (connection);
+			nm_ip_route_get_dest_binary (item, &route.network);
+			route.plen = nm_ip_route_get_prefix (item);
+			nm_ip_route_get_next_hop_binary (item, &route.gateway);
+			route.source = NM_IP_CONFIG_SOURCE_VPN;
+			route.metric = route_metric;
 
 			/* Ignore host routes to the VPN gateway since NM adds one itself
 			 * below.  Since NM knows more about the routing situation than
@@ -1346,7 +1440,7 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 			nm_ip6_config_add_route (config, &route);
 		}
 
-		g_slist_free_full (routes, (GDestroyNotify) nm_ip6_route_unref);
+		g_slist_free_full (routes, (GDestroyNotify) nm_ip_route_unref);
 	}
 
 	val = (GValue *) g_hash_table_lookup (config_hash, NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT);
@@ -1356,8 +1450,9 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 	/* Merge in user overrides from the NMConnection's IPv6 setting */
 	nm_ip6_config_merge_setting (config,
 	                             nm_connection_get_setting_ip6_config (priv->connection),
-	                             vpn_routing_metric (connection));
+	                             route_metric);
 
+	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);
@@ -1367,8 +1462,8 @@ nm_vpn_connection_ip6_config_get (DBusGProxy *proxy,
 static gboolean
 connect_timeout_cb (gpointer user_data)
 {
-	NMVPNConnection *connection = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 
 	priv->connect_timeout = 0;
 
@@ -1384,9 +1479,9 @@ connect_timeout_cb (gpointer user_data)
 }
 
 static void
-connect_success (NMVPNConnection *connection)
+connect_success (NMVpnConnection *connection)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 
 	/* 40 second timeout waiting for IP config signal from VPN service */
 	priv->connect_timeout = g_timeout_add_seconds (40, connect_timeout_cb, connection);
@@ -1398,8 +1493,8 @@ connect_success (NMVPNConnection *connection)
 static void
 connect_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	GError *err = NULL;
 
 	nm_log_info (LOGD_VPN, "VPN connection '%s' (Connect) reply received.",
@@ -1420,8 +1515,8 @@ connect_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
 static void
 connect_interactive_cb (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	GError *err = NULL;
 
 	nm_log_info (LOGD_VPN, "VPN connection '%s' (ConnectInteractive) reply received.",
@@ -1452,8 +1547,9 @@ static GHashTable *
 _hash_with_username (NMConnection *connection, const char *username)
 {
 	NMConnection *dup;
-	NMSettingVPN *s_vpn;
+	NMSettingVpn *s_vpn;
 	GHashTable *hash;
+	GVariant *dict;
 	const char *existing;
 
 	/* Shortcut if we weren't given a username or if there already was one in
@@ -1462,23 +1558,30 @@ _hash_with_username (NMConnection *connection, const char *username)
 	s_vpn = nm_connection_get_setting_vpn (connection);
 	g_assert (s_vpn);
 	existing = nm_setting_vpn_get_user_name (s_vpn);
-	if (username == NULL || existing)
-		return nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL);
+	if (username == NULL || existing) {
+		dict = nm_connection_to_dbus (connection, NM_CONNECTION_SERIALIZE_ALL);
+		hash = nm_utils_connection_dict_to_hash (dict);
+		g_variant_unref (dict);
+		return hash;
+	}
 
-	dup = nm_connection_duplicate (connection);
+	dup = nm_simple_connection_new_clone (connection);
 	g_assert (dup);
 	s_vpn = nm_connection_get_setting_vpn (dup);
 	g_assert (s_vpn);
 	g_object_set (s_vpn, NM_SETTING_VPN_USER_NAME, username, NULL);
-	hash = nm_connection_to_hash (dup, NM_SETTING_HASH_FLAG_ALL);
+	dict = nm_connection_to_dbus (dup, NM_CONNECTION_SERIALIZE_ALL);
 	g_object_unref (dup);
+
+	hash = nm_utils_connection_dict_to_hash (dict);
+	g_variant_unref (dict);
 	return hash;
 }
 
 static void
-really_activate (NMVPNConnection *connection, const char *username)
+really_activate (NMVpnConnection *connection, const char *username)
 {
-	NMVPNConnectionPrivate *priv;
+	NMVpnConnectionPrivate *priv;
 	NMAgentManager *agent_mgr;
 	GHashTable *details;
 
@@ -1542,15 +1645,20 @@ really_activate (NMVPNConnection *connection, const char *username)
 }
 
 void
-nm_vpn_connection_activate (NMVPNConnection *connection)
+nm_vpn_connection_activate (NMVpnConnection *connection)
 {
-	NMVPNConnectionPrivate *priv;
+	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 ());
@@ -1588,15 +1696,26 @@ nm_vpn_connection_activate (NMVPNConnection *connection)
 }
 
 NMConnection *
-nm_vpn_connection_get_connection (NMVPNConnection *connection)
+nm_vpn_connection_get_connection (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
 
 	return NM_VPN_CONNECTION_GET_PRIVATE (connection)->connection;
 }
 
-NMVPNConnectionState
-nm_vpn_connection_get_vpn_state (NMVPNConnection *connection)
+const char*
+nm_vpn_connection_get_connection_id (NMVpnConnection *connection)
+{
+	NMConnection *c;
+
+	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
+
+	c = NM_VPN_CONNECTION_GET_PRIVATE (connection)->connection;
+	return c ? nm_connection_get_id (c) : NULL;
+}
+
+NMVpnConnectionState
+nm_vpn_connection_get_vpn_state (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NM_VPN_CONNECTION_STATE_UNKNOWN);
 
@@ -1604,7 +1723,7 @@ nm_vpn_connection_get_vpn_state (NMVPNConnection *connection)
 }
 
 const char *
-nm_vpn_connection_get_banner (NMVPNConnection *connection)
+nm_vpn_connection_get_banner (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
 
@@ -1612,7 +1731,7 @@ nm_vpn_connection_get_banner (NMVPNConnection *connection)
 }
 
 NMIP4Config *
-nm_vpn_connection_get_ip4_config (NMVPNConnection *connection)
+nm_vpn_connection_get_ip4_config (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
 
@@ -1620,7 +1739,7 @@ nm_vpn_connection_get_ip4_config (NMVPNConnection *connection)
 }
 
 NMIP6Config *
-nm_vpn_connection_get_ip6_config (NMVPNConnection *connection)
+nm_vpn_connection_get_ip6_config (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
 
@@ -1628,7 +1747,7 @@ nm_vpn_connection_get_ip6_config (NMVPNConnection *connection)
 }
 
 const char *
-nm_vpn_connection_get_ip_iface (NMVPNConnection *connection)
+nm_vpn_connection_get_ip_iface (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), NULL);
 
@@ -1636,7 +1755,7 @@ nm_vpn_connection_get_ip_iface (NMVPNConnection *connection)
 }
 
 int
-nm_vpn_connection_get_ip_ifindex (NMVPNConnection *connection)
+nm_vpn_connection_get_ip_ifindex (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), -1);
 
@@ -1644,7 +1763,7 @@ nm_vpn_connection_get_ip_ifindex (NMVPNConnection *connection)
 }
 
 guint32
-nm_vpn_connection_get_ip4_internal_gateway (NMVPNConnection *connection)
+nm_vpn_connection_get_ip4_internal_gateway (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), 0);
 
@@ -1652,7 +1771,7 @@ nm_vpn_connection_get_ip4_internal_gateway (NMVPNConnection *connection)
 }
 
 struct in6_addr *
-nm_vpn_connection_get_ip6_internal_gateway (NMVPNConnection *connection)
+nm_vpn_connection_get_ip6_internal_gateway (NMVpnConnection *connection)
 {
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), 0);
 
@@ -1660,8 +1779,8 @@ nm_vpn_connection_get_ip6_internal_gateway (NMVPNConnection *connection)
 }
 
 void
-nm_vpn_connection_disconnect (NMVPNConnection *connection,
-                              NMVPNConnectionStateReason reason,
+nm_vpn_connection_disconnect (NMVpnConnection *connection,
+                              NMVpnConnectionStateReason reason,
                               gboolean quitting)
 {
 	g_return_if_fail (NM_IS_VPN_CONNECTION (connection));
@@ -1670,11 +1789,11 @@ nm_vpn_connection_disconnect (NMVPNConnection *connection,
 }
 
 gboolean
-nm_vpn_connection_deactivate (NMVPNConnection *connection,
-                              NMVPNConnectionStateReason reason,
+nm_vpn_connection_deactivate (NMVpnConnection *connection,
+                              NMVpnConnectionStateReason reason,
                               gboolean quitting)
 {
-	NMVPNConnectionPrivate *priv;
+	NMVpnConnectionPrivate *priv;
 	gboolean success = FALSE;
 
 	g_return_val_if_fail (NM_IS_VPN_CONNECTION (connection), FALSE);
@@ -1692,8 +1811,8 @@ nm_vpn_connection_deactivate (NMVPNConnection *connection,
 static void
 plugin_need_secrets_cb  (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	GError *error = NULL;
 	char *setting_name;
 
@@ -1741,11 +1860,13 @@ plugin_need_secrets_cb  (DBusGProxy *proxy, DBusGProxyCall *call, void *user_dat
 static void
 plugin_new_secrets_cb  (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (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),
@@ -1764,8 +1885,8 @@ get_secrets_cb (NMSettingsConnection *connection,
                 GError *error,
                 gpointer user_data)
 {
-	NMVPNConnection *self = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMVpnConnection *self = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 	GHashTable *hash;
 
 	g_return_if_fail (NM_CONNECTION (connection) == priv->connection);
@@ -1813,12 +1934,12 @@ get_secrets_cb (NMSettingsConnection *connection,
 }
 
 static void
-get_secrets (NMVPNConnection *self,
+get_secrets (NMVpnConnection *self,
              SecretsReq secrets_idx,
              const char **hints)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
-	NMSettingsGetSecretsFlags flags = NM_SETTINGS_GET_SECRETS_FLAG_NONE;
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE;
 	GError *error = NULL;
 
 	g_return_if_fail (secrets_idx < SECRETS_REQ_LAST);
@@ -1831,21 +1952,21 @@ get_secrets (NMVPNConnection *self,
 
 	switch (priv->secrets_idx) {
 	case SECRETS_REQ_SYSTEM:
-		flags = NM_SETTINGS_GET_SECRETS_FLAG_ONLY_SYSTEM;
+		flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM;
 		break;
 	case SECRETS_REQ_EXISTING:
-		flags = NM_SETTINGS_GET_SECRETS_FLAG_NONE;
+		flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE;
 		break;
 	case SECRETS_REQ_NEW:
 	case SECRETS_REQ_INTERACTIVE:
-		flags = NM_SETTINGS_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+		flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 		break;
 	default:
 		g_assert_not_reached ();
 	}
 
 	if (nm_active_connection_get_user_requested (NM_ACTIVE_CONNECTION (self)))
-		flags |= NM_SETTINGS_GET_SECRETS_FLAG_USER_REQUESTED;
+		flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_USER_REQUESTED;
 
 	priv->secrets_id = nm_settings_connection_get_secrets (NM_SETTINGS_CONNECTION (priv->connection),
 	                                                       nm_active_connection_get_subject (NM_ACTIVE_CONNECTION (self)),
@@ -1871,8 +1992,8 @@ plugin_interactive_secrets_required (DBusGProxy *proxy,
                                      const char **secrets,
                                      gpointer user_data)
 {
-	NMVPNConnection *connection = NM_VPN_CONNECTION (user_data);
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
+	NMVpnConnection *connection = NM_VPN_CONNECTION (user_data);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (connection);
 	guint32 secrets_len = secrets ? g_strv_length ((char **) secrets) : 0;
 	char **hints;
 	guint32 i;
@@ -1900,9 +2021,42 @@ plugin_interactive_secrets_required (DBusGProxy *proxy,
 /******************************************************************************/
 
 static void
-nm_vpn_connection_init (NMVPNConnection *self)
+device_changed (NMActiveConnection *active,
+                NMDevice *new_device,
+                NMDevice *old_device)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
+	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)
+{
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (self);
 
 	priv->vpn_state = STATE_WAITING;
 	priv->secrets_idx = SECRETS_REQ_SYSTEM;
@@ -1922,7 +2076,7 @@ constructed (GObject *object)
 static void
 dispose (GObject *object)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
 
 	if (priv->connect_hash) {
 		g_hash_table_destroy (priv->connect_hash);
@@ -1953,7 +2107,7 @@ dispose (GObject *object)
 static void
 finalize (GObject *object)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
 
 	g_free (priv->banner);
 	g_free (priv->ip_iface);
@@ -1974,7 +2128,7 @@ static void
 get_property (GObject *object, guint prop_id,
 		    GValue *value, GParamSpec *pspec)
 {
-	NMVPNConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
+	NMVpnConnectionPrivate *priv = NM_VPN_CONNECTION_GET_PRIVATE (object);
 	NMDevice *parent_dev;
 
 	switch (prop_id) {
@@ -2007,12 +2161,12 @@ get_property (GObject *object, guint prop_id,
 }
 
 static void
-nm_vpn_connection_class_init (NMVPNConnectionClass *connection_class)
+nm_vpn_connection_class_init (NMVpnConnectionClass *connection_class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (connection_class);
 	NMActiveConnectionClass *active_class = NM_ACTIVE_CONNECTION_CLASS (connection_class);
 
-	g_type_class_add_private (connection_class, sizeof (NMVPNConnectionPrivate));
+	g_type_class_add_private (connection_class, sizeof (NMVpnConnectionPrivate));
 
 	/* virtual methods */
 	object_class->get_property = get_property;
@@ -2020,25 +2174,26 @@ 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);
 
 	/* properties */
-	g_object_class_install_property (object_class, PROP_VPN_STATE,
-		g_param_spec_uint (NM_VPN_CONNECTION_VPN_STATE,
-		                   "VpnState",
-		                   "Current VPN state",
-		                   NM_VPN_CONNECTION_STATE_UNKNOWN,
-		                   NM_VPN_CONNECTION_STATE_DISCONNECTED,
-		                   NM_VPN_CONNECTION_STATE_UNKNOWN,
-		                   G_PARAM_READABLE));
-
-	g_object_class_install_property (object_class, PROP_BANNER,
-		g_param_spec_string (NM_VPN_CONNECTION_BANNER,
-		                     "Banner",
-		                     "Login Banner",
-		                     NULL,
-		                     G_PARAM_READABLE));
+	g_object_class_install_property
+		(object_class, PROP_VPN_STATE,
+		 g_param_spec_uint (NM_VPN_CONNECTION_VPN_STATE, "", "",
+		                    NM_VPN_CONNECTION_STATE_UNKNOWN,
+		                    NM_VPN_CONNECTION_STATE_DISCONNECTED,
+		                    NM_VPN_CONNECTION_STATE_UNKNOWN,
+		                    G_PARAM_READABLE |
+		                    G_PARAM_STATIC_STRINGS));
+
+	g_object_class_install_property
+		(object_class, PROP_BANNER,
+		 g_param_spec_string (NM_VPN_CONNECTION_BANNER, "", "",
+		                      NULL,
+		                      G_PARAM_READABLE |
+		                      G_PARAM_STATIC_STRINGS));
 
 	g_object_class_override_property (object_class, PROP_IP4_CONFIG,
 	                                  NM_ACTIVE_CONNECTION_IP4_CONFIG);
@@ -2060,6 +2215,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);