summary refs log tree commit diff
path: root/src/devices/wwan
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2015-05-05 17:48:57 +0200
committerMichael Biebl <biebl@debian.org>2015-05-05 17:48:57 +0200
commitf408e27bccfacf347605a8d98649975a68f38a17 (patch)
tree654fd6695c31511baf919b1c0870d119a352ed75 /src/devices/wwan
parent2c032d8f1c6292c1338a615e6ec40252889ba85c (diff)
Imported Upstream version 1.0.2 upstream/1.0.2
Diffstat (limited to 'src/devices/wwan')
-rw-r--r--src/devices/wwan/Makefile.in30
-rw-r--r--src/devices/wwan/nm-device-modem.c71
-rw-r--r--src/devices/wwan/nm-modem-broadband.c385
-rw-r--r--src/devices/wwan/nm-modem.c310
-rw-r--r--src/devices/wwan/nm-modem.h24
-rw-r--r--src/devices/wwan/wwan-exports.ver2
6 files changed, 602 insertions, 220 deletions
diff --git a/src/devices/wwan/Makefile.in b/src/devices/wwan/Makefile.in
index 18cbfdd3..1a8aef3b 100644
--- a/src/devices/wwan/Makefile.in
+++ b/src/devices/wwan/Makefile.in
@@ -1,7 +1,7 @@
-# Makefile.in generated by automake 1.13.4 from Makefile.am.
+# Makefile.in generated by automake 1.15 from Makefile.am.
 # @configure_input@
 
-# Copyright (C) 1994-2013 Free Software Foundation, Inc.
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
 
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -15,7 +15,17 @@
 @SET_MAKE@
 
 VPATH = @srcdir@
-am__is_gnu_make = test -n '$(MAKEFILE_LIST)' && test -n '$(MAKELEVEL)'
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
 am__make_running_with_option = \
   case $${target_option-} in \
       ?) ;; \
@@ -79,8 +89,6 @@ POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
 subdir = src/devices/wwan
-DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
-	$(top_srcdir)/build-aux/depcomp
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/compiler_warnings.m4 \
@@ -97,6 +105,7 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
 	$(top_srcdir)/m4/vapigen.m4 $(top_srcdir)/configure.ac
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
 mkinstalldirs = $(install_sh) -d
 CONFIG_HEADER = $(top_builddir)/config.h
 CONFIG_CLEAN_FILES =
