about summary refs log tree commit diff
path: root/src/nm-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-device.c')
-rw-r--r--src/nm-device.c3827
1 files changed, 3827 insertions, 0 deletions
diff --git a/src/nm-device.c b/src/nm-device.c
new file mode 100644
index 00000000..176d90f9
--- /dev/null
+++ b/src/nm-device.c
@@ -0,0 +1,3827 @@
+/* -*- 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) 2005 - 2010 Red Hat, Inc.
+ * Copyright (C) 2006 - 2008 Novell, Inc.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+#include "nm-glib-compat.h"
+#include "nm-device-interface.h"
+#include "nm-device.h"
+#include "nm-device-private.h"
+#include "NetworkManagerUtils.h"
+#include "nm-system.h"
+#include "nm-dhcp-manager.h"
+#include "nm-dbus-manager.h"
+#include "nm-named-manager.h"
+#include "nm-utils.h"
+#include "nm-logging.h"
+#include "nm-netlink-monitor.h"
+#include "nm-setting-ip4-config.h"
+#include "nm-setting-ip6-config.h"
+#include "nm-setting-connection.h"
+#include "nm-dnsmasq-manager.h"
+#include "nm-dhcp4-config.h"
+#include "nm-ip6-manager.h"
+#include "nm-marshal.h"
+#include "nm-rfkill.h"
+
+#define NM_ACT_REQUEST_IP4_CONFIG "nm-act-request-ip4-config"
+#define NM_ACT_REQUEST_IP6_CONFIG "nm-act-request-ip6-config"
+
+static void device_interface_init (NMDeviceInterface *device_interface_class);
+
+G_DEFINE_TYPE_EXTENDED (NMDevice, nm_device, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT,
+						G_IMPLEMENT_INTERFACE (NM_TYPE_DEVICE_INTERFACE, device_interface_init))
+
+enum {
+	AUTOCONNECT_ALLOWED,
+	LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define NM_DEVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEVICE, NMDevicePrivate))
+
+typedef struct {
+	gboolean disposed;
+	gboolean initialized;
+
+	NMDeviceState state;
+	guint         failed_to_disconnected_id;
+	guint         unavailable_to_disconnected_id;
+
+	char *        udi;
+	char *        path;
+	char *        iface;   /* may change, could be renamed by user */
+	int           ifindex;
+	char *        ip_iface;
+	int           ip_ifindex;
+	NMDeviceType  type;
+	char *        type_desc;
+	guint32       capabilities;
+	char *        driver;
+	gboolean      managed; /* whether managed by NM or not */
+	RfKillType    rfkill_type;
+
+	guint32         ip4_address;
+
+	NMActRequest *  act_request;
+	guint           act_source_id;
+	gpointer        act_source_func;
+	guint           act_source6_id;
+	gpointer        act_source6_func;
+	gulong          secrets_updated_id;
+	gulong          secrets_failed_id;
+
+	gboolean        ip4_ready;
+	gboolean        ip6_ready;
+
+	/* Generic DHCP stuff */
+	NMDHCPManager * dhcp_manager;
+	guint32         dhcp_timeout;
+	GByteArray *    dhcp_anycast_address;
+
+	/* IP4 configuration info */
+	NMIP4Config *   ip4_config;			/* Config from DHCP, PPP, or system config files */
+	NMDHCPClient *  dhcp4_client;
+	gulong          dhcp4_state_sigid;
+	gulong          dhcp4_timeout_sigid;
+	NMDHCP4Config * dhcp4_config;
+
+	/* dnsmasq stuff for shared connections */
+	NMDnsMasqManager *dnsmasq_manager;
+	gulong            dnsmasq_state_id;
+
+	/* avahi-autoipd stuff */
+	GPid    aipd_pid;
+	guint   aipd_watch;
+	guint   aipd_timeout;
+	guint32 aipd_addr;
+
+	/* IP6 configuration info */
+	NMIP6Config *  ip6_config;
+	NMIP6Manager * ip6_manager;
+	gulong         ip6_addrconf_sigid;
+	gulong         ip6_config_changed_sigid;
+	gboolean       ip6_waiting_for_config;
+
+	char *         ip6_accept_ra_path;
+	guint32        ip6_accept_ra_save;
+
+	NMDHCPClient *  dhcp6_client;
+	guint32         dhcp6_mode;
+	gulong          dhcp6_state_sigid;
+	gulong          dhcp6_timeout_sigid;
+	NMDHCP6Config * dhcp6_config;
+
+	/* inhibit autoconnect feature */
+	gboolean	autoconnect_inhibit;
+} NMDevicePrivate;
+
+static gboolean check_connection_compatible (NMDeviceInterface *device,
+                                             NMConnection *connection,
+                                             GError **error);
+static gboolean nm_device_activate (NMDeviceInterface *device,
+                                    NMActRequest *req,
+                                    GError **error);
+static void nm_device_deactivate (NMDeviceInterface *device, NMDeviceStateReason reason);
+static gboolean device_disconnect (NMDeviceInterface *device, GError **error);
+static gboolean spec_match_list (NMDeviceInterface *device, const GSList *specs);
+static NMConnection *connection_match_config (NMDeviceInterface *device, const GSList *connections);
+
+static void nm_device_activate_schedule_stage5_ip_config_commit (NMDevice *self, int family);
+
+static void nm_device_take_down (NMDevice *dev, gboolean wait, NMDeviceStateReason reason);
+
+static gboolean nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware);
+static gboolean nm_device_is_up (NMDevice *self);
+
+static gboolean nm_device_set_ip4_config (NMDevice *dev,
+                                          NMIP4Config *config,
+                                          gboolean assumed,
+                                          NMDeviceStateReason *reason);
+static gboolean nm_device_set_ip6_config (NMDevice *dev,
+                                          NMIP6Config *config,
+                                          gboolean assumed,
+                                          NMDeviceStateReason *reason);
+
+static NMActStageReturn dhcp6_start (NMDevice *self,
+                                     NMConnection *connection,
+                                     guint32 dhcp_opt,
+                                     NMDeviceStateReason *reason);
+
+static void addrconf6_cleanup (NMDevice *self);
+static void dhcp6_cleanup (NMDevice *self, gboolean stop);
+static void dhcp4_cleanup (NMDevice *self, gboolean stop);
+
+
+static void
+device_interface_init (NMDeviceInterface *device_interface_class)
+{
+	/* interface implementation */
+	device_interface_class->check_connection_compatible = check_connection_compatible;
+	device_interface_class->activate = nm_device_activate;
+	device_interface_class->deactivate = nm_device_deactivate;
+	device_interface_class->disconnect = device_disconnect;
+	device_interface_class->spec_match_list = spec_match_list;
+	device_interface_class->connection_match_config = connection_match_config;
+}
+
+
+static void
+nm_device_init (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->type = NM_DEVICE_TYPE_UNKNOWN;
+	priv->capabilities = NM_DEVICE_CAP_NONE;
+	priv->state = NM_DEVICE_STATE_UNMANAGED;
+	priv->dhcp_timeout = 0;
+	priv->rfkill_type = RFKILL_TYPE_UNKNOWN;
+}
+
+static void
+update_accept_ra_save (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+	const char *ip_iface;
+	char *new_path;
+
+	g_return_if_fail (self != NULL);
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	ip_iface = nm_device_get_ip_iface (self);
+
+	new_path = g_strdup_printf ("/proc/sys/net/ipv6/conf/%s/accept_ra", ip_iface);
+	g_assert (new_path);
+
+	if (priv->ip6_accept_ra_path) {
+		/* If the IP iface is different from before, use the new value */
+		if (!strcmp (new_path, priv->ip6_accept_ra_path)) {
+			g_free (new_path);
+			return;
+		}
+		g_free (priv->ip6_accept_ra_path);
+	}
+
+	/* Grab the original value of "accept_ra" so we can restore it when NM exits */
+	priv->ip6_accept_ra_path = new_path;
+	if (!nm_utils_get_proc_sys_net_value (priv->ip6_accept_ra_path,
+	                                      ip_iface,
+	                                      &priv->ip6_accept_ra_save)) {
+		g_free (priv->ip6_accept_ra_path);
+		priv->ip6_accept_ra_path = NULL;
+	}
+}
+
+static GObject*
+constructor (GType type,
+			 guint n_construct_params,
+			 GObjectConstructParam *construct_params)
+{
+	GObject *object;
+	NMDevice *dev;
+	NMDevicePrivate *priv;
+
+	object = G_OBJECT_CLASS (nm_device_parent_class)->constructor (type,
+													   n_construct_params,
+													   construct_params);
+	if (!object)
+		return NULL;
+
+	dev = NM_DEVICE (object);
+	priv = NM_DEVICE_GET_PRIVATE (dev);
+
+	if (!priv->udi) {
+		nm_log_err (LOGD_DEVICE, "No device udi provided, ignoring");
+		goto error;
+	}
+
+	if (!priv->iface) {
+		nm_log_err (LOGD_DEVICE, "No device interface provided, ignoring");
+		goto error;
+	}
+
+	priv->capabilities |= NM_DEVICE_GET_CLASS (dev)->get_generic_capabilities (dev);
+	if (!(priv->capabilities & NM_DEVICE_CAP_NM_SUPPORTED)) {
+		nm_log_warn (LOGD_DEVICE, "(%s): Device unsupported, ignoring.", priv->iface);
+		goto error;
+	}
+
+	if (NM_DEVICE_GET_CLASS (dev)->update_hw_address)
+		NM_DEVICE_GET_CLASS (dev)->update_hw_address (dev);
+
+	priv->dhcp_manager = nm_dhcp_manager_get ();
+
+	update_accept_ra_save (dev);
+
+	priv->initialized = TRUE;
+	return object;
+
+error:
+	g_object_unref (dev);
+	return NULL;
+}
+
+static gboolean
+nm_device_hw_is_up (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (NM_DEVICE_GET_CLASS (self)->hw_is_up)
+		return NM_DEVICE_GET_CLASS (self)->hw_is_up (self);
+
+	return TRUE;
+}
+
+static guint32
+real_get_generic_capabilities (NMDevice *dev)
+{
+	return 0;
+}
+
+void
+nm_device_set_path (NMDevice *self, const char *path)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (self != NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->path == NULL);
+
+	priv->path = g_strdup (path);
+}
+
+const char *
+nm_device_get_path (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->path;
+}
+
+const char *
+nm_device_get_udi (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->udi;
+}
+
+/*
+ * Get/set functions for iface
+ */
+const char *
+nm_device_get_iface (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->iface;
+}
+
+int
+nm_device_get_ifindex (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, 0);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ifindex;
+}
+
+const char *
+nm_device_get_ip_iface (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (self != NULL, NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	/* If it's not set, default to iface */
+	return priv->ip_iface ? priv->ip_iface : priv->iface;
+}
+
+int
+nm_device_get_ip_ifindex (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (self != NULL, 0);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	/* If it's not set, default to iface */
+	return priv->ip_iface ? priv->ip_ifindex : priv->ifindex;
+}
+
+void
+nm_device_set_ip_iface (NMDevice *self, const char *iface)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_free (priv->ip_iface);
+	priv->ip_ifindex = 0;
+
+	priv->ip_iface = g_strdup (iface);
+	if (priv->ip_iface) {
+		priv->ip_ifindex = nm_netlink_iface_to_index (priv->ip_iface);
+		if (!priv->ip_ifindex) {
+			nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", iface);
+		}
+	}
+}
+
+
+/*
+ * Get/set functions for driver
+ */
+const char *
+nm_device_get_driver (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->driver;
+}
+
+
+/*
+ * Get/set functions for type
+ */
+NMDeviceType
+nm_device_get_device_type (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_TYPE_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (self)->type;
+}
+
+
+int
+nm_device_get_priority (NMDevice *dev)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (dev), -1);
+
+	return (int) nm_device_get_device_type (dev);
+}
+
+
+/*
+ * Accessor for capabilities
+ */
+guint32
+nm_device_get_capabilities (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NM_DEVICE_CAP_NONE);
+
+	return NM_DEVICE_GET_PRIVATE (self)->capabilities;
+}
+
+/*
+ * Accessor for type-specific capabilities
+ */
+guint32
+nm_device_get_type_capabilities (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NM_DEVICE_CAP_NONE);
+
+	return NM_DEVICE_GET_CLASS (self)->get_type_capabilities (self);
+}
+
+static guint32
+real_get_type_capabilities (NMDevice *self)
+{
+	return NM_DEVICE_CAP_NONE;
+}
+
+
+const char *
+nm_device_get_type_desc (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->type_desc;
+}
+
+/*
+ * nm_device_get_act_request
+ *
+ * Return the devices activation request, if any.
+ *
+ */
+NMActRequest *
+nm_device_get_act_request (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->act_request;
+}
+
+
+gboolean
+nm_device_is_available (NMDevice *self)
+{
+	if (NM_DEVICE_GET_CLASS (self)->is_available)
+		return NM_DEVICE_GET_CLASS (self)->is_available (self);
+	return TRUE;
+}
+
+static gboolean
+autoconnect_allowed_accumulator (GSignalInvocationHint *ihint,
+                                 GValue *return_accu,
+                                 const GValue *handler_return, gpointer data)
+{
+	if (!g_value_get_boolean (handler_return))
+		g_value_set_boolean (return_accu, FALSE);
+	return TRUE;
+}
+
+gboolean
+nm_device_autoconnect_allowed (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	GValue instance = { 0, };
+	GValue retval = { 0, };
+
+	g_value_init (&instance, G_TYPE_OBJECT);
+	g_value_take_object (&instance, self);
+
+	g_value_init (&retval, G_TYPE_BOOLEAN);
+	if (priv->autoconnect_inhibit)
+		g_value_set_boolean (&retval, FALSE);
+	else
+		g_value_set_boolean (&retval, TRUE);
+
+	/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
+	 * value being changed if no handlers are connected */
+	g_signal_emitv (&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval);
+	return g_value_get_boolean (&retval);
+}
+
+NMConnection *
+nm_device_get_best_auto_connection (NMDevice *dev,
+                                    GSList *connections,
+                                    char **specific_object)
+{
+	guint32 caps;
+
+	g_return_val_if_fail (NM_IS_DEVICE (dev), NULL);
+	g_return_val_if_fail (specific_object != NULL, NULL);
+	g_return_val_if_fail (*specific_object == NULL, NULL);
+
+	caps = nm_device_get_capabilities (dev);
+	/* Don't use devices that SUCK */
+	if (!(caps & NM_DEVICE_CAP_NM_SUPPORTED))
+		return NULL;
+
+	if (!NM_DEVICE_GET_CLASS (dev)->get_best_auto_connection)
+		return NULL;
+
+	return NM_DEVICE_GET_CLASS (dev)->get_best_auto_connection (dev, connections, specific_object);
+}
+
+static void
+dnsmasq_state_changed_cb (NMDnsMasqManager *manager, guint32 status, gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+
+	switch (status) {
+	case NM_DNSMASQ_STATUS_DEAD:
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+activation_source_clear (NMDevice *self, gboolean remove_source, int family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint *act_source_id;
+	gpointer *act_source_func;
+
+	if (family == AF_INET6) {
+		act_source_id = &priv->act_source6_id;
+		act_source_func = &priv->act_source6_func;
+	} else {
+		act_source_id = &priv->act_source_id;
+		act_source_func = &priv->act_source_func;
+	}
+
+	if (*act_source_id) {
+		if (remove_source)
+			g_source_remove (*act_source_id);
+		*act_source_id = 0;
+		*act_source_func = NULL;
+	}
+}
+
+static void
+activation_source_schedule (NMDevice *self, GSourceFunc func, int family)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	guint *act_source_id;
+	gpointer *act_source_func;
+
+	if (family == AF_INET6) {
+		act_source_id = &priv->act_source6_id;
+		act_source_func = &priv->act_source6_func;
+	} else {
+		act_source_id = &priv->act_source_id;
+		act_source_func = &priv->act_source_func;
+	}
+
+	if (*act_source_id) {
+		nm_log_err (LOGD_DEVICE, "activation stage already scheduled");
+	}
+
+	/* Don't bother rescheduling the same function that's about to
+	 * run anyway.  Fixes issues with crappy wireless drivers sending
+	 * streams of associate events before NM has had a chance to process
+	 * the first one.
+	 */
+	if (!*act_source_id || (*act_source_func != func)) {
+		activation_source_clear (self, TRUE, family);
+		*act_source_id = g_idle_add (func, self);
+		*act_source_func = func;
+	}
+}
+
+gboolean
+nm_device_ip_config_should_fail (NMDevice *self, gboolean ip6)
+{
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingIP4Config *s_ip4;
+	NMSettingIP6Config *s_ip6;
+
+	g_return_val_if_fail (self != NULL, TRUE);
+
+	req = nm_device_get_act_request (self);
+	g_assert (req);
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	/* Fail the connection if the failed IP method is required to complete */
+	if (ip6) {
+		s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+		if (s_ip6 && !nm_setting_ip6_config_get_may_fail (s_ip6))
+			return TRUE;
+	} else {
+		s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+		if (s_ip4 && !nm_setting_ip4_config_get_may_fail (s_ip4))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+ip6_addrconf_complete (NMIP6Manager *ip6_manager,
+                       int ifindex,
+                       guint dhcp_opts,
+                       gboolean success,
+                       gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMConnection *connection;
+	NMActStageReturn ret;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	NMDeviceState state;
+
+	if (ifindex != nm_device_get_ip_ifindex (self))
+		return;
+	req = nm_device_get_act_request (self);
+	if (!req)
+		return;
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	if (!priv->ip6_waiting_for_config)
+		return;
+
+	priv->ip6_waiting_for_config = FALSE;
+
+	if (!success) {
+		nm_device_activate_schedule_stage4_ip6_config_timeout (self);
+		return;
+	}
+
+	priv->dhcp6_mode = dhcp_opts;
+
+	/* If addrconf is all that's required, we're done */
+	if (priv->dhcp6_mode == IP6_DHCP_OPT_NONE) {
+		nm_device_activate_schedule_stage4_ip6_config_get (self);
+		return;
+	}
+
+	/* If the router said to use DHCP for managed or otherconf, do it */
+
+	/* Don't re-start DHCPv6 if it's already in progress */
+	state = nm_device_interface_get_state (NM_DEVICE_INTERFACE (self));
+	if ((state != NM_DEVICE_STATE_IP_CONFIG) || priv->dhcp6_client)
+		return;
+
+	nm_log_info (LOGD_DEVICE | LOGD_DHCP6,
+	             "Activation (%s) Stage 3 of 5 (IP Configure Start) starting DHCPv6"
+	             " as requested by IPv6 router...",
+	             priv->iface);
+
+	ret = dhcp6_start (self, connection, priv->dhcp6_mode, &reason);
+	switch (ret) {
+	case NM_ACT_STAGE_RETURN_SUCCESS:
+		/* Shouldn't get this, but handle it anyway */
+		g_warn_if_reached ();
+		nm_device_activate_schedule_stage4_ip6_config_get (self);
+		break;
+	case NM_ACT_STAGE_RETURN_POSTPONE:
+		/* Success; wait for DHCPv6 to complete */
+		break;
+	default:
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		break;
+	}
+}
+
+static void
+ip6_config_changed (NMIP6Manager *ip6_manager,
+                    int ifindex,
+                    guint dhcp_opts,
+                    gboolean success,
+                    gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+
+	if (ifindex != nm_device_get_ip_ifindex (self))
+		return;
+	if (!nm_device_get_act_request (self))
+		return;
+
+	/* FIXME: re-run DHCPv6 here to get any new nameservers or whatever */
+
+	if (!success && (nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED)) {
+		nm_device_state_changed (self,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		return;
+	}
+
+	nm_device_activate_schedule_stage4_ip6_config_get (self);
+}
+
+static gboolean
+ip6_method_matches (NMConnection *connection, const char *match)
+{
+	NMSettingIP6Config *s_ip6;
+	const char *method = NULL;
+
+	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);
+		g_assert (method);
+	}
+
+	/* Treat missing IP6 setting as IGNORE */
+	if (!s_ip6 && !strcmp (match, NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+		return TRUE;
+
+	return method && !strcmp (method, match);
+}
+
+static gboolean
+addrconf6_setup (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMConnection *connection;
+	NMSettingIP6Config *s_ip6;
+
+	req = nm_device_get_act_request (self);
+	g_assert (req);
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	if (!priv->ip6_manager) {
+		priv->ip6_manager = nm_ip6_manager_get ();
+		priv->ip6_addrconf_sigid = g_signal_connect (priv->ip6_manager,
+		                                             "addrconf-complete",
+		                                             G_CALLBACK (ip6_addrconf_complete),
+		                                             self);
+		priv->ip6_config_changed_sigid = g_signal_connect (priv->ip6_manager,
+		                                                   "config-changed",
+		                                                   G_CALLBACK (ip6_config_changed),
+		                                                   self);
+	}
+
+	s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	nm_ip6_manager_prepare_interface (priv->ip6_manager,
+	                                  nm_device_get_ip_ifindex (self),
+	                                  s_ip6,
+	                                  priv->ip6_accept_ra_path);
+	priv->ip6_waiting_for_config = TRUE;
+
+	return TRUE;
+}
+
+static void
+addrconf6_cleanup (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->ip6_manager)
+		return;
+
+	if (priv->ip6_addrconf_sigid) {
+		g_signal_handler_disconnect (priv->ip6_manager,
+		                             priv->ip6_addrconf_sigid);
+		priv->ip6_addrconf_sigid = 0;
+	}
+	if (priv->ip6_config_changed_sigid) {
+		g_signal_handler_disconnect (priv->ip6_manager,
+		                             priv->ip6_config_changed_sigid);
+		priv->ip6_config_changed_sigid = 0;
+	}
+
+	nm_ip6_manager_cancel_addrconf (priv->ip6_manager, nm_device_get_ip_ifindex (self));
+	g_object_unref (priv->ip6_manager);
+	priv->ip6_manager = NULL;
+}
+
+static NMActStageReturn
+real_act_stage1_prepare (NMDevice *self, NMDeviceStateReason *reason)
+{
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+/*
+ * nm_device_activate_stage1_device_prepare
+ *
+ * Prepare for device activation
+ *
+ */
+static gboolean
+nm_device_activate_stage1_device_prepare (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const char *iface;
+	NMActStageReturn ret;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, 0);
+
+	priv->ip4_ready = priv->ip6_ready = FALSE;
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) started...", iface);
+	nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage1_prepare (self, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE) {
+		goto out;
+	} else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+
+	nm_device_activate_schedule_stage2_device_config (self);
+
+out:
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) complete.", iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage1_device_prepare
+ *
+ * Prepare a device for activation
+ *
+ */
+void
+nm_device_activate_schedule_stage1_device_prepare (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage1_device_prepare, 0);
+
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 1 of 5 (Device Prepare) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+static NMActStageReturn
+real_act_stage2_config (NMDevice *dev, NMDeviceStateReason *reason)
+{
+	/* Nothing to do */
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+/*
+ * nm_device_activate_stage2_device_config
+ *
+ * Determine device parameters and set those on the device, ie
+ * for wireless devices, set SSID, keys, etc.
+ *
+ */
+static gboolean
+nm_device_activate_stage2_device_config (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	const char *     iface;
+	NMActStageReturn ret;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	gboolean no_firmware = FALSE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, 0);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) starting...", iface);
+	nm_device_state_changed (self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+
+	if (!nm_device_bring_up (self, FALSE, &no_firmware)) {
+		if (no_firmware)
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_FIRMWARE_MISSING);
+		else
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		goto out;
+	}
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage2_config (self, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+		goto out;
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+	{
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);	
+
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) successful.", iface);
+
+	nm_device_activate_schedule_stage3_ip_config_start (self);
+
+out:
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) complete.", iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage2_device_config
+ *
+ * Schedule setup of the hardware device
+ *
+ */
+void
+nm_device_activate_schedule_stage2_device_config (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage2_device_config, 0);
+
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 2 of 5 (Device Configure) scheduled...",
+	         nm_device_get_iface (self));
+}
+
+static void
+aipd_timeout_remove (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->aipd_timeout) {
+		g_source_remove (priv->aipd_timeout);
+		priv->aipd_timeout = 0;
+	}
+}
+
+static void
+aipd_cleanup (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->aipd_watch) {
+		g_source_remove (priv->aipd_watch);
+		priv->aipd_watch = 0;
+	}
+
+	if (priv->aipd_pid > 0) {
+		kill (priv->aipd_pid, SIGKILL);
+
+		/* ensure the child is reaped */
+		nm_log_dbg (LOGD_AUTOIP4, "waiting for avahi-autoipd pid %d to exit", priv->aipd_pid);
+		waitpid (priv->aipd_pid, NULL, 0);
+		nm_log_dbg (LOGD_AUTOIP4, "avahi-autoip pid %d cleaned up", priv->aipd_pid);
+
+		priv->aipd_pid = -1;
+	}
+
+	aipd_timeout_remove (self);
+
+	priv->aipd_addr = 0;
+}
+
+static NMIP4Config *
+aipd_get_ip4_config (NMDevice *self, NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMIP4Config *config = NULL;
+	NMIP4Address *addr;
+	NMIP4Route *route;
+
+	g_return_val_if_fail (priv->aipd_addr > 0, NULL);
+
+	config = nm_ip4_config_new ();
+	if (!config) {
+		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		return NULL;
+	}
+
+	addr = nm_ip4_address_new ();
+	nm_ip4_address_set_address (addr, (guint32) priv->aipd_addr);
+	nm_ip4_address_set_prefix (addr, 16);
+	nm_ip4_config_take_address (config, addr);
+
+	/* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
+	route = nm_ip4_route_new ();
+	nm_ip4_route_set_dest (route, (guint32) htonl (0xE0000000L));
+	nm_ip4_route_set_prefix (route, 4);
+	nm_ip4_route_set_next_hop (route, (guint32) 0);
+	nm_ip4_route_set_metric (route, 0);
+	nm_ip4_config_take_route (config, route);
+
+	return config;	
+}
+
+static gboolean
+handle_autoip_change (NMDevice *self, NMDeviceStateReason *reason)
+{
+	NMActRequest *req;
+	NMConnection *connection;
+	NMIP4Config *config;
+
+	g_return_val_if_fail (reason != NULL, FALSE);
+
+	config = aipd_get_ip4_config (self, reason);
+	if (!config) {
+		nm_log_err (LOGD_AUTOIP4, "failed to get autoip config for rebind");
+		return FALSE;
+	}
+
+	req = nm_device_get_act_request (self);
+	g_assert (req);
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	g_object_set_data (G_OBJECT (req), NM_ACT_REQUEST_IP4_CONFIG, config);
+
+	if (!nm_device_set_ip4_config (self, config, FALSE, reason)) {
+		nm_log_err (LOGD_AUTOIP4, "(%s): failed to update IP4 config in response to autoip event.",
+		            nm_device_get_iface (self));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
+#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
+
+void
+nm_device_handle_autoip4_event (NMDevice *self,
+                                const char *event,
+                                const char *address)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActRequest *req;
+	NMConnection *connection = NULL;
+	NMSettingIP4Config *s_ip4 = NULL;
+	NMDeviceState state;
+	const char *iface, *method = NULL;
+
+	g_return_if_fail (event != NULL);
+
+	req = nm_device_get_act_request (self);
+	if (!req)
+		return;
+
+	connection = nm_act_request_get_connection (req);
+	if (!connection)
+		return;
+
+	/* Ignore if the connection isn't an AutoIP connection */
+	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 || !method || strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+		return;
+
+	iface = nm_device_get_iface (self);
+	state = nm_device_get_state (self);
+
+	if (strcmp (event, "BIND") == 0) {
+		struct in_addr ip;
+		NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+		if (inet_pton (AF_INET, address, &ip) <= 0) {
+			nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd.",
+			            iface, address);
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
+			return;
+		}
+
+		if ((ip.s_addr & IPV4LL_NETMASK) != IPV4LL_NETWORK) {
+			nm_log_err (LOGD_AUTOIP4, "(%s): invalid address %s received from avahi-autoipd (not link-local).",
+			            iface, address);
+			nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
+			return;
+		}
+
+		switch (state) {
+		case NM_DEVICE_STATE_IP_CONFIG:
+			if (priv->aipd_addr) {
+				nm_log_warn (LOGD_AUTOIP4, "(%s): already have autoip address!", iface);
+				return;
+			}
+
+			priv->aipd_addr = ip.s_addr;
+			aipd_timeout_remove (self);
+			nm_device_activate_schedule_stage4_ip4_config_get (self);
+			break;
+		case NM_DEVICE_STATE_ACTIVATED:
+			priv->aipd_addr = ip.s_addr;
+			if (!handle_autoip_change (self, &reason))
+				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+			break;
+		default:
+			nm_log_warn (LOGD_AUTOIP4, "(%s): unexpected avahi-autoip event %s for %s.",
+			            iface, event, address);
+			break;
+		}
+	} else {
+		nm_log_warn (LOGD_AUTOIP4, "(%s): autoip address %s no longer valid because '%s'.",
+		            iface, address, event);
+
+		/* The address is gone; terminate the connection or fail activation */
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+	}
+}
+
+static void
+aipd_watch_cb (GPid pid, gint status, gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceState state;
+	const char *iface;
+
+	if (!priv->aipd_watch)
+		return;
+	priv->aipd_watch = 0;
+
+	iface = nm_device_get_iface (self);
+
+	if (WIFEXITED (status)) {
+		nm_log_dbg (LOGD_AUTOIP4, "(%s): avahi-autoipd exited with error code %d",
+		            iface, WEXITSTATUS (status));
+	} else if (WIFSTOPPED (status)) {
+		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd stopped unexpectedly with signal %d",
+		            iface, WSTOPSIG (status));
+	} else if (WIFSIGNALED (status)) {
+		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died with signal %d",
+		             iface, WTERMSIG (status));
+	} else {
+		nm_log_warn (LOGD_AUTOIP4, "(%s): avahi-autoipd died from an unknown cause", iface);
+	}
+
+	aipd_cleanup (self);
+
+	state = nm_device_get_state (self);
+	if (nm_device_is_activating (self) || (state == NM_DEVICE_STATE_ACTIVATED))
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
+}
+
+static gboolean
+aipd_timeout_cb (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->aipd_timeout)
+		return FALSE;
+	priv->aipd_timeout = 0;
+
+	nm_log_info (LOGD_AUTOIP4, "(%s): avahi-autoipd timed out.", nm_device_get_iface (self));
+	aipd_cleanup (self);
+
+	if (nm_device_get_state (self) == NM_DEVICE_STATE_IP_CONFIG)
+		nm_device_activate_schedule_stage4_ip4_config_timeout (self);
+
+	return FALSE;
+}
+
+static void
+aipd_child_setup (gpointer user_data G_GNUC_UNUSED)
+{
+	/* We are in the child process at this point.
+	 * Give child it's own program group for signal
+	 * separation.
+	 */
+	pid_t pid = getpid ();
+	setpgid (pid, pid);
+}
+
+static gboolean
+aipd_exec (NMDevice *self, GError **error)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	char *argv[5];
+	gboolean success = FALSE;
+	const char **aipd_binary = NULL;
+	static const char *aipd_paths[] = {
+		"/usr/sbin/avahi-autoipd",
+		"/usr/local/sbin/avahi-autoipd",
+		NULL
+	};
+
+	aipd_cleanup (self);
+
+	/* Find avahi-autoipd */
+	aipd_binary = aipd_paths;
+	while (*aipd_binary != NULL) {
+		if (g_file_test (*aipd_binary, G_FILE_TEST_EXISTS))
+			break;
+		aipd_binary++;
+	}
+
+	if (!*aipd_binary) {
+		g_set_error (error, 0, 0, "%s", "couldn't find avahi-autoipd");
+		return FALSE;
+	}
+
+	argv[0] = (char *) (*aipd_binary);
+	argv[1] = "--script";
+	argv[2] = LIBEXECDIR "/nm-avahi-autoipd.action";
+	argv[3] = (char *) nm_device_get_ip_iface (self);
+	argv[4] = NULL;
+
+	success = g_spawn_async ("/", argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+	                         &aipd_child_setup, NULL, &(priv->aipd_pid), error);
+	if (!success)
+		return FALSE;
+
+	/* Monitor the child process so we know when it dies */
+	priv->aipd_watch = g_child_watch_add (priv->aipd_pid, aipd_watch_cb, self);
+
+	/* Start a timeout to bound the address attempt */
+	priv->aipd_timeout = g_timeout_add_seconds (20, aipd_timeout_cb, self);
+
+	return TRUE;
+}
+
+static void
+dhcp4_add_option_cb (gpointer key, gpointer value, gpointer user_data)
+{
+	nm_dhcp4_config_add_option (NM_DHCP4_CONFIG (user_data),
+	                            (const char *) key,
+	                            (const char *) value);
+}
+
+static void
+dhcp6_add_option_cb (gpointer key, gpointer value, gpointer user_data)
+{
+	nm_dhcp6_config_add_option (NM_DHCP6_CONFIG (user_data),
+	                            (const char *) key,
+	                            (const char *) value);
+}
+
+static void
+handle_dhcp_lease_change (NMDevice *device, gboolean ipv6)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMIP4Config *ip4_config;
+	NMSettingIP4Config *s_ip4;
+	NMIP6Config *ip6_config;
+	NMSettingIP6Config *s_ip6;
+	NMConnection *connection;
+	NMActRequest *req;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	gboolean assumed;
+
+	req = nm_device_get_act_request (device);
+	g_assert (req);
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+	assumed = nm_act_request_get_assumed (req);
+
+	if (ipv6) {
+		ip6_config = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE);
+		if (!ip6_config) {
+			nm_log_warn (LOGD_DHCP6, "(%s): failed to get DHCPv6 config for rebind",
+			             nm_device_get_ip_iface (device));
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+			return;
+		}
+
+		s_ip6 = NM_SETTING_IP6_CONFIG (nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG));
+		nm_utils_merge_ip6_config (ip6_config, s_ip6);
+
+		g_object_set_data (G_OBJECT (req), NM_ACT_REQUEST_IP6_CONFIG, ip6_config);
+
+		if (nm_device_set_ip6_config (device, ip6_config, assumed, &reason)) {
+			nm_dhcp6_config_reset (priv->dhcp6_config);
+			nm_dhcp_client_foreach_option (priv->dhcp6_client,
+			                               dhcp6_add_option_cb,
+			                               priv->dhcp6_config);
+		} else {
+			nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv6 config in response to DHCP event.",
+			             nm_device_get_ip_iface (device));
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		}
+	} else {
+		ip4_config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE);
+		if (!ip4_config) {
+			nm_log_warn (LOGD_DHCP6, "(%s): failed to get DHCPv4 config for rebind",
+			             nm_device_get_ip_iface (device));
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+			return;
+		}
+
+		s_ip4 = NM_SETTING_IP4_CONFIG (nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG));
+		nm_utils_merge_ip4_config (ip4_config, s_ip4);
+
+		g_object_set_data (G_OBJECT (req), NM_ACT_REQUEST_IP4_CONFIG, ip4_config);
+
+		if (nm_device_set_ip4_config (device, ip4_config, assumed, &reason)) {
+			nm_dhcp4_config_reset (priv->dhcp4_config);
+			nm_dhcp_client_foreach_option (priv->dhcp4_client,
+			                               dhcp4_add_option_cb,
+			                               priv->dhcp4_config);
+		} else {
+			nm_log_warn (LOGD_DHCP6, "(%s): failed to update IPv4 config in response to DHCP event.",
+			             nm_device_get_ip_iface (device));
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		}
+	}
+}
+
+static void
+dhcp_state_changed (NMDHCPClient *client,
+					NMDHCPState state,
+					gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDeviceState dev_state;
+	gboolean ipv6;
+
+	ipv6 = nm_dhcp_client_get_ipv6 (client);
+	dev_state = nm_device_get_state (device);
+
+	if (ipv6) {
+		nm_log_dbg (LOGD_DHCP6, "(%s): new DHCPv6 client state %d",
+		            nm_device_get_iface (device), dev_state);
+	} else {
+		nm_log_dbg (LOGD_DHCP4, "(%s): new DHCPv4 client state %d",
+		            nm_device_get_iface (device), dev_state);
+	}
+
+	switch (state) {
+	case DHC_BOUND4:     /* lease obtained */
+	case DHC_BOUND6:
+	case DHC_RENEW4:     /* lease renewed */
+	case DHC_RENEW6:     /* lease renewed */
+	case DHC_REBOOT:     /* have valid lease, but now obtained a different one */
+	case DHC_REBIND4:    /* new, different lease */
+	case DHC_REBIND6:    /* new, different lease */
+		if (dev_state == NM_DEVICE_STATE_IP_CONFIG) {
+			if (ipv6)
+				nm_device_activate_schedule_stage4_ip6_config_get (device);
+			else
+				nm_device_activate_schedule_stage4_ip4_config_get (device);
+		} else if (dev_state == NM_DEVICE_STATE_ACTIVATED)
+			handle_dhcp_lease_change (device, ipv6);
+		break;
+	case DHC_TIMEOUT: /* timed out contacting DHCP server */
+		if (ipv6) {
+			nm_dhcp6_config_reset (priv->dhcp6_config);
+			if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG)
+				nm_device_activate_schedule_stage4_ip6_config_timeout (device);
+		} else {
+			nm_dhcp4_config_reset (priv->dhcp4_config);
+			if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG)
+				nm_device_activate_schedule_stage4_ip4_config_timeout (device);
+		}
+		break;
+	case DHC_END: /* dhclient exited normally */
+		/* In IPv6 info-only mode, the client doesn't handle leases so it
+		 * may exit right after getting a response from the server.  That's
+		 * normal.  In that case we just ignore the exit.
+		 */
+		if (ipv6 && (priv->dhcp6_mode == IP6_DHCP_OPT_OTHERCONF))
+			break;
+		/* Otherwise, fall through */
+	case DHC_FAIL: /* all attempts to contact server timed out, sleeping */
+	case DHC_ABEND: /* dhclient exited abnormally */
+		if (ipv6)
+			nm_dhcp6_config_reset (priv->dhcp6_config);
+		else
+			nm_dhcp4_config_reset (priv->dhcp4_config);
+
+		/* dhclient quit and can't get/renew a lease; so kill the connection */
+		if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+			if (ipv6)
+				nm_device_activate_schedule_stage4_ip6_config_timeout (device);
+			else
+				nm_device_activate_schedule_stage4_ip4_config_timeout (device);
+		} else if (nm_device_get_state (device) == NM_DEVICE_STATE_ACTIVATED)
+			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		break;
+	case DHC_STOP:
+	case DHC_STOP6:
+	case DHC_EXPIRE:
+	case DHC_EXPIRE6:
+		if (dev_state == NM_DEVICE_STATE_ACTIVATED) {
+			if (ipv6)
+				nm_dhcp6_config_reset (priv->dhcp6_config);
+			else
+				nm_dhcp4_config_reset (priv->dhcp4_config);
+
+			/* dhclient quit and can't get/renew a lease; so kill the connection */
+			nm_device_state_changed (device,
+			                         NM_DEVICE_STATE_FAILED,
+			                         NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+dhcp_timeout (NMDHCPClient *client, gpointer user_data)
+{
+	NMDevice *device = NM_DEVICE (user_data);
+
+	if (!nm_device_get_act_request (device))
+		return;
+
+	nm_dhcp_client_stop (client);
+
+	if (nm_device_get_state (device) == NM_DEVICE_STATE_IP_CONFIG) {
+		if (nm_dhcp_client_get_ipv6 (client))
+			nm_device_activate_schedule_stage4_ip6_config_timeout (device);
+		else
+			nm_device_activate_schedule_stage4_ip4_config_timeout (device);
+	}
+}
+
+static NMActStageReturn
+real_act_stage3_ip4_config_start (NMDevice *self, NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingConnection *s_con;
+	NMSettingIP4Config *s_ip4;
+	NMActRequest *req;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
+	const char *ip_iface, *method = NULL, *uuid;
+
+	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Use the IP interface (not the control interface) for IP stuff */
+	ip_iface = nm_device_get_ip_iface (self);
+
+	/* Make sure the interface is up before trying to do anything with it */
+	if (!nm_system_device_is_up_with_iface (ip_iface))
+		nm_system_device_set_up_down_with_iface (ip_iface, TRUE, NULL);
+
+	req = nm_device_get_act_request (self);
+	connection = nm_act_request_get_connection (req);
+	s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+	g_assert (s_con);
+	uuid = nm_setting_connection_get_uuid (s_con);
+
+	s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+
+	/* If we did not receive IP4 configuration information, default to DHCP */
+	if (s_ip4)
+		method = nm_setting_ip4_config_get_method (s_ip4);
+
+	if (!s_ip4 || !method || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+		guint8 *anycast = NULL;
+
+		/* Begin a DHCP transaction on the interface */
+
+		if (priv->dhcp_anycast_address)
+			anycast = priv->dhcp_anycast_address->data;
+
+		/* Clear old exported DHCP options */
+		if (priv->dhcp4_config)
+			g_object_unref (priv->dhcp4_config);
+		priv->dhcp4_config = nm_dhcp4_config_new ();
+
+		priv->dhcp4_client = nm_dhcp_manager_start_ip4 (priv->dhcp_manager,
+		                                                ip_iface,
+		                                                uuid,
+		                                                s_ip4,
+		                                                priv->dhcp_timeout,
+		                                                anycast);
+		if (priv->dhcp4_client) {
+			priv->dhcp4_state_sigid = g_signal_connect (priv->dhcp4_client,
+			                                            "state-changed",
+			                                            G_CALLBACK (dhcp_state_changed),
+			                                            self);
+			priv->dhcp4_timeout_sigid = g_signal_connect (priv->dhcp4_client,
+			                                              "timeout",
+			                                              G_CALLBACK (dhcp_timeout),
+			                                              self);
+
+			/* DHCP devices will be notified by the DHCP manager when
+			 * stuff happens.	
+			 */
+			ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else {
+			*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
+			ret = NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	} else if (s_ip4 && !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+		GError *error = NULL;
+		const char *iface = nm_device_get_iface (self);
+
+		/* Start avahi-autoipd */
+		if (aipd_exec (self, &error)) {
+			nm_log_info (LOGD_DEVICE | LOGD_AUTOIP4,
+			             "Activation (%s) Stage 3 of 5 (IP Configure Start) started"
+			             " avahi-autoipd...", iface);
+			ret = NM_ACT_STAGE_RETURN_POSTPONE;
+		} else {
+			nm_log_info (LOGD_DEVICE | LOGD_AUTOIP4,
+			             "Activation (%s) Stage 3 of 5 (IP Configure Start) failed"
+			             " to start avahi-autoipd: %s", iface, error->message);
+			g_error_free (error);
+			aipd_cleanup (self);
+			*reason = NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED;
+			ret = NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	} else if (s_ip4 && !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+		/* Nothing to do... */
+		priv->ip4_ready = TRUE;
+		ret = NM_ACT_STAGE_RETURN_STOP;
+	}
+
+	return ret;
+}
+
+static NMActStageReturn
+dhcp6_start (NMDevice *self,
+             NMConnection *connection,
+             guint32 dhcp_opt,
+             NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	guint8 *anycast = NULL;
+	NMSettingIP6Config *s_ip6;
+	NMSettingConnection *s_con;
+	const char *uuid;
+	const char *ip_iface;
+
+	if (!connection) {
+		NMActRequest *req;
+
+		req = nm_device_get_act_request (self);
+		g_assert (req);
+		connection = nm_act_request_get_connection (req);
+		g_assert (connection);
+	}
+
+	/* Begin a DHCP transaction on the interface */
+
+	if (priv->dhcp_anycast_address)
+		anycast = priv->dhcp_anycast_address->data;
+
+	/* Clear old exported DHCP options */
+	if (priv->dhcp6_config)
+		g_object_unref (priv->dhcp6_config);
+	priv->dhcp6_config = nm_dhcp6_config_new ();
+
+	s_con = (NMSettingConnection *) nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION);
+	g_assert (s_con);
+	uuid = nm_setting_connection_get_uuid (s_con);
+
+	s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+
+	ip_iface = nm_device_get_ip_iface (self);
+	priv->dhcp6_client = nm_dhcp_manager_start_ip6 (priv->dhcp_manager,
+	                                                ip_iface,
+	                                                uuid,
+	                                                s_ip6,
+	                                                priv->dhcp_timeout,
+	                                                anycast,
+	                                                (dhcp_opt == IP6_DHCP_OPT_OTHERCONF) ? TRUE : FALSE);
+	if (priv->dhcp6_client) {
+		priv->dhcp6_state_sigid = g_signal_connect (priv->dhcp6_client,
+		                                            "state-changed",
+		                                            G_CALLBACK (dhcp_state_changed),
+		                                            self);
+		priv->dhcp6_timeout_sigid = g_signal_connect (priv->dhcp6_client,
+		                                              "timeout",
+		                                              G_CALLBACK (dhcp_timeout),
+		                                              self);
+
+		/* DHCP devices will be notified by the DHCP manager when stuff happens */
+		ret = NM_ACT_STAGE_RETURN_POSTPONE;
+	} else {
+		*reason = NM_DEVICE_STATE_REASON_DHCP_START_FAILED;
+		ret = NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return ret;
+}
+
+static NMActStageReturn
+real_act_stage3_ip6_config_start (NMDevice *self, NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	const char *ip_iface;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMActRequest *req;
+	NMConnection *connection;
+
+	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	req = nm_device_get_act_request (self);
+	g_assert (req);
+	connection = nm_act_request_get_connection (req);
+	g_assert (connection);
+
+	ip_iface = nm_device_get_ip_iface (self);
+
+	update_accept_ra_save (self);
+
+	priv->dhcp6_mode = IP6_DHCP_OPT_NONE;
+
+	if (   ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	    || ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+		if (!addrconf6_setup (self)) {
+			*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+			goto out;
+		}
+		nm_ip6_manager_begin_addrconf (priv->ip6_manager, nm_device_get_ip_ifindex (self));
+		ret = NM_ACT_STAGE_RETURN_POSTPONE;
+	} else if (ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+		/* reset the saved RA value when ipv6 is ignored */
+		if (priv->ip6_accept_ra_path) {
+			nm_utils_do_sysctl (priv->ip6_accept_ra_path,
+			                    priv->ip6_accept_ra_save ? "1\n" : "0\n");
+		}
+
+		priv->ip6_ready = TRUE;
+		ret = NM_ACT_STAGE_RETURN_STOP;
+	} else if (ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+		/* Router advertisements shouldn't be used in manual mode */
+		if (priv->ip6_accept_ra_path)
+			nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0\n");
+		ret = NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	/* Other methods (shared) aren't implemented yet */
+
+out:
+	return ret;
+}
+
+
+/*
+ * nm_device_activate_stage3_ip_config_start
+ *
+ * Begin automatic/manual IP configuration
+ *
+ */
+static gboolean
+nm_device_activate_stage3_ip_config_start (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	const char *iface;
+	NMActStageReturn ret;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, 0);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) started...", iface);
+	nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip4_config_start (self, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
+		nm_device_activate_schedule_stage4_ip4_config_get (self);
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
+		/* Nothing to do */
+	} else
+		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage3_ip6_config_start (self, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_SUCCESS)
+		nm_device_activate_schedule_stage4_ip6_config_get (self);
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	} else if (ret == NM_ACT_STAGE_RETURN_STOP) {
+		/* Nothing to do */
+	} else
+		g_assert (ret == NM_ACT_STAGE_RETURN_POSTPONE);
+
+out:
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) complete.", iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage3_ip_config_start
+ *
+ * Schedule IP configuration start
+ */
+void
+nm_device_activate_schedule_stage3_ip_config_start (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage3_ip_config_start, 0);
+
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 3 of 5 (IP Configure Start) scheduled.",
+	             nm_device_get_iface (self));
+}
+
+static GHashTable *shared_ips = NULL;
+
+static void
+release_shared_ip (gpointer data)
+{
+	g_hash_table_remove (shared_ips, data);
+}
+
+static guint32
+reserve_shared_ip (void)
+{
+	guint32 start = (guint32) ntohl (0x0a2a2b01); /* 10.42.43.1 */
+	guint32 count = 0;
+
+	while (g_hash_table_lookup (shared_ips, GUINT_TO_POINTER (start + count))) {
+		count += ntohl (0x100);
+		if (count > ntohl (0xFE00)) {
+			nm_log_err (LOGD_SHARING, "ran out of shared IP addresses!");
+			return 0;
+		}
+	}
+
+	g_hash_table_insert (shared_ips, GUINT_TO_POINTER (start + count), GUINT_TO_POINTER (TRUE));
+	return start + count;
+}
+
+static NMIP4Config *
+nm_device_new_ip4_shared_config (NMDevice *self, NMDeviceStateReason *reason)
+{
+	NMIP4Config *config = NULL;
+	NMIP4Address *addr;
+	guint32 tmp_addr;
+
+	g_return_val_if_fail (self != NULL, NULL);
+
+	if (G_UNLIKELY (shared_ips == NULL))
+		shared_ips = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	tmp_addr = reserve_shared_ip ();
+	if (!tmp_addr) {
+		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		return NULL;
+	}
+
+	config = nm_ip4_config_new ();
+	addr = nm_ip4_address_new ();
+	nm_ip4_address_set_address (addr, tmp_addr);
+	nm_ip4_address_set_prefix (addr, 24);
+	nm_ip4_config_take_address (config, addr);
+
+	/* Remove the address lock when the object gets disposed */
+	g_object_set_data_full (G_OBJECT (config), "shared-ip",
+	                        GUINT_TO_POINTER (tmp_addr), release_shared_ip);
+
+	return config;
+}
+
+static NMActStageReturn
+real_act_stage4_get_ip4_config (NMDevice *self,
+                                NMIP4Config **config,
+                                NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMConnection *connection;
+	NMSettingIP4Config *s_ip4;
+	const char *ip_iface;
+
+	g_return_val_if_fail (config != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (*config == NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Use the IP interface (not the control interface) for IP stuff */
+	ip_iface = nm_device_get_ip_iface (self);
+
+	connection = nm_act_request_get_connection (nm_device_get_act_request (self));
+	g_assert (connection);
+
+	s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+
+	if (priv->dhcp4_client) {
+		/* DHCP */
+		*config = nm_dhcp_client_get_ip4_config (priv->dhcp4_client, FALSE);
+		if (*config) {
+			/* Merge user-defined overrides into the IP4Config to be applied */
+			nm_utils_merge_ip4_config (*config, s_ip4);
+
+			nm_dhcp4_config_reset (priv->dhcp4_config);
+			nm_dhcp_client_foreach_option (priv->dhcp4_client,
+			                               dhcp4_add_option_cb,
+			                               priv->dhcp4_config);
+
+			/* Notify of new DHCP4 config */
+			g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP4_CONFIG);
+		} else
+			*reason = NM_DEVICE_STATE_REASON_DHCP_ERROR;
+	} else {
+		/* Not DHCP */
+		const char *method;
+
+		g_assert (s_ip4);
+
+		method = nm_setting_ip4_config_get_method (s_ip4);
+		g_assert (method);
+
+		if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
+			*config = aipd_get_ip4_config (self, reason);
+		} else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+			*config = nm_ip4_config_new ();
+			if (*config) {
+				/* Merge user-defined overrides into the IP4Config to be applied */
+				nm_utils_merge_ip4_config (*config, s_ip4);
+			} else
+				*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		} else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+			*config = nm_device_new_ip4_shared_config (self, reason);
+			if (*config)
+				priv->dnsmasq_manager = nm_dnsmasq_manager_new (ip_iface);
+		} else if (!strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+			ret = NM_ACT_STAGE_RETURN_SUCCESS;
+	}
+
+	if (!*config) {
+		/* Make sure device is up even if config fails */
+		nm_device_bring_up (self, FALSE, NULL);
+	} else
+		ret = NM_ACT_STAGE_RETURN_SUCCESS;
+
+	return ret;
+}
+
+/*
+ * nm_device_activate_stage4_ip4_config_get
+ *
+ * Retrieve the correct IPv4 config.
+ *
+ */
+static gboolean
+nm_device_activate_stage4_ip4_config_get (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMIP4Config *ip4_config = NULL;
+	NMActStageReturn ret;
+	const char *iface = NULL;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, AF_INET);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Get) started...",
+	             iface);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_get_ip4_config (self, &ip4_config, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+		goto out;
+	else if (!ip4_config || (ret == NM_ACT_STAGE_RETURN_FAILURE)) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);	
+
+	g_object_set_data (G_OBJECT (nm_device_get_act_request (self)),
+					   NM_ACT_REQUEST_IP4_CONFIG, ip4_config);
+
+	nm_device_activate_schedule_stage5_ip_config_commit (self, AF_INET);
+
+out:
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Get) complete.",
+	             iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage4_ip4_config_get
+ *
+ * Schedule creation of the IPv4 config
+ *
+ */
+void
+nm_device_activate_schedule_stage4_ip4_config_get (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage4_ip4_config_get, AF_INET);
+
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Get) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+
+static NMActStageReturn
+real_act_stage4_ip4_config_timeout (NMDevice *self,
+									NMIP4Config **config,
+									NMDeviceStateReason *reason)
+{
+	g_return_val_if_fail (config != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (*config == NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Notify of invalid DHCP4 config object */
+	g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP4_CONFIG);
+
+	if (nm_device_ip_config_should_fail (self, FALSE)) {
+		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+
+/*
+ * nm_device_activate_stage4_ip4_config_timeout
+ *
+ * Time out on retrieving the IPv4 config.
+ *
+ */
+static gboolean
+nm_device_activate_stage4_ip4_config_timeout (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMIP4Config *ip4_config = NULL;
+	const char *iface;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, AF_INET);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Timeout) started...",
+	             iface);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip4_config_timeout (self, &ip4_config, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+		goto out;
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);	
+
+	if (ip4_config) {
+		g_object_set_data (G_OBJECT (nm_device_get_act_request (self)),
+						   NM_ACT_REQUEST_IP4_CONFIG, ip4_config);
+	}
+
+	nm_device_activate_schedule_stage5_ip_config_commit (self, AF_INET);
+
+out:
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Timeout) complete.",
+	             iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage4_ip4_config_timeout
+ *
+ * Deal with a timeout of the IPv4 configuration
+ *
+ */
+void
+nm_device_activate_schedule_stage4_ip4_config_timeout (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage4_ip4_config_timeout, AF_INET);
+
+	nm_log_info (LOGD_DEVICE | LOGD_IP4,
+	             "Activation (%s) Stage 4 of 5 (IP4 Configure Timeout) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+static void
+merge_dhcp_config_to_master (NMIP6Config *dst, NMIP6Config *src)
+{
+	guint32 i;
+
+	g_return_if_fail (src != NULL);
+	g_return_if_fail (dst != NULL);
+
+	/* addresses */
+	for (i = 0; i < nm_ip6_config_get_num_addresses (src); i++)
+		nm_ip6_config_add_address (dst, nm_ip6_config_get_address (src, i));
+
+	/* ptp address; only replace if src doesn't have one */
+	if (!nm_ip6_config_get_ptp_address (dst))
+		nm_ip6_config_set_ptp_address (dst, nm_ip6_config_get_ptp_address (src));
+
+	/* nameservers */
+	for (i = 0; i < nm_ip6_config_get_num_nameservers (src); i++)
+		nm_ip6_config_add_nameserver (dst, nm_ip6_config_get_nameserver (src, i));
+
+	/* routes */
+	for (i = 0; i < nm_ip6_config_get_num_routes (src); i++)
+		nm_ip6_config_add_route (dst, nm_ip6_config_get_route (src, i));
+
+	/* domains */
+	for (i = 0; i < nm_ip6_config_get_num_domains (src); i++)
+		nm_ip6_config_add_domain (dst, nm_ip6_config_get_domain (src, i));
+
+	/* dns searches */
+	for (i = 0; i < nm_ip6_config_get_num_searches (src); i++)
+		nm_ip6_config_add_search (dst, nm_ip6_config_get_search (src, i));
+
+	if (!nm_ip6_config_get_mss (dst))
+		nm_ip6_config_set_mss (dst, nm_ip6_config_get_mss (src));
+}
+
+static NMActStageReturn
+real_act_stage4_get_ip6_config (NMDevice *self,
+                                NMIP6Config **config,
+                                NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMConnection *connection;
+	NMSettingIP6Config *s_ip6;
+	const char *ip_iface;
+
+	g_return_val_if_fail (config != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (*config == NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (reason != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Use the IP interface (not the control interface) for IP stuff */
+	ip_iface = nm_device_get_ip_iface (self);
+
+	connection = nm_act_request_get_connection (nm_device_get_act_request (self));
+	g_assert (connection);
+
+	if (   ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+	    || ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
+		*config = nm_ip6_manager_get_ip6_config (priv->ip6_manager,
+		                                         nm_device_get_ip_ifindex (self));
+		if (!*config) {
+			*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+			goto out;
+		}
+	} else if (ip6_method_matches (connection, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
+		*config = nm_ip6_config_new ();
+		if (!*config) {
+			*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+			goto out;
+		}
+	}
+
+	/* Autoconf might have triggered DHCPv6 too */
+	if (priv->dhcp6_client) {
+		NMIP6Config *dhcp;
+
+		dhcp = nm_dhcp_client_get_ip6_config (priv->dhcp6_client, FALSE);
+		if (!dhcp) {
+			*reason = NM_DEVICE_STATE_REASON_DHCP_ERROR;
+			goto out;
+		}
+
+		/* For "managed" and DHCP-only setups, we use only the DHCP-supplied
+		 * IPv6 config.  But when autoconf is enabled, we have to merge the
+		 * autoconf config and the DHCP-supplied config, then merge the
+		 * user's overrides from the connection to get the final configuration
+		 * that gets applied to the device.
+		 */
+		if (*config) {
+			/* Merge autoconf and DHCP configs */
+			merge_dhcp_config_to_master (*config, dhcp);
+			g_object_unref (dhcp);
+			dhcp = NULL;
+		} else {
+			*config = dhcp;
+		}
+
+		/* Copy the new DHCPv6 configuration into the DHCP config object that's
+		 * exported over D-Bus to clients.
+		 */
+		nm_dhcp6_config_reset (priv->dhcp6_config);
+		nm_dhcp_client_foreach_option (priv->dhcp6_client,
+		                               dhcp6_add_option_cb,
+		                               priv->dhcp6_config);
+
+		/* Notify of new DHCP6 config */
+		g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP6_CONFIG);
+	}
+
+	/* Merge user-defined overrides into the IP6Config to be applied */
+	if (*config) {
+		s_ip6 = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+		nm_utils_merge_ip6_config (*config, s_ip6);
+	}
+
+out:
+	return *config ? NM_ACT_STAGE_RETURN_SUCCESS : NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+/*
+ * nm_device_activate_stage4_ip6_config_get
+ *
+ * Retrieve the correct IPv6 config.
+ *
+ */
+static gboolean
+nm_device_activate_stage4_ip6_config_get (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMIP6Config *ip6_config = NULL;
+	NMActStageReturn ret;
+	const char *iface = NULL;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, AF_INET6);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Get) started...",
+	             iface);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_get_ip6_config (self, &ip6_config, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+		goto out;
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE)
+	{
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+
+	g_object_set_data (G_OBJECT (nm_device_get_act_request (self)),
+					   NM_ACT_REQUEST_IP6_CONFIG, ip6_config);
+
+	nm_device_activate_schedule_stage5_ip_config_commit (self, AF_INET6);
+
+out:
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Get) complete.",
+	             iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage4_ip6_config_get
+ *
+ * Schedule creation of the IPv6 config
+ *
+ */
+void
+nm_device_activate_schedule_stage4_ip6_config_get (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage4_ip6_config_get, AF_INET6);
+
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Get) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+
+static NMActStageReturn
+real_act_stage4_ip6_config_timeout (NMDevice *self,
+									NMIP6Config **config,
+									NMDeviceStateReason *reason)
+{
+	g_return_val_if_fail (config != NULL, NM_ACT_STAGE_RETURN_FAILURE);
+	g_return_val_if_fail (*config == NULL, NM_ACT_STAGE_RETURN_FAILURE);
+
+	/* Notify of invalid DHCP4 config object */
+	g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP6_CONFIG);
+
+	if (nm_device_ip_config_should_fail (self, TRUE)) {
+		*reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+
+/*
+ * nm_device_activate_stage4_ip6_config_timeout
+ *
+ * Time out on retrieving the IPv6 config.
+ *
+ */
+static gboolean
+nm_device_activate_stage4_ip6_config_timeout (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMIP6Config *ip6_config = NULL;
+	const char *iface;
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, AF_INET6);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Timeout) started...",
+	             iface);
+
+	ret = NM_DEVICE_GET_CLASS (self)->act_stage4_ip6_config_timeout (self, &ip6_config, &reason);
+	if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
+		goto out;
+	else if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+	g_assert (ret == NM_ACT_STAGE_RETURN_SUCCESS);
+
+	if (ip6_config) {
+		g_object_set_data (G_OBJECT (nm_device_get_act_request (self)),
+						   NM_ACT_REQUEST_IP6_CONFIG, ip6_config);
+	}
+
+	nm_device_activate_schedule_stage5_ip_config_commit (self, AF_INET6);
+
+out:
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Timeout) complete.",
+	             iface);
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage4_ip6_config_timeout
+ *
+ * Deal with a timeout of the IPv6 configuration
+ *
+ */
+void
+nm_device_activate_schedule_stage4_ip6_config_timeout (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	activation_source_schedule (self, nm_device_activate_stage4_ip6_config_timeout, AF_INET6);
+
+	nm_log_info (LOGD_DEVICE | LOGD_IP6,
+	             "Activation (%s) Stage 4 of 5 (IP6 Configure Timeout) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+static void
+share_child_setup (gpointer user_data G_GNUC_UNUSED)
+{
+	/* We are in the child process at this point */
+	pid_t pid = getpid ();
+	setpgid (pid, pid);
+}
+
+static gboolean
+share_init (void)
+{
+	int status;
+	char *modules[] = { "ip_tables", "iptable_nat", "nf_nat_ftp", "nf_nat_irc",
+	                    "nf_nat_sip", "nf_nat_tftp", "nf_nat_pptp", "nf_nat_h323",
+	                    NULL };
+	char **iter;
+
+	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_forward", "1\n")) {
+		nm_log_err (LOGD_SHARING, "Error starting IP forwarding: (%d) %s",
+					errno, strerror (errno));
+		return FALSE;
+	}
+
+	if (!nm_utils_do_sysctl ("/proc/sys/net/ipv4/ip_dynaddr", "1\n")) {
+		nm_log_err (LOGD_SHARING, "error starting IP forwarding: (%d) %s",
+					errno, strerror (errno));
+	}
+
+	for (iter = modules; *iter; iter++) {
+		char *argv[3] = { "/sbin/modprobe", *iter, NULL };
+		char *envp[1] = { NULL };
+		GError *error = NULL;
+
+		if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
+		                   share_child_setup, NULL, NULL, NULL, &status, &error)) {
+			nm_log_err (LOGD_SHARING, "error loading NAT module %s: (%d) %s",
+			            *iter, error ? error->code : 0,
+			            (error && error->message) ? error->message : "unknown");
+			if (error)
+				g_error_free (error);
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+add_share_rule (NMActRequest *req, const char *table, const char *fmt, ...)
+{
+	va_list args;
+	char *cmd;
+
+	va_start (args, fmt);
+	cmd = g_strdup_vprintf (fmt, args);
+	va_end (args);
+
+	nm_act_request_add_share_rule (req, table, cmd);
+	g_free (cmd);
+}
+
+static gboolean
+start_sharing (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActRequest *req;
+	GError *error = NULL;
+	char str_addr[INET_ADDRSTRLEN + 1];
+	char str_mask[INET_ADDRSTRLEN + 1];
+	guint32 netmask, network;
+	NMIP4Config *ip4_config;
+	NMIP4Address *ip4_addr;
+	const char *ip_iface;
+
+	ip_iface = nm_device_get_ip_iface (self);
+
+	ip4_config = nm_device_get_ip4_config (self);
+	if (!ip4_config)
+		return FALSE;
+
+	ip4_addr = nm_ip4_config_get_address (ip4_config, 0);
+	if (!ip4_addr || !nm_ip4_address_get_address (ip4_addr))
+		return FALSE;
+
+	netmask = nm_utils_ip4_prefix_to_netmask (nm_ip4_address_get_prefix (ip4_addr));
+	if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask)))
+		return FALSE;
+
+	network = nm_ip4_address_get_address (ip4_addr) & netmask;
+	if (!inet_ntop (AF_INET, &network, str_addr, sizeof (str_addr)))
+		return FALSE;
+
+	if (!share_init ())
+		return FALSE;
+
+	req = nm_device_get_act_request (self);
+	g_assert (req);
+
+	add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT", ip_iface);
+	add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT", ip_iface);
+	add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT", ip_iface);
+	add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT", ip_iface);
+	add_share_rule (req, "filter", "FORWARD --in-interface %s --jump REJECT", ip_iface);
+	add_share_rule (req, "filter", "FORWARD --out-interface %s --jump REJECT", ip_iface);
+	add_share_rule (req, "filter", "FORWARD --in-interface %s --out-interface %s --jump ACCEPT", ip_iface, ip_iface);
+	add_share_rule (req, "filter", "FORWARD --source %s/%s --in-interface %s --jump ACCEPT", str_addr, str_mask, ip_iface);
+	add_share_rule (req, "filter", "FORWARD --destination %s/%s --out-interface %s --match state --state ESTABLISHED,RELATED --jump ACCEPT", str_addr, str_mask, ip_iface);
+	add_share_rule (req, "nat", "POSTROUTING --source %s/%s --destination ! %s/%s --jump MASQUERADE", str_addr, str_mask, str_addr, str_mask);
+
+	nm_act_request_set_shared (req, TRUE);
+
+	if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, ip4_config, &error)) {
+		nm_log_err (LOGD_SHARING, "(%s/%s): failed to start dnsmasq: %s",
+		            nm_device_get_iface (self), ip_iface, error->message);
+		g_error_free (error);
+		nm_act_request_set_shared (req, FALSE);
+		return FALSE;
+	}
+
+	priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed",
+	                                           G_CALLBACK (dnsmasq_state_changed_cb),
+	                                           self);
+	return TRUE;
+}
+
+/*
+ * nm_device_activate_stage5_ip_config_commit
+ *
+ * Commit the IP config on the device
+ *
+ */
+static gboolean
+nm_device_activate_stage5_ip_config_commit (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMActRequest *act_request;
+	NMIP4Config *ip4_config = NULL;
+	NMIP6Config *ip6_config = NULL;
+	const char *iface, *method = NULL;
+	NMConnection *connection;
+	NMSettingIP4Config *s_ip4;
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	gboolean assumed;
+
+	/* Get the new IP4 and IP6 configs; since this stage gets rerun
+	 * when automatic configuration changes (DHCP lease renewal, new
+	 * IPv6 router advertisement, etc), it's possible that only one of
+	 * them will be set.
+	 */
+	act_request = nm_device_get_act_request (self);
+
+	ip4_config = g_object_get_data (G_OBJECT (act_request),
+									NM_ACT_REQUEST_IP4_CONFIG);
+	g_object_set_data (G_OBJECT (act_request),
+					   NM_ACT_REQUEST_IP4_CONFIG, NULL);
+
+	ip6_config = g_object_get_data (G_OBJECT (act_request),
+									NM_ACT_REQUEST_IP6_CONFIG);
+	g_object_set_data (G_OBJECT (act_request),
+					   NM_ACT_REQUEST_IP6_CONFIG, NULL);
+
+	/* Clear the activation source ID now that this stage has run */
+	activation_source_clear (self, FALSE, 0);
+
+	iface = nm_device_get_iface (self);
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IP Configure Commit) started...",
+	             iface);
+
+	assumed = nm_act_request_get_assumed (priv->act_request);
+
+	if (!ip6_config && !ip4_config) {
+		nm_log_info (LOGD_DEVICE,
+		             "Activation (%s) Stage 5 of 5 (IP Configure Commit) failed (no IP configuration found)",
+				     iface);
+		nm_device_state_changed (self,
+		                         NM_DEVICE_STATE_FAILED,
+		                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		goto out;
+	}
+
+	if (ip4_config && !nm_device_set_ip4_config (self, ip4_config, assumed, &reason)) {
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+
+	if (ip6_config && !nm_device_set_ip6_config (self, ip6_config, assumed, &reason)) {
+		nm_log_info (LOGD_DEVICE | LOGD_IP6,
+		             "Activation (%s) Stage 5 of 5 (IP Configure Commit) IPv6 failed",
+				     iface);
+		nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, reason);
+		goto out;
+	}
+
+	connection = nm_act_request_get_connection (nm_device_get_act_request (self));
+
+	if (ip4_config) {
+		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_SHARED)) {
+			if (!start_sharing (self)) {
+				nm_log_warn (LOGD_SHARING, "Activation (%s) Stage 5 of 5 (IP Configure Commit) start sharing failed.", iface);
+				nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
+				goto out;
+			}
+		}
+	}
+
+	nm_device_state_changed (self, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);
+
+out:
+	nm_log_info (LOGD_DEVICE, "Activation (%s) Stage 5 of 5 (IP Configure Commit) complete.",
+	             iface);
+
+	/* Balance IP config creation; device takes ownership in set_ip*_config() */
+	if (ip4_config)
+		g_object_unref (ip4_config);
+	if (ip6_config)
+		g_object_unref (ip6_config);
+
+	return FALSE;
+}
+
+
+/*
+ * nm_device_activate_schedule_stage5_ip_config_commit
+ *
+ * Schedule commit of the IP config
+ */
+static void
+nm_device_activate_schedule_stage5_ip_config_commit (NMDevice *self, int family)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	g_return_if_fail (priv->act_request);
+
+	if (family == AF_INET)
+		priv->ip4_ready = TRUE;
+	else if (family == AF_INET6)
+		priv->ip6_ready = TRUE;
+
+	/* Note that these are only set FALSE at stage3, so once you've
+	 * made it all the way through activation once, you can jump back
+	 * into stage4 (eg, for a DHCP lease change) and not worry about
+	 * needing both IPv4 and IPv6 to complete.
+	 */
+	if (!priv->ip4_ready || !priv->ip6_ready)
+		return;
+
+	activation_source_schedule (self, nm_device_activate_stage5_ip_config_commit, 0);
+
+	nm_log_info (LOGD_DEVICE,
+	             "Activation (%s) Stage 5 of 5 (IP Configure Commit) scheduled...",
+	             nm_device_get_iface (self));
+}
+
+
+static void
+clear_act_request (NMDevice *self)
+{
+	NMDevicePrivate * priv;
+
+	g_return_if_fail (self != NULL);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->act_request)
+		return;
+
+	if (priv->secrets_updated_id) {
+		g_signal_handler_disconnect (priv->act_request,
+		                             priv->secrets_updated_id);
+		priv->secrets_updated_id = 0;
+	}
+
+	if (priv->secrets_failed_id) {
+		g_signal_handler_disconnect (priv->act_request,
+		                             priv->secrets_failed_id);
+		priv->secrets_failed_id = 0;
+	}
+
+	nm_act_request_set_default (priv->act_request, FALSE);
+
+	g_object_unref (priv->act_request);
+	priv->act_request = NULL;
+}
+
+static void
+delayed_transitions_clear (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->failed_to_disconnected_id) {
+		nm_log_dbg (LOGD_DEVICE, "(%s): clearing failed->disconnected transition",
+		            nm_device_get_iface (self));
+		g_source_remove (priv->failed_to_disconnected_id);
+		priv->failed_to_disconnected_id = 0;
+	}
+	if (priv->unavailable_to_disconnected_id) {
+		nm_log_dbg (LOGD_DEVICE, "(%s): clearing unavailable->disconnected transition",
+		            nm_device_get_iface (self));
+		g_source_remove (priv->unavailable_to_disconnected_id);
+		priv->unavailable_to_disconnected_id = 0;
+	}
+}
+
+static void
+dhcp4_cleanup (NMDevice *self, gboolean stop)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (priv->dhcp4_config) {
+		g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP4_CONFIG);
+		g_object_unref (priv->dhcp4_config);
+		priv->dhcp4_config = NULL;
+	}
+
+	if (priv->dhcp4_client) {
+		/* Stop any ongoing DHCP transaction on this device */
+		if (priv->dhcp4_state_sigid) {
+			g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_state_sigid);
+			priv->dhcp4_state_sigid = 0;
+		}
+
+		if (priv->dhcp4_timeout_sigid) {
+			g_signal_handler_disconnect (priv->dhcp4_client, priv->dhcp4_timeout_sigid);
+			priv->dhcp4_timeout_sigid = 0;
+		}
+
+		if (stop)
+			nm_dhcp_client_stop (priv->dhcp4_client);
+
+		g_object_unref (priv->dhcp4_client);
+		priv->dhcp4_client = NULL;
+	}
+}
+
+static void
+dhcp6_cleanup (NMDevice *self, gboolean stop)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	priv->dhcp6_mode = IP6_DHCP_OPT_NONE;
+
+	if (priv->dhcp6_config) {
+		g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_DHCP6_CONFIG);
+		g_object_unref (priv->dhcp6_config);
+		priv->dhcp6_config = NULL;
+	}
+
+	if (priv->dhcp6_client) {
+		if (priv->dhcp6_state_sigid) {
+			g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_state_sigid);
+			priv->dhcp6_state_sigid = 0;
+		}
+
+		if (priv->dhcp6_timeout_sigid) {
+			g_signal_handler_disconnect (priv->dhcp6_client, priv->dhcp6_timeout_sigid);
+			priv->dhcp6_timeout_sigid = 0;
+		}
+
+		if (stop)
+			nm_dhcp_client_stop (priv->dhcp6_client);
+
+		g_object_unref (priv->dhcp6_client);
+		priv->dhcp6_client = NULL;
+	}
+}
+
+static void
+dnsmasq_cleanup (NMDevice *self)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!priv->dnsmasq_manager)
+		return;
+
+	if (priv->dnsmasq_state_id) {
+		g_signal_handler_disconnect (priv->dnsmasq_manager, priv->dnsmasq_state_id);
+		priv->dnsmasq_state_id = 0;
+	}
+
+	nm_dnsmasq_manager_stop (priv->dnsmasq_manager);
+	g_object_unref (priv->dnsmasq_manager);
+	priv->dnsmasq_manager = NULL;
+}
+
+/*
+ * nm_device_deactivate_quickly
+ *
+ * Quickly deactivate a device, for things like sleep, etc.  Doesn't
+ * clean much stuff up, and nm_device_deactivate() should be called
+ * on the device eventually.
+ *
+ */
+gboolean
+nm_device_deactivate_quickly (NMDevice *self)
+{
+	NMDevicePrivate *priv;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+
+	/* Break the activation chain */
+	activation_source_clear (self, TRUE, AF_INET);
+	activation_source_clear (self, TRUE, AF_INET6);
+
+	/* Clear any delayed transitions */
+	delayed_transitions_clear (self);
+
+	dhcp4_cleanup (self, TRUE);
+	dhcp6_cleanup (self, TRUE);
+	addrconf6_cleanup (self);
+	dnsmasq_cleanup (self);
+	aipd_cleanup (self);
+
+	/* Turn off router advertisements until they are needed */
+	if (priv->ip6_accept_ra_path)
+		nm_utils_do_sysctl (priv->ip6_accept_ra_path, "0\n");
+
+	/* Call device type-specific deactivation */
+	if (NM_DEVICE_GET_CLASS (self)->deactivate_quickly)
+		NM_DEVICE_GET_CLASS (self)->deactivate_quickly (self);
+
+	/* Tear down an existing activation request */
+	clear_act_request (self);
+
+	return TRUE;
+}
+
+/*
+ * nm_device_deactivate
+ *
+ * Remove a device's routing table entries and IP address.
+ *
+ */
+static void
+nm_device_deactivate (NMDeviceInterface *device, NMDeviceStateReason reason)
+{
+	NMDevice *self = NM_DEVICE (device);
+	NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
+
+	g_return_if_fail (self != NULL);
+
+	nm_log_info (LOGD_DEVICE, "(%s): deactivating device (reason: %d).",
+	             nm_device_get_iface (self), reason);
+
+	nm_device_deactivate_quickly (self);
+
+	/* Take out any entries in the routing table and any IP address the device had. */
+	nm_system_device_flush_routes (self, nm_device_get_ip6_config (self) ? AF_UNSPEC : AF_INET);
+	nm_system_device_flush_addresses (self);
+	nm_device_update_ip4_address (self);	
+
+	/* Clean up nameservers and addresses */
+	nm_device_set_ip4_config (self, NULL, FALSE, &ignored);
+	nm_device_set_ip6_config (self, NULL, FALSE, &ignored);
+
+	/* Call device type-specific deactivation */
+	if (NM_DEVICE_GET_CLASS (self)->deactivate)
+		NM_DEVICE_GET_CLASS (self)->deactivate (self);
+}
+
+static gboolean
+device_disconnect (NMDeviceInterface *device,
+                   GError **error)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (NM_DEVICE (device));
+
+	priv->autoconnect_inhibit = TRUE;	
+	nm_device_state_changed (NM_DEVICE (device), NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_USER_REQUESTED);
+	return TRUE;
+}
+
+static gboolean
+check_connection_compatible (NMDeviceInterface *dev_iface,
+                             NMConnection *connection,
+                             GError **error)
+{
+	NMDeviceClass *klass = NM_DEVICE_GET_CLASS (NM_DEVICE (dev_iface));
+
+	if (klass->check_connection_compatible)
+		return klass->check_connection_compatible (NM_DEVICE (dev_iface), connection, error);
+
+	return TRUE;
+}
+
+static void
+connection_secrets_updated_cb (NMActRequest *req,
+                               NMConnection *connection,
+                               GSList *updated_settings,
+                               RequestSecretsCaller caller,
+                               gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+
+	if (NM_DEVICE_GET_CLASS (self)->connection_secrets_updated)
+		NM_DEVICE_GET_CLASS (self)->connection_secrets_updated (self, connection, updated_settings, caller);
+}
+
+static void
+connection_secrets_failed_cb (NMActRequest *req,
+                              NMConnection *connection,
+                              const char *setting_name,
+                              RequestSecretsCaller caller,
+                              gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+
+	nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
+}
+
+static gboolean
+device_activation_precheck (NMDevice *self, NMConnection *connection, GError **error)
+{
+	NMConnection *current_connection;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
+
+	if (nm_device_get_state (self) != NM_DEVICE_STATE_ACTIVATED)
+		return TRUE;
+
+	if (!nm_device_is_activating (self))
+		return TRUE;
+
+	// FIXME: why not just check connection path & service?
+	current_connection = nm_act_request_get_connection (nm_device_get_act_request (self));
+	if (nm_connection_compare (connection, current_connection, NM_SETTING_COMPARE_FLAG_EXACT)) {
+		/* Already activating or activated with the same connection */
+		g_set_error (error,
+		             NM_DEVICE_INTERFACE_ERROR,
+		             NM_DEVICE_INTERFACE_ERROR_CONNECTION_ACTIVATING,
+		             "%s", "Connection is already activating");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+nm_device_activate (NMDeviceInterface *device,
+                    NMActRequest *req,
+                    GError **error)
+{
+	NMDevice *self = NM_DEVICE (device);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	if (!device_activation_precheck (self, nm_act_request_get_connection (req), error)) {
+		g_assert (*error);
+		return FALSE;
+	}
+
+	priv->act_request = g_object_ref (req);
+	priv->secrets_updated_id = g_signal_connect (req,
+										"connection-secrets-updated",
+										G_CALLBACK (connection_secrets_updated_cb),
+										device);
+	priv->secrets_failed_id = g_signal_connect (req,
+									    "connection-secrets-failed",
+									    G_CALLBACK (connection_secrets_failed_cb),
+									    device);
+
+	if (!nm_act_request_get_assumed (req)) {
+		/* HACK: update the state a bit early to avoid a race between the 
+		 * scheduled stage1 handler and nm_policy_device_change_check() thinking
+		 * that the activation request isn't deferred because the deferred bit
+		 * gets cleared a bit too early, when the connection becomes valid.
+		 */
+		nm_device_state_changed (self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
+		nm_device_activate_schedule_stage1_device_prepare (self);
+	} else {
+		/* If it's an assumed connection, let the device subclass short-circuit
+		 * the normal connection process and just copy its IP configs from the
+		 * interface.
+		 */
+		nm_device_state_changed (self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+		nm_device_activate_schedule_stage3_ip_config_start (self);
+	}
+
+	return TRUE;
+}
+
+/*
+ * nm_device_is_activating
+ *
+ * Return whether or not the device is currently activating itself.
+ *
+ */
+gboolean
+nm_device_is_activating (NMDevice *device)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+
+	switch (nm_device_get_state (device)) {
+	case NM_DEVICE_STATE_PREPARE:
+	case NM_DEVICE_STATE_CONFIG:
+	case NM_DEVICE_STATE_NEED_AUTH:
+	case NM_DEVICE_STATE_IP_CONFIG:
+		return TRUE;
+		break;
+	default:
+		break;
+	}
+
+	/* There's a small race between the time when stage 1 is scheduled
+	 * and when the device actually sets STATE_PREPARE when the activation
+	 * handler is actually run.  If there's an activation handler scheduled
+	 * we're activating anyway.
+	 */
+	if (priv->act_source_id)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+gboolean
+nm_device_can_interrupt_activation (NMDevice *self)
+{
+	gboolean	interrupt = FALSE;
+
+	g_return_val_if_fail (self != NULL, FALSE);
+
+	if (NM_DEVICE_GET_CLASS (self)->can_interrupt_activation)
+		interrupt = NM_DEVICE_GET_CLASS (self)->can_interrupt_activation (self);
+	return interrupt;
+}
+
+/* IP Configuration stuff */
+
+NMDHCP4Config *
+nm_device_get_dhcp4_config (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->dhcp4_config;
+}
+
+NMIP4Config *
+nm_device_get_ip4_config (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip4_config;
+}
+
+
+static gboolean
+nm_device_set_ip4_config (NMDevice *self,
+                          NMIP4Config *new_config,
+                          gboolean assumed,
+                          NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv;
+	const char *ip_iface;
+	NMIP4Config *old_config = NULL;
+	gboolean success = TRUE;
+	NMIP4ConfigCompareFlags diff = NM_IP4_COMPARE_FLAG_ALL;
+	NMNamedManager *named_mgr;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (reason != NULL, FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	ip_iface = nm_device_get_ip_iface (self);
+
+	old_config = priv->ip4_config;
+
+	if (new_config && old_config)
+		diff = nm_ip4_config_diff (new_config, old_config);
+
+	/* No actual change, do nothing */
+	if (diff == NM_IP4_COMPARE_FLAG_NONE)
+		return TRUE;
+
+	named_mgr = nm_named_manager_get ();
+	if (old_config) {
+		/* Remove any previous IP4 Config from the named manager */
+		nm_named_manager_remove_ip4_config (named_mgr, ip_iface, old_config);
+		g_object_unref (old_config);
+		priv->ip4_config = NULL;
+	}
+
+	if (new_config) {
+		priv->ip4_config = g_object_ref (new_config);
+
+		/* Don't touch the device's actual IP config if the connection is
+		 * assumed when NM starts.
+		 */
+		if (!assumed)
+			success = nm_system_apply_ip4_config (ip_iface, new_config, nm_device_get_priority (self), diff);
+
+		if (success || assumed) {
+			/* Export over D-Bus */
+			if (!nm_ip4_config_get_dbus_path (new_config))
+				nm_ip4_config_export (new_config);
+
+			/* Add the DNS information to the named manager */
+			nm_named_manager_add_ip4_config (named_mgr, ip_iface, new_config, NM_NAMED_IP_CONFIG_TYPE_DEFAULT);
+
+			nm_device_update_ip4_address (self);
+		}
+	}
+	g_object_unref (named_mgr);
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_IP4_CONFIG);
+
+	return success;
+}
+
+/*
+ * nm_device_get_ip4_address
+ *
+ * Get a device's IPv4 address
+ *
+ */
+guint32
+nm_device_get_ip4_address (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, 0);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip4_address;
+}
+
+
+void
+nm_device_update_ip4_address (NMDevice *self)
+{
+	struct ifreq req;
+	guint32 new_address;
+	int fd;
+	
+	g_return_if_fail (self  != NULL);
+
+	fd = socket (PF_INET, SOCK_DGRAM, 0);
+	if (fd < 0) {
+		nm_log_err (LOGD_IP4, "couldn't open control socket.");
+		return;
+	}
+
+	memset (&req, 0, sizeof (struct ifreq));
+	strncpy (req.ifr_name, nm_device_get_ip_iface (self), IFNAMSIZ);
+	if (ioctl (fd, SIOCGIFADDR, &req) == 0) {
+		new_address = ((struct sockaddr_in *)(&req.ifr_addr))->sin_addr.s_addr;
+		if (new_address != nm_device_get_ip4_address (self))
+			NM_DEVICE_GET_PRIVATE (self)->ip4_address = new_address;
+	}
+	close (fd);
+}
+
+static gboolean
+nm_device_set_ip6_config (NMDevice *self,
+                          NMIP6Config *new_config,
+                          gboolean assumed,
+                          NMDeviceStateReason *reason)
+{
+	NMDevicePrivate *priv;
+	const char *ip_iface;
+	NMIP6Config *old_config = NULL;
+	gboolean success = TRUE;
+	NMIP6ConfigCompareFlags diff = NM_IP6_COMPARE_FLAG_ALL;
+	NMNamedManager *named_mgr;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+	g_return_val_if_fail (reason != NULL, FALSE);
+
+	priv = NM_DEVICE_GET_PRIVATE (self);
+	ip_iface = nm_device_get_ip_iface (self);
+
+	old_config = priv->ip6_config;
+
+	if (new_config && old_config)
+		diff = nm_ip6_config_diff (new_config, old_config);
+
+	/* No actual change, do nothing */
+	if (diff == NM_IP6_COMPARE_FLAG_NONE)
+		return TRUE;
+
+	named_mgr = nm_named_manager_get ();
+	if (old_config) {
+		/* Remove any previous IP6 Config from the named manager */
+		nm_named_manager_remove_ip6_config (named_mgr, ip_iface, old_config);
+		g_object_unref (old_config);
+		priv->ip6_config = NULL;
+	}
+
+	if (new_config) {
+		priv->ip6_config = g_object_ref (new_config);
+
+		/* Don't touch the device's actual IP config if the connection is
+		 * assumed when NM starts.
+		 */
+		if (!assumed)
+			success = nm_system_apply_ip6_config (ip_iface, new_config, nm_device_get_priority (self), diff);
+
+		if (success || assumed) {
+			/* Export over D-Bus */
+			if (!nm_ip6_config_get_dbus_path (new_config))
+				nm_ip6_config_export (new_config);
+
+			/* Add the DNS information to the named manager */
+			nm_named_manager_add_ip6_config (named_mgr, ip_iface, new_config, NM_NAMED_IP_CONFIG_TYPE_DEFAULT);
+		}
+	}
+	g_object_unref (named_mgr);
+
+	g_object_notify (G_OBJECT (self), NM_DEVICE_INTERFACE_IP6_CONFIG);
+
+	return success;
+}
+
+NMDHCP6Config *
+nm_device_get_dhcp6_config (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->dhcp6_config;
+}
+
+NMIP6Config *
+nm_device_get_ip6_config (NMDevice *self)
+{
+	g_return_val_if_fail (self != NULL, NULL);
+	g_return_val_if_fail (NM_IS_DEVICE (self), NULL);
+
+	return NM_DEVICE_GET_PRIVATE (self)->ip6_config;
+}
+
+static gboolean
+nm_device_is_up (NMDevice *self)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (NM_DEVICE_GET_CLASS (self)->is_up)
+		return NM_DEVICE_GET_CLASS (self)->is_up (self);
+
+	return TRUE;
+}
+
+gboolean
+nm_device_hw_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
+{
+	gboolean success;
+	guint32 tries = 0;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (nm_device_hw_is_up (self))
+		goto out;
+
+	nm_log_info (LOGD_HW, "(%s): bringing up device.", nm_device_get_iface (self));
+
+	if (NM_DEVICE_GET_CLASS (self)->hw_bring_up) {
+		success = NM_DEVICE_GET_CLASS (self)->hw_bring_up (self, no_firmware);
+		if (!success)
+			return FALSE;
+	}
+
+	/* Wait for the device to come up if requested */
+	while (block && !nm_device_hw_is_up (self) && (tries++ < 50))
+		g_usleep (200);
+
+	if (!nm_device_hw_is_up (self)) {
+		nm_log_warn (LOGD_HW, "(%s): device not up after timeout!", nm_device_get_iface (self));
+		return FALSE;
+	}
+
+out:
+	/* Can only get HW address of some devices when they are up */
+	if (NM_DEVICE_GET_CLASS (self)->update_hw_address)
+		NM_DEVICE_GET_CLASS (self)->update_hw_address (self);
+
+	nm_device_update_ip4_address (self);
+	return TRUE;
+}
+
+void
+nm_device_hw_take_down (NMDevice *self, gboolean block)
+{
+	guint32 tries = 0;
+
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	if (!nm_device_hw_is_up (self))
+		return;
+
+	nm_log_info (LOGD_HW, "(%s): taking down device.", nm_device_get_iface (self));
+
+	if (NM_DEVICE_GET_CLASS (self)->hw_take_down)
+		NM_DEVICE_GET_CLASS (self)->hw_take_down (self);
+
+	/* Wait for the device to come up if requested */
+	while (block && nm_device_hw_is_up (self) && (tries++ < 50))
+		g_usleep (200);
+}
+
+static gboolean
+nm_device_bring_up (NMDevice *self, gboolean block, gboolean *no_firmware)
+{
+	gboolean success = FALSE;
+
+	g_return_val_if_fail (NM_IS_DEVICE (self), FALSE);
+
+	if (!nm_device_hw_bring_up (self, block, no_firmware))
+		return FALSE;
+
+	if (nm_device_is_up (self))
+		return TRUE;
+
+	nm_log_info (LOGD_HW, "(%s): preparing device.", nm_device_get_iface (self));
+
+	if (NM_DEVICE_GET_CLASS (self)->bring_up)
+		success = NM_DEVICE_GET_CLASS (self)->bring_up (self);
+
+	return success;
+}
+
+static void
+nm_device_take_down (NMDevice *self, gboolean block, NMDeviceStateReason reason)
+{
+	g_return_if_fail (NM_IS_DEVICE (self));
+
+	if (nm_device_get_act_request (self))
+		nm_device_interface_deactivate (NM_DEVICE_INTERFACE (self), reason);
+
+	if (nm_device_is_up (self)) {
+		nm_log_info (LOGD_HW, "(%s): cleaning up...", nm_device_get_iface (self));
+
+		if (NM_DEVICE_GET_CLASS (self)->take_down)
+			NM_DEVICE_GET_CLASS (self)->take_down (self);
+	}
+
+	nm_device_hw_take_down (self, block);
+}
+
+static void
+dispose (GObject *object)
+{
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	gboolean take_down = TRUE;
+
+	if (priv->disposed || !priv->initialized)
+		goto out;
+
+	priv->disposed = TRUE;
+
+	/* Don't down can-assume-connection capable devices that are activated with
+	 * a connection that can be assumed.
+	 */
+	if (   nm_device_interface_can_assume_connection (NM_DEVICE_INTERFACE (self))
+	    && (nm_device_get_state (self) == NM_DEVICE_STATE_ACTIVATED)) {
+		NMConnection *connection;
+	    NMSettingIP4Config *s_ip4;
+		const char *method = NULL;
+
+		/* Only system connections can be left up */
+		connection = nm_act_request_get_connection (priv->act_request);
+		if (   connection
+		    && (nm_connection_get_scope (connection) == NM_CONNECTION_SCOPE_SYSTEM)) {
+
+			/* Only static or DHCP IPv4 connections can be left up.
+			 * All IPv6 connections can be left up, so we don't have
+			 * to check that.
+			 */
+			s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG);
+			g_assert (s_ip4);
+
+			method = nm_setting_ip4_config_get_method (s_ip4);
+			if (   !method
+			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)
+			    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+				take_down = FALSE;
+		}
+	}
+
+	/* Clear any delayed transitions */
+	delayed_transitions_clear (self);
+
+	/* Clean up and stop DHCP */
+	dhcp4_cleanup (self, take_down);
+	dhcp6_cleanup (self, take_down);
+	addrconf6_cleanup (self);
+	dnsmasq_cleanup (self);
+
+	/* Take the device itself down and clear its IPv4 configuration */
+	if (priv->managed && take_down) {
+		NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
+
+		nm_device_take_down (self, FALSE, NM_DEVICE_STATE_REASON_REMOVED);
+		nm_device_set_ip4_config (self, NULL, FALSE, &ignored);
+	}
+
+	/* reset the saved RA value */
+	if (priv->ip6_accept_ra_path) {
+		nm_utils_do_sysctl (priv->ip6_accept_ra_path,
+		                    priv->ip6_accept_ra_save ? "1\n" : "0\n");
+	}
+	g_free (priv->ip6_accept_ra_path);
+
+	activation_source_clear (self, TRUE, AF_INET);
+	activation_source_clear (self, TRUE, AF_INET6);
+
+	clear_act_request (self);
+
+out:
+	G_OBJECT_CLASS (nm_device_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	g_object_unref (priv->dhcp_manager);
+
+	g_free (priv->udi);
+	g_free (priv->iface);
+	g_free (priv->ip_iface);
+	g_free (priv->driver);
+	g_free (priv->type_desc);
+	if (priv->dhcp_anycast_address)
+		g_byte_array_free (priv->dhcp_anycast_address, TRUE);
+
+	G_OBJECT_CLASS (nm_device_parent_class)->finalize (object);
+}
+
+
+static void
+set_property (GObject *object, guint prop_id,
+			  const GValue *value, GParamSpec *pspec)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (object);
+ 
+	switch (prop_id) {
+	case NM_DEVICE_INTERFACE_PROP_UDI:
+		/* construct-only */
+		priv->udi = g_strdup (g_value_get_string (value));
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IFACE:
+		g_free (priv->iface);
+		priv->ifindex = 0;
+		priv->iface = g_value_dup_string (value);
+		if (priv->iface) {
+			priv->ifindex = nm_netlink_iface_to_index (priv->iface);
+			if (priv->ifindex < 0) {
+				nm_log_warn (LOGD_HW, "(%s): failed to look up interface index", priv->iface);
+			}
+		}
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DRIVER:
+		priv->driver = g_strdup (g_value_get_string (value));
+		break;
+	case NM_DEVICE_INTERFACE_PROP_CAPABILITIES:
+		priv->capabilities = g_value_get_uint (value);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IP4_ADDRESS:
+		priv->ip4_address = g_value_get_uint (value);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_MANAGED:
+		priv->managed = g_value_get_boolean (value);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DEVICE_TYPE:
+		g_return_if_fail (priv->type == NM_DEVICE_TYPE_UNKNOWN);
+		priv->type = g_value_get_uint (value);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_TYPE_DESC:
+		g_free (priv->type_desc);
+		priv->type_desc = g_value_dup_string (value);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_RFKILL_TYPE:
+		priv->rfkill_type = g_value_get_uint (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+			  GValue *value, GParamSpec *pspec)
+{
+	NMDevice *self = NM_DEVICE (object);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+	NMDeviceState state;
+
+	state = nm_device_get_state (self);
+
+	switch (prop_id) {
+	case NM_DEVICE_INTERFACE_PROP_UDI:
+		g_value_set_string (value, priv->udi);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IFACE:
+		g_value_set_string (value, priv->iface);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IFINDEX:
+		g_value_set_int (value, priv->ifindex);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DRIVER:
+		g_value_set_string (value, priv->driver);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_CAPABILITIES:
+		g_value_set_uint (value, priv->capabilities);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IP4_ADDRESS:
+		g_value_set_uint (value, priv->ip4_address);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IP4_CONFIG:
+		if ((state == NM_DEVICE_STATE_ACTIVATED) || (state == NM_DEVICE_STATE_IP_CONFIG)) {
+			if (priv->ip4_config) {
+				g_value_set_boxed (value, nm_ip4_config_get_dbus_path (priv->ip4_config));
+				break;
+			}
+		}
+		g_value_set_boxed (value, "/");
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DHCP4_CONFIG:
+		if (   ((state == NM_DEVICE_STATE_ACTIVATED) || (state == NM_DEVICE_STATE_IP_CONFIG))
+		    && priv->dhcp4_client)
+			g_value_set_boxed (value, nm_dhcp4_config_get_dbus_path (priv->dhcp4_config));
+		else
+			g_value_set_boxed (value, "/");
+		break;
+	case NM_DEVICE_INTERFACE_PROP_IP6_CONFIG:
+		if ((state == NM_DEVICE_STATE_ACTIVATED) || (state == NM_DEVICE_STATE_IP_CONFIG)) {
+			if (priv->ip6_config) {
+				g_value_set_boxed (value, nm_ip6_config_get_dbus_path (priv->ip6_config));
+				break;
+			}
+		}
+		g_value_set_boxed (value, "/");
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DHCP6_CONFIG:
+		if (   ((state == NM_DEVICE_STATE_ACTIVATED) || (state == NM_DEVICE_STATE_IP_CONFIG))
+		    && priv->dhcp6_client)
+			g_value_set_boxed (value, nm_dhcp6_config_get_dbus_path (priv->dhcp6_config));
+		else
+			g_value_set_boxed (value, "/");
+		break;
+	case NM_DEVICE_INTERFACE_PROP_STATE:
+		g_value_set_uint (value, priv->state);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_DEVICE_TYPE:
+		g_value_set_uint (value, priv->type);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_MANAGED:
+		g_value_set_boolean (value, priv->managed);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_TYPE_DESC:
+		g_value_set_string (value, priv->type_desc);
+		break;
+	case NM_DEVICE_INTERFACE_PROP_RFKILL_TYPE:
+		g_value_set_uint (value, priv->rfkill_type);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+
+static void
+nm_device_class_init (NMDeviceClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMDevicePrivate));
+
+	/* Virtual methods */
+	object_class->dispose = dispose;
+	object_class->finalize = finalize;
+	object_class->set_property = set_property;
+	object_class->get_property = get_property;
+	object_class->constructor = constructor;
+
+	klass->get_type_capabilities = real_get_type_capabilities;
+	klass->get_generic_capabilities = real_get_generic_capabilities;
+	klass->act_stage1_prepare = real_act_stage1_prepare;
+	klass->act_stage2_config = real_act_stage2_config;
+	klass->act_stage3_ip4_config_start = real_act_stage3_ip4_config_start;
+	klass->act_stage3_ip6_config_start = real_act_stage3_ip6_config_start;
+	klass->act_stage4_get_ip4_config = real_act_stage4_get_ip4_config;
+	klass->act_stage4_get_ip6_config = real_act_stage4_get_ip6_config;
+	klass->act_stage4_ip4_config_timeout = real_act_stage4_ip4_config_timeout;
+	klass->act_stage4_ip6_config_timeout = real_act_stage4_ip6_config_timeout;
+
+	/* Properties */
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_UDI,
+									  NM_DEVICE_INTERFACE_UDI);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_IFACE,
+									  NM_DEVICE_INTERFACE_IFACE);
+
+	g_object_class_override_property (object_class,
+	                                  NM_DEVICE_INTERFACE_PROP_IFINDEX,
+	                                  NM_DEVICE_INTERFACE_IFINDEX);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_DRIVER,
+									  NM_DEVICE_INTERFACE_DRIVER);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_CAPABILITIES,
+									  NM_DEVICE_INTERFACE_CAPABILITIES);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_IP4_ADDRESS,
+									  NM_DEVICE_INTERFACE_IP4_ADDRESS);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_IP4_CONFIG,
+									  NM_DEVICE_INTERFACE_IP4_CONFIG);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_DHCP4_CONFIG,
+									  NM_DEVICE_INTERFACE_DHCP4_CONFIG);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_IP6_CONFIG,
+									  NM_DEVICE_INTERFACE_IP6_CONFIG);
+
+	g_object_class_override_property (object_class,
+	                                  NM_DEVICE_INTERFACE_PROP_DHCP6_CONFIG,
+	                                  NM_DEVICE_INTERFACE_DHCP6_CONFIG);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_STATE,
+									  NM_DEVICE_INTERFACE_STATE);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_DEVICE_TYPE,
+									  NM_DEVICE_INTERFACE_DEVICE_TYPE);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_MANAGED,
+									  NM_DEVICE_INTERFACE_MANAGED);
+
+	g_object_class_override_property (object_class,
+									  NM_DEVICE_INTERFACE_PROP_TYPE_DESC,
+									  NM_DEVICE_INTERFACE_TYPE_DESC);
+
+	g_object_class_override_property (object_class,
+	                                  NM_DEVICE_INTERFACE_PROP_RFKILL_TYPE,
+	                                  NM_DEVICE_INTERFACE_RFKILL_TYPE);
+
+	signals[AUTOCONNECT_ALLOWED] =
+		g_signal_new ("autoconnect-allowed",
+		              G_OBJECT_CLASS_TYPE (object_class),
+		              G_SIGNAL_RUN_LAST,
+		              0,
+		              autoconnect_allowed_accumulator, NULL,
+		              _nm_marshal_BOOLEAN__VOID,
+		              G_TYPE_BOOLEAN, 0);
+}
+
+static gboolean
+failed_to_disconnected (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_log_dbg (LOGD_DEVICE, "(%s): running failed->disconnected transition",
+	            nm_device_get_iface (self));
+	priv->failed_to_disconnected_id = 0;
+	nm_device_state_changed (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
+	return FALSE;
+}
+
+static gboolean
+unavailable_to_disconnected (gpointer user_data)
+{
+	NMDevice *self = NM_DEVICE (user_data);
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
+
+	nm_log_dbg (LOGD_DEVICE, "(%s): running unavailable->disconnected transition",
+	            nm_device_get_iface (self));
+	priv->unavailable_to_disconnected_id = 0;
+	nm_device_state_changed (self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
+	return FALSE;
+}
+
+void
+nm_device_state_changed (NMDevice *device,
+                         NMDeviceState state,
+                         NMDeviceStateReason reason)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	NMDeviceState old_state;
+	NMActRequest *req;
+	gboolean no_firmware = FALSE;
+
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	if (priv->state == state)
+		return;
+
+	old_state = priv->state;
+	priv->state = state;
+
+	nm_log_info (LOGD_DEVICE, "(%s): device state change: %d -> %d (reason %d)",
+	             nm_device_get_iface (device), old_state, state, reason);
+
+	/* Clear any delayed transitions */
+	delayed_transitions_clear (device);
+
+	/* Cache the activation request for the dispatcher */
+	req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
+
+	/* Handle the new state here; but anything that could trigger
+	 * another state change should be done below.
+	 */
+	switch (state) {
+	case NM_DEVICE_STATE_UNMANAGED:
+		if (old_state > NM_DEVICE_STATE_UNMANAGED)
+			nm_device_take_down (device, TRUE, reason);
+		break;
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		if (old_state == NM_DEVICE_STATE_UNMANAGED) {
+			if (!nm_device_bring_up (device, TRUE, &no_firmware) && no_firmware) {
+				nm_log_warn (LOGD_HW, "%s: firmware may be missing.", nm_device_get_iface (device));
+			}
+		}
+		/* Ensure the device gets deactivated in response to stuff like
+		 * carrier changes or rfkill.  But don't deactivate devices that are
+		 * about to assume a connection since that defeats the purpose of
+		 * assuming the device's existing connection.
+		 */
+		if (reason != NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED)
+			nm_device_interface_deactivate (NM_DEVICE_INTERFACE (device), reason);
+		break;
+	case NM_DEVICE_STATE_DISCONNECTED:
+		if (old_state != NM_DEVICE_STATE_UNAVAILABLE)
+			nm_device_interface_deactivate (NM_DEVICE_INTERFACE (device), reason);
+		break;
+	default:
+		priv->autoconnect_inhibit = FALSE;
+		break;
+	}
+
+	g_object_notify (G_OBJECT (device), NM_DEVICE_INTERFACE_STATE);
+	g_signal_emit_by_name (device, "state-changed", state, old_state, reason);
+
+	/* Post-process the event after internal notification */
+
+	switch (state) {
+	case NM_DEVICE_STATE_UNAVAILABLE:
+		/* If the device can activate now (ie, it's got a carrier, the supplicant
+		 * is active, or whatever) schedule a delayed transition to DISCONNECTED
+		 * to get things rolling.  The device can't transition immediately becuase
+		 * we can't change states again from the state handler for a variety of
+		 * reasons.
+		 */
+		if (nm_device_is_available (device)) {
+			nm_log_dbg (LOGD_DEVICE, "(%s): device is available, will transition to DISCONNECTED",
+			            nm_device_get_iface (device));
+			priv->unavailable_to_disconnected_id = g_idle_add (unavailable_to_disconnected, device);
+		} else {
+			nm_log_dbg (LOGD_DEVICE, "(%s): device not yet available for transition to DISCONNECTED",
+			            nm_device_get_iface (device));
+		}
+		break;
+	case NM_DEVICE_STATE_ACTIVATED:
+		nm_log_info (LOGD_DEVICE, "Activation (%s) successful, device activated.",
+		             nm_device_get_iface (device));
+		nm_utils_call_dispatcher ("up", nm_act_request_get_connection (req), device, NULL);
+		break;
+	case NM_DEVICE_STATE_FAILED:
+		nm_log_warn (LOGD_DEVICE, "Activation (%s) failed.", nm_device_get_iface (device));
+		/* Schedule the transition to DISCONNECTED.  The device can't transition
+		 * immediately becuase we can't change states again from the state
+		 * handler for a variety of reasons.
+		 */
+		priv->failed_to_disconnected_id = g_idle_add (failed_to_disconnected, device);
+		break;
+	default:
+		break;
+	}
+
+	if (old_state == NM_DEVICE_STATE_ACTIVATED)
+		nm_utils_call_dispatcher ("down", nm_act_request_get_connection (req), device, NULL);
+
+	/* Dispose of the cached activation request */
+	if (req)
+		g_object_unref (req);
+}
+
+NMDeviceState
+nm_device_get_state (NMDevice *device)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (device), NM_DEVICE_STATE_UNKNOWN);
+
+	return NM_DEVICE_GET_PRIVATE (device)->state;
+}
+
+gboolean
+nm_device_get_managed (NMDevice *device)
+{
+	g_return_val_if_fail (NM_IS_DEVICE (device), FALSE);
+
+	return NM_DEVICE_GET_PRIVATE (device)->managed;
+}
+
+void
+nm_device_set_managed (NMDevice *device,
+                       gboolean managed,
+                       NMDeviceStateReason reason)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE (device);
+	if (priv->managed == managed)
+		return;
+
+	priv->managed = managed;
+	nm_log_info (LOGD_DEVICE, "(%s): now %s",
+	             nm_device_get_iface (device),
+	             managed ? "managed" : "unmanaged");
+
+	g_object_notify (G_OBJECT (device), NM_DEVICE_INTERFACE_MANAGED);
+
+	/* If now managed, jump to unavailable */
+	if (managed)
+		nm_device_state_changed (device, NM_DEVICE_STATE_UNAVAILABLE, reason);
+	else
+		nm_device_state_changed (device, NM_DEVICE_STATE_UNMANAGED, reason);
+}
+
+static gboolean
+spec_match_list (NMDeviceInterface *device, const GSList *specs)
+{
+	NMDevice *self;
+
+	g_return_val_if_fail (device != NULL, FALSE);
+
+	self = NM_DEVICE (device);
+	if (NM_DEVICE_GET_CLASS (self)->spec_match_list)
+		return NM_DEVICE_GET_CLASS (self)->spec_match_list (self, specs);
+
+	return FALSE;
+}
+
+static NMConnection *
+connection_match_config (NMDeviceInterface *device, const GSList *connections)
+{
+	g_return_val_if_fail (device != NULL, FALSE);
+
+	if (NM_DEVICE_GET_CLASS (device)->connection_match_config)
+		return NM_DEVICE_GET_CLASS (device)->connection_match_config (NM_DEVICE (device), connections);
+	return NULL;
+}
+
+void
+nm_device_set_dhcp_timeout (NMDevice *device, guint32 timeout)
+{
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	NM_DEVICE_GET_PRIVATE (device)->dhcp_timeout = timeout;
+}
+
+void
+nm_device_set_dhcp_anycast_address (NMDevice *device, guint8 *addr)
+{
+	NMDevicePrivate *priv;
+
+	g_return_if_fail (NM_IS_DEVICE (device));
+
+	priv = NM_DEVICE_GET_PRIVATE (device);
+
+	if (priv->dhcp_anycast_address) {
+		g_byte_array_free (priv->dhcp_anycast_address, TRUE);
+		priv->dhcp_anycast_address = NULL;
+	}
+
+	if (addr) {
+		priv->dhcp_anycast_address = g_byte_array_sized_new (ETH_ALEN);
+		g_byte_array_append (priv->dhcp_anycast_address, addr, ETH_ALEN);
+	}
+}
+
+
+void
+nm_device_clear_autoconnect_inhibit (NMDevice *device)
+{
+	NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device);
+	g_return_if_fail (priv);
+	priv->autoconnect_inhibit = FALSE;
+}
+