summary refs log tree commit diff
path: root/src/nm-policy.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2012-06-29 20:12:44 +0200
committerMichael Biebl <biebl@debian.org>2012-06-29 20:12:44 +0200
commit867254ea7c2b193fecf8cd36cc6e5dc53c290d92 (patch)
tree7c698b403882736ab70c3efd524c3460f08c391c /src/nm-policy.c
parentde06e5715e780baade318f3490ac7a4c9ce84e32 (diff)
Imported Upstream version 0.9.5.95 upstream/0.9.5.95
Diffstat (limited to 'src/nm-policy.c')
-rw-r--r--src/nm-policy.c725
1 files changed, 492 insertions, 233 deletions
diff --git a/src/nm-policy.c b/src/nm-policy.c
index babf1649..57b1ee36 100644
--- a/src/nm-policy.c
+++ b/src/nm-policy.c
@@ -15,7 +15,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * Copyright (C) 2004 - 2011 Red Hat, Inc.
+ * Copyright (C) 2004 - 2012 Red Hat, Inc.
  * Copyright (C) 2007 - 2008 Novell, Inc.
  */
 
@@ -81,7 +81,7 @@ static void schedule_activate_all (NMPolicy *policy);
 
 
 static NMDevice *
-get_best_ip4_device (NMManager *manager, NMActRequest **out_req)
+get_best_ip4_device (NMManager *manager)
 {
 	GSList *devices, *iter;
 	NMDevice *best = NULL;
@@ -89,8 +89,6 @@ get_best_ip4_device (NMManager *manager, NMActRequest **out_req)
 
 	g_return_val_if_fail (manager != NULL, NULL);
 	g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
-	g_return_val_if_fail (out_req != NULL, NULL);
-	g_return_val_if_fail (*out_req == NULL, NULL);
 
 	devices = nm_manager_get_devices (manager);
 	for (iter = devices; iter; iter = g_slist_next (iter)) {
@@ -148,7 +146,6 @@ get_best_ip4_device (NMManager *manager, NMActRequest **out_req)
 		if (prio > 0 && prio < best_prio) {
 			best = dev;
 			best_prio = prio;
-			*out_req = req;
 		}
 	}
 
@@ -156,7 +153,7 @@ get_best_ip4_device (NMManager *manager, NMActRequest **out_req)
 }
 
 static NMDevice *