@@ -216,6 +225,8 @@ am__define_uniq_tagged_files = \
   done | $(am__uniquify_input)`
 ETAGS = etags
 CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+	$(top_srcdir)/build-aux/depcomp
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
 ALL_LINGUAS = @ALL_LINGUAS@
@@ -242,7 +253,6 @@ CXXCPP = @CXXCPP@
 CXXDEPMODE = @CXXDEPMODE@
 CXXFLAGS = @CXXFLAGS@
 CYGPATH_W = @CYGPATH_W@
-DATADIRNAME = @DATADIRNAME@
 DBUS_CFLAGS = @DBUS_CFLAGS@
 DBUS_GLIB_100_CFLAGS = @DBUS_GLIB_100_CFLAGS@
 DBUS_GLIB_100_LIBS = @DBUS_GLIB_100_LIBS@
@@ -277,6 +287,7 @@ GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
 GNUTLS_LIBS = @GNUTLS_LIBS@
 GREP = @GREP@
 GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
 GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
 GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
 GTKDOC_MKPDF = @GTKDOC_MKPDF@
@@ -553,7 +564,6 @@ $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__confi
 	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/devices/wwan/Makefile'; \
 	$(am__cd) $(top_srcdir) && \
 	  $(AUTOMAKE) --gnu src/devices/wwan/Makefile
-.PRECIOUS: Makefile
 Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
 	@case '$?' in \
 	  *config.status*) \
@@ -631,14 +641,14 @@ distclean-compile:
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c $<
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
 
 .c.obj:
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
 @am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'`
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
 
 .c.lo:
 @am__fastdepCC_TRUE@	$(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@@ -866,6 +876,8 @@ uninstall-am: uninstall-pkglibLTLIBRARIES
 	mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
 	uninstall-am uninstall-pkglibLTLIBRARIES
 
+.PRECIOUS: Makefile
+
 include $(GLIB_MAKEFILE)
 
 @GNOME_CODE_COVERAGE_RULES@
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index f681e59c..f819d1d7 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -295,16 +295,19 @@ modem_state_cb (NMModem *modem,
 		 * device's enabled/disabled state.
 		 */
 		nm_modem_set_mm_enabled (priv->modem, priv->rf_enabled);
+
+		/* Now allow connections without a PIN to be available */
+		nm_device_recheck_available_connections (device);
 	}
 
-	if ((dev_state >= NM_DEVICE_STATE_DISCONNECTED) && !nm_device_is_available (device)) {
+	if ((dev_state >= NM_DEVICE_STATE_DISCONNECTED) && !nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_UNAVAILABLE,
 		                         NM_DEVICE_STATE_REASON_MODEM_FAILED);
 		return;
 	}
 
-	if ((dev_state == NM_DEVICE_STATE_UNAVAILABLE) && nm_device_is_available (device)) {
+	if ((dev_state == NM_DEVICE_STATE_UNAVAILABLE) && nm_device_is_available (device, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)) {
 		nm_device_state_changed (device,
 		                         NM_DEVICE_STATE_DISCONNECTED,
 		                         NM_DEVICE_STATE_REASON_MODEM_AVAILABLE);
@@ -391,6 +394,7 @@ check_connection_compatible (NMDevice *device, NMConnection *connection)
 static gboolean
 check_connection_available (NMDevice *device,
                             NMConnection *connection,
+                            NMDeviceCheckConAvailableFlags flags,
                             const char *specific_object)
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (device);
@@ -433,6 +437,50 @@ deactivate (NMDevice *device)
 	nm_modem_deactivate (NM_DEVICE_MODEM_GET_PRIVATE (device)->modem, device);
 }
 
+/***********************************************************/
+
+static gboolean
+deactivate_async_finish (NMDevice *self,
+                         GAsyncResult *res,
+                         GError **error)
+{
+	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+modem_deactivate_async_ready (NMModem *modem,
+                              GAsyncResult *res,
+                              GSimpleAsyncResult *simple)
+{
+	GError *error = NULL;
+
+	if (!nm_modem_deactivate_async_finish (modem, res, &error))
+		g_simple_async_result_take_error (simple, error);
+	g_simple_async_result_complete (simple);
+	g_object_unref (simple);
+}
+
+static void
+deactivate_async (NMDevice *self,
+                  GCancellable *cancellable,
+                  GAsyncReadyCallback callback,
+                  gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+
+	simple = g_simple_async_result_new (G_OBJECT (self),
+	                                    callback,
+	                                    user_data,
+	                                    deactivate_async);
+	nm_modem_deactivate_async (NM_DEVICE_MODEM_GET_PRIVATE (self)->modem,
+	                           self,
+	                           cancellable,
+	                           (GAsyncReadyCallback) modem_deactivate_async_ready,
+	                           simple);
+}
+
+/***********************************************************/
+
 static NMActStageReturn
 act_stage1_prepare (NMDevice *device, NMDeviceStateReason *reason)
 {
@@ -536,24 +584,19 @@ set_enabled (NMDevice *device, gboolean enabled)
 }
 
 static gboolean
-is_available (NMDevice *device)
+is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
 	NMDeviceModem *self = NM_DEVICE_MODEM (device);
-	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (device);
+	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (self);
 	NMModemState modem_state;
 
-	if (!priv->rf_enabled) {
-		_LOGD (LOGD_MB, "not available because WWAN airplane mode is on");
+	if (!priv->rf_enabled)
 		return FALSE;
-	}
 
 	g_assert (priv->modem);
 	modem_state = nm_modem_get_state (priv->modem);
-	if (modem_state <= NM_MODEM_STATE_INITIALIZING) {
-		_LOGD (LOGD_MB, "not available because modem is not ready (%s)",
-		       nm_modem_state_to_string (modem_state));
+	if (modem_state <= NM_MODEM_STATE_INITIALIZING)
 		return FALSE;
-	}
 
 	return TRUE;
 }
@@ -638,7 +681,7 @@ set_modem (NMDeviceModem *self, NMModem *modem)
 
 static void
 set_property (GObject *object, guint prop_id,
-			  const GValue *value, GParamSpec *pspec)
+              const GValue *value, GParamSpec *pspec)
 {
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object);
 
@@ -661,7 +704,7 @@ set_property (GObject *object, guint prop_id,
 
 static void
 get_property (GObject *object, guint prop_id,
-			  GValue *value, GParamSpec *pspec)
+              GValue *value, GParamSpec *pspec)
 {
 	NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE (object);
 
@@ -711,6 +754,8 @@ nm_device_modem_class_init (NMDeviceModemClass *mclass)
 	device_class->check_connection_compatible = check_connection_compatible;
 	device_class->check_connection_available = check_connection_available;
 	device_class->complete_connection = complete_connection;
+	device_class->deactivate_async = deactivate_async;
+	device_class->deactivate_async_finish = deactivate_async_finish;
 	device_class->deactivate = deactivate;
 	device_class->act_stage1_prepare = act_stage1_prepare;
 	device_class->act_stage2_config = act_stage2_config;
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index f8239b79..4bb72fd1 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -42,7 +42,7 @@ struct _NMModemBroadbandPrivate {
 	MMModemSimple *simple_iface;
 
 	/* Connection setup */
-	MMSimpleConnectProperties *connect_properties;
+
 	MMBearer *bearer;
 	MMBearerIpConfig *ipv4_config;
 	MMBearerIpConfig *ipv6_config;
@@ -176,78 +176,6 @@ get_bearer_ip_method (MMBearerIpConfig *config)
 	return NM_MODEM_IP_METHOD_UNKNOWN;
 }
 
-static void
-connect_ready (MMModemSimple *simple_iface,
-               GAsyncResult *res,
-               NMModemBroadband *self)
-{
-	GError *error = NULL;
-	NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
-	NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
-
-	g_clear_object (&self->priv->connect_properties);
-
-	self->priv->bearer = mm_modem_simple_connect_finish (simple_iface, res, &error);
-	if (!self->priv->bearer) {
-		if (g_error_matches (error,
-		                     MM_MOBILE_EQUIPMENT_ERROR,
-		                     MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
-			(g_error_matches (error,
-		                          MM_CORE_ERROR,
-		                          MM_CORE_ERROR_UNAUTHORIZED) &&
-			 mm_modem_get_unlock_required (self->priv->modem_iface) == MM_MODEM_LOCK_SIM_PIN)) {
-			/* Request PIN */
-			ask_for_pin (self);
-		} else {
-			/* Strip remote error info before logging it */
-			if (g_dbus_error_is_remote_error (error))
-				g_dbus_error_strip_remote_error (error);
-
-			nm_log_warn (LOGD_MB, "(%s) failed to connect modem: %s",
-			             nm_modem_get_uid (NM_MODEM (self)),
-			             error && error->message ? error->message : "(unknown)");
-			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error));
-		}
-
-		g_clear_error (&error);
-		g_object_unref (self);
-		return;
-	}
-
-	/* Grab IP configurations */
-	self->priv->ipv4_config = mm_bearer_get_ipv4_config (self->priv->bearer);
-	if (self->priv->ipv4_config)
-		ip4_method = get_bearer_ip_method (self->priv->ipv4_config);
-
-	self->priv->ipv6_config = mm_bearer_get_ipv6_config (self->priv->bearer);
-	if (self->priv->ipv6_config)
-		ip6_method = get_bearer_ip_method (self->priv->ipv6_config);
-
-	if (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN &&
-	    ip6_method == NM_MODEM_IP_METHOD_UNKNOWN) {
-		nm_log_warn (LOGD_MB, "(%s) failed to connect modem: invalid bearer IP configuration",
-		             nm_modem_get_uid (NM_MODEM (self)));
-
-		error = g_error_new_literal (NM_DEVICE_ERROR,
-		                             NM_DEVICE_ERROR_INVALID_CONNECTION,
-		                             "invalid bearer IP configuration");
-		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, error);
-		g_error_free (error);
-		g_object_unref (self);
-		return;
-	}
-
-	g_object_set (self,
-	              NM_MODEM_DATA_PORT,  mm_bearer_get_interface (self->priv->bearer),
-	              NM_MODEM_IP4_METHOD, ip4_method,
-	              NM_MODEM_IP6_METHOD, ip6_method,
-	              NM_MODEM_IP_TIMEOUT, mm_bearer_get_ip_timeout (self->priv->bearer),
-	              NULL);
-
-	g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, TRUE, NM_DEVICE_STATE_REASON_NONE);
-	g_object_unref (self);
-}
-
 static MMSimpleConnectProperties *
 create_cdma_connect_properties (NMConnection *connection)
 {
@@ -266,15 +194,12 @@ create_cdma_connect_properties (NMConnection *connection)
 }
 
 static MMSimpleConnectProperties *
