summary refs log tree commit diff
path: root/src/nm-policy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-policy.c')
-rw-r--r--src/nm-policy.c1138
1 files changed, 1138 insertions, 0 deletions
diff --git a/src/nm-policy.c b/src/nm-policy.c
new file mode 100644
index 00000000..455545b6
--- /dev/null
+++ b/src/nm-policy.c
@@ -0,0 +1,1138 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager -- Network link manager
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * 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 - 2010 Red Hat, Inc.
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <ctype.h>
+
+#include "nm-policy.h"
+#include "NetworkManagerUtils.h"
+#include "nm-wifi-ap.h"
+#include "nm-activation-request.h"
+#include "nm-logging.h"
+#include "nm-device-interface.h"
+#include "nm-device.h"
+#include "nm-device-wifi.h"
+#include "nm-device-ethernet.h"
+#include "nm-device-modem.h"
+#include "nm-dbus-manager.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-connection.h"
+#include "nm-system.h"
+#include "nm-named-manager.h"
+#include "nm-vpn-manager.h"
+#include "nm-policy-hosts.h"
+#include "nm-policy-hostname.h"
+
+struct NMPolicy {
+	NMManager *manager;
+	guint update_state_id;
+	GSList *pending_activation_checks;
+	GSList *signal_ids;
+	GSList *dev_signal_ids;
+
+	NMVPNManager *vpn_manager;
+	gulong vpn_activated_id;
+	gulong vpn_deactivated_id;
+
+	NMDevice *default_device4;
+	NMDevice *default_device6;
+
+	HostnameThread *lookup;
+
+	char *orig_hostname; /* hostname at NM start time */
+};
+
+#define INVALID_TAG "invalid"
+
+static const char *
+get_connection_id (NMConnection *connection)
+{
+	NMSettingConnection *s_con;
+
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
+
+	s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+	g_return_val_if_fail (s_con != NULL, NULL);
+
+	return nm_setting_connection_get_id (s_con);
+}
+
+static NMDevice *
+get_best_ip4_device (NMManager *manager, NMActRequest **out_req)
+{
+	GSList *devices, *iter;
+	NMDevice *best = NULL;
+	int best_prio = G_MAXINT;
+
+	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)) {
+		NMDevice *dev = NM_DEVICE (iter->data);
+		NMActRequest *req;
+		NMConnection *connection;
+		NMIP4Config *ip4_config;
+		NMSettingIP4Config *s_ip4;
+		int prio;
+		guint i;
+		gboolean can_default = FALSE;
+		const char *method = NULL;
+
+		if (nm_device_get_state (dev) != NM_DEVICE_STATE_ACTIVATED)
+			continue;
+
+		ip4_config = nm_device_get_ip4_config (dev);
+		if (!ip4_config)
+			continue;
+
+		req = nm_device_get_act_request (dev);
+		g_assert (req);
+		connection = nm_act_request_get_connection (req);
+		g_assert (connection);
+
+		/* Never set the default route through an IPv4LL-addressed device */
+		s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+		if (s_ip4)
+			method = nm_setting_ip4_config_get_method (s_ip4);
+
+		if (s_ip4 && !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+			continue;
+
+		/* Make sure at least one of this device's IP addresses has a gateway */
+		for (i = 0; i < nm_ip4_config_get_num_addresses (ip4_config); i++) {
+			NMIP4Address *addr;
+
+			addr = nm_ip4_config_get_address (ip4_config, i);
+			if (nm_ip4_address_get_gateway (addr)) {
+				can_default = TRUE;
+				break;
+			}
+		}
+
+		if (!can_default && !NM_IS_DEVICE_MODEM (dev))
+			continue;
+
+		/* 'never-default' devices can't ever be the default */
+		if (s_ip4 && nm_setting_ip4_config_get_never_default (s_ip4))
+			continue;
+
+		prio = nm_device_get_priority (dev);
+		if (prio > 0 && prio < best_prio) {
+			best = dev;
+			best_prio = prio;
+			*out_req = req;
+		}
+	}
+
+	return best;
+}
+
+static NMDevice *
+get_best_ip6_device (NMManager *manager, NMActRequest **out_req)
+{
+	GSList *devices, *iter;
+	NMDevice *best = NULL;
+	int best_prio = G_MAXINT;
+
+	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)) {
+		NMDevice *dev = NM_DEVICE (iter->data);
+		NMActRequest *req;
+		NMConnection *connection;
+		NMIP6Config *ip6_config;
+		NMSettingIP6Config *s_ip6;
+		int prio;
+		guint i;
+		gboolean can_default = FALSE;
+		const char *method = NULL;
+
+		if (nm_device_get_state (dev) != NM_DEVICE_STATE_ACTIVATED)
+			continue;
+
+		ip6_config = nm_device_get_ip6_config (dev);
+		if (!ip6_config)
+			continue;
+
+		req = nm_device_get_act_request (dev);
+		g_assert (req);
+		connection = nm_act_request_get_connection (req);
+		g_assert (connection);
+
+		/* Never set the default route through an IPv4LL-addressed device */
+		s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+		if (s_ip6)
+			method = nm_setting_ip6_config_get_method (s_ip6);
+
+		if (method && !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+			continue;
+
+		/* Make sure at least one of this device's IP addresses has a gateway */
+		for (i = 0; i < nm_ip6_config_get_num_addresses (ip6_config); i++) {
+			NMIP6Address *addr;
+
+			addr = nm_ip6_config_get_address (ip6_config, i);
+			if (nm_ip6_address_get_gateway (addr)) {
+				can_default = TRUE;
+				break;
+			}
+		}
+
+		if (!can_default && !NM_IS_DEVICE_MODEM (dev))
+			continue;
+
+		/* 'never-default' devices can't ever be the default */
+		if (s_ip6 && nm_setting_ip6_config_get_never_default (s_ip6))
+			continue;
+
+		prio = nm_device_get_priority (dev);
+		if (prio > 0 && prio < best_prio) {
+			best = dev;
+			best_prio = prio;
+			*out_req = req;
+		}
+	}
+
+	return best;
+}
+
+static void
+_set_hostname (const char *new_hostname, const char *msg)
+{
+	if (nm_policy_set_system_hostname (new_hostname, msg))
+		nm_utils_call_dispatcher ("hostname", NULL, NULL, NULL);
+}
+
+static void
+lookup_callback (HostnameThread *thread,
+                 int result,
+                 const char *hostname,
+                 gpointer user_data)
+{
+	NMPolicy *policy = (NMPolicy *) user_data;
+	char *msg;
+
+	/* Update the hostname if the calling lookup thread is the in-progress one */
+	if (!hostname_thread_is_dead (thread) && (thread == policy->lookup)) {
+		policy->lookup = NULL;
+		if (!hostname) {
+			/* No valid IP4 config (!!); fall back to localhost.localdomain */
+			msg = g_strdup_printf ("address lookup failed: %d", result);
+			_set_hostname (NULL, msg);
+			g_free (msg);
+		} else
+			_set_hostname (hostname, "from address lookup");
+	}
+	hostname_thread_free (thread);
+}
+
+static void
+update_system_hostname (NMPolicy *policy, NMDevice *best)
+{
+	char *configured_hostname = NULL;
+	NMActRequest *best_req = NULL;
+	NMDHCP4Config *dhcp4_config;
+	NMIP4Config *ip4_config;
+	NMIP4Address *addr;
+
+	g_return_if_fail (policy != NULL);
+
+	if (policy->lookup) {
+		hostname_thread_kill (policy->lookup);
+		policy->lookup = NULL;
+	}
+
+	/* Hostname precedence order:
+	 *
+	 * 1) a configured hostname (from system-settings)
+	 * 2) automatic hostname from the default device's config (DHCP, VPN, etc)
+	 * 3) the original hostname when NM started
+	 * 4) reverse-DNS of the best device's IPv4 address
+	 *
+	 */
+
+	/* Try a persistent hostname first */
+	g_object_get (G_OBJECT (policy->manager), NM_MANAGER_HOSTNAME, &configured_hostname, NULL);
+	if (configured_hostname) {
+		_set_hostname (configured_hostname, "from system configuration");
+		g_free (configured_hostname);
+		return;
+	}
+
+	/* Try automatically determined hostname from the best device's IP config */
+	if (!best)
+		best = get_best_ip4_device (policy->manager, &best_req);
+
+	if (!best) {
+		/* No best device; fall back to original hostname or if there wasn't
+		 * one, 'localhost.localdomain'
+		 */
+		_set_hostname (policy->orig_hostname, "no default device");
+		return;
+	}
+
+	/* Grab a hostname out of the device's DHCP4 config */
+	dhcp4_config = nm_device_get_dhcp4_config (best);
+	if (dhcp4_config) {
+		const char *dhcp4_hostname, *p;
+
+		p = dhcp4_hostname = nm_dhcp4_config_get_option (dhcp4_config, "host_name");
+		if (dhcp4_hostname && strlen (dhcp4_hostname)) {
+			/* Sanity check */
+			while (*p) {
+				if (!isblank (*p++)) {
+					_set_hostname (dhcp4_hostname, "from DHCP");
+					return;
+				}
+			}
+			nm_log_warn (LOGD_DNS, "DHCP-provided hostname '%s' looks invalid; ignoring it",
+			             dhcp4_hostname);
+		}
+	}
+
+	/* If no automatically-configured hostname, try using the hostname from
+	 * when NM started up.
+	 */
+	if (policy->orig_hostname) {
+		_set_hostname (policy->orig_hostname, "from system startup");
+		return;
+	}
+
+	/* No configured hostname, no automatically determined hostname, and
+	 * no bootup hostname. Start reverse DNS of the current IP address.
+	 */
+	ip4_config = nm_device_get_ip4_config (best);
+	if (   !ip4_config
+	    || (nm_ip4_config_get_num_nameservers (ip4_config) == 0)
+	    || (nm_ip4_config_get_num_addresses (ip4_config) == 0)) {
+		/* No valid IP4 config (!!); fall back to localhost.localdomain */
+		_set_hostname (NULL, "no IPv4 config");
+		return;
+	}
+
+	addr = nm_ip4_config_get_address (ip4_config, 0);
+	g_assert (addr); /* checked for > 1 address above */
+
+	/* Start the hostname lookup thread */
+	policy->lookup = hostname_thread_new (nm_ip4_address_get_address (addr), lookup_callback, policy);
+	if (!policy->lookup) {
+		/* Fall back to 'localhost.localdomain' */
+		_set_hostname (NULL, "error starting hostname thread");
+	}
+}
+
+static void
+update_ip4_routing_and_dns (NMPolicy *policy, gboolean force_update)
+{
+	NMNamedIPConfigType dns_type = NM_NAMED_IP_CONFIG_TYPE_BEST_DEVICE;
+	NMDevice *best = NULL;
+	NMActRequest *best_req = NULL;
+	NMNamedManager *named_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;
+
+	best = get_best_ip4_device (policy->manager, &best_req);
+	if (!best)
+		goto out;
+	if (!force_update && (best == policy->default_device4))
+		goto out;
+
+	/* 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;
+		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);
+		s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (vpn_connection, NM_TYPE_SETTING_IP4_CONFIG);
+		if (s_ip4 && nm_setting_ip4_config_get_never_default (s_ip4))
+			can_default = FALSE;
+
+		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);
+			connection = nm_vpn_connection_get_connection (candidate);
+			ip4_config = nm_vpn_connection_get_ip4_config (candidate);
+			addr = nm_ip4_config_get_address (ip4_config, 0);
+
+			parent = nm_vpn_connection_get_parent_device (candidate);
+			parent_ip4 = nm_device_get_ip4_config (parent);
+
+			nm_system_replace_default_ip4_route_vpn (ip_iface,
+			                                         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_iface (parent),
+			                                         nm_ip4_config_get_mss (parent_ip4));
+
+			dns_type = NM_NAMED_IP_CONFIG_TYPE_VPN;
+		}
+		g_object_unref (candidate);
+	}
+	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);
+		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_iface, nm_ip4_address_get_gateway (addr), nm_ip4_config_get_mss (ip4_config));
+
+		dns_type = NM_NAMED_IP_CONFIG_TYPE_BEST_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;
+	}
+
+	/* 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;
+
+		req = nm_device_get_act_request (dev);
+		if (req && (req != best_req))
+			nm_act_request_set_default (req, FALSE);
+	}
+
+	named_mgr = nm_named_manager_get ();
+	nm_named_manager_add_ip4_config (named_mgr, ip_iface, ip4_config, dns_type);
+	g_object_unref (named_mgr);
+
+	/* Now set new default active connection _after_ updating DNS info, so that
+	 * if the connection is shared dnsmasq picks up the right stuff.
+	 */
+	if (best_req)
+		nm_act_request_set_default (best_req, TRUE);
+
+	if (connection)
+		s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+
+	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);
+	} else {
+		nm_log_info (LOGD_CORE, "Policy set (%s) as default for IPv4 routing and DNS.", ip_iface);
+	}
+
+out:
+	policy->default_device4 = best;
+}
+
+static void
+update_ip6_routing_and_dns (NMPolicy *policy, gboolean force_update)
+{
+	NMNamedIPConfigType dns_type = NM_NAMED_IP_CONFIG_TYPE_BEST_DEVICE;
+	NMDevice *best = NULL;
+	NMActRequest *best_req = NULL;
+	NMNamedManager *named_mgr;
+	GSList *devices = NULL, *iter;
+#if NOT_YET
+	GSList *vpns;
+#endif
+	NMIP6Config *ip6_config = NULL;
+	NMIP6Address *addr;
+	const char *ip_iface = NULL;
+	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 NOT_YET
+	/* 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;
+		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 = (NMSettingIP6Config *) nm_connection_get_setting (vpn_connection, NM_TYPE_SETTING_IP6_CONFIG);
+		if (s_ip6 && nm_setting_ip6_config_get_never_default (s_ip6))
+			can_default = FALSE;
+
+		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);
+
+			parent = nm_vpn_connection_get_parent_device (candidate);
+			parent_ip6 = nm_device_get_ip6_config (parent);
+
+			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));
+
+			dns_type = NM_NAMED_IP_CONFIG_TYPE_VPN;
+		}
+		g_object_unref (candidate);
+	}
+	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);
+		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_iface, nm_ip6_address_get_gateway (addr));
+
+		dns_type = NM_NAMED_IP_CONFIG_TYPE_BEST_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;
+	}
+
+	/* 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;
+
+		req = nm_device_get_act_request (dev);
+		if (req && (req != best_req))
+			nm_act_request_set_default6 (req, FALSE);
+	}
+
+	named_mgr = nm_named_manager_get ();
+	nm_named_manager_add_ip6_config (named_mgr, ip_iface, ip6_config, dns_type);
+	g_object_unref (named_mgr);
+
+	/* Now set new default active connection _after_ updating DNS info, so that
+	 * if the connection is shared dnsmasq picks up the right stuff.
+	 */
+	if (best_req)
+		nm_act_request_set_default6 (best_req, TRUE);
+
+	if (connection)
+		s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+
+	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);
+	} else {
+		nm_log_info (LOGD_CORE, "Policy set (%s) as default for IPv6 routing and DNS.", ip_iface);
+	}
+
+out:
+	policy->default_device6 = best;
+}
+
+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);
+
+	/* Update the system hostname */
+	update_system_hostname (policy, policy->default_device4);
+}
+
+typedef struct {
+	NMPolicy *policy;
+	NMDevice *device;
+	guint id;
+} ActivateData;
+
+static gboolean
+auto_activate_device (gpointer user_data)
+{
+	ActivateData *data = (ActivateData *) user_data;
+	NMPolicy *policy;
+	NMConnection *best_connection;
+	char *specific_object = NULL;
+	GSList *connections, *iter;
+
+	g_assert (data);
+	policy = data->policy;
+
+	// FIXME: if a device is already activating (or activated) with a connection
+	// but another connection now overrides the current one for that device,
+	// deactivate the device and activate the new connection instead of just
+	// bailing if the device is already active
+	if (nm_device_get_act_request (data->device))
+		goto out;
+
+	/* System connections first, then user connections */
+	connections = nm_manager_get_connections (policy->manager, NM_CONNECTION_SCOPE_SYSTEM);
+	connections = g_slist_concat (connections, nm_manager_get_connections (policy->manager, NM_CONNECTION_SCOPE_USER));
+
+	/* Remove connections that are in the invalid list. */
+	iter = connections;
+	while (iter) {
+		NMConnection *iter_connection = NM_CONNECTION (iter->data);
+		GSList *next = g_slist_next (iter);
+
+		if (g_object_get_data (G_OBJECT (iter_connection), INVALID_TAG)) {
+			connections = g_slist_remove_link (connections, iter);
+			g_object_unref (iter_connection);
+			g_slist_free (iter);
+		}
+		iter = next;
+	}
+
+	best_connection = nm_device_get_best_auto_connection (data->device, connections, &specific_object);
+	if (best_connection) {
+		GError *error = NULL;
+		const char *device_path;
+
+		device_path = nm_device_get_path (data->device);
+		if (!nm_manager_activate_connection (policy->manager,
+		                                     best_connection,
+		                                     specific_object,
+		                                     device_path,
+		                                     FALSE,
+		                                     &error)) {
+			NMSettingConnection *s_con;
+
+			s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (best_connection, NM_TYPE_SETTING_CONNECTION));
+			g_assert (s_con);
+
+			nm_log_info (LOGD_DEVICE, "Connection '%s' auto-activation failed: (%d) %s",
+			             nm_setting_connection_get_id (s_con), error->code, error->message);
+			g_error_free (error);
+		}
+	}
+
+	g_slist_foreach (connections, (GFunc) g_object_unref, NULL);
+	g_slist_free (connections);
+
+ out:
+	/* Remove this call's handler ID */
+	policy->pending_activation_checks = g_slist_remove (policy->pending_activation_checks, data);
+	g_object_unref (data->device);
+	g_free (data);
+
+	return FALSE;
+}
+
+/*****************************************************************************/
+
+static void
+vpn_connection_activated (NMVPNManager *manager,
+                          NMVPNConnection *vpn,
+                          gpointer user_data)
+{
+	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+}
+
+static void
+vpn_connection_deactivated (NMVPNManager *manager,
+                            NMVPNConnection *vpn,
+                            NMVPNConnectionState state,
+                            NMVPNConnectionStateReason reason,
+                            gpointer user_data)
+{
+	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+}
+
+static void
+global_state_changed (NMManager *manager, NMState state, gpointer user_data)
+{
+}
+
+static void
+hostname_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data)
+{
+	update_system_hostname ((NMPolicy *) user_data, NULL);
+}
+
+static void
+sleeping_changed (NMManager *manager, GParamSpec *pspec, gpointer user_data)
+{
+	gboolean sleeping = FALSE, enabled = FALSE;
+	GSList *connections, *iter;
+
+	g_object_get (G_OBJECT (manager), NM_MANAGER_SLEEPING, &sleeping, NULL);
+	g_object_get (G_OBJECT (manager), NM_MANAGER_NETWORKING_ENABLED, &enabled, NULL);
+
+	/* Clear the invalid flag on all connections so they'll get retried on wakeup */
+	if (sleeping || !enabled) {
+		connections = nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_SYSTEM);
+		connections = g_slist_concat (connections, nm_manager_get_connections (manager, NM_CONNECTION_SCOPE_USER));
+		for (iter = connections; iter; iter = g_slist_next (iter))
+			g_object_set_data (G_OBJECT (iter->data), INVALID_TAG, NULL);
+		g_slist_free (connections);
+	}
+}
+
+static void
+schedule_activate_check (NMPolicy *policy, NMDevice *device, guint delay_seconds)
+{
+	ActivateData *data;
+	GSList *iter;
+	NMDeviceState state;
+
+	if (nm_manager_get_state (policy->manager) == NM_STATE_ASLEEP)
+		return;
+
+	state = nm_device_interface_get_state (NM_DEVICE_INTERFACE (device));
+	if (state < NM_DEVICE_STATE_DISCONNECTED)
+		return;
+
+	if (!nm_device_autoconnect_allowed (device))
+		return;
+
+	for (iter = policy->pending_activation_checks; iter; iter = g_slist_next (iter)) {
+		/* Only one pending activation check at a time */
+		if (((ActivateData *) iter->data)->device == device)
+			return;
+	}
+
+	data = g_malloc0 (sizeof (ActivateData));
+	g_return_if_fail (data != NULL);
+
+	data->policy = policy;
+	data->device = g_object_ref (device);
+	data->id = delay_seconds ? g_timeout_add_seconds (delay_seconds, auto_activate_device, data) : g_idle_add (auto_activate_device, data);
+	policy->pending_activation_checks = g_slist_append (policy->pending_activation_checks, data);
+}
+
+static NMConnection *
+get_device_connection (NMDevice *device)
+{
+	NMActRequest *req;
+
+	req = nm_device_get_act_request (device);
+	if (!req)
+		return NULL;
+
+	return nm_act_request_get_connection (req);
+}
+
+static void
+device_state_changed (NMDevice *device,
+                      NMDeviceState new_state,
+                      NMDeviceState old_state,
+                      NMDeviceStateReason reason,
+                      gpointer user_data)
+{
+	NMPolicy *policy = (NMPolicy *) user_data;
+	NMConnection *connection = get_device_connection (device);
+
+	switch (new_state) {
+	case NM_DEVICE_STATE_FAILED:
+		/* Mark the connection invalid if it failed during activation so that
+		 * it doesn't get automatically chosen over and over and over again.
+		 */
+		if (connection) {
+			gboolean fail = FALSE;
+
+			if (IS_ACTIVATING_STATE (old_state)) {
+				nm_log_info (LOGD_DEVICE, "Marking connection '%s' invalid.", get_connection_id (connection));
+				fail = TRUE;
+			} else if (   (old_state == NM_DEVICE_STATE_ACTIVATED)
+			         && (reason == NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED)) {
+				nm_log_info (LOGD_DEVICE, "Marking connection '%s' invalid because IP configuration expired.",
+				             get_connection_id (connection));
+				fail = TRUE;
+			}
+
+			if (fail) {
+				g_object_set_data (G_OBJECT (connection), INVALID_TAG, GUINT_TO_POINTER (TRUE));
+				nm_connection_clear_secrets (connection);
+			}
+		}
+		schedule_activate_check (policy, device, 3);
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		if (connection) {
+			/* Clear the invalid tag on the connection */
+			g_object_set_data (G_OBJECT (connection), INVALID_TAG, NULL);
+
+			/* And clear secrets so they will always be requested from the
+			 * settings service when the next connection is made.
+			 */
+			nm_connection_clear_secrets (connection);
+		}
+
+		update_routing_and_dns (policy, FALSE);
+		break;
+	case NM_DEVICE_STATE_UNMANAGED:
+	case NM_DEVICE_STATE_UNAVAILABLE:
+	case NM_DEVICE_STATE_DISCONNECTED:
+		update_routing_and_dns (policy, FALSE);
+		schedule_activate_check (policy, device, 0);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+device_ip_config_changed (NMDevice *device,
+                          GParamSpec *pspec,
+                          gpointer user_data)
+{
+	update_routing_and_dns ((NMPolicy *) user_data, TRUE);
+}
+
+static void
+wireless_networks_changed (NMDeviceWifi *device, NMAccessPoint *ap, gpointer user_data)
+{
+	schedule_activate_check ((NMPolicy *) user_data, NM_DEVICE (device), 0);
+}
+
+typedef struct {
+	gulong id;
+	NMDevice *device;
+} DeviceSignalID;
+
+static GSList *
+add_device_signal_id (GSList *list, gulong id, NMDevice *device)
+{
+	DeviceSignalID *data;
+
+	data = g_malloc0 (sizeof (DeviceSignalID));
+	if (!data)
+		return list;
+
+	data->id = id;
+	data->device = device;
+	return g_slist_append (list, data);
+}
+
+static void
+device_added (NMManager *manager, NMDevice *device, gpointer user_data)
+{
+	NMPolicy *policy = (NMPolicy *) user_data;
+	gulong id;
+
+	id = g_signal_connect (device, "state-changed",
+	                       G_CALLBACK (device_state_changed),
+	                       policy);
+	policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
+
+	id = g_signal_connect (device, "notify::" NM_DEVICE_INTERFACE_IP4_CONFIG,
+	                       G_CALLBACK (device_ip_config_changed),
+	                       policy);
+	policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
+
+	id = g_signal_connect (device, "notify::" NM_DEVICE_INTERFACE_IP6_CONFIG,
+	                       G_CALLBACK (device_ip_config_changed),
+	                       policy);
+	policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
+
+	if (NM_IS_DEVICE_WIFI (device)) {
+		id = g_signal_connect (device, "access-point-added",
+		                       G_CALLBACK (wireless_networks_changed),
+		                       policy);
+		policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
+
+		id = g_signal_connect (device, "access-point-removed",
+		                       G_CALLBACK (wireless_networks_changed),
+		                       policy);
+		policy->dev_signal_ids = add_device_signal_id (policy->dev_signal_ids, id, device);
+	}
+}
+
+static void
+device_removed (NMManager *manager, NMDevice *device, gpointer user_data)
+{
+	NMPolicy *policy = (NMPolicy *) user_data;
+	GSList *iter;
+
+	/* Clear any idle callbacks for this device */
+	iter = policy->pending_activation_checks;
+	while (iter) {
+		ActivateData *data = (ActivateData *) iter->data;
+		GSList *next = g_slist_next (iter);
+
+		if (data->device == device) {
+			g_source_remove (data->id);
+			g_object_unref (data->device);
+			g_free (data);
+			policy->pending_activation_checks = g_slist_delete_link (policy->pending_activation_checks, iter);
+		}
+		iter = next;
+	}
+
+	/* Clear any signal handlers for this device */
+	iter = policy->dev_signal_ids;
+	while (iter) {
+		DeviceSignalID *data = (DeviceSignalID *) iter->data;
+		GSList *next = g_slist_next (iter);
+
+		if (data->device == device) {
+			g_signal_handler_disconnect (data->device, data->id);
+			g_free (data);
+			policy->dev_signal_ids = g_slist_delete_link (policy->dev_signal_ids, iter);
+		}
+		iter = next;
+	}
+
+	update_routing_and_dns (policy, FALSE);
+}
+
+static void
+schedule_activate_all (NMPolicy *policy)
+{
+	GSList *iter, *devices;
+
+	devices = nm_manager_get_devices (policy->manager);
+	for (iter = devices; iter; iter = g_slist_next (iter))
+		schedule_activate_check (policy, NM_DEVICE (iter->data), 0);
+}
+
+static void
+connections_added (NMManager *manager,
+                   NMConnectionScope scope,
+                   gpointer user_data)
+{
+	schedule_activate_all ((NMPolicy *) user_data);
+}
+
+static void
+connection_added (NMManager *manager,
+                  NMConnection *connection,
+                  NMConnectionScope scope,
+                  gpointer user_data)
+{
+	schedule_activate_all ((NMPolicy *) user_data);
+}
+
+static void
+connection_updated (NMManager *manager,
+                    NMConnection *connection,
+                    NMConnectionScope scope,
+                    gpointer user_data)
+{
+	/* Clear the invalid tag on the connection if it got updated. */
+	g_object_set_data (G_OBJECT (connection), INVALID_TAG, NULL);
+
+	schedule_activate_all ((NMPolicy *) user_data);
+}
+
+static void
+connection_removed (NMManager *manager,
+                    NMConnection *connection,
+                    NMConnectionScope scope,
+                    gpointer user_data)
+{
+	NMSettingConnection *s_con;
+	GPtrArray *list;
+	int i;
+
+	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
+	if (!s_con)
+		return;
+
+	list = nm_manager_get_active_connections_by_connection (manager, connection);
+	if (!list)
+		return;
+
+	for (i = 0; i < list->len; i++) {
+		char *path = g_ptr_array_index (list, i);
+		GError *error = NULL;
+
+		if (!nm_manager_deactivate_connection (manager, path, NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, &error)) {
+			nm_log_warn (LOGD_DEVICE, "Connection '%s' disappeared, but error deactivating it: (%d) %s",
+			             nm_setting_connection_get_id (s_con), error->code, error->message);
+			g_error_free (error);
+		}
+		g_free (path);
+	}
+	g_ptr_array_free (list, TRUE);
+}
+
+NMPolicy *
+nm_policy_new (NMManager *manager, NMVPNManager *vpn_manager)
+{
+	NMPolicy *policy;
+	static gboolean initialized = FALSE;
+	gulong id;
+	char hostname[HOST_NAME_MAX + 2];
+
+	g_return_val_if_fail (NM_IS_MANAGER (manager), NULL);
+	g_return_val_if_fail (initialized == FALSE, NULL);
+
+	policy = g_malloc0 (sizeof (NMPolicy));
+	policy->manager = g_object_ref (manager);
+	policy->update_state_id = 0;
+
+	/* Grab hostname on startup and use that if nothing provides one */
+	memset (hostname, 0, sizeof (hostname));
+	if (gethostname (&hostname[0], HOST_NAME_MAX) == 0) {
+		/* only cache it if it's a valid hostname */
+		if (strlen (hostname) && strcmp (hostname, "localhost") && strcmp (hostname, "localhost.localdomain"))
+			policy->orig_hostname = g_strdup (hostname);
+	}
+
+	policy->vpn_manager = g_object_ref (vpn_manager);
+	id = g_signal_connect (policy->vpn_manager, "connection-activated",
+	                       G_CALLBACK (vpn_connection_activated), policy);
+	policy->vpn_activated_id = id;
+	id = g_signal_connect (policy->vpn_manager, "connection-deactivated",
+	                       G_CALLBACK (vpn_connection_deactivated), policy);
+	policy->vpn_deactivated_id = id;
+
+	id = g_signal_connect (manager, "state-changed",
+	                       G_CALLBACK (global_state_changed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "notify::" NM_MANAGER_HOSTNAME,
+	                       G_CALLBACK (hostname_changed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "notify::" NM_MANAGER_SLEEPING,
+	                       G_CALLBACK (sleeping_changed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "notify::" NM_MANAGER_NETWORKING_ENABLED,
+	                       G_CALLBACK (sleeping_changed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "device-added",
+	                       G_CALLBACK (device_added), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "device-removed",
+	                       G_CALLBACK (device_removed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	/* Large batch of connections added, manager doesn't want us to
+	 * process each one individually.
+	 */
+	id = g_signal_connect (manager, "connections-added",
+	                       G_CALLBACK (connections_added), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	/* Single connection added */
+	id = g_signal_connect (manager, "connection-added",
+	                       G_CALLBACK (connection_added), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "connection-updated",
+	                       G_CALLBACK (connection_updated), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	id = g_signal_connect (manager, "connection-removed",
+	                       G_CALLBACK (connection_removed), policy);
+	policy->signal_ids = g_slist_append (policy->signal_ids, (gpointer) id);
+
+	return policy;
+}
+
+void
+nm_policy_destroy (NMPolicy *policy)
+{
+	GSList *iter;
+
+	g_return_if_fail (policy != NULL);
+
+	/* Tell any existing hostname lookup thread to die, it'll get cleaned up
+	 * by the lookup thread callback.
+	  */
+	if (policy->lookup) {
+		hostname_thread_kill (policy->lookup);
+		policy->lookup = NULL;
+	}
+
+	for (iter = policy->pending_activation_checks; iter; iter = g_slist_next (iter)) {
+		ActivateData *data = (ActivateData *) iter->data;
+
+		g_source_remove (data->id);
+		g_object_unref (data->device);
+		g_free (data);
+	}
+	g_slist_free (policy->pending_activation_checks);
+
+	g_signal_handler_disconnect (policy->vpn_manager, policy->vpn_activated_id);
+	g_signal_handler_disconnect (policy->vpn_manager, policy->vpn_deactivated_id);
+
+	for (iter = policy->signal_ids; iter; iter = g_slist_next (iter))
+		g_signal_handler_disconnect (policy->manager, (gulong) iter->data);
+	g_slist_free (policy->signal_ids);
+
+	for (iter = policy->dev_signal_ids; iter; iter = g_slist_next (iter)) {
+		DeviceSignalID *data = (DeviceSignalID *) iter->data;
+
+		g_signal_handler_disconnect (data->device, data->id);
+		g_free (data);
+	}
+	g_slist_free (policy->dev_signal_ids);
+
+	g_free (policy->orig_hostname);
+
+	g_object_unref (policy->manager);
+	g_free (policy);
+}
+