-get_best_ip6_device (NMManager *manager, NMActRequest **out_req)
+get_best_ip6_device (NMManager *manager)
 {
 	GSList *devices, *iter;
 	NMDevice *best = NULL;
@@ -164,8 +161,6 @@ get_best_ip6_device (NMManager *manager, NMActRequest **out_req)
 
 	g_return_val_if_fail (manager != NULL, NULL);
 	g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
-	g_return_val_if_fail (out_req != NULL, NULL);
-	g_return_val_if_fail (*out_req == NULL, NULL);
 
 	devices = nm_manager_get_devices (manager);
 	for (iter = devices; iter; iter = g_slist_next (iter)) {
@@ -222,7 +217,6 @@ get_best_ip6_device (NMManager *manager, NMActRequest **out_req)
 		if (prio > 0 && prio < best_prio) {
 			best = dev;
 			best_prio = prio;
-			*out_req = req;
 		}
 	}
 
@@ -295,8 +289,6 @@ static void
 update_system_hostname (NMPolicy *policy, NMDevice *best4, NMDevice *best6)
 {
 	char *configured_hostname = NULL;
-	NMActRequest *best_req4 = NULL;
-	NMActRequest *best_req6 = NULL;
 	const char *dhcp_hostname, *p;
 
 	g_return_if_fail (policy != NULL);
@@ -325,9 +317,9 @@ update_system_hostname (NMPolicy *policy, NMDevice *best4, NMDevice *best6)
 
 	/* Try automatically determined hostname from the best device's IP config */
 	if (!best4)
-		best4 = get_best_ip4_device (policy->manager, &best_req4);
+		best4 = get_best_ip4_device (policy->manager);
 	if (!best6)
-		best6 = get_best_ip6_device (policy->manager, &best_req6);
+		best6 = get_best_ip6_device (policy->manager);
 
 	if (!best4 && !best6) {
 		/* No best device; fall back to original hostname or if there wasn't
@@ -433,270 +425,370 @@ update_system_hostname (NMPolicy *policy, NMDevice *best4, NMDevice *best6)
 }
 
 static void
-update_ip4_routing_and_dns (NMPolicy *policy, gboolean force_update)
+update_default_ac (NMPolicy *policy,
+                   NMActiveConnection *best,
+                   void (*set_active_func)(NMActiveConnection*, gboolean))
 {
-	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
-	NMDevice *best = NULL;
-	NMActRequest *best_req = NULL;
-	NMDnsManager *dns_mgr;
-	GSList *devices = NULL, *iter, *vpns;
-	NMIP4Config *ip4_config = NULL;
-	NMIP4Address *addr;
-	const char *ip_iface = NULL;
-	NMConnection *connection = NULL;
-	NMSettingConnection *s_con = NULL;
-	const char *connection_id;
-	int ip_ifindex = 0;
+	GSList *devices, *vpns, *iter;
+	NMActRequest *req;
 
-	best = get_best_ip4_device (policy->manager, &best_req);
-	if (!best)
-		goto out;
-	if (!force_update && (best == policy->default_device4))
-		goto out;
+	/* Clear the 'default[6]' flag on all active connections that aren't the new
+	 * default active connection.  We'll set the new default after; this ensures
+	 * we don't ever have two marked 'default[6]' simultaneously.
+	 */
+	devices = nm_manager_get_devices (policy->manager);
+	for (iter = devices; iter; iter = g_slist_next (iter)) {
+		req = nm_device_get_act_request (NM_DEVICE (iter->data));
+		if (req && (NM_ACTIVE_CONNECTION (req) != best))
+			set_active_func (NM_ACTIVE_CONNECTION (req), FALSE);
+	}
+
+	vpns = nm_vpn_manager_get_active_connections (policy->vpn_manager);
+	for (iter = vpns; iter; iter = g_slist_next (iter)) {
+		if (NM_ACTIVE_CONNECTION (iter->data) != best)
+			set_active_func (NM_ACTIVE_CONNECTION (iter->data), FALSE);
+	}
+	g_slist_free (vpns);
+
+	/* Mark new default active connection */
+	if (best)
+		set_active_func (best, TRUE);
+}
+
+static NMIP4Config *
+get_best_ip4_config (NMPolicy *policy,
+                     gboolean ignore_never_default,
+                     const char **out_ip_iface,
+                     int *out_ip_ifindex,
+                     NMActiveConnection **out_ac,
+                     NMDevice **out_device,
+                     NMVPNConnection **out_vpn)
+{
+	GSList *vpns, *iter;
+	NMDevice *device;
+	NMActRequest *req = NULL;
+	NMIP4Config *ip4_config = NULL;
 
 	/* If a VPN connection is active, it is preferred */
 	vpns = nm_vpn_manager_get_active_connections (policy->vpn_manager);
 	for (iter = vpns; iter; iter = g_slist_next (iter)) {
 		NMVPNConnection *candidate = NM_VPN_CONNECTION (iter->data);
-		NMConnection *vpn_connection;
+		NMIP4Config *vpn_ip4;
+		NMConnection *tmp;
 		NMSettingIP4Config *s_ip4;
-		gboolean can_default = TRUE;
 		NMVPNConnectionState vpn_state;
 
-		/* If it's marked 'never-default', don't make it default */
-		vpn_connection = nm_vpn_connection_get_connection (candidate);
-		g_assert (vpn_connection);
-
-		/* Check the active IP4 config from the VPN service daemon */
-		ip4_config = nm_vpn_connection_get_ip4_config (candidate);
-		if (ip4_config && nm_ip4_config_get_never_default (ip4_config))
-			can_default = FALSE;
-
-		/* Check the user's preference from the NMConnection */
-		s_ip4 = nm_connection_get_setting_ip4_config (vpn_connection);
-		if (s_ip4 && nm_setting_ip4_config_get_never_default (s_ip4))
-			can_default = FALSE;
+		tmp = nm_vpn_connection_get_connection (candidate);
+		g_assert (tmp);
 
 		vpn_state = nm_vpn_connection_get_vpn_state (candidate);
-		if (can_default && (vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED)) {
-			NMIP4Config *parent_ip4;
-			NMDevice *parent;
-
-			ip_iface = nm_vpn_connection_get_ip_iface (candidate);
-			ip_ifindex = nm_vpn_connection_get_ip_ifindex (candidate);
-			connection = nm_vpn_connection_get_connection (candidate);
-			addr = nm_ip4_config_get_address (ip4_config, 0);
+		if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
+			continue;
 
-			parent = nm_vpn_connection_get_parent_device (candidate);
-			parent_ip4 = nm_device_get_ip4_config (parent);
+		vpn_ip4 = nm_vpn_connection_get_ip4_config (candidate);
+		if (!vpn_ip4)
+			continue;
 
-			nm_system_replace_default_ip4_route_vpn (ip_ifindex,
-			                                         nm_ip4_address_get_gateway (addr),
-			                                         nm_vpn_connection_get_ip4_internal_gateway (candidate),
-			                                         nm_ip4_config_get_mss (ip4_config),
-			                                         nm_device_get_ip_ifindex (parent),
-			                                         nm_ip4_config_get_mss (parent_ip4));
+		if (ignore_never_default == FALSE) {
+			/* Check for a VPN-provided config never-default */
+			if (nm_ip4_config_get_never_default (vpn_ip4))
+				continue;
 
-			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+			/* Check the user's preference from the NMConnection */
+			s_ip4 = nm_connection_get_setting_ip4_config (tmp);
+			if (s_ip4 && nm_setting_ip4_config_get_never_default (s_ip4))
+				continue;
 		}
+
+		ip4_config = vpn_ip4;
+		if (out_vpn)
+			*out_vpn = candidate;
+		if (out_ac)
+			*out_ac = NM_ACTIVE_CONNECTION (candidate);
+		if (out_ip_iface)
+			*out_ip_iface = nm_vpn_connection_get_ip_iface (candidate);
+		if (out_ip_ifindex)
+			*out_ip_ifindex = nm_vpn_connection_get_ip_ifindex (candidate);
+		break;
 	}
 	g_slist_free (vpns);
 
-	/* The best device gets the default route if a VPN connection didn't */
-	if (!ip_iface || !ip4_config) {
-		connection = nm_act_request_get_connection (best_req);
-		ip_iface = nm_device_get_ip_iface (best);
-		ip_ifindex = nm_device_get_ip_ifindex (best);
-		ip4_config = nm_device_get_ip4_config (best);
-		g_assert (ip4_config);
-		addr = nm_ip4_config_get_address (ip4_config, 0);
-
-		nm_system_replace_default_ip4_route (ip_ifindex,
-		                                     nm_ip4_address_get_gateway (addr),
-		                                     nm_ip4_config_get_mss (ip4_config));
-		dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
+	/* If no VPN connections, we use the best device instead */
+	if (!ip4_config) {
+		device = get_best_ip4_device (policy->manager);
+		if (device) {
+			ip4_config = nm_device_get_ip4_config (device);
+			g_assert (ip4_config);
+			req = nm_device_get_act_request (device);
+			g_assert (req);
+
+			if (out_device)
+				*out_device = device;
+			if (out_ac)
+				*out_ac = NM_ACTIVE_CONNECTION (req);
+			if (out_ip_iface)
+				*out_ip_iface = nm_device_get_ip_iface (device);
+			if (out_ip_ifindex)
+				*out_ip_ifindex = nm_device_get_ip_ifindex (device);
+		}
 	}
 
-	if (!ip_iface || !ip4_config) {
-		nm_log_warn (LOGD_CORE, "couldn't determine IP interface (%p) or IPv4 config (%p)!",
-		             ip_iface, ip4_config);
-		goto out;
-	}
+	return ip4_config;
+}
 
-	/* Update the default active connection.  Only mark the new default
-	 * active connection after setting default = FALSE on all other connections
-	 * first.  The order is important, we don't want two connections marked
-	 * default at the same time ever.
-	 */
-	devices = nm_manager_get_devices (policy->manager);
-	for (iter = devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *dev = NM_DEVICE (iter->data);
-		NMActRequest *req;
+static void
+update_ip4_dns (NMPolicy *policy, NMDnsManager *dns_mgr)
+{
+	NMIP4Config *ip4_config;
+	const char *ip_iface = NULL;
+	NMVPNConnection *vpn = NULL;
+	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
 
-		req = nm_device_get_act_request (dev);
-		if (req && (req != best_req))
-			nm_active_connection_set_default (NM_ACTIVE_CONNECTION (req), FALSE);
+	ip4_config = get_best_ip4_config (policy, TRUE, &ip_iface, NULL, NULL, NULL, &vpn);
+	if (ip4_config) {
+		if (vpn)
+			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+
+		/* Tell the DNS manager this config is preferred by re-adding it with
+		 * a different IP config type.
+		 */
+		nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, ip4_config, dns_type);
 	}
+}
 
-	dns_mgr = nm_dns_manager_get (NULL);
-	nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, ip4_config, dns_type);
-	g_object_unref (dns_mgr);
+static void
+update_ip4_routing (NMPolicy *policy, gboolean force_update)
+{
+	NMDevice *best = NULL, *parent;
+	NMConnection *connection = NULL;
+	NMVPNConnection *vpn = NULL;
+	NMActiveConnection *best_ac = NULL;
+	NMIP4Config *ip4_config = NULL, *parent_ip4;
+	NMIP4Address *addr;
+	const char *ip_iface = NULL;
+	int ip_ifindex = -1;
+	guint32 parent_mss;
 
-	/* Now set new default active connection _after_ updating DNS info, so that
-	 * if the connection is shared dnsmasq picks up the right stuff.
+	/* Note that we might have an IPv4 VPN tunneled over an IPv6-only device,
+	 * so we can get (vpn != NULL && best == NULL).
 	 */
-	if (best_req)
-		nm_active_connection_set_default (NM_ACTIVE_CONNECTION (best_req), TRUE);
+	ip4_config = get_best_ip4_config (policy, FALSE, &ip_iface, &ip_ifindex, &best_ac, &best, &vpn);
+	if (!ip4_config) {
+		policy->default_device4 = NULL;
+		return;
+	}
+	g_assert ((best || vpn) && best_ac);
 
-	if (connection)
-		s_con = nm_connection_get_setting_connection (connection);
+	if (!force_update && best && (best == policy->default_device4))
+		return;
 
-	connection_id = s_con ? nm_setting_connection_get_id (s_con) : NULL;
-	if (connection_id) {
-		nm_log_info (LOGD_CORE, "Policy set '%s' (%s) as default for IPv4 routing and DNS.", connection_id, ip_iface);
+	/* We set the default route to the gateway of the first IP address of the config */
+	addr = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (addr);
+
+	if (vpn) {
+		parent = nm_vpn_connection_get_parent_device (vpn);
+		parent_ip4 = nm_device_get_ip4_config (parent);
+		parent_mss = parent_ip4 ? nm_ip4_config_get_mss (parent_ip4) : 0;
+
+		nm_system_replace_default_ip4_route_vpn (ip_ifindex,
+		                                         nm_ip4_address_get_gateway (addr),
+		                                         nm_vpn_connection_get_ip4_internal_gateway (vpn),
+		                                         nm_ip4_config_get_mss (ip4_config),
+		                                         nm_device_get_ip_ifindex (parent),
+		                                         parent_mss);
 	} else {
-		nm_log_info (LOGD_CORE, "Policy set (%s) as default for IPv4 routing and DNS.", ip_iface);
+		nm_system_replace_default_ip4_route (ip_ifindex,
+		                                     nm_ip4_address_get_gateway (addr),
+		                                     nm_ip4_config_get_mss (ip4_config));
 	}
 
-out:
+	update_default_ac (policy, best_ac, nm_active_connection_set_default);
 	policy->default_device4 = best;
+
+	connection = nm_active_connection_get_connection (best_ac);
+	nm_log_info (LOGD_CORE, "Policy set '%s' (%s) as default for IPv4 routing and DNS.",
+	             nm_connection_get_id (connection), ip_iface);
 }
 
-static void
-update_ip6_routing_and_dns (NMPolicy *policy, gboolean force_update)
+static NMIP6Config *
+get_best_ip6_config (NMPolicy *policy,
+                     gboolean ignore_never_default,
+                     const char **out_ip_iface,
+                     int *out_ip_ifindex,
+                     NMActiveConnection **out_ac,
+                     NMDevice **out_device,
+                     NMVPNConnection **out_vpn)
 {
-	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
-	NMDevice *best = NULL;
-	NMActRequest *best_req = NULL;
-	NMDnsManager *dns_mgr;
-	GSList *devices = NULL, *iter;
-#if 0
-	GSList *vpns;
-#endif
+	GSList *vpns, *iter;
+	NMDevice *device;
+	NMActRequest *req = NULL;
 	NMIP6Config *ip6_config = NULL;
-	NMIP6Address *addr;
-	const char *ip_iface = NULL;
-	int ip_ifindex = -1;
-	NMConnection *connection = NULL;
-	NMSettingConnection *s_con = NULL;
-	const char *connection_id;
 
-	best = get_best_ip6_device (policy->manager, &best_req);
-	if (!best)
-		goto out;
-	if (!force_update && (best == policy->default_device6))
-		goto out;
-
-#if 0
 	/* If a VPN connection is active, it is preferred */
 	vpns = nm_vpn_manager_get_active_connections (policy->vpn_manager);
 	for (iter = vpns; iter; iter = g_slist_next (iter)) {
 		NMVPNConnection *candidate = NM_VPN_CONNECTION (iter->data);
-		NMConnection *vpn_connection;
+		NMIP6Config *vpn_ip6;
+		NMConnection *tmp;
 		NMSettingIP6Config *s_ip6;
-		gboolean can_default = TRUE;
 		NMVPNConnectionState vpn_state;
 
-		/* If it's marked 'never-default', don't make it default */
-		vpn_connection = nm_vpn_connection_get_connection (candidate);
-		g_assert (vpn_connection);
-		s_ip6 = nm_connection_get_setting_ip6_config (vpn_connection);
-		if (s_ip6 && nm_setting_ip6_config_get_never_default (s_ip6))
-			can_default = FALSE;
+		tmp = nm_vpn_connection_get_connection (candidate);
+		g_assert (tmp);
 
 		vpn_state = nm_vpn_connection_get_vpn_state (candidate);
-		if (can_default && (vpn_state == NM_VPN_CONNECTION_STATE_ACTIVATED)) {
-			NMIP6Config *parent_ip6;
-			NMDevice *parent;
-
-			ip_iface = nm_vpn_connection_get_ip_iface (candidate);
-			connection = nm_vpn_connection_get_connection (candidate);
-			ip6_config = nm_vpn_connection_get_ip6_config (candidate);
-			addr = nm_ip6_config_get_address (ip6_config, 0);
+		if (vpn_state != NM_VPN_CONNECTION_STATE_ACTIVATED)
+			continue;
 
-			parent = nm_vpn_connection_get_parent_device (candidate);
-			parent_ip6 = nm_device_get_ip6_config (parent);
+		vpn_ip6 = nm_vpn_connection_get_ip6_config (candidate);
+		if (!vpn_ip6)
+			continue;
 
-			nm_system_replace_default_ip6_route_vpn (ip_iface,
-			                                         nm_ip6_address_get_gateway (addr),
-			                                         nm_vpn_connection_get_ip4_internal_gateway (candidate),
-			                                         nm_ip6_config_get_mss (ip4_config),
-			                                         nm_device_get_ip_iface (parent),
-			                                         nm_ip6_config_get_mss (parent_ip4));
+		if (ignore_never_default == FALSE) {
+			/* Check for a VPN-provided config never-default */
+			if (nm_ip6_config_get_never_default (vpn_ip6))
+				continue;
 
-			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+			/* Check the user's preference from the NMConnection */
+			s_ip6 = nm_connection_get_setting_ip6_config (tmp);
+			if (s_ip6 && nm_setting_ip6_config_get_never_default (s_ip6))
+				continue;
 		}
+
+		ip6_config = vpn_ip6;
+		if (out_vpn)
+			*out_vpn = candidate;
+		if (out_ac)
+			*out_ac = NM_ACTIVE_CONNECTION (candidate);
+		if (out_ip_iface)
+			*out_ip_iface = nm_vpn_connection_get_ip_iface (candidate);
+		if (out_ip_ifindex)
+			*out_ip_ifindex = nm_vpn_connection_get_ip_ifindex (candidate);
+		break;
 	}
 	g_slist_free (vpns);
-#endif
 
-	/* The best device gets the default route if a VPN connection didn't */
-	if (!ip_iface || !ip6_config) {
-		connection = nm_act_request_get_connection (best_req);
-		ip_iface = nm_device_get_ip_iface (best);
-		ip_ifindex = nm_device_get_ip_ifindex (best);
-		ip6_config = nm_device_get_ip6_config (best);
-		g_assert (ip6_config);
-		addr = nm_ip6_config_get_address (ip6_config, 0);
-
-		nm_system_replace_default_ip6_route (ip_ifindex, nm_ip6_address_get_gateway (addr));
-
-		dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
+	/* If no VPN connections, we use the best device instead */
+	if (!ip6_config) {
+		device = get_best_ip6_device (policy->manager);
+		if (device) {
+			req = nm_device_get_act_request (device);
+			g_assert (req);
+			ip6_config = nm_device_get_ip6_config (device);
+			g_assert (ip6_config);
+
+			if (out_device)
+				*out_device = device;
+			if (out_ac)
+				*out_ac = NM_ACTIVE_CONNECTION (req);
+			if (out_ip_iface)
+				*out_ip_iface = nm_device_get_ip_iface (device);
+			if (out_ip_ifindex)
+				*out_ip_ifindex = nm_device_get_ip_ifindex (device);
+		}
 	}
 
-	if (!ip_iface || !ip6_config) {
-		nm_log_warn (LOGD_CORE, "couldn't determine IP interface (%p) or IPv6 config (%p)!",
-		             ip_iface, ip6_config);
-		goto out;
-	}
+	return ip6_config;
+}
 
-	/* Update the default active connection.  Only mark the new default
-	 * active connection after setting default = FALSE on all other connections
-	 * first.  The order is important, we don't want two connections marked
-	 * default at the same time ever.
-	 */
-	devices = nm_manager_get_devices (policy->manager);
-	for (iter = devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *dev = NM_DEVICE (iter->data);
-		NMActRequest *req;
+static void
+update_ip6_dns (NMPolicy *policy, NMDnsManager *dns_mgr)
+{
+	NMIP6Config *ip6_config;
+	const char *ip_iface = NULL;
+	NMVPNConnection *vpn = NULL;
+	NMDnsIPConfigType dns_type = NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE;
 
-		req = nm_device_get_act_request (dev);
-		if (req && (req != best_req))
-			nm_active_connection_set_default6 (NM_ACTIVE_CONNECTION (req), FALSE);
+	ip6_config = get_best_ip6_config (policy, TRUE, &ip_iface, NULL, NULL, NULL, &vpn);
+	if (ip6_config) {
+		if (vpn)
+			dns_type = NM_DNS_IP_CONFIG_TYPE_VPN;
+
+		/* Tell the DNS manager this config is preferred by re-adding it with
+		 * a different IP config type.
+		 */
+		nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, dns_type);
 	}
+}
 
-	dns_mgr = nm_dns_manager_get (NULL);
-	nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, dns_type);
-	g_object_unref (dns_mgr);
+static void
+update_ip6_routing (NMPolicy *policy, gboolean force_update)
+{
+	NMDevice *best = NULL, *parent;
+	NMConnection *connection = NULL;
+	NMVPNConnection *vpn = NULL;
+	NMActiveConnection *best_ac = NULL;
+	NMIP6Config *ip6_config = NULL, *parent_ip6;
+	NMIP6Address *addr;
+	const char *ip_iface = NULL;
+	int ip_ifindex = -1;
+	guint32 parent_mss;
 
-	/* Now set new default active connection _after_ updating DNS info, so that
-	 * if the connection is shared dnsmasq picks up the right stuff.
+	/* Note that we might have an IPv6 VPN tunneled over an IPv4-only device,
+	 * so we can get (vpn != NULL && best == NULL).
 	 */
-	if (best_req)
-		nm_active_connection_set_default6 (NM_ACTIVE_CONNECTION (best_req), TRUE);
+	ip6_config = get_best_ip6_config (policy, FALSE, &ip_iface, &ip_ifindex, &best_ac, &best, &vpn);
+	if (!ip6_config) {
+		policy->default_device6 = NULL;
+		return;
+	}
+	g_assert ((best || vpn) && best_ac);
 
-	if (connection)
-		s_con = nm_connection_get_setting_connection (connection);
+	if (!force_update && best && (best == policy->default_device6))
+		return;
 
-	connection_id = s_con ? nm_setting_connection_get_id (s_con) : NULL;
-	if (connection_id) {
-		nm_log_info (LOGD_CORE, "Policy set '%s' (%s) as default for IPv6 routing and DNS.", connection_id, ip_iface);
+	/* We set the default route to the gateway of the first IP address of the config */
+	addr = nm_ip6_config_get_address (ip6_config, 0);
+	g_assert (addr);
+
+	if (vpn) {
+		parent = nm_vpn_connection_get_parent_device (vpn);
+		parent_ip6 = nm_device_get_ip6_config (parent);
+		parent_mss = parent_ip6 ? nm_ip6_config_get_mss (parent_ip6) : 0;
+
+		nm_system_replace_default_ip6_route_vpn (ip_ifindex,
+		                                         nm_ip6_address_get_gateway (addr),
+		                                         nm_vpn_connection_get_ip6_internal_gateway (vpn),
+		                                         nm_ip6_config_get_mss (ip6_config),
+		                                         nm_device_get_ip_ifindex (parent),
+		                                         parent_mss);
 	} else {
-		nm_log_info (LOGD_CORE, "Policy set (%s) as default for IPv6 routing and DNS.", ip_iface);
+		if (memcmp (nm_ip6_address_get_gateway (addr)->s6_addr, in6addr_any.s6_addr, sizeof (in6addr_any.s6_addr)) != 0)
+			nm_system_replace_default_ip6_route (ip_ifindex, nm_ip6_address_get_gateway (addr));
+		else if (nm_ip6_config_get_gateway (ip6_config))
+			nm_system_replace_default_ip6_route (ip_ifindex, nm_ip6_config_get_gateway (ip6_config));
+		else
+			nm_log_dbg (LOGD_IP6, "missing default IPv6 route");
 	}
 
-out:
+	update_default_ac (policy, best_ac, nm_active_connection_set_default6);
 	policy->default_device6 = best;
+
+	connection = nm_active_connection_get_connection (best_ac);
+	nm_log_info (LOGD_CORE, "Policy set '%s' (%s) as default for IPv6 routing and DNS.",
+	             nm_connection_get_id (connection), ip_iface);
 }
 
 static void
 update_routing_and_dns (NMPolicy *policy, gboolean force_update)
 {
-	update_ip4_routing_and_dns (policy, force_update);
-	update_ip6_routing_and_dns (policy, force_update);
+	NMDnsManager *mgr;
+
+	mgr = nm_dns_manager_get (NULL);
+	nm_dns_manager_begin_updates (mgr, __func__);
+
+	update_ip4_dns (policy, mgr);
+	update_ip6_dns (policy, mgr);
+
+	update_ip4_routing (policy, force_update);
+	update_ip6_routing (policy, force_update);
 
 	/* Update the system hostname */
 	update_system_hostname (policy, policy->default_device4, policy->default_device6);
+
+	nm_dns_manager_end_updates (mgr, __func__);
+	g_object_unref (mgr);
 }
 
 static void
@@ -842,17 +934,96 @@ vpn_connection_activated (NMVPNManager *manager,
                           NMVPNConnection *vpn,
                           gpointer user_data)
 {
+	NMDnsManager *mgr;
+	NMIP4Config *ip4_config;
+	NMIP6Config *ip6_config;
+	const char *ip_iface;
+
+	mgr = nm_dns_manager_get (NULL);
+	nm_dns_manager_begin_updates (mgr, __func__);
+
+	ip_iface = nm_vpn_connection_get_ip_iface (vpn);
+
+	/* Add the VPN connection's IP configs from DNS */
+
+	ip4_config = nm_vpn_connection_get_ip4_config (vpn);
+	if (ip4_config)
+		nm_dns_manager_add_ip4_config (mgr, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_VPN);
+
+	ip6_config = nm_vpn_connection_get_ip6_config (vpn);
+	if (ip6_config)
+		nm_dns_manager_add_ip6_config (mgr, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_VPN);
+
 	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+
+	nm_dns_manager_end_updates (mgr, __func__);
 }
 
 static void
 vpn_connection_deactivated (NMVPNManager *manager,
                             NMVPNConnection *vpn,
-                            NMVPNConnectionState state,
+                            NMVPNConnectionState new_state,
+                            NMVPNConnectionState old_state,
                             NMVPNConnectionStateReason reason,
                             gpointer user_data)
 {
+	NMDnsManager *mgr;
+	NMIP4Config *ip4_config, *parent_ip4 = NULL;
+	NMIP6Config *ip6_config, *parent_ip6 = NULL;
+	const char *ip_iface;
+	NMDevice *parent;
+
+	mgr = nm_dns_manager_get (NULL);
+	nm_dns_manager_begin_updates (mgr, __func__);
+
+	ip_iface = nm_vpn_connection_get_ip_iface (vpn);
+	parent = nm_vpn_connection_get_parent_device (vpn);
+
+	ip4_config = nm_vpn_connection_get_ip4_config (vpn);
+	if (ip4_config) {
+		/* Remove the VPN connection's IP4 config from DNS */
+		nm_dns_manager_remove_ip4_config (mgr, ip_iface, ip4_config);
+
+		/* Re-apply routes and addresses of the VPN connection's parent interface,
+		 * which the VPN might have overridden.
+		 */
+		if (parent) {
+			parent_ip4 = nm_device_get_ip4_config (parent);
+			if (parent_ip4) {
+				if (!nm_system_apply_ip4_config (nm_device_get_ip_ifindex (parent),
+				                                 parent_ip4,
+				                                 nm_device_get_priority (parent),
+				                                 NM_IP4_COMPARE_FLAG_ADDRESSES | NM_IP4_COMPARE_FLAG_ROUTES)) {
+					nm_log_err (LOGD_VPN, "failed to re-apply VPN parent device IPv4 addresses and routes.");
+				}
+			}
+		}
+	}
+
+	ip6_config = nm_vpn_connection_get_ip6_config (vpn);
+	if (ip6_config) {
+		/* Remove the VPN connection's IP6 config from DNS */
+		nm_dns_manager_remove_ip6_config (mgr, ip_iface, ip6_config);
+
+		/* Re-apply routes and addresses of the VPN connection's parent interface,
+		 * which the VPN might have overridden.
+		 */
+		if (parent) {
+			parent_ip6 = nm_device_get_ip6_config (parent);
+			if (parent_ip6) {
+				if (!nm_system_apply_ip6_config (nm_device_get_ip_ifindex (parent),
+				                                 parent_ip6,
+				                                 nm_device_get_priority (parent),
+				                                 NM_IP6_COMPARE_FLAG_ADDRESSES | NM_IP6_COMPARE_FLAG_ROUTES)) {
+					nm_log_err (LOGD_VPN, "failed to re-apply VPN parent device IPv6 addresses and routes.");
+				}
+			}
+		}
+	}
+
 	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+
+	nm_dns_manager_end_updates (mgr, __func__);
 }
 
 static void
@@ -976,15 +1147,6 @@ reset_connections_retries (gpointer user_data)
 	return FALSE;
 }
 
-static NMConnection *
-get_device_connection (NMDevice *device)
-{
-	NMActRequest *req = NULL;
-
-	req = nm_device_get_act_request (device);
-	return req ? nm_act_request_get_connection (req) : NULL;
-}
-
 static void schedule_activate_all (NMPolicy *policy);
 
 static void
@@ -1025,7 +1187,11 @@ device_state_changed (NMDevice *device,
                       gpointer user_data)
 {
 	NMPolicy *policy = (NMPolicy *) user_data;
-	NMConnection *connection = get_device_connection (device);
+	NMConnection *connection = nm_device_get_connection (device);
+	const char *ip_iface = nm_device_get_ip_iface (device);
+	NMIP4Config *ip4_config;
+	NMIP6Config *ip6_config;
+	NMDnsManager *dns_mgr;
 
 	if (connection)
 		g_object_set_data (G_OBJECT (connection), FAILURE_REASON_TAG, GUINT_TO_POINTER (0));
@@ -1081,20 +1247,33 @@ device_state_changed (NMDevice *device,
 			nm_connection_clear_secrets (connection);
 		}
 
-		update_routing_and_dns (policy, FALSE);
+		/* Add device's new IPv4 and IPv6 configs to DNS */
+
+		dns_mgr = nm_dns_manager_get (NULL);
+		nm_dns_manager_begin_updates (dns_mgr, __func__);
+
+		ip4_config = nm_device_get_ip4_config (device);
+		if (ip4_config) {
+			nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, ip4_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			update_ip4_dns (policy, dns_mgr);
+		}
+		ip6_config = nm_device_get_ip6_config (device);
+		if (ip6_config) {
+			nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, ip6_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+			update_ip6_dns (policy, dns_mgr);
+		}
+
+		nm_dns_manager_end_updates (dns_mgr, __func__);
+		g_object_unref (dns_mgr);
+
+		/* And make sure the best devices have the default route */
+		update_ip4_routing (policy, FALSE);
+		update_ip6_routing (policy, FALSE);
 		break;
 	case NM_DEVICE_STATE_UNMANAGED:
-		if (   old_state == NM_DEVICE_STATE_UNAVAILABLE
-		    || old_state == NM_DEVICE_STATE_DISCONNECTED) {
-			/* If the device was never activated, there's no point in
-			 * updating routing or DNS.  This allows us to keep the previous
-			 * resolv.conf or routes from before NM started if no device was
-			 * ever managed by NM.
-			 */
-			break;
-		}
 	case NM_DEVICE_STATE_UNAVAILABLE:
-		update_routing_and_dns (policy, FALSE);
+		if (old_state > NM_DEVICE_STATE_DISCONNECTED)
+			update_routing_and_dns (policy, FALSE);
 		break;
 	case NM_DEVICE_STATE_DISCONNECTED:
 		/* Reset RETRIES_TAG when carrier on. If cable was unplugged
@@ -1102,8 +1281,10 @@ device_state_changed (NMDevice *device,
 		if (reason == NM_DEVICE_STATE_REASON_CARRIER && old_state == NM_DEVICE_STATE_UNAVAILABLE)
 			reset_retries_all (policy->settings, device);
 
+		if (old_state > NM_DEVICE_STATE_DISCONNECTED)
+			update_routing_and_dns (policy, FALSE);
+
 		/* Device is now available for auto-activation */
-		update_routing_and_dns (policy, FALSE);
 		schedule_activate_check (policy, device, 0);
 		break;
 
@@ -1119,11 +1300,86 @@ device_state_changed (NMDevice *device,
 }
 
 static void
-device_ip_config_changed (NMDevice *device,
-                          GParamSpec *pspec,
-                          gpointer user_data)
+device_ip4_config_changed (NMDevice *device,
+                           NMIP4Config *new_config,
+                           NMIP4Config *old_config,
+                           gpointer user_data)
 {
-	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+	NMPolicy *policy = user_data;
+	NMDnsManager *dns_mgr;
+	const char *ip_iface = nm_device_get_ip_iface (device);
+	NMIP4ConfigCompareFlags diff = NM_IP4_COMPARE_FLAG_ALL;
+
+	dns_mgr = nm_dns_manager_get (NULL);
+	nm_dns_manager_begin_updates (dns_mgr, __func__);
+
+	/* Old configs get removed immediately */
+	if (old_config)
+		nm_dns_manager_remove_ip4_config (dns_mgr, ip_iface, old_config);
+
+	/* Ignore IP config changes while the device is activating, because we'll
+	 * catch all the changes when the device moves to ACTIVATED state.
+	 * Prevents unecessary changes to DNS information.
+	 */
+	if (!nm_device_is_activating (device)) {
+		if (new_config)
+			nm_dns_manager_add_ip4_config (dns_mgr, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+		update_ip4_dns (policy, dns_mgr);
+
+		/* Only change routing if something actually changed */
+		diff = nm_ip4_config_diff (new_config, old_config);
+		if (diff & (NM_IP4_COMPARE_FLAG_ADDRESSES | NM_IP4_COMPARE_FLAG_PTP_ADDRESS | NM_IP4_COMPARE_FLAG_ROUTES))
+			update_ip4_routing (policy, TRUE);
+	}
+
+	nm_dns_manager_end_updates (dns_mgr, __func__);
+	g_object_unref (dns_mgr);
+}
+
+static void
+device_ip6_config_changed (NMDevice *device,
+                           NMIP6Config *new_config,
+                           NMIP6Config *old_config,
+                           gpointer user_data)
+{
+	NMPolicy *policy = user_data;
+	NMDnsManager *dns_mgr;
+	const char *ip_iface = nm_device_get_ip_iface (device);
+	NMIP4ConfigCompareFlags diff = NM_IP4_COMPARE_FLAG_ALL;
+
+	dns_mgr = nm_dns_manager_get (NULL);
+	nm_dns_manager_begin_updates (dns_mgr, __func__);
+
+	/* Old configs get removed immediately */
+	if (old_config)
+		nm_dns_manager_remove_ip6_config (dns_mgr, ip_iface, old_config);
+
+	/* Ignore IP config changes while the device is activating, because we'll
+	 * catch all the changes when the device moves to ACTIVATED state.
+	 * Prevents unecessary changes to DNS information.
+	 */
+	if (!nm_device_is_activating (device)) {
+		if (new_config)
+			nm_dns_manager_add_ip6_config (dns_mgr, ip_iface, new_config, NM_DNS_IP_CONFIG_TYPE_DEFAULT);
+		update_ip6_dns (policy, dns_mgr);
+
+		/* Only change routing if something actually changed */
+		diff = nm_ip6_config_diff (new_config, old_config);
+		if (diff & (NM_IP6_COMPARE_FLAG_ADDRESSES | NM_IP6_COMPARE_FLAG_PTP_ADDRESS | NM_IP6_COMPARE_FLAG_ROUTES))
+			update_ip6_routing (policy, TRUE);
+	}
+
+	nm_dns_manager_end_updates (dns_mgr, __func__);
+	g_object_unref (dns_mgr);
+}
+
+static void
+device_autoconnect_changed (NMDevice *device,
+                            GParamSpec *pspec,
+                            gpointer user_data)
+{
+	if (nm_device_get_autoconnect (device))
+		schedule_activate_check ((NMPolicy *) user_data, device, 0);
 }
 
 static void
@@ -1167,8 +1423,9 @@ device_added (NMManager *manager, NMDevice *device, gpointer user_data)
 	NMPolicy *policy = (NMPolicy *) user_data;
 
 	_connect_device_signal (policy, device, "state-changed", device_state_changed);
-	_connect_device_signal (policy, device, "notify::" NM_DEVICE_IP4_CONFIG, device_ip_config_changed);
-	_connect_device_signal (policy, device, "notify::" NM_DEVICE_IP6_CONFIG, device_ip_config_changed);
+	_connect_device_signal (policy, device, NM_DEVICE_IP4_CONFIG_CHANGED, device_ip4_config_changed);
+	_connect_device_signal (policy, device, NM_DEVICE_IP6_CONFIG_CHANGED, device_ip6_config_changed);
+	_connect_device_signal (policy, device, "notify::" NM_DEVICE_AUTOCONNECT, device_autoconnect_changed);
 
 	switch (nm_device_get_device_type (device)) {
 	case NM_DEVICE_TYPE_WIFI:
@@ -1215,7 +1472,9 @@ device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
 		iter = next;
 	}
 
-	update_routing_and_dns (policy, FALSE);
+	/* Don't update routing and DNS here as we've already handled that
+	 * for devices that need it when the device's state changed to UNMANAGED.
+	 */
 }
 
 static void
@@ -1273,7 +1532,7 @@ firewall_update_zone (NMPolicy *policy, NMConnection *connection)
 	for (iter = devices; iter; iter = g_slist_next (iter)) {
 		NMDevice *dev = NM_DEVICE (iter->data);
 
-		if (   (get_device_connection (dev) == connection)
+		if (   (nm_device_get_connection (dev) == connection)
 		    && (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED)) {
 			nm_firewall_manager_add_or_change_zone (policy->fw_manager,
 			                                        nm_device_get_ip_iface (dev),
@@ -1299,7 +1558,7 @@ firewall_started (NMFirewallManager *manager,
 	for (iter = devices; iter; iter = g_slist_next (iter)) {
 		NMDevice *dev = NM_DEVICE (iter->data);
 
-		connection = get_device_connection (dev);
+		connection = nm_device_get_connection (dev);
 		s_con = nm_connection_get_setting_connection (connection);
 		if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) {
 			nm_firewall_manager_add_or_change_zone (policy->fw_manager,