-create_gsm_connect_properties (NMModem *modem,
-                               NMConnection *connection,
-                               GError **error)
+create_gsm_connect_properties (NMConnection *connection)
 {
 	NMSettingGsm *setting;
 	NMSettingPpp *s_ppp;
 	MMSimpleConnectProperties *properties;
 	const gchar *str;
-	NMModemIPType ip_type;
 
 	setting = nm_connection_get_setting_gsm (connection);
 	properties = mm_simple_connect_properties_new ();
@@ -329,23 +254,137 @@ create_gsm_connect_properties (NMModem *modem,
 		mm_simple_connect_properties_set_allowed_auth (properties, allowed_auth);
 	}
 
-	/* Determine IP types to use when connecting */
-	ip_type = nm_modem_get_connection_ip_type (modem, connection, error);
-	if (ip_type == NM_MODEM_IP_TYPE_UNKNOWN) {
-		g_object_unref (properties);
-		return NULL;
+	return properties;
+}
+
+typedef struct {
+	NMModemBroadband *self;
+	MMModemCapability caps;
+	MMSimpleConnectProperties *connect_properties;
+	GArray *ip_types;
+	guint ip_types_i;
+	GError *first_error;
+} ActStageContext;
+
+static void
+act_stage_context_free (ActStageContext *ctx)
+{
+	g_clear_error (&ctx->first_error);
+	g_clear_pointer (&ctx->ip_types, (GDestroyNotify) g_array_unref);
+	g_clear_object (&ctx->connect_properties);
+	g_object_unref (ctx->self);
+	g_slice_free (ActStageContext, ctx);
+}
+
+static void act_stage_context_step (ActStageContext *ctx);
+
+static void
+connect_ready (MMModemSimple *simple_iface,
+               GAsyncResult *res,
+               ActStageContext *ctx)
+{
+	GError *error = NULL;
+	NMModemIPMethod ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
+	NMModemIPMethod ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
+
+	ctx->self->priv->bearer = mm_modem_simple_connect_finish (simple_iface, res, &error);
+	if (!ctx->self->priv->bearer) {
+		if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
+		    (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED) &&
+		     mm_modem_get_unlock_required (ctx->self->priv->modem_iface) == MM_MODEM_LOCK_SIM_PIN)) {
+			/* Request PIN */
+			ask_for_pin (ctx->self);
+			g_error_free (error);
+			act_stage_context_free (ctx);
+			return;
+		}
+
+		/* Save the error, if it's the first one */
+		if (!ctx->first_error) {
+			/* Strip remote error info before saving it */
+			if (g_dbus_error_is_remote_error (error))
+				g_dbus_error_strip_remote_error (error);
+			ctx->first_error = error;
+		} else
+			g_error_free (error);
+
+		/* If the modem/provider lies and the IP type we tried isn't supported,
+		 * retry with the next one, if any.
+		 */
+		ctx->ip_types_i++;
+		act_stage_context_step (ctx);
+		return;
 	}
 
-	if (ip_type == NM_MODEM_IP_TYPE_IPV4)
-		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV4);
-	else if (ip_type == NM_MODEM_IP_TYPE_IPV6)
-		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV6);
-	else if (ip_type == NM_MODEM_IP_TYPE_IPV4V6)
-		mm_simple_connect_properties_set_ip_type (properties, MM_BEARER_IP_FAMILY_IPV4V6);
-	else
-		g_assert_not_reached ();
+	/* Grab IP configurations */
+	ctx->self->priv->ipv4_config = mm_bearer_get_ipv4_config (ctx->self->priv->bearer);
+	if (ctx->self->priv->ipv4_config)
+		ip4_method = get_bearer_ip_method (ctx->self->priv->ipv4_config);
 
-	return properties;
+	ctx->self->priv->ipv6_config = mm_bearer_get_ipv6_config (ctx->self->priv->bearer);
+	if (ctx->self->priv->ipv6_config)
+		ip6_method = get_bearer_ip_method (ctx->self->priv->ipv6_config);
+
+	if (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN &&
+	    ip6_method == NM_MODEM_IP_METHOD_UNKNOWN) {
+		nm_log_warn (LOGD_MB, "(%s): failed to connect modem: invalid bearer IP configuration",
+		             nm_modem_get_uid (NM_MODEM (ctx->self)));
+		g_signal_emit_by_name (ctx->self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+		act_stage_context_free (ctx);
+		return;
+	}
+
+	g_object_set (ctx->self,
+	              NM_MODEM_DATA_PORT,  mm_bearer_get_interface (ctx->self->priv->bearer),
+	              NM_MODEM_IP4_METHOD, ip4_method,
+	              NM_MODEM_IP6_METHOD, ip6_method,
+	              NM_MODEM_IP_TIMEOUT, mm_bearer_get_ip_timeout (ctx->self->priv->bearer),
+	              NULL);
+
+	g_signal_emit_by_name (ctx->self, NM_MODEM_PREPARE_RESULT, TRUE, NM_DEVICE_STATE_REASON_NONE);
+	act_stage_context_free (ctx);
+}
+
+static void
+act_stage_context_step (ActStageContext *ctx)
+{
+	if (ctx->ip_types_i < ctx->ip_types->len) {
+		NMModemIPType current;
+
+		current = g_array_index (ctx->ip_types, NMModemIPType, ctx->ip_types_i);
+
+		if (current == NM_MODEM_IP_TYPE_IPV4)
+			mm_simple_connect_properties_set_ip_type (ctx->connect_properties, MM_BEARER_IP_FAMILY_IPV4);
+		else if (current == NM_MODEM_IP_TYPE_IPV6)
+			mm_simple_connect_properties_set_ip_type (ctx->connect_properties, MM_BEARER_IP_FAMILY_IPV6);
+		else if (current == NM_MODEM_IP_TYPE_IPV4V6)
+			mm_simple_connect_properties_set_ip_type (ctx->connect_properties, MM_BEARER_IP_FAMILY_IPV4V6);
+		else
+			g_assert_not_reached ();
+
+		nm_log_dbg (LOGD_MB, "(%s): launching connection with ip type '%s'",
+		            nm_modem_get_uid (NM_MODEM (ctx->self)),
+		            nm_modem_ip_type_to_string (current));
+
+		mm_modem_simple_connect (ctx->self->priv->simple_iface,
+		                         ctx->connect_properties,
+		                         NULL,
+		                         (GAsyncReadyCallback)connect_ready,
+		                         ctx);
+		return;
+	}
+
+	/* If we have a saved error from a previous attempt, use it */
+	if (!ctx->first_error)
+		ctx->first_error = g_error_new_literal (NM_DEVICE_ERROR,
+		                                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+		                                        "invalid bearer IP configuration");
+
+	nm_log_warn (LOGD_MB, "(%s): failed to connect modem: %s",
+	             nm_modem_get_uid (NM_MODEM (ctx->self)),
+	             ctx->first_error->message);
+	g_signal_emit_by_name (ctx->self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (ctx->first_error));
+	act_stage_context_free (ctx);
 }
 
 static NMActStageReturn
@@ -354,42 +393,55 @@ act_stage1_prepare (NMModem *_self,
                     NMDeviceStateReason *reason)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
-	MMModemCapability caps;
+	ActStageContext *ctx;
 	GError *error = NULL;
 
-	g_clear_object (&self->priv->connect_properties);
+	/* Make sure we can get the Simple interface from the modem */
+	if (!self->priv->simple_iface) {
+		self->priv->simple_iface = mm_object_get_modem_simple (self->priv->modem_object);
+		if (!self->priv->simple_iface) {
+			nm_log_warn (LOGD_MB, "(%s) cannot access the Simple mobile broadband modem interface",
+			             nm_modem_get_uid (NM_MODEM (self)));
+			*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+		}
+	}
+
+	/* Allocate new context for this activation stage attempt */
+	ctx = g_slice_new0 (ActStageContext);
+	ctx->self = NM_MODEM_BROADBAND (g_object_ref (self));
+	ctx->caps = mm_modem_get_current_capabilities (self->priv->modem_iface);
 
-	caps = mm_modem_get_current_capabilities (self->priv->modem_iface);
-	if (MODEM_CAPS_3GPP (caps))
-		self->priv->connect_properties = create_gsm_connect_properties (_self, connection, &error);
-	else if (MODEM_CAPS_3GPP2 (caps))
-		self->priv->connect_properties = create_cdma_connect_properties (connection);
+	/* Create core connect properties based on the modem capabilities */
+	if (MODEM_CAPS_3GPP (ctx->caps))
+		ctx->connect_properties = create_gsm_connect_properties (connection);
+	else if (MODEM_CAPS_3GPP2 (ctx->caps))
+		ctx->connect_properties = create_cdma_connect_properties (connection);
 	else {
-		nm_log_warn (LOGD_MB, "(%s) not a mobile broadband modem",
-					 nm_modem_get_uid (NM_MODEM (self)));
+		nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': not a mobile broadband modem",
+		             nm_modem_get_uid (NM_MODEM (self)),
+		             nm_connection_get_id (connection));
+		act_stage_context_free (ctx);
 		*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
+	g_assert (ctx->connect_properties);
 
-	if (error) {
+	/* Checkout list of IP types that we need to use in the retries */
+	ctx->ip_types = nm_modem_get_connection_ip_type (NM_MODEM (self), connection, &error);
+	if (!ctx->ip_types) {
 		nm_log_warn (LOGD_MB, "(%s): Failed to connect '%s': %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
 		             nm_connection_get_id (connection),
-		             error->message);
+		             error ? error->message : "unknown error");
 		g_clear_error (&error);
+		act_stage_context_free (ctx);
 		*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
 		return NM_ACT_STAGE_RETURN_FAILURE;
 	}
 
-	if (!self->priv->simple_iface)
-		self->priv->simple_iface = mm_object_get_modem_simple (self->priv->modem_object);
-
 	g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (self->priv->simple_iface), MODEM_CONNECT_TIMEOUT_SECS * 1000);
-	mm_modem_simple_connect (self->priv->simple_iface,
-	                         self->priv->connect_properties,
-	                         NULL,
-	                         (GAsyncReadyCallback)connect_ready,
-	                         g_object_ref (self));
+	act_stage_context_step (ctx);
 
 	return NM_ACT_STAGE_RETURN_POSTPONE;
 }
@@ -566,9 +618,9 @@ set_power_state_low_ready (MMModem *modem,
 
 	if (!mm_modem_set_power_state_finish (modem, result, &error)) {
 		/* Log but ignore errors; not all modems support low power state */
-		nm_log_dbg (LOGD_MB, "(%s) failed to set modem low power state: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             error && error->message ? error->message : "(unknown)");
+		nm_log_dbg (LOGD_MB, "(%s): failed to set modem low power state: %s",
+		            nm_modem_get_uid (NM_MODEM (self)),
+		            error && error->message ? error->message : "(unknown)");
 		g_clear_error (&error);
 	}
 
@@ -591,7 +643,7 @@ modem_disable_ready (MMModem *modem_iface,
 		                          (GAsyncReadyCallback) set_power_state_low_ready,
 		                          g_object_ref (self));
 	} else {
-		nm_log_warn (LOGD_MB, "(%s) failed to disable modem: %s",
+		nm_log_warn (LOGD_MB, "(%s): failed to disable modem: %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
 		             error && error->message ? error->message : "(unknown)");
 		nm_modem_set_prev_state (NM_MODEM (self), "disable failed");
@@ -708,7 +760,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 
 	/* DNS servers */
 	dns = mm_bearer_ip_config_get_dns (self->priv->ipv4_config);
-	for (i = 0; dns[i]; i++) {
+	for (i = 0; dns && dns[i]; i++) {
 		if (   ip4_string_to_num (dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
@@ -761,9 +813,9 @@ stage3_ip6_done (NMModemBroadband *self)
 		/* DHCP/SLAAC is allowed to skip addresses; other methods require it */
 		if (ip_method != NM_MODEM_IP_METHOD_AUTO) {
 			error = g_error_new (NM_DEVICE_ERROR,
-				                 NM_DEVICE_ERROR_INVALID_CONNECTION,
-				                 "(%s) retrieving IPv6 configuration failed: no address given",
-				                 nm_modem_get_uid (NM_MODEM (self)));
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "(%s) retrieving IPv6 configuration failed: no address given",
+			                     nm_modem_get_uid (NM_MODEM (self)));
 		}
 		goto out;
 	}
@@ -792,10 +844,10 @@ stage3_ip6_done (NMModemBroadband *self)
 	if (address_string) {
 		if (!inet_pton (AF_INET6, address_string, (void *) &(address.address))) {
 			error = g_error_new (NM_DEVICE_ERROR,
-				                 NM_DEVICE_ERROR_INVALID_CONNECTION,
-				                 "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'",
-				                 nm_modem_get_uid (NM_MODEM (self)),
-				                 address_string);
+			                     NM_DEVICE_ERROR_INVALID_CONNECTION,
+			                     "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'",
+			                     nm_modem_get_uid (NM_MODEM (self)),
+			                     address_string);
 			goto out;
 		}
 		nm_log_info (LOGD_MB, "  gateway %s", address_string);
@@ -844,20 +896,47 @@ stage3_ip6_config_request (NMModem *_self, NMDeviceStateReason *reason)
 
 typedef struct {
 	NMModemBroadband *self;
+	GSimpleAsyncResult *result;
+	GCancellable *cancellable;
 	gboolean warn;
-} SimpleDisconnectContext;
+} DisconnectContext;
 
 static void
-simple_disconnect_context_free (SimpleDisconnectContext *ctx)
+disconnect_context_complete (DisconnectContext *ctx)
 {
+	g_simple_async_result_complete_in_idle (ctx->result);
+	if (ctx->cancellable)
+		g_object_unref (ctx->cancellable);
+	g_object_unref (ctx->result);
 	g_object_unref (ctx->self);
-	g_slice_free (SimpleDisconnectContext, ctx);
+	g_slice_free (DisconnectContext, ctx);
+}
+
+static gboolean
+disconnect_context_complete_if_cancelled (DisconnectContext *ctx)
+{
+	GError *error = NULL;
+
+	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		disconnect_context_complete (ctx);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+disconnect_finish (NMModem *self,
+                   GAsyncResult *res,
+                   GError **error)
+{
+	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
 }
 
 static void
 simple_disconnect_ready (MMModemSimple *modem_iface,
                          GAsyncResult *res,
-                         SimpleDisconnectContext *ctx)
+                         DisconnectContext *ctx)
 {
 	GError *error = NULL;
 
@@ -865,33 +944,48 @@ simple_disconnect_ready (MMModemSimple *modem_iface,
 		if (ctx->warn)
 			nm_log_warn (LOGD_MB, "(%s) failed to disconnect modem: %s",
 			             nm_modem_get_uid (NM_MODEM (ctx->self)),
-			             error && error->message ? error->message : "(unknown)");
-		g_clear_error (&error);
+			             error->message);
+		g_simple_async_result_take_error (ctx->result, error);
 	}
 
-	simple_disconnect_context_free (ctx);
+	disconnect_context_complete (ctx);
 }
 
 static void
-disconnect (NMModem *modem,
-            gboolean warn)
+disconnect (NMModem *self,
+            gboolean warn,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
 {
-	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
-	SimpleDisconnectContext *ctx;
-
-	if (!self->priv->simple_iface)
-		return;
+	DisconnectContext *ctx;
 
-	ctx = g_slice_new (SimpleDisconnectContext);
+	ctx = g_slice_new (DisconnectContext);
 	ctx->self = g_object_ref (self);
-
+	ctx->result = g_simple_async_result_new (G_OBJECT (self),
+	                                         callback,
+	                                         user_data,
+	                                         disconnect);
 	/* Don't bother warning on FAILED since the modem is already gone */
 	ctx->warn = warn;
 
+	/* Setup cancellable */
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	if (disconnect_context_complete_if_cancelled (ctx))
+		return;
+
+	/* If no simple iface, we're done */
+	if (!ctx->self->priv->simple_iface) {
+		disconnect_context_complete (ctx);
+		return;
+	}
+
+	nm_log_dbg (LOGD_MB, "(%s): notifying ModemManager about the modem disconnection",
+	            nm_modem_get_uid (NM_MODEM (ctx->self)));
 	mm_modem_simple_disconnect (
 		ctx->self->priv->simple_iface,
 		NULL, /* bearer path; if NULL given ALL get disconnected */
-		NULL, /* cancellable */
+		cancellable,
 		(GAsyncReadyCallback)simple_disconnect_ready,
 		ctx);
 }
@@ -899,7 +993,7 @@ disconnect (NMModem *modem,
 /*****************************************************************************/
 
 static void
-deactivate (NMModem *_self, NMDevice *device)
+deactivate_cleanup (NMModem *_self, NMDevice *device)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (_self);
 
@@ -913,7 +1007,7 @@ deactivate (NMModem *_self, NMDevice *device)
 	self->priv->pin_tries = 0;
 
 	/* Chain up parent's */
-	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate (_self, device);
+	NM_MODEM_CLASS (nm_modem_broadband_parent_class)->deactivate_cleanup (_self, device);
 }
 
 /*****************************************************************************/
@@ -1027,7 +1121,7 @@ get_sim_ready (MMModem *modem,
 		              NULL);
 		g_object_unref (new_sim);
 	} else {
-		nm_log_warn (LOGD_MB, "(%s) failed to retrieve SIM object: %s",
+		nm_log_warn (LOGD_MB, "(%s): failed to retrieve SIM object: %s",
 		             nm_modem_get_uid (NM_MODEM (self)),
 		             error && error->message ? error->message : "(unknown)");
 	}
@@ -1062,7 +1156,7 @@ nm_modem_broadband_init (NMModemBroadband *self)
 static void
 set_property (GObject *object,
               guint prop_id,
-			  const GValue *value,
+              const GValue *value,
               GParamSpec *pspec)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (object);
@@ -1095,7 +1189,7 @@ set_property (GObject *object,
 static void
 get_property (GObject *object,
               guint prop_id,
-			  GValue *value,
+              GValue *value,
               GParamSpec *pspec)
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (object);
@@ -1142,7 +1236,8 @@ nm_modem_broadband_class_init (NMModemBroadbandClass *klass)
 	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
 	modem_class->stage3_ip6_config_request = stage3_ip6_config_request;
 	modem_class->disconnect = disconnect;
-	modem_class->deactivate = deactivate;
+	modem_class->disconnect_finish = disconnect_finish;
+	modem_class->deactivate_cleanup = deactivate_cleanup;
 	modem_class->set_mm_enabled = set_mm_enabled;
 	modem_class->get_user_pass = get_user_pass;
 	modem_class->check_connection_compatible = check_connection_compatible;
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index ba1db8a4..69eaddf3 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -175,23 +175,23 @@ nm_modem_set_mm_enabled (NMModem *self,
 	NMModemState prev_state = priv->state;
 
 	if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) {
-		nm_log_dbg (LOGD_MB, "(%s) cannot enable modem: already enabled",
+		nm_log_dbg (LOGD_MB, "(%s): cannot enable modem: already enabled",
 		            nm_modem_get_uid (self));
 		return;
 	}
 	if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) {
-		nm_log_dbg (LOGD_MB, "(%s) cannot disable modem: already disabled",
+		nm_log_dbg (LOGD_MB, "(%s): cannot disable modem: already disabled",
 		            nm_modem_get_uid (self));
 		return;
 	}
 
 	if (priv->state <= NM_MODEM_STATE_INITIALIZING) {
-		nm_log_dbg (LOGD_MB, "(%s) cannot enable/disable modem: initializing or failed",
+		nm_log_dbg (LOGD_MB, "(%s): cannot enable/disable modem: initializing or failed",
 		            nm_modem_get_uid (self));
 		return;
 	} else if (priv->state == NM_MODEM_STATE_LOCKED) {
 		/* Don't try to enable if the modem is locked since that will fail */
-		nm_log_warn (LOGD_MB, "(%s) cannot enable/disable modem: locked",
+		nm_log_warn (LOGD_MB, "(%s): cannot enable/disable modem: locked",
 		             nm_modem_get_uid (self));
 
 		/* Try to unlock the modem if it's being enabled */
@@ -221,17 +221,39 @@ nm_modem_get_supported_ip_types (NMModem *self)
 	return NM_MODEM_GET_PRIVATE (self)->ip_types;
 }
 
+const gchar *
+nm_modem_ip_type_to_string (NMModemIPType ip_type)
+{
+	switch (ip_type) {
+	case NM_MODEM_IP_TYPE_IPV4:
+		return "ipv4";
+	case NM_MODEM_IP_TYPE_IPV6:
+		return "ipv6";
+	case NM_MODEM_IP_TYPE_IPV4V6:
+		return "ipv4v6";
+	default:
+		g_return_val_if_reached ("unknown");
+	}
+}
+
+static GArray *
+build_single_ip_type_array (NMModemIPType type)
+{
+	return g_array_append_val (g_array_sized_new (FALSE, FALSE, sizeof (NMModemIPType), 1), type);
+}
+
 /**
  * nm_modem_get_connection_ip_type:
  * @self: the #NMModem
  * @connection: the #NMConnection to determine IP type to use
  *
- * Given a modem and a connection, determine which NMModemIpType to use
+ * Given a modem and a connection, determine which #NMModemIPTypes to use
  * when connecting.
  *
- * Returns: a single %NMModemIpType value
+ * Returns: an array of #NMModemIpType values, in the order in which they
+ * should be tried.
  */
-NMModemIPType
+GArray *
 nm_modem_get_connection_ip_type (NMModem *self,
                                  NMConnection *connection,
                                  GError **error)
@@ -265,9 +287,9 @@ nm_modem_get_connection_ip_type (NMModem *self,
 			                     NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
 			                     "Connection requested IPv4 but IPv4 is "
 			                     "unsuported by the modem.");
-			return NM_MODEM_IP_TYPE_UNKNOWN;
+			return NULL;
 		}
-		return NM_MODEM_IP_TYPE_IPV4;
+		return build_single_ip_type_array (NM_MODEM_IP_TYPE_IPV4);
 	}
 
 	if (ip6 && !ip4) {
@@ -277,38 +299,54 @@ nm_modem_get_connection_ip_type (NMModem *self,
 			                     NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
 			                     "Connection requested IPv6 but IPv6 is "
 			                     "unsuported by the modem.");
-			return NM_MODEM_IP_TYPE_UNKNOWN;
+			return NULL;
 		}
-		return NM_MODEM_IP_TYPE_IPV6;
+		return build_single_ip_type_array (NM_MODEM_IP_TYPE_IPV6);
 	}
 
 	if (ip4 && ip6) {
-		/* Modem supports dual-stack */
-		if (priv->ip_types & NM_MODEM_IP_TYPE_IPV4V6)
-			return NM_MODEM_IP_TYPE_IPV4V6;
+		NMModemIPType type;
+		GArray *out;
 
-		/* Both IPv4 and IPv6 requested, but modem doesn't support dual-stack;
-		 * if one method is marked "may-fail" then use the other.
-		 */
-		if (ip6_may_fail)
-			return NM_MODEM_IP_TYPE_IPV4;
-		else if (ip4_may_fail)
-			return NM_MODEM_IP_TYPE_IPV6;
+		out = g_array_sized_new (FALSE, FALSE, sizeof (NMModemIPType), 3);
 
+		/* Modem supports dual-stack? */
+		if (priv->ip_types & NM_MODEM_IP_TYPE_IPV4V6) {
+			type = NM_MODEM_IP_TYPE_IPV4V6;
+			g_array_append_val (out, type);
+		}
+
+		/* If IPv6 may-fail=false, we should NOT try IPv4 as fallback */
+		if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV4) && ip6_may_fail) {
+			type = NM_MODEM_IP_TYPE_IPV4;
+			g_array_append_val (out, type);
+		}
+
+		/* If IPv4 may-fail=false, we should NOT try IPv6 as fallback */
+		if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV6) && ip4_may_fail) {
+			type = NM_MODEM_IP_TYPE_IPV6;
+			g_array_append_val (out, type);
+		}
+
+		if (out->len > 0)
+			return out;
+
+		/* Error... */
+		g_array_unref (out);
 		g_set_error_literal (error,
 		                     NM_DEVICE_ERROR,
 		                     NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
 		                     "Connection requested both IPv4 and IPv6 "
 		                     "but dual-stack addressing is unsupported "
 		                     "by the modem.");
-		return NM_MODEM_IP_TYPE_UNKNOWN;
+		return NULL;
 	}
 
 	g_set_error_literal (error,
 	                     NM_DEVICE_ERROR,
 	                     NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
 	                     "Connection specified no IP configuration!");
-	return NM_MODEM_IP_TYPE_UNKNOWN;
+	return NULL;
 }
 
 /*****************************************************************************/
@@ -343,9 +381,9 @@ set_data_port (NMModem *self, const char *new_data_port)
 
 static void
 ppp_ip4_config (NMPPPManager *ppp_manager,
-				const char *iface,
-				NMIP4Config *config,
-				gpointer user_data)
+                const char *iface,
+                NMIP4Config *config,
+                gpointer user_data)
 {
 	NMModem *self = NM_MODEM (user_data);
 	guint32 i, num;
@@ -416,9 +454,9 @@ ppp_ip6_config (NMPPPManager *ppp_manager,
 
 static void
 ppp_stats (NMPPPManager *ppp_manager,
-		   guint32 in_bytes,
-		   guint32 out_bytes,
-		   gpointer user_data)
+           guint32 in_bytes,
+           guint32 out_bytes,
+           gpointer user_data)
 {
 	NMModem *self = NM_MODEM (user_data);
 	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (self);
@@ -464,7 +502,8 @@ ppp_stage3_ip_config_start (NMModem *self,
 	/* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
 	 * use the default one (30s) */
 	if (priv->mm_ip_timeout > 0) {
-		nm_log_info (LOGD_PPP, "using modem-specified IP timeout: %u seconds",
+		nm_log_info (LOGD_PPP, "(%s): using modem-specified IP timeout: %u seconds",
+		             nm_modem_get_uid (self),
 		             priv->mm_ip_timeout);
 		ip_timeout = priv->mm_ip_timeout;
 	}
@@ -486,7 +525,8 @@ ppp_stage3_ip_config_start (NMModem *self,
 
 		ret = NM_ACT_STAGE_RETURN_POSTPONE;
 	} else {
-		nm_log_err (LOGD_PPP, "error starting PPP: (%d) %s",
+		nm_log_err (LOGD_PPP, "(%s): error starting PPP: (%d) %s",
+		            nm_modem_get_uid (self),
 		            error ? error->code : -1,
 		            error && error->message ? error->message : "(unknown)");
 		g_error_free (error);
@@ -705,7 +745,7 @@ modem_secrets_cb (NMActRequest *req,
 	priv->secrets_id = 0;
 
 	if (error)
-		nm_log_warn (LOGD_MB, "%s", error->message);
+		nm_log_warn (LOGD_MB, "(%s): %s", nm_modem_get_uid (self), error->message);
 
 	g_signal_emit (self, signals[AUTH_RESULT], 0, error);
 }
@@ -839,13 +879,12 @@ nm_modem_complete_connection (NMModem *self,
 /*****************************************************************************/
 
 static void
-deactivate (NMModem *self, NMDevice *device)
+deactivate_cleanup (NMModem *self, NMDevice *device)
 {
 	NMModemPrivate *priv;
 	int ifindex;
 
 	g_return_if_fail (NM_IS_MODEM (self));
-	g_return_if_fail (NM_IS_DEVICE (device));
 
 	priv = NM_MODEM_GET_PRIVATE (self);
 
@@ -864,15 +903,19 @@ deactivate (NMModem *self, NMDevice *device)
 		priv->ppp_manager = NULL;
 	}
 
-	if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC ||
-	    priv->ip4_method == NM_MODEM_IP_METHOD_AUTO ||
-	    priv->ip6_method == NM_MODEM_IP_METHOD_STATIC ||
-	    priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) {
-		ifindex = nm_device_get_ip_ifindex (device);
-		if (ifindex > 0) {
-			nm_platform_route_flush (ifindex);
-			nm_platform_address_flush (ifindex);
-			nm_platform_link_set_down (ifindex);
+	if (device) {
+		g_return_if_fail (NM_IS_DEVICE (device));
+
+		if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC ||
+		    priv->ip4_method == NM_MODEM_IP_METHOD_AUTO ||
+		    priv->ip6_method == NM_MODEM_IP_METHOD_STATIC ||
+		    priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) {
+			ifindex = nm_device_get_ip_ifindex (device);
+			if (ifindex > 0) {
+				nm_platform_route_flush (ifindex);
+				nm_platform_address_flush (ifindex);
+				nm_platform_link_set_down (ifindex);
+			}
 		}
 	}
 	priv->ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
@@ -884,10 +927,176 @@ deactivate (NMModem *self, NMDevice *device)
 
 /*****************************************************************************/
 
+typedef enum {
+	DEACTIVATE_CONTEXT_STEP_FIRST,
+	DEACTIVATE_CONTEXT_STEP_CLEANUP,
+	DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP,
+	DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT,
+	DEACTIVATE_CONTEXT_STEP_LAST
+} DeactivateContextStep;
+
+typedef struct {
+	NMModem *self;
+	NMDevice *device;
+	GCancellable *cancellable;
+	GSimpleAsyncResult *result;
+	DeactivateContextStep step;
+	NMPPPManager *ppp_manager;
+} DeactivateContext;
+
+static void
+deactivate_context_complete (DeactivateContext *ctx)
+{
+	if (ctx->ppp_manager)
+		g_object_unref (ctx->ppp_manager);
+	if (ctx->cancellable)
+		g_object_unref (ctx->cancellable);
+	g_simple_async_result_complete_in_idle (ctx->result);
+	g_object_unref (ctx->result);
+	g_object_unref (ctx->device);
+	g_object_unref (ctx->self);
+	g_slice_free (DeactivateContext, ctx);
+}
+
+gboolean
+nm_modem_deactivate_async_finish (NMModem *self,
+                                  GAsyncResult *res,
+                                  GError **error)
+{
+	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void deactivate_step (DeactivateContext *ctx);
+
+static void
+disconnect_ready (NMModem *self,
+                  GAsyncResult *res,
+                  DeactivateContext *ctx)
+{
+	GError *error = NULL;
+
+	if (!NM_MODEM_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		deactivate_context_complete (ctx);
+		return;
+	}
+
+	/* Go on */
+	ctx->step++;
+	deactivate_step (ctx);
+}
+
+static void
+ppp_manager_stop_ready (NMPPPManager *ppp_manager,
+                        GAsyncResult *res,
+                        DeactivateContext *ctx)
+{
+	GError *error = NULL;
+
+	if (!nm_ppp_manager_stop_finish (ppp_manager, res, &error)) {
+		nm_log_warn (LOGD_MB, "(%s): cannot stop PPP manager: %s",
+		             nm_modem_get_uid (ctx->self),
+		             error->message);
+		g_simple_async_result_take_error (ctx->result, error);
+		deactivate_context_complete (ctx);
+		return;
+	}
+
+	/* Go on */
+	ctx->step++;
+	deactivate_step (ctx);
+}
+
+static void
+deactivate_step (DeactivateContext *ctx)
+{
+	NMModemPrivate *priv = NM_MODEM_GET_PRIVATE (ctx->self);
+	GError *error = NULL;
+
+	/* Check cancellable in each step */
+	if (g_cancellable_set_error_if_cancelled (ctx->cancellable, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		deactivate_context_complete (ctx);
+		return;
+	}
+
+	switch (ctx->step) {
+	case DEACTIVATE_CONTEXT_STEP_FIRST:
+		ctx->step++;
+		/* Fall down */
+
+	case DEACTIVATE_CONTEXT_STEP_CLEANUP:
+		/* Make sure we keep a ref to the PPP manager if there is one */
+		if (priv->ppp_manager)
+			ctx->ppp_manager = g_object_ref (priv->ppp_manager);
+		/* Run cleanup */
+		NM_MODEM_GET_CLASS (ctx->self)->deactivate_cleanup (ctx->self, ctx->device);
+		ctx->step++;
+		/* Fall down */
+
+	case DEACTIVATE_CONTEXT_STEP_PPP_MANAGER_STOP:
+		/* If we have a PPP manager, stop it */
+		if (ctx->ppp_manager) {
+			nm_ppp_manager_stop (ctx->ppp_manager,
+			                     ctx->cancellable,
+			                     (GAsyncReadyCallback) ppp_manager_stop_ready,
+			                     ctx);
+			return;
+		}
+		ctx->step++;
+		/* Fall down */
+
+	case DEACTIVATE_CONTEXT_STEP_MM_DISCONNECT:
+		/* Disconnect asynchronously */
+		NM_MODEM_GET_CLASS (ctx->self)->disconnect (ctx->self,
+		                                            FALSE,
+		                                            ctx->cancellable,
+		                                            (GAsyncReadyCallback) disconnect_ready,
+		                                            ctx);
+		return;
+
+	case DEACTIVATE_CONTEXT_STEP_LAST:
+		nm_log_dbg (LOGD_MB, "(%s): modem deactivation finished",
+		            nm_modem_get_uid (ctx->self));
+		deactivate_context_complete (ctx);
+		return;
+	}
+
+	g_assert_not_reached ();
+}
+
+void
+nm_modem_deactivate_async (NMModem *self,
+                           NMDevice *device,
+                           GCancellable *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer user_data)
+{
+	DeactivateContext *ctx;
+
+	ctx = g_slice_new0 (DeactivateContext);
+	ctx->self = g_object_ref (self);
+	ctx->device = g_object_ref (device);
+	ctx->result = g_simple_async_result_new (G_OBJECT (self),
+	                                         callback,
+	                                         user_data,
+	                                         nm_modem_deactivate_async);
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+
+	/* Start */
+	ctx->step = DEACTIVATE_CONTEXT_STEP_FIRST;
+	deactivate_step (ctx);
+}
+
+/*****************************************************************************/
+
 void
 nm_modem_deactivate (NMModem *self, NMDevice *device)
 {
-	NM_MODEM_GET_CLASS (self)->deactivate (self, device);
+	/* First cleanup */
+	NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, device);
+	/* Then disconnect without waiting */
+	NM_MODEM_GET_CLASS (self)->disconnect (self, FALSE, NULL, NULL, NULL);
 }
 
 /*****************************************************************************/
@@ -912,7 +1121,6 @@ nm_modem_device_state_changed (NMModem *self,
 	switch (new_state) {
 	case NM_DEVICE_STATE_UNMANAGED:
 	case NM_DEVICE_STATE_UNAVAILABLE:
-	case NM_DEVICE_STATE_DISCONNECTED:
 	case NM_DEVICE_STATE_FAILED:
 		if (priv->act_request) {
 			cancel_get_secrets (self);
@@ -924,7 +1132,9 @@ nm_modem_device_state_changed (NMModem *self,
 			/* Don't bother warning on FAILED since the modem is already gone */
 			if (new_state == NM_DEVICE_STATE_FAILED)
 				warn = FALSE;
-			NM_MODEM_GET_CLASS (self)->disconnect (self, warn);
+			/* First cleanup */
+			NM_MODEM_GET_CLASS (self)->deactivate_cleanup (self, NULL);
+			NM_MODEM_GET_CLASS (self)->disconnect (self, warn, NULL, NULL, NULL);
 		}
 		break;
 	default:
@@ -1030,8 +1240,8 @@ nm_modem_init (NMModem *self)
 
 static GObject*
 constructor (GType type,
-			 guint n_construct_params,
-			 GObjectConstructParam *construct_params)
+             guint n_construct_params,
+             GObjectConstructParam *construct_params)
 {
 	GObject *object;
 	NMModemPrivate *priv;
@@ -1056,7 +1266,7 @@ constructor (GType type,
 
 	return object;
 
- err:
+err:
 	g_object_unref (object);
 	return NULL;
 }
@@ -1209,7 +1419,7 @@ nm_modem_class_init (NMModemClass *klass)
 
 	klass->act_stage1_prepare = act_stage1_prepare;
 	klass->stage3_ip6_config_request = stage3_ip6_config_request;
-	klass->deactivate = deactivate;
+	klass->deactivate_cleanup = deactivate_cleanup;
 
 	/* Properties */
 
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index 2a4d9173..2cace890 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -143,9 +143,16 @@ typedef struct {
 
 	void (*set_mm_enabled)                     (NMModem *self, gboolean enabled);
 
-	void (*disconnect)                         (NMModem *self, gboolean warn);
+	void     (*disconnect)                     (NMModem *self,
+	                                            gboolean warn,
+	                                            GCancellable *cancellable,
+	                                            GAsyncReadyCallback callback,
+	                                            gpointer user_data);
+	gboolean (*disconnect_finish)              (NMModem *self,
+	                                            GAsyncResult *res,
+	                                            GError **error);
 
-	void (*deactivate)                         (NMModem *self, NMDevice *device);
+	void     (*deactivate_cleanup)             (NMModem *self, NMDevice *device);
 
 	gboolean (*owns_port)                      (NMModem *self, const char *iface);
 
@@ -218,6 +225,15 @@ gboolean nm_modem_get_secrets (NMModem *modem,
 
 void nm_modem_deactivate (NMModem *modem, NMDevice *device);
 
+void     nm_modem_deactivate_async        (NMModem *self,
+                                           NMDevice *device,
+                                           GCancellable *cancellable,
+                                           GAsyncReadyCallback callback,
+                                           gpointer user_data);
+gboolean nm_modem_deactivate_async_finish (NMModem *self,
+                                           GAsyncResult *res,
+                                           GError **error);
+
 void nm_modem_device_state_changed (NMModem *modem,
                                     NMDeviceState new_state,
                                     NMDeviceState old_state,
@@ -237,7 +253,7 @@ NMModemIPType nm_modem_get_supported_ip_types (NMModem *self);
 /* For the modem-manager only */
 void          nm_modem_emit_removed (NMModem *self);
 
-NMModemIPType nm_modem_get_connection_ip_type (NMModem *self,
+GArray       *nm_modem_get_connection_ip_type (NMModem *self,
                                                NMConnection *connection,
                                                GError **error);
 
@@ -246,6 +262,8 @@ void nm_modem_emit_ip6_config_result (NMModem *self,
                                       NMIP6Config *config,
                                       GError *error);
 
+const gchar *nm_modem_ip_type_to_string (NMModemIPType ip_type);
+
 G_END_DECLS
 
 #endif /* __NETWORKMANAGER_MODEM_H__ */
diff --git a/src/devices/wwan/wwan-exports.ver b/src/devices/wwan/wwan-exports.ver
index c23ab24b..23412de6 100644
--- a/src/devices/wwan/wwan-exports.ver
+++ b/src/devices/wwan/wwan-exports.ver
@@ -5,6 +5,8 @@ global:
 	nm_modem_check_connection_compatible;
 	nm_modem_complete_connection;
 	nm_modem_deactivate;
+	nm_modem_deactivate_async;
+	nm_modem_deactivate_async_finish;
 	nm_modem_device_state_changed;
 	nm_modem_get_capabilities;
 	nm_modem_get_control_port;