summary refs log tree commit diff
path: root/src/devices/wwan
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
committerMichael Biebl <biebl@debian.org>2016-08-26 02:18:32 +0200
commit7514efc2f38c9ace4557d4e69d68e7d380389030 (patch)
tree7fb00fda86cfcc2ca377f191633a7cfdbfea7ca3 /src/devices/wwan
parentd6201f5d8daada3d64a0a3e0038e14eebec683ce (diff)
Imported Upstream version 1.4.0 upstream/1.4.0
Diffstat (limited to 'src/devices/wwan')
-rw-r--r--src/devices/wwan/Makefile.am13
-rw-r--r--src/devices/wwan/Makefile.in51
-rw-r--r--src/devices/wwan/nm-device-modem.c33
-rw-r--r--src/devices/wwan/nm-modem-broadband.c158
-rw-r--r--src/devices/wwan/nm-modem-broadband.h6
-rw-r--r--src/devices/wwan/nm-modem-enum-types.c1
-rw-r--r--src/devices/wwan/nm-modem-manager.c372
-rw-r--r--src/devices/wwan/nm-modem-ofono.c1202
-rw-r--r--src/devices/wwan/nm-modem-ofono.h53
-rw-r--r--src/devices/wwan/nm-modem.c8
-rw-r--r--src/devices/wwan/nm-modem.h5
-rw-r--r--src/devices/wwan/nm-wwan-factory.c4
-rw-r--r--src/devices/wwan/nm-wwan-factory.h2
13 files changed, 1678 insertions, 230 deletions
diff --git a/src/devices/wwan/Makefile.am b/src/devices/wwan/Makefile.am
index 1777eb36..0d061a79 100644
--- a/src/devices/wwan/Makefile.am
+++ b/src/devices/wwan/Makefile.am
@@ -16,6 +16,7 @@ AM_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	$(GLIB_CFLAGS) \
+	$(LIBSYSTEMD_CFLAGS) \
 	$(MM_GLIB_CFLAGS)
 
 BUILT_SOURCES = $(null)
@@ -26,7 +27,7 @@ pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la
 
 GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
-GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
+GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM --fhead '\#include "nm-default.h"\n'
 nm_modem_enum_types_sources = $(srcdir)/nm-modem.h
 
 BUILT_SOURCES += $(GLIB_GENERATED)
@@ -41,6 +42,13 @@ libnm_wwan_la_SOURCES = \
 	\
 	$(GLIB_GENERATED)
 
+if WITH_OFONO
+libnm_wwan_la_SOURCES += \
+	nm-modem-ofono.c \
+	nm-modem-ofono.h \
+	$(NULL)
+endif
+
 WWAN_SYMBOL_VIS_FILE=$(srcdir)/wwan-exports.ver
 
 libnm_wwan_la_LDFLAGS = \
@@ -49,6 +57,7 @@ libnm_wwan_la_LDFLAGS = \
 libnm_wwan_la_LIBADD = \
 	$(top_builddir)/introspection/libnmdbus.la \
 	$(GLIB_LIBS) \
+	$(LIBSYSTEMD_LIBS) \
 	$(MM_GLIB_LIBS)
 
 ###########################################################
@@ -79,7 +88,9 @@ if ENABLE_TESTS
 
 check-local:
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE)
+	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 endif
 
diff --git a/src/devices/wwan/Makefile.in b/src/devices/wwan/Makefile.in
index 89328665..6d395567 100644
--- a/src/devices/wwan/Makefile.in
+++ b/src/devices/wwan/Makefile.in
@@ -88,10 +88,16 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 build_triplet = @build@
 host_triplet = @host@
+@WITH_OFONO_TRUE@am__append_1 = \
+@WITH_OFONO_TRUE@	nm-modem-ofono.c \
+@WITH_OFONO_TRUE@	nm-modem-ofono.h \
+@WITH_OFONO_TRUE@	$(NULL)
+
 subdir = src/devices/wwan
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
-am__aclocal_m4_deps = $(top_srcdir)/m4/ax_lib_readline.m4 \
-	$(top_srcdir)/m4/compiler_warnings.m4 \
+am__aclocal_m4_deps = $(top_srcdir)/m4/attributes.m4 \
+	$(top_srcdir)/m4/ax_lib_readline.m4 \
+	$(top_srcdir)/m4/compiler_options.m4 \
 	$(top_srcdir)/m4/gettext.m4 \
 	$(top_srcdir)/m4/gnome-code-coverage.m4 \
 	$(top_srcdir)/m4/gtk-doc.m4 $(top_srcdir)/m4/iconv.m4 \
@@ -157,10 +163,16 @@ libnm_device_plugin_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
 	$(LDFLAGS) -o $@
 libnm_wwan_la_DEPENDENCIES =  \
 	$(top_builddir)/introspection/libnmdbus.la \
-	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+	$(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1)
+am__libnm_wwan_la_SOURCES_DIST = nm-modem-broadband.c \
+	nm-modem-broadband.h nm-modem-manager.c nm-modem-manager.h \
+	nm-modem.c nm-modem.h nm-modem-enum-types.h \
+	nm-modem-enum-types.c nm-modem-ofono.c nm-modem-ofono.h
 am__objects_1 = nm-modem-enum-types.lo
+@WITH_OFONO_TRUE@am__objects_2 = nm-modem-ofono.lo
 am_libnm_wwan_la_OBJECTS = nm-modem-broadband.lo nm-modem-manager.lo \
-	nm-modem.lo $(am__objects_1)
+	nm-modem.lo $(am__objects_1) $(am__objects_2)
 libnm_wwan_la_OBJECTS = $(am_libnm_wwan_la_OBJECTS)
 libnm_wwan_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
 	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@@ -202,7 +214,7 @@ am__v_CCLD_1 =
 SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \
 	$(libnm_wwan_la_SOURCES)
 DIST_SOURCES = $(libnm_device_plugin_wwan_la_SOURCES) \
-	$(libnm_wwan_la_SOURCES)
+	$(am__libnm_wwan_la_SOURCES_DIST)
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
     n|no|NO) false;; \
@@ -264,6 +276,7 @@ DHCLIENT_PATH = @DHCLIENT_PATH@
 DHCPCD_PATH = @DHCPCD_PATH@
 DISTRO_NETWORK_SERVICE = @DISTRO_NETWORK_SERVICE@
 DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
 DNSMASQ_PATH = @DNSMASQ_PATH@
 DNSSEC_TRIGGER_SCRIPT = @DNSSEC_TRIGGER_SCRIPT@
 DSYMUTIL = @DSYMUTIL@
@@ -320,13 +333,14 @@ INTROSPECTION_MAKEFILE = @INTROSPECTION_MAKEFILE@
 INTROSPECTION_SCANNER = @INTROSPECTION_SCANNER@
 INTROSPECTION_TYPELIBDIR = @INTROSPECTION_TYPELIBDIR@
 IPTABLES_PATH = @IPTABLES_PATH@
+JANSSON_CFLAGS = @JANSSON_CFLAGS@
+JANSSON_LIBS = @JANSSON_LIBS@
 KERNEL_FIRMWARE_DIR = @KERNEL_FIRMWARE_DIR@
 LCOV = @LCOV@
 LD = @LD@
 LDFLAGS = @LDFLAGS@
 LIBAUDIT_CFLAGS = @LIBAUDIT_CFLAGS@
 LIBAUDIT_LIBS = @LIBAUDIT_LIBS@
-LIBDL = @LIBDL@
 LIBICONV = @LIBICONV@
 LIBINTL = @LIBINTL@
 LIBM = @LIBM@
@@ -338,6 +352,8 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LIBSOUP_CFLAGS = @LIBSOUP_CFLAGS@
 LIBSOUP_LIBS = @LIBSOUP_LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
 LIBTEAMDCTL_CFLAGS = @LIBTEAMDCTL_CFLAGS@
 LIBTEAMDCTL_LIBS = @LIBTEAMDCTL_LIBS@
 LIBTOOL = @LIBTOOL@
@@ -398,6 +414,8 @@ QT_CFLAGS = @QT_CFLAGS@
 QT_LIBS = @QT_LIBS@
 RANLIB = @RANLIB@
 READLINE_LIBS = @READLINE_LIBS@
+SANITIZERS = @SANITIZERS@
+SANITIZER_ENV = @SANITIZER_ENV@
 SED = @SED@
 SELINUX_CFLAGS = @SELINUX_CFLAGS@
 SELINUX_LIBS = @SELINUX_LIBS@
@@ -433,6 +451,7 @@ ac_ct_AR = @ac_ct_AR@
 ac_ct_CC = @ac_ct_CC@
 ac_ct_CXX = @ac_ct_CXX@
 ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_RANLIB = @ac_ct_RANLIB@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -508,6 +527,7 @@ AM_CPPFLAGS = \
 	-DG_LOG_DOMAIN=\""NetworkManager-wwan"\" \
 	-DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_INSIDE_DAEMON \
 	$(GLIB_CFLAGS) \
+	$(LIBSYSTEMD_CFLAGS) \
 	$(MM_GLIB_CFLAGS)
 
 BUILT_SOURCES = $(null) $(GLIB_GENERATED)
@@ -516,18 +536,11 @@ pkglib_LTLIBRARIES = libnm-wwan.la libnm-device-plugin-wwan.la
 ###########################################################
 GLIB_GENERATED = nm-modem-enum-types.h nm-modem-enum-types.c
 GLIB_MKENUMS_H_FLAGS = --identifier-prefix NM
-GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM
+GLIB_MKENUMS_C_FLAGS = --identifier-prefix NM --fhead '\#include "nm-default.h"\n'
 nm_modem_enum_types_sources = $(srcdir)/nm-modem.h
-libnm_wwan_la_SOURCES = \
-	nm-modem-broadband.c \
-	nm-modem-broadband.h \
-	nm-modem-manager.c \
-	nm-modem-manager.h \
-	nm-modem.c \
-	nm-modem.h \
-	\
-	$(GLIB_GENERATED)
-
+libnm_wwan_la_SOURCES = nm-modem-broadband.c nm-modem-broadband.h \
+	nm-modem-manager.c nm-modem-manager.h nm-modem.c nm-modem.h \
+	$(GLIB_GENERATED) $(am__append_1)
 WWAN_SYMBOL_VIS_FILE = $(srcdir)/wwan-exports.ver
 libnm_wwan_la_LDFLAGS = \
 	-avoid-version \
@@ -536,6 +549,7 @@ libnm_wwan_la_LDFLAGS = \
 libnm_wwan_la_LIBADD = \
 	$(top_builddir)/introspection/libnmdbus.la \
 	$(GLIB_LIBS) \
+	$(LIBSYSTEMD_LIBS) \
 	$(MM_GLIB_LIBS)
 
 
@@ -646,6 +660,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-broadband.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-enum-types.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-manager.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem-ofono.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-modem.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nm-wwan-factory.Plo@am__quote@
 
@@ -897,7 +912,9 @@ include $(GLIB_MAKEFILE)
 
 @ENABLE_TESTS_TRUE@check-local:
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-device-plugin-wwan.so $(SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-device-plugin-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 @ENABLE_TESTS_TRUE@	$(top_srcdir)/tools/check-exports.sh $(builddir)/.libs/libnm-wwan.so $(WWAN_SYMBOL_VIS_FILE)
+@ENABLE_TESTS_TRUE@	LD_BIND_NOW=1 LD_PRELOAD=$(builddir)/.libs/libnm-wwan.so $(top_builddir)/src/NetworkManager --version >/dev/null
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/src/devices/wwan/nm-device-modem.c b/src/devices/wwan/nm-device-modem.c
index a8361c49..0f96dafb 100644
--- a/src/devices/wwan/nm-device-modem.c
+++ b/src/devices/wwan/nm-device-modem.c
@@ -60,6 +60,7 @@ static void
 ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 {
 	NMDevice *device = NM_DEVICE (user_data);
+	NMDeviceModem *self = NM_DEVICE_MODEM (user_data);
 
 	switch (nm_device_get_state (device)) {
 	case NM_DEVICE_STATE_PREPARE:
@@ -73,7 +74,18 @@ ppp_failed (NMModem *modem, NMDeviceStateReason reason, gpointer user_data)
 	case NM_DEVICE_STATE_ACTIVATED:
 		if (nm_device_activate_ip4_state_in_conf (device))
 			nm_device_activate_schedule_ip4_config_timeout (device);
-		else {
+		else if (nm_device_activate_ip6_state_in_conf (device))
+			nm_device_activate_schedule_ip6_config_timeout (device);
+		else if (nm_device_activate_ip4_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else if (nm_device_activate_ip6_state_done (device)) {
+			nm_device_ip_method_failed (device,
+			                            AF_INET6,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		} else {
+			_LOGW (LOGD_MB, "PPP failure in unexpected state %u", (guint) nm_device_get_state (device));
 			nm_device_state_changed (device,
 			                         NM_DEVICE_STATE_FAILED,
 			                         NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
@@ -159,8 +171,9 @@ modem_ip4_config_result (NMModem *modem,
 	if (error) {
 		_LOGW (LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s",
 		       error->message);
-
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		nm_device_ip_method_failed (device,
+		                            AF_INET,
+		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 	} else {
 		nm_device_set_wwan_ip4_config (device, config);
 		nm_device_activate_schedule_ip4_config_result (device, NULL);
@@ -184,9 +197,11 @@ modem_ip6_config_result (NMModem *modem,
 	g_return_if_fail (nm_device_activate_ip6_state_in_conf (device) == TRUE);
 
 	if (error) {
-		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s", error->message);
-
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+		_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: %s",
+		       error->message);
+		nm_device_ip_method_failed (device,
+		                            AF_INET6,
+		                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		return;
 	}
 
@@ -201,7 +216,9 @@ modem_ip6_config_result (NMModem *modem,
 			nm_device_activate_schedule_ip6_config_result (device);
 		else {
 			_LOGW (LOGD_MB | LOGD_IP6, "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
-			nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+			nm_device_ip_method_failed (device,
+			                            AF_INET6,
+			                            NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
 		}
 		return;
 	}
@@ -211,7 +228,7 @@ modem_ip6_config_result (NMModem *modem,
 	g_assert (ignored == NULL);
 	switch (ret) {
 	case NM_ACT_STAGE_RETURN_FAILURE:
-		nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, reason);
+		nm_device_ip_method_failed (device, AF_INET6, reason);
 		break;
 	case NM_ACT_STAGE_RETURN_STOP:
 		/* all done */
diff --git a/src/devices/wwan/nm-modem-broadband.c b/src/devices/wwan/nm-modem-broadband.c
index 1a6e6b25..727c8409 100644
--- a/src/devices/wwan/nm-modem-broadband.c
+++ b/src/devices/wwan/nm-modem-broadband.c
@@ -30,6 +30,8 @@
 #include "nm-device-private.h"
 #include "nm-platform.h"
 
+#define NM_MODEM_BROADBAND_MODEM "modem"
+
 G_DEFINE_TYPE (NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM)
 
 typedef enum {
@@ -89,8 +91,35 @@ enum {
 
 /*****************************************************************************/
 
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG_PREFIX_NAME "modem-broadband"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        \
+        if (nm_logging_enabled (_level, (_NMLOG_DOMAIN))) { \
+            NMModemBroadband *const __self = (self); \
+            char __prefix_name[128]; \
+            const char *__uid; \
+            \
+            _nm_log (_level, (_NMLOG_DOMAIN), 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (__self \
+                         ? ({ \
+                                ((__uid = nm_modem_get_uid ((NMModem *) __self)) \
+                                    ? nm_sprintf_buf (__prefix_name, "[%s]", __uid) \
+                                    : "(null)"); \
+                            }) \
+                         : "") \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
 static NMDeviceStateReason
-translate_mm_error (GError *error)
+translate_mm_error (NMModemBroadband *self, GError *error)
 {
 	NMDeviceStateReason reason;
 
@@ -122,7 +151,7 @@ translate_mm_error (GError *error)
 		reason = NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT;
 	else {
 		/* unable to map the ModemManager error to a NM_DEVICE_STATE_REASON */
-		nm_log_dbg (LOGD_MB, "unmapped error detected: '%s'", error->message);
+		_LOGD ("unmapped error detected: '%s'", error->message);
 		reason = NM_DEVICE_STATE_REASON_UNKNOWN;
 	}
 
@@ -288,7 +317,7 @@ connect_context_clear (NMModemBroadband *self)
 
 		g_clear_error (&ctx->first_error);
 		g_clear_pointer (&ctx->ip_types, (GDestroyNotify) g_array_unref);
-		g_clear_object (&ctx->cancellable);
+		nm_clear_g_cancellable (&ctx->cancellable);
 		g_clear_object (&ctx->connection);
 		g_clear_object (&ctx->connect_properties);
 		g_clear_object (&ctx->self);
@@ -360,8 +389,7 @@ connect_ready (MMModemSimple *simple_iface,
 
 	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)));
+		_LOGW ("failed to connect modem: invalid bearer IP configuration");
 		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
 		connect_context_clear (self);
 		return;
@@ -389,7 +417,7 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 		return;
 
 	if (!self->priv->ctx || self->priv->ctx->step != CONNECT_STEP_UNLOCK)
-		return;
+		g_return_if_reached ();
 
 	if (error) {
 		if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN) ||
@@ -397,7 +425,7 @@ send_pin_ready (MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 		     mm_modem_get_unlock_required (self->priv->modem_iface) == MM_MODEM_LOCK_SIM_PIN)) {
 			ask_for_pin (self);
 		} else {
-			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (error));
+			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (self, error));
 		}
 		return;
 	}
@@ -459,9 +487,8 @@ connect_context_step (NMModemBroadband *self)
 		else if (MODEM_CAPS_3GPP2 (ctx->caps))
 			ctx->connect_properties = create_cdma_connect_properties (ctx->connection);
 		else {
-			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 (ctx->connection));
+			_LOGW ("failed to connect '%s': not a mobile broadband modem",
+			       nm_connection_get_id (ctx->connection));
 
 			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
 			connect_context_clear (self);
@@ -472,10 +499,9 @@ connect_context_step (NMModemBroadband *self)
 		/* Build up list of IP types that we need to use in the retries */
 		ctx->ip_types = nm_modem_get_connection_ip_type (NM_MODEM (self), ctx->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 (ctx->connection),
-			             error->message);
+			_LOGW ("failed to connect '%s': %s",
+			       nm_connection_get_id (ctx->connection),
+			       error->message);
 			g_clear_error (&error);
 
 			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED);
@@ -502,10 +528,9 @@ connect_context_step (NMModemBroadband *self)
 			else
 				g_assert_not_reached ();
 
-			nm_log_dbg (LOGD_MB, "(%s): launching connection with ip type '%s' (try %d)",
-			            nm_modem_get_uid (NM_MODEM (self)),
-			            nm_modem_ip_type_to_string (current),
-			            ctx->ip_type_tries + 1);
+			_LOGD ("launching connection with ip type '%s' (try %d)",
+			       nm_modem_ip_type_to_string (current),
+			       ctx->ip_type_tries + 1);
 
 			mm_modem_simple_connect (self->priv->simple_iface,
 			                         ctx->connect_properties,
@@ -528,10 +553,9 @@ connect_context_step (NMModemBroadband *self)
 				                                        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 (self)),
-					     ctx->first_error->message);
-			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (ctx->first_error));
+			_LOGW ("failed to connect modem: %s",
+			       ctx->first_error->message);
+			g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE, translate_mm_error (self, ctx->first_error));
 		}
 
 		connect_context_clear (self);
@@ -550,8 +574,7 @@ act_stage1_prepare (NMModem *_self,
 	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)));
+			_LOGW ("cannot access the Simple mobile broadband modem interface");
 			*reason = NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED;
 			return NM_ACT_STAGE_RETURN_FAILURE;
 		}
@@ -745,9 +768,8 @@ 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)),
-		            NM_G_ERROR_MSG (error));
+		_LOGD ("failed to set modem low power state: %s",
+		       NM_G_ERROR_MSG (error));
 		g_clear_error (&error);
 	}
 
@@ -770,9 +792,8 @@ 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_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_LOGW ("failed to disable modem: %s",
+		       NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "disable failed");
 		g_clear_error (&error);
 	}
@@ -789,9 +810,8 @@ modem_enable_ready (MMModem *modem_iface,
 	GError *error = NULL;
 
 	if (!mm_modem_enable_finish (modem_iface, res, &error)) {
-		nm_log_warn (LOGD_MB, "(%s) failed to enable modem: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_LOGW ("failed to enable modem: %s",
+		       NM_G_ERROR_MSG (error));
 		nm_modem_set_prev_state (NM_MODEM (self), "enable failed");
 		g_clear_error (&error);
 	}
@@ -854,8 +874,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	g_assert (self->priv->ipv4_config);
 	g_assert (self->priv->bearer);
 
-	nm_log_info (LOGD_MB, "(%s): IPv4 static configuration:",
-	             nm_modem_get_uid (NM_MODEM (self)));
+	_LOGI ("IPv4 static configuration:");
 
 	/* Fully fail if invalid IP address retrieved */
 	address_string = mm_bearer_ip_config_get_address (self->priv->ipv4_config);
@@ -880,15 +899,15 @@ static_stage3_ip4_done (NMModemBroadband *self)
 	address.address = address_network;
 	address.peer_address = address_network;
 	address.plen = mm_bearer_ip_config_get_prefix (self->priv->ipv4_config);
-	address.source = NM_IP_CONFIG_SOURCE_WWAN;
+	address.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
 	if (address.plen <= 32)
 		nm_ip4_config_add_address (config, &address);
 
-	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
+	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	if (gw) {
 		nm_ip4_config_set_gateway (config, gw);
-		nm_log_info (LOGD_MB, "  gateway %s", gw_string);
+		_LOGI ("  gateway %s", gw_string);
 	}
 
 	/* DNS servers */
@@ -897,7 +916,7 @@ static_stage3_ip4_done (NMModemBroadband *self)
 		if (   ip4_string_to_num (dns[i], &address_network)
 		    && address_network > 0) {
 			nm_ip4_config_add_nameserver (config, address_network);
-			nm_log_info (LOGD_MB, "  DNS %s", dns[i]);
+			_LOGI ("  DNS %s", dns[i]);
 		}
 	}
 
@@ -964,8 +983,7 @@ stage3_ip6_done (NMModemBroadband *self)
 		goto out;
 	}
 
-	nm_log_info (LOGD_MB, "(%s): IPv6 base configuration:",
-	             nm_modem_get_uid (NM_MODEM (self)));
+	_LOGI ("IPv6 base configuration:");
 
 	data_port = mm_bearer_get_interface (self->priv->bearer);
 	g_assert (data_port);
@@ -975,7 +993,7 @@ stage3_ip6_done (NMModemBroadband *self)
 	if (address.plen <= 128)
 		nm_ip6_config_add_address (config, &address);
 
-	nm_log_info (LOGD_MB, "  address %s/%d", address_string, address.plen);
+	_LOGI ("  address %s/%d", address_string, address.plen);
 
 	address_string = mm_bearer_ip_config_get_gateway (self->priv->ipv6_config);
 	if (address_string) {
@@ -987,7 +1005,7 @@ stage3_ip6_done (NMModemBroadband *self)
 			                     address_string);
 			goto out;
 		}
-		nm_log_info (LOGD_MB, "  gateway %s", address_string);
+		_LOGI ("  gateway %s", address_string);
 		nm_ip6_config_set_gateway (config, &address.address);
 	} else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
 		/* Gateway required for the 'static' method */
@@ -1005,7 +1023,7 @@ stage3_ip6_done (NMModemBroadband *self)
 
 		if (inet_pton (AF_INET6, dns[i], &addr)) {
 			nm_ip6_config_add_nameserver (config, &addr);
-			nm_log_info (LOGD_MB, "  DNS %s", dns[i]);
+			_LOGI ("  DNS %s", dns[i]);
 		}
 	}
 
@@ -1050,19 +1068,6 @@ disconnect_context_complete (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)
@@ -1079,9 +1084,10 @@ simple_disconnect_ready (MMModemSimple *modem_iface,
 
 	if (!mm_modem_simple_disconnect_finish (modem_iface, res, &error)) {
 		if (ctx->warn && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) {
-			nm_log_warn (LOGD_MB, "(%s) failed to disconnect modem: %s",
-			             nm_modem_get_uid (NM_MODEM (ctx->self)),
-			             error->message);
+			NMModemBroadband *self = ctx->self;
+
+			_LOGW ("failed to disconnect modem: %s",
+			       error->message);
 		}
 		g_simple_async_result_take_error (ctx->result, error);
 	}
@@ -1098,9 +1104,11 @@ disconnect (NMModem *modem,
 {
 	NMModemBroadband *self = NM_MODEM_BROADBAND (modem);
 	DisconnectContext *ctx;
+	GError *error = NULL;
 
 	connect_context_clear (self);
 	ctx = g_slice_new (DisconnectContext);
+	ctx->cancellable = NULL;
 	ctx->self = g_object_ref (self);
 	ctx->result = g_simple_async_result_new (G_OBJECT (self),
 	                                         callback,
@@ -1109,10 +1117,12 @@ disconnect (NMModem *modem,
 	/* 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))
+	/* Already cancelled? */
+	if (g_cancellable_set_error_if_cancelled (cancellable, &error)) {
+		g_simple_async_result_take_error (ctx->result, error);
+		disconnect_context_complete (ctx);
 		return;
+	}
 
 	/* If no simple iface, we're done */
 	if (!ctx->self->priv->simple_iface) {
@@ -1120,14 +1130,13 @@ disconnect (NMModem *modem,
 		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 */
-		cancellable,
-		(GAsyncReadyCallback)simple_disconnect_ready,
-		ctx);
+	_LOGD ("notifying ModemManager about the modem disconnection");
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	mm_modem_simple_disconnect (ctx->self->priv->simple_iface,
+	                            NULL, /* bearer path; if NULL given ALL get disconnected */
+	                            cancellable,
+	                            (GAsyncReadyCallback) simple_disconnect_ready,
+	                            ctx);
 }
 
 /*****************************************************************************/
@@ -1274,9 +1283,10 @@ get_sim_ready (MMModem *modem,
 		if (self->priv->ctx && self->priv->ctx->step == CONNECT_STEP_WAIT_FOR_SIM)
 			connect_context_step (self);
 	} else {
-		nm_log_warn (LOGD_MB, "(%s): failed to retrieve SIM object: %s",
-		             nm_modem_get_uid (NM_MODEM (self)),
-		             NM_G_ERROR_MSG (error));
+		_NMLOG (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND)
+		            ? LOGL_INFO : LOGL_WARN,
+		        "failed to retrieve SIM object: %s",
+		        NM_G_ERROR_MSG (error));
 	}
 	g_clear_error (&error);
 	g_object_unref (self);
diff --git a/src/devices/wwan/nm-modem-broadband.h b/src/devices/wwan/nm-modem-broadband.h
index a54e63a6..6607f7f5 100644
--- a/src/devices/wwan/nm-modem-broadband.h
+++ b/src/devices/wwan/nm-modem-broadband.h
@@ -23,8 +23,6 @@
 
 #include "nm-modem.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_MODEM_BROADBAND            (nm_modem_broadband_get_type ())
 #define NM_MODEM_BROADBAND(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_BROADBAND, NMModemBroadband))
 #define NM_MODEM_BROADBAND_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass))
@@ -32,8 +30,6 @@ G_BEGIN_DECLS
 #define NM_IS_MODEM_BROADBAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_MODEM_BROADBAND))
 #define NM_MODEM_BROADBAND_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_MODEM_BROADBAND, NMModemBroadbandClass))
 
-#define NM_MODEM_BROADBAND_MODEM "modem"
-
 typedef struct _NMModemBroadband        NMModemBroadband;
 typedef struct _NMModemBroadbandClass   NMModemBroadbandClass;
 typedef struct _NMModemBroadbandPrivate NMModemBroadbandPrivate;
@@ -51,6 +47,4 @@ GType nm_modem_broadband_get_type (void);
 
 NMModem *nm_modem_broadband_new (GObject *object, GError **error);
 
-G_END_DECLS
-
 #endif /* __NETWORKMANAGER_MODEM_BROADBAND_H__ */
diff --git a/src/devices/wwan/nm-modem-enum-types.c b/src/devices/wwan/nm-modem-enum-types.c
index 2d02ede9..f999b084 100644
--- a/src/devices/wwan/nm-modem-enum-types.c
+++ b/src/devices/wwan/nm-modem-enum-types.c
@@ -6,6 +6,7 @@
 #include "config.h"
 
 #include "nm-modem-enum-types.h"
+#include "nm-default.h"
 
 #include "nm-modem.h"
 
diff --git a/src/devices/wwan/nm-modem-manager.c b/src/devices/wwan/nm-modem-manager.c
index e26321b0..964485fc 100644
--- a/src/devices/wwan/nm-modem-manager.c
+++ b/src/devices/wwan/nm-modem-manager.c
@@ -17,20 +17,30 @@
  *
  * Copyright (C) 2009 - 2014 Red Hat, Inc.
  * Copyright (C) 2009 Novell, Inc.
- * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 - 2013 Canonical Ltd.
  */
 
 #include "nm-default.h"
 
-#include <string.h>
+#include "nm-modem-manager.h"
 
+#include <string.h>
 #include <libmm-glib.h>
 
-#include "nm-modem-manager.h"
+#if HAVE_LIBSYSTEMD
+#include <systemd/sd-daemon.h>
+#else
+#define sd_booted() FALSE
+#endif
+
 #include "nm-dbus-compat.h"
 #include "nm-modem.h"
 #include "nm-modem-broadband.h"
 
+#if WITH_OFONO
+#include "nm-modem-ofono.h"
+#endif
+
 #define MODEM_POKE_INTERVAL 120
 
 G_DEFINE_TYPE (NMModemManager, nm_modem_manager, G_TYPE_OBJECT)
@@ -39,9 +49,13 @@ struct _NMModemManagerPrivate {
 	GDBusConnection *dbus_connection;
 	MMManager *modem_manager;
 	guint mm_launch_id;
-	guint mm_name_owner_changed_id;
-	guint mm_object_added_id;
-	guint mm_object_removed_id;
+	gulong mm_name_owner_changed_id;
+	gulong mm_object_added_id;
+	gulong mm_object_removed_id;
+
+#if WITH_OFONO
+	GDBusProxy *ofono_proxy;
+#endif
 
 	/* Common */
 	GHashTable *modems;
@@ -79,34 +93,14 @@ remove_one_modem (gpointer key, gpointer value, gpointer user_data)
 }
 
 static void
-modem_manager_clear_signals (NMModemManager *self)
+clear_modem_manager (NMModemManager *self)
 {
 	if (!self->priv->modem_manager)
 		return;
-
-	if (self->priv->mm_name_owner_changed_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_name_owner_changed_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_name_owner_changed_id);
-		self->priv->mm_name_owner_changed_id = 0;
-	}
-
-	if (self->priv->mm_object_added_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_object_added_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_object_added_id);
-		self->priv->mm_object_added_id = 0;
-	}
-
-	if (self->priv->mm_object_removed_id) {
-		if (g_signal_handler_is_connected (self->priv->modem_manager,
-		                                   self->priv->mm_object_removed_id))
-			g_signal_handler_disconnect (self->priv->modem_manager,
-			                             self->priv->mm_object_removed_id);
-		self->priv->mm_object_removed_id = 0;
-	}
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_name_owner_changed_id);
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_added_id);
+	nm_clear_g_signal_handler (self->priv->modem_manager, &self->priv->mm_object_removed_id);
+	g_clear_object (&self->priv->modem_manager);
 }
 
 static void
@@ -183,7 +177,7 @@ modem_manager_available (NMModemManager *self)
 
 static void schedule_modem_manager_relaunch (NMModemManager *self,
                                              guint n_seconds);
-static void ensure_client                   (NMModemManager *self);
+static void ensure_modem_manager (NMModemManager *self);
 
 static void
 modem_manager_name_owner_changed (MMManager *modem_manager,
@@ -199,10 +193,9 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	if (!name_owner) {
 		nm_log_info (LOGD_MB, "ModemManager disappeared from bus");
 
-#if !HAVE_SYSTEMD
 		/* If not managed by systemd, schedule relaunch */
-		schedule_modem_manager_relaunch (self, 0);
-#endif
+		if (!sd_booted ())
+			schedule_modem_manager_relaunch (self, 0);
 
 		return;
 	}
@@ -214,16 +207,173 @@ modem_manager_name_owner_changed (MMManager *modem_manager,
 	 * nor 'object-removed' if it was created while there was no ModemManager in
 	 * the bus. This hack avoids this issue until we get a GIO with the fix
 	 * included... */
-	modem_manager_clear_signals (self);
-	g_clear_object (&self->priv->modem_manager);
-	ensure_client (self);
+	clear_modem_manager (self);
+	ensure_modem_manager (self);
 
 	/* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
 	 * modem_manager_available (self);
 	 */
 }
 
-#if !HAVE_SYSTEMD
+#if WITH_OFONO
+static void
+ofono_create_modem (NMModemManager *self, const char *path)
+{
+	NMModem *modem = NULL;
+
+	/* Ensure duplicate modems aren't created.  Because we're not using the
+	 * ObjectManager interface there's a race during oFono startup where we
+	 * receive ModemAdded signals before GetModems() returns, so some of the
+	 * modems returned from GetModems() may already have been created.
+	 */
+	if (!g_hash_table_lookup (self->priv->modems, path)) {
+		modem = nm_modem_ofono_new (path);
+		if (modem)
+			handle_new_modem (self, modem);
+		else
+			nm_log_warn (LOGD_MB, "Failed to create oFono modem for %s", path);
+	}
+}
+
+static void
+ofono_signal_cb (GDBusProxy *proxy,
+                 gchar *sender_name,
+                 gchar *signal_name,
+                 GVariant *parameters,
+                 gpointer user_data)
+{
+	NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gchar *object_path;
+	NMModem *modem;
+
+	if (g_strcmp0 (signal_name, "ModemAdded") == 0) {
+		g_variant_get (parameters, "(oa{sv})", &object_path, NULL);
+		nm_log_info (LOGD_MB, "oFono modem appeared: %s", object_path);
+
+		ofono_create_modem (NM_MODEM_MANAGER (user_data), object_path);
+		g_free (object_path);
+	} else if (g_strcmp0 (signal_name, "ModemRemoved") == 0) {
+		g_variant_get (parameters, "(o)", &object_path);
+		nm_log_info (LOGD_MB, "oFono modem removed: %s", object_path);
+
+		modem = (NMModem *) g_hash_table_lookup (self->priv->modems, object_path);
+		if (modem) {
+			nm_modem_emit_removed (modem);
+			g_hash_table_remove (self->priv->modems, object_path);
+		} else {
+			nm_log_warn (LOGD_MB, "could not remove modem %s, not found in table",
+			             object_path);
+		}
+		g_free (object_path);
+	}
+}
+
+static void
+ofono_enumerate_devices_done (GDBusProxy *proxy, GAsyncResult *res, gpointer user_data)
+{
+	NMModemManager *manager = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+	GVariant *results;
+	GVariantIter *iter;
+	const char *path;
+
+	results = g_dbus_proxy_call_finish (proxy, res, &error);
+	if (results) {
+		g_variant_get (results, "(a(oa{sv}))", &iter);
+		while (g_variant_iter_loop (iter, "(&oa{sv})", &path, NULL))
+			ofono_create_modem (manager, path);
+		g_variant_iter_free (iter);
+		g_variant_unref (results);
+	}
+
+	if (error) {
+		nm_log_warn (LOGD_MB, "failed to enumerate oFono devices: %s",
+		             error->message);
+	}
+}
+
+static void
+ofono_check_name_owner (NMModemManager *self)
+{
+	gs_free char *name_owner = NULL;
+
+	name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->priv->ofono_proxy));
+	if (name_owner) {
+		nm_log_info (LOGD_MB, "oFono is now available");
+
+		g_dbus_proxy_call (self->priv->ofono_proxy,
+		                   "GetModems",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   -1,
+		                   NULL,
+		                   (GAsyncReadyCallback) ofono_enumerate_devices_done,
+		                   g_object_ref (self));
+	} else {
+		GHashTableIter iter;
+		NMModem *modem;
+
+		nm_log_info (LOGD_MB, "oFono disappeared from bus");
+
+		/* Remove any oFono modems that might be left around */
+		g_hash_table_iter_init (&iter, self->priv->modems);
+		while (g_hash_table_iter_next (&iter, NULL, (gpointer) &modem)) {
+			if (NM_IS_MODEM_OFONO (modem)) {
+				nm_modem_emit_removed (modem);
+				g_hash_table_iter_remove (&iter);
+			}
+		}
+	}
+}
+
+static void
+ofono_name_owner_changed (GDBusProxy *ofono_proxy,
+                          GParamSpec *pspec,
+                          NMModemManager *self)
+{
+	ofono_check_name_owner (self);
+}
+
+static void
+ofono_proxy_new_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+
+	self->priv->ofono_proxy = g_dbus_proxy_new_finish (res, &error);
+	if (error) {
+		nm_log_warn (LOGD_MB, "error getting oFono bus proxy: %s", error->message);
+		return;
+	}
+
+	g_signal_connect (self->priv->ofono_proxy,
+	                  "notify::g-name-owner",
+	                  G_CALLBACK (ofono_name_owner_changed),
+	                  self);
+
+	g_signal_connect (self->priv->ofono_proxy,
+	                  "g-signal",
+	                  G_CALLBACK (ofono_signal_cb),
+	                  self);
+
+	ofono_check_name_owner (self);
+}
+
+static void
+ensure_ofono_client (NMModemManager *self)
+{
+	g_assert (self->priv->dbus_connection);
+	g_dbus_proxy_new (self->priv->dbus_connection,
+	                  G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                  NULL,
+	                  OFONO_DBUS_SERVICE,
+	                  OFONO_DBUS_PATH,
+	                  OFONO_DBUS_INTERFACE,
+	                  NULL,
+	                  (GAsyncReadyCallback) ofono_proxy_new_cb,
+	                  g_object_ref (self));
+}
+#endif
 
 static void
 modem_manager_poke_cb (GDBusConnection *connection,
@@ -235,19 +385,18 @@ modem_manager_poke_cb (GDBusConnection *connection,
 
 	result = g_dbus_connection_call_finish (connection, res, &error);
 	if (error) {
-		/* Ignore common errors when MM is not installed and such */
-		if (   !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_EXEC_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FORK_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FAILED)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_TIMEOUT)
-		    && !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
-			nm_log_dbg (LOGD_MB, "error poking ModemManager: %s", error->message);
+		nm_log_warn (LOGD_MB, "error poking ModemManager: %s",
+					error ? error->message : "");
+
+		/* Don't reschedule poke is MM service doesn't exist. */
+		if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)
+			&& !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND)) {
+
+			/* Setup timeout to relaunch */
+			schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
 		}
-		g_error_free (error);
 
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
+		g_error_free (error);
 	} else
 		g_variant_unref (result);
 
@@ -273,25 +422,21 @@ modem_manager_poke (NMModemManager *self)
 	                        g_object_ref (self)); /* user_data */
 }
 
-#endif /* HAVE_SYSTEMD */
-
 static void
 modem_manager_check_name_owner (NMModemManager *self)
 {
-	gchar *name_owner;
+	gs_free gchar *name_owner = NULL;
 
 	name_owner = g_dbus_object_manager_client_get_name_owner (G_DBUS_OBJECT_MANAGER_CLIENT (self->priv->modem_manager));
 	if (name_owner) {
 		/* Available! */
 		modem_manager_available (self);
-		g_free (name_owner);
 		return;
 	}
 
-#if !HAVE_SYSTEMD
 	/* If the lifecycle is not managed by systemd, poke */
-	modem_manager_poke (self);
-#endif
+	if (!sd_booted ())
+		modem_manager_poke (self);
 }
 
 static void
@@ -303,7 +448,8 @@ manager_new_ready (GObject *source,
 
 	GError *error = NULL;
 
-	g_assert (!self->priv->modem_manager);
+	g_return_if_fail (!self->priv->modem_manager);
+
 	self->priv->modem_manager = mm_manager_new_finish (res, &error);
 	if (!self->priv->modem_manager) {
 		/* We're not really supposed to get any error here. If we do get one,
@@ -316,20 +462,20 @@ manager_new_ready (GObject *source,
 	} else {
 		/* Setup signals in the GDBusObjectManagerClient */
 		self->priv->mm_name_owner_changed_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "notify::name-owner",
-			                  G_CALLBACK (modem_manager_name_owner_changed),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "notify::name-owner",
+		                      G_CALLBACK (modem_manager_name_owner_changed),
+		                      self);
 		self->priv->mm_object_added_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "object-added",
-			                  G_CALLBACK (modem_object_added),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "object-added",
+		                      G_CALLBACK (modem_object_added),
+		                      self);
 		self->priv->mm_object_removed_id =
-			g_signal_connect (self->priv->modem_manager,
-			                  "object-removed",
-			                  G_CALLBACK (modem_object_removed),
-			                  self);
+		    g_signal_connect (self->priv->modem_manager,
+		                      "object-removed",
+		                      G_CALLBACK (modem_object_removed),
+		                      self);
 
 		modem_manager_check_name_owner (self);
 	}
@@ -339,7 +485,7 @@ manager_new_ready (GObject *source,
 }
 
 static void
-ensure_client (NMModemManager *self)
+ensure_modem_manager (NMModemManager *self)
 {
 	g_assert (self->priv->dbus_connection);
 
@@ -359,46 +505,12 @@ ensure_client (NMModemManager *self)
 	modem_manager_check_name_owner (self);
 }
 
-static void
-bus_get_ready (GObject *source,
-               GAsyncResult *res,
-               NMModemManager *self)
-{
-	/* Note we always get an extra reference to self here */
-
-	GError *error = NULL;
-
-	self->priv->dbus_connection = g_bus_get_finish (res, &error);
-	if (!self->priv->dbus_connection) {
-		nm_log_warn (LOGD_CORE, "error getting bus connection: %s", error->message);
-		g_error_free (error);
-		/* Setup timeout to relaunch */
-		schedule_modem_manager_relaunch (self, MODEM_POKE_INTERVAL);
-	} else {
-		/* Got the bus, ensure client */
-		ensure_client (self);
-	}
-
-	/* Balance refcount */
-	g_object_unref (self);
-}
-
 static gboolean
-ensure_bus (NMModemManager *self)
+mm_launch_cb (NMModemManager *self)
 {
-	/* Clear launch ID */
 	self->priv->mm_launch_id = 0;
-
-	if (!self->priv->dbus_connection)
-		g_bus_get (G_BUS_TYPE_SYSTEM,
-		           NULL,
-		           (GAsyncReadyCallback)bus_get_ready,
-		           g_object_ref (self));
-	else
-		/* If bus is already available, ensure client */
-		ensure_client (self);
-
-	return FALSE;
+	ensure_modem_manager (self);
+	return G_SOURCE_REMOVE;
 }
 
 static void
@@ -407,11 +519,31 @@ schedule_modem_manager_relaunch (NMModemManager *self,
 {
 	/* No need to pass an extra reference to self; timeout/idle will be
 	 * cancelled if the object gets disposed. */
-
 	if (n_seconds)
-		self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)ensure_bus, self);
+		self->priv->mm_launch_id = g_timeout_add_seconds (n_seconds, (GSourceFunc)mm_launch_cb, self);
 	else
-		self->priv->mm_launch_id = g_idle_add ((GSourceFunc)ensure_bus, self);
+		self->priv->mm_launch_id = g_idle_add ((GSourceFunc)mm_launch_cb, self);
+}
+
+static void
+bus_get_ready (GObject *source,
+               GAsyncResult *res,
+               gpointer user_data)
+{
+	gs_unref_object NMModemManager *self = NM_MODEM_MANAGER (user_data);
+	gs_free_error GError *error = NULL;
+
+	self->priv->dbus_connection = g_bus_get_finish (res, &error);
+	if (!self->priv->dbus_connection) {
+		nm_log_warn (LOGD_MB, "error getting bus connection: %s", error->message);
+		return;
+	}
+
+	/* Got the bus, ensure clients */
+	ensure_modem_manager (self);
+#if WITH_OFONO
+	ensure_ofono_client (self);
+#endif
 }
 
 /************************************************************************/
@@ -423,7 +555,11 @@ nm_modem_manager_init (NMModemManager *self)
 
 	self->priv->modems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
 
-	schedule_modem_manager_relaunch (self, 0);
+	// FIXME: this doesn't handle bus-daemon restart
+	g_bus_get (G_BUS_TYPE_SYSTEM,
+	           NULL,
+	           (GAsyncReadyCallback)bus_get_ready,
+	           g_object_ref (self));
 }
 
 static void
@@ -433,8 +569,16 @@ dispose (GObject *object)
 
 	nm_clear_g_source (&self->priv->mm_launch_id);
 
-	modem_manager_clear_signals (self);
-	g_clear_object (&self->priv->modem_manager);
+	clear_modem_manager (self);
+
+#if WITH_OFONO
+	if (self->priv->ofono_proxy) {
+		g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_name_owner_changed, self);
+		g_signal_handlers_disconnect_by_func (self->priv->ofono_proxy, ofono_signal_cb, self);
+		g_clear_object (&self->priv->ofono_proxy);
+	}
+#endif
+
 	g_clear_object (&self->priv->dbus_connection);
 
 	if (self->priv->modems) {
diff --git a/src/devices/wwan/nm-modem-ofono.c b/src/devices/wwan/nm-modem-ofono.c
new file mode 100644
index 00000000..86492d3d
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.c
@@ -0,0 +1,1202 @@
+/* -*- 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) 2013 - 2016 Canonical Ltd.
+ */
+
+#include "nm-default.h"
+
+#include "nm-modem-ofono.h"
+
+#include <string.h>
+
+#include "nm-core-internal.h"
+#include "nm-device-private.h"
+#include "nm-modem.h"
+#include "nm-platform.h"
+
+G_DEFINE_TYPE (NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
+
+#define NM_MODEM_OFONO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_MODEM_OFONO, NMModemOfonoPrivate))
+
+#define VARIANT_IS_OF_TYPE_BOOLEAN(v)      ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_BOOLEAN) ))
+#define VARIANT_IS_OF_TYPE_STRING(v)       ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING) ))
+#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v)  ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_OBJECT_PATH) ))
+#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_STRING_ARRAY) ))
+#define VARIANT_IS_OF_TYPE_DICTIONARY(v)   ((v) != NULL && ( g_variant_is_of_type ((v), G_VARIANT_TYPE_DICTIONARY) ))
+
+typedef struct {
+	GHashTable *connect_properties;
+
+	GDBusProxy *modem_proxy;
+	GDBusProxy *connman_proxy;
+	GDBusProxy *context_proxy;
+	GDBusProxy *sim_proxy;
+
+	GError *property_error;
+
+	char *context_path;
+	char *imsi;
+
+	gboolean modem_online;
+	gboolean gprs_attached;
+
+	NMIP4Config *ip4_config;
+} NMModemOfonoPrivate;
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_MB
+#define _NMLOG_PREFIX_NAME "modem-ofono"
+#define _NMLOG(level, ...) \
+    G_STMT_START { \
+        const NMLogLevel _level = (level); \
+        \
+        if (nm_logging_enabled (_level, (_NMLOG_DOMAIN))) { \
+            NMModemOfono *const __self = (self); \
+            char __prefix_name[128]; \
+            const char *__uid; \
+            \
+            _nm_log (_level, (_NMLOG_DOMAIN), 0, \
+                     "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                     _NMLOG_PREFIX_NAME, \
+                     (__self \
+                         ? ({ \
+                                ((__uid = nm_modem_get_uid ((NMModem *) __self)) \
+                                    ? nm_sprintf_buf (__prefix_name, "[%s]", __uid) \
+                                    : "(null)"); \
+                            }) \
+                         : "") \
+                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+        } \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static gboolean
+ip_string_to_network_address (const gchar *str,
+                              guint32 *out)
+{
+	guint32 addr = 0;
+	gboolean success = FALSE;
+
+	if (!str || inet_pton (AF_INET, str, &addr) != 1)
+		addr = 0;
+	else
+		success = TRUE;
+
+	*out = (guint32)addr;
+	return success;
+}
+
+static void
+get_capabilities (NMModem *_self,
+                  NMDeviceModemCapabilities *modem_caps,
+                  NMDeviceModemCapabilities *current_caps)
+{
+	/* FIXME: auto-detect capabilities to allow LTE */
+	*modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+	*current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
+}
+
+static void
+update_modem_state (NMModemOfono *self)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+	NMModemState new_state = NM_MODEM_STATE_DISABLED;
+	const char *reason = NULL;
+
+	_LOGI ("'Attached': %s 'Online': %s 'IMSI': %s",
+	       priv->gprs_attached ? "true" : "false",
+	       priv->modem_online ? "true" : "false",
+	       priv->imsi);
+
+	if (priv->modem_online == FALSE) {
+		reason = "modem 'Online=false'";
+	} else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) {
+		reason = "modem not ready";
+	} else if (priv->gprs_attached == FALSE) {
+		new_state = NM_MODEM_STATE_SEARCHING;
+		reason = "modem searching";
+	} else {
+		new_state = NM_MODEM_STATE_REGISTERED;
+		reason = "modem ready";
+	}
+
+	if (state != new_state)
+		nm_modem_set_state (NM_MODEM (self), new_state, reason);
+}
+
+/* Disconnect */
+typedef struct {
+	NMModemOfono *self;
+	GSimpleAsyncResult *result;
+	GCancellable *cancellable;
+	gboolean warn;
+} DisconnectContext;
+
+static void
+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 (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 *result,
+                   GError **error)
+{
+	return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+disconnect_done (GDBusProxy *proxy,
+				 GAsyncResult *result,
+				 gpointer user_data)
+{
+	DisconnectContext *ctx = (DisconnectContext*) user_data;
+	NMModemOfono *self = ctx->self;
+	GError *error = NULL;
+
+	g_dbus_proxy_call_finish (proxy, result, &error);
+	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+		_LOGD ("disconnect cancelled");
+		return;
+	}
+
+	if (error) {
+		if (ctx->warn)
+			_LOGW ("failed to disconnect modem: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	_LOGD ("modem disconnected");
+
+	update_modem_state (self);
+	disconnect_context_complete (ctx);
+}
+
+static void
+disconnect (NMModem *modem,
+            gboolean warn,
+            GCancellable *cancellable,
+            GAsyncReadyCallback callback,
+            gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	DisconnectContext *ctx;
+	NMModemState state = nm_modem_get_state (NM_MODEM (self));
+
+	_LOGD ("warn: %s modem_state: %s",
+	       warn ? "TRUE" : "FALSE",
+	       nm_modem_state_to_string (state));
+
+	if (state != NM_MODEM_STATE_CONNECTED)
+		return;
+
+	ctx = g_slice_new (DisconnectContext);
+	ctx->self = g_object_ref (self);
+	ctx->warn = warn;
+
+	if (callback) {
+		ctx->result = g_simple_async_result_new (G_OBJECT (self),
+		                                         callback,
+		                                         user_data,
+		                                         disconnect);
+	}
+	/* Setup cancellable */
+	ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+	if (disconnect_context_complete_if_cancelled (ctx))
+		return;
+
+	nm_modem_set_state (NM_MODEM (self),
+	                    NM_MODEM_STATE_DISCONNECTING,
+	                    nm_modem_state_to_string (NM_MODEM_STATE_DISCONNECTING));
+
+	g_dbus_proxy_call (priv->context_proxy,
+	                   "SetProperty",
+	                   g_variant_new ("(sv)",
+	                                  "Active",
+	                                  g_variant_new ("b", warn)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) disconnect_done,
+	                   ctx);
+}
+
+static void
+deactivate_cleanup (NMModem *_self, NMDevice *device)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (_self);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	/* TODO: cancel SimpleConnect() if any */
+
+	g_clear_object (&priv->ip4_config);
+
+	/* Chain up parent's */
+	NM_MODEM_CLASS (nm_modem_ofono_parent_class)->deactivate_cleanup (_self, device);
+}
+
+
+static gboolean
+check_connection_compatible (NMModem *modem,
+                             NMConnection *connection)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMSettingConnection *s_con;
+	NMSettingGsm *s_gsm;
+	const char *uuid;
+	const char *id;
+
+	s_con = nm_connection_get_setting_connection (connection);
+	g_assert (s_con);
+
+	uuid = nm_connection_get_uuid (connection);
+	id = nm_connection_get_id (connection);
+
+	s_gsm = nm_connection_get_setting_gsm (connection);
+	if (!s_gsm)
+		return FALSE;
+
+	if (!priv->imsi) {
+		_LOGW ("skipping %s/%s: no IMSI", uuid, id);
+		return FALSE;
+	}
+
+	if (strcmp (nm_setting_connection_get_connection_type (s_con), NM_SETTING_GSM_SETTING_NAME)) {
+		_LOGD ("skipping %s/%s: not GSM", uuid, id);
+		return FALSE;
+	}
+
+	if (!g_strrstr (id, "/context")) {
+		_LOGD ("skipping %s/%s: unexpected ID", uuid, id);
+		return FALSE;
+	}
+
+	if (!g_strrstr (id, priv->imsi)) {
+		_LOGD ("skipping %s/%s: ID doesn't contain IMSI", uuid, id);
+		return FALSE;
+	}
+
+	_LOGD ("%s/%s compatible with IMSI %s", uuid, id, priv->imsi);
+	return TRUE;
+}
+
+static void
+handle_sim_property (GDBusProxy *proxy,
+                     const char *property,
+                     GVariant *v,
+                     gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (g_strcmp0 (property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING (v)) {
+		gsize length;
+		const char *value_str = g_variant_get_string (v, &length);
+
+		_LOGD ("SubscriberIdentify found");
+
+		/* Check for empty DBus string value */
+		if (length &&
+			g_strcmp0 (value_str, "(null)") != 0 &&
+			g_strcmp0 (value_str, priv->imsi) != 0) {
+
+			if (priv->imsi != NULL) {
+				_LOGW ("SimManager:'SubscriberIdentity' changed: %s", priv->imsi);
+				g_free(priv->imsi);
+			}
+
+			priv->imsi = g_strdup (value_str);
+			update_modem_state (self);
+		}
+	}
+}
+
+static void
+sim_property_changed (GDBusProxy *proxy,
+                      const char *property,
+                      GVariant *v,
+                      gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_sim_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+sim_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting sim properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	_LOGD ("sim v_properties is type: %s", g_variant_get_type_string (v_properties));
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+	if (!v_dict) {
+		_LOGW ("error getting sim properties: no v_dict");
+		return;
+	}
+
+	_LOGD ("sim v_dict is type: %s", g_variant_get_type_string (v_dict));
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_sim_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+static void
+handle_sim_iface (NMModemOfono *self, gboolean found)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	_LOGD ("SimManager interface %sfound", found ? "" : "not ");
+
+	if (!found && priv->sim_proxy) {
+		_LOGI ("SimManager interface disappeared");
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->sim_proxy);
+		g_clear_pointer (&priv->imsi, g_free);
+		update_modem_state (self);
+	} else if (found && !priv->sim_proxy) {
+		GError *error = NULL;
+
+		_LOGI ("found new SimManager interface");
+
+		priv->sim_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+		                                                 G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                                                 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                                                 NULL, /* GDBusInterfaceInfo */
+		                                                 OFONO_DBUS_SERVICE,
+		                                                 nm_modem_get_path (NM_MODEM (self)),
+		                                                 OFONO_DBUS_INTERFACE_SIM_MANAGER,
+		                                                 NULL, /* GCancellable */
+		                                                 &error);
+		if (priv->sim_proxy == NULL) {
+			_LOGW ("failed to create SimManager proxy: %s", error->message);
+			g_error_free (error);
+			return;
+		}
+
+		/* Watch for custom ofono PropertyChanged signals */
+		_nm_dbus_signal_connect (priv->sim_proxy,
+		                         "PropertyChanged",
+		                         G_VARIANT_TYPE ("(sv)"),
+		                         G_CALLBACK (sim_property_changed),
+		                         self);
+
+		g_dbus_proxy_call (priv->sim_proxy,
+		                   "GetProperties",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   20000,
+		                   NULL,
+		                   (GAsyncReadyCallback) sim_get_properties_done,
+		                   g_object_ref (self));
+	}
+}
+
+static void
+handle_connman_property (GDBusProxy *proxy,
+                         const char *property,
+                         GVariant *v,
+                         gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (g_strcmp0 (property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+		gboolean attached = g_variant_get_boolean (v);
+		gboolean old_attached = priv->gprs_attached;
+
+		_LOGD ("Attached: %s", attached ? "True" : "False");
+
+		if (priv->gprs_attached != attached) {
+			priv->gprs_attached = attached;
+
+			_LOGI ("Attached %s -> %s",
+			       old_attached ? "true" : "false",
+			       attached ? "true" : "false");
+
+			update_modem_state (self);
+		}
+	}
+}
+
+static void
+connman_property_changed (GDBusProxy *proxy,
+                        const char *property,
+                        GVariant *v,
+                        gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_connman_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+connman_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+		                                       result,
+		                                       G_VARIANT_TYPE ("(a{sv})"),
+		                                       &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting connman properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_connman_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+static void
+handle_connman_iface (NMModemOfono *self, gboolean found)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	_LOGD ("ConnectionManager interface %sfound", found ? "" : "not ");
+
+	if (!found && priv->connman_proxy) {
+		_LOGI ("ConnectionManager interface disappeared");
+
+		g_signal_handlers_disconnect_by_data (priv->connman_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->connman_proxy);
+
+		/* The connection manager proxy disappeared, we should
+		 * consider the modem disabled.
+		 */
+		priv->gprs_attached = FALSE;
+
+		update_modem_state (self);
+	} else if (found && !priv->connman_proxy) {
+		GError *error = NULL;
+
+		_LOGI ("found new ConnectionManager interface");
+
+		priv->connman_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+		                                                     G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+		                                                     | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+		                                                     NULL, /* GDBusInterfaceInfo */
+		                                                     OFONO_DBUS_SERVICE,
+		                                                     nm_modem_get_path (NM_MODEM (self)),
+		                                                     OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
+		                                                     NULL, /* GCancellable */
+		                                                     &error);
+		if (priv->connman_proxy == NULL) {
+			_LOGW ("failed to create ConnectionManager proxy: %s", error->message);
+			g_error_free (error);
+			return;
+		}
+
+		/* Watch for custom ofono PropertyChanged signals */
+		_nm_dbus_signal_connect (priv->connman_proxy,
+		                         "PropertyChanged",
+		                         G_VARIANT_TYPE ("(sv)"),
+		                         G_CALLBACK (connman_property_changed),
+		                         self);
+
+		g_dbus_proxy_call (priv->connman_proxy,
+		                   "GetProperties",
+		                   NULL,
+		                   G_DBUS_CALL_FLAGS_NONE,
+		                   20000,
+		                   NULL,
+		                   (GAsyncReadyCallback) connman_get_properties_done,
+		                   g_object_ref (self));
+	}
+}
+
+static void
+handle_modem_property (GDBusProxy *proxy,
+                       const char *property,
+                       GVariant *v,
+                       gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if ((g_strcmp0 (property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN (v)) {
+		gboolean online = g_variant_get_boolean (v);
+
+		_LOGD ("Online: %s", online ? "True" : "False");
+
+		if (online != priv->modem_online) {
+			priv->modem_online = online;
+			_LOGI ("modem is now %s", online ? "Online" : "Offline");
+			update_modem_state (self);
+		}
+
+	} else if ((g_strcmp0 (property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY (v)) {
+		const char **array, **iter;
+		gboolean found_connman = FALSE;
+		gboolean found_sim = FALSE;
+
+		_LOGD ("Interfaces found");
+
+		array = g_variant_get_strv (v, NULL);
+		if (array) {
+			for (iter = array; *iter; iter++) {
+				if (g_strcmp0 (OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0)
+					found_sim = TRUE;
+				else if (g_strcmp0 (OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0)
+					found_connman = TRUE;
+			}
+			g_free (array);
+		}
+
+		handle_sim_iface (self, found_sim);
+		handle_connman_iface (self, found_connman);
+	}
+}
+
+static void
+modem_property_changed (GDBusProxy *proxy,
+                        const char *property,
+                        GVariant *v,
+                        gpointer user_data)
+{
+	GVariant *v_child = g_variant_get_child_value (v, 0);
+
+	handle_modem_property (proxy, property, v_child, user_data);
+	g_variant_unref (v_child);
+}
+
+static void
+modem_get_properties_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	GError *error = NULL;
+	GVariant *v_properties, *v_dict, *v;
+	GVariantIter i;
+	const char *property;
+
+	v_properties = _nm_dbus_proxy_call_finish (proxy,
+	                                           result,
+	                                           G_VARIANT_TYPE ("(a{sv})"),
+	                                           &error);
+	if (!v_properties) {
+		g_dbus_error_strip_remote_error (error);
+		_LOGW ("error getting modem properties: %s", error->message);
+		g_error_free (error);
+		return;
+	}
+
+	v_dict = g_variant_get_child_value (v_properties, 0);
+	if (!v_dict) {
+		_LOGW ("error getting modem properties: no v_dict");
+		return;
+	}
+
+	/*
+	 * TODO:
+	 * 1) optimize by looking up properties ( Online, Interfaces ), instead
+	 *    of iterating
+	 *
+	 * 2) reduce code duplication between all of the get_properties_done
+	 *    functions in this class.
+	 */
+
+	g_variant_iter_init (&i, v_dict);
+	while (g_variant_iter_next (&i, "{&sv}", &property, &v)) {
+		handle_modem_property (NULL, property, v, self);
+		g_variant_unref (v);
+	}
+
+	g_variant_unref (v_dict);
+	g_variant_unref (v_properties);
+}
+
+NMModem *
+nm_modem_ofono_new (const char *path)
+{
+	gs_free char *basename = NULL;
+
+	g_return_val_if_fail (path != NULL, NULL);
+
+	nm_log_info (LOGD_MB, "ofono: creating new Ofono modem path %s", path);
+
+	/* Use short modem name (not its object path) as the NM device name (which
+	 * comes from NM_MODEM_UID)and the device ID.
+	 */
+	basename = g_path_get_basename (path);
+
+	return (NMModem *) g_object_new (NM_TYPE_MODEM_OFONO,
+	                                 NM_MODEM_PATH, path,
+	                                 NM_MODEM_UID, basename,
+	                                 NM_MODEM_DEVICE_ID, basename,
+	                                 NM_MODEM_CONTROL_PORT, "ofono", /* mandatory */
+	                                 NM_MODEM_DRIVER, "ofono",
+	                                 NM_MODEM_STATE, NM_MODEM_STATE_INITIALIZING,
+	                                 NULL);
+}
+
+static void
+stage1_prepare_done (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	g_clear_pointer (&priv->connect_properties, g_hash_table_destroy);
+
+	g_dbus_proxy_call_finish (proxy, result, &error);
+	if (error) {
+		_LOGW ("connection failed: %s", error->message);
+
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_BUSY);
+		/*
+		 * FIXME: add code to check for InProgress so that the
+		 * connection doesn't continue to try and activate,
+		 * leading to the connection being disabled, and a 5m
+		 * timeout...
+		 */
+
+		g_clear_error (&error);
+	}
+}
+
+static void
+context_property_changed (GDBusProxy *proxy,
+                          const char *property,
+                          GVariant *v,
+                          gpointer user_data)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_NONE;
+	NMPlatformIP4Address addr;
+	gboolean ret = FALSE;
+	GVariant *v_dict;
+	const gchar *s, *addr_s;
+	const gchar **array, **iter;
+	guint32 address_network, gateway_network;
+	guint prefix = 0;
+
+	_LOGD ("PropertyChanged: %s", property);
+
+	/*
+	 * TODO: might be a good idea and re-factor this to mimic bluez-device,
+	 * ie. have this function just check the key, and call a sub-func to
+	 * handle the action.
+	 */
+
+	if (g_strcmp0 (property, "Settings") != 0)
+		return;
+
+	v_dict = g_variant_get_child_value (v, 0);
+	if (!v_dict) {
+		_LOGW ("error getting IPv4 Settings: no v_dict");
+		goto out;
+	}
+
+	_LOGI ("IPv4 static Settings:");
+
+	if (g_variant_lookup (v_dict, "Interface", "&s", &s)) {
+		if (s && strlen (s)) {
+			_LOGD ("Interface: %s", s);
+			g_object_set (self,
+			              NM_MODEM_DATA_PORT, g_strdup (s),
+			              NM_MODEM_IP4_METHOD, NM_MODEM_IP_METHOD_STATIC,
+			              NULL);
+		} else {
+			_LOGW ("Settings 'Interface'; empty");
+			goto out;
+		}
+
+	} else {
+		_LOGW ("Settings 'Interface' missing");
+		goto out;
+	}
+
+	/* TODO: verify handling of ip4_config; check other places it's used... */
+	g_clear_object (&priv->ip4_config);
+
+	memset (&addr, 0, sizeof (addr));
+
+	/*
+	 * TODO:
+	 *
+	 * NM 1.2 changed the NMIP4Config constructor to take an ifindex
+	 * ( vs. void pre 1.2 ), to tie config instance to a specific
+	 * platform interface.
+	 *
+	 * This doesn't work for ofono, as the devices are created
+	 * dynamically ( eg. ril_0, ril_1 ) in NMModemManager.  The
+	 * device created doesn't really map directly to a platform
+	 * link.  The closest would be one of the devices owned by
+	 * rild ( eg. ccmin0 ), which is passed to us above as
+	 * 'Interface'.
+	 *
+	 * This needs discussion with upstream.
+	 */
+	priv->ip4_config = nm_ip4_config_new (0);
+
+	/* TODO: simply if/else error logic! */
+
+	if (g_variant_lookup (v_dict, "Address", "&s", &addr_s)) {
+		_LOGD ("Address: %s", addr_s);
+
+		if (ip_string_to_network_address (addr_s, &address_network)) {
+			addr.address = address_network;
+			addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
+		} else {
+			_LOGW ("can't convert 'Address' %s to addr", s);
+			goto out;
+		}
+
+	} else {
+		_LOGW ("Settings 'Address' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "Netmask", "&s", &s)) {
+		_LOGD ("Netmask: %s", s);
+
+		if (s && ip_string_to_network_address (s, &address_network)) {
+			prefix = nm_utils_ip4_netmask_to_prefix (address_network);
+			if (prefix > 0)
+				addr.plen = prefix;
+		} else {
+			_LOGW ("invalid 'Netmask': %s", s);
+			goto out;
+		}
+	} else {
+		_LOGW ("Settings 'Netmask' missing");
+		goto out;
+	}
+
+	_LOGI ("Address: %s/%d", addr_s, prefix);
+
+	nm_ip4_config_add_address (priv->ip4_config, &addr);
+
+	if (g_variant_lookup (v_dict, "Gateway", "&s", &s)) {
+		if (s && ip_string_to_network_address (s, &gateway_network)) {
+			_LOGI ("Gateway: %s", s);
+			nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+		} else {
+			_LOGW ("invalid 'Gateway': %s", s);
+			goto out;
+		}
+		nm_ip4_config_set_gateway (priv->ip4_config, gateway_network);
+	} else {
+		_LOGW ("Settings 'Gateway' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "DomainNameServers", "^a&s", &array)) {
+		if (array) {
+			for (iter = array; *iter; iter++) {
+				if (ip_string_to_network_address (*iter, &address_network) && address_network > 0) {
+					_LOGI ("DNS: %s", *iter);
+					nm_ip4_config_add_nameserver (priv->ip4_config, address_network);
+				} else {
+					_LOGW ("invalid NameServer: %s", *iter);
+				}
+			}
+
+			if (iter == array) {
+				_LOGW ("Settings: 'DomainNameServers': none specified");
+				g_free (array);
+				goto out;
+			}
+			g_free (array);
+		}
+	} else {
+		_LOGW ("Settings 'DomainNameServers' missing");
+		goto out;
+	}
+
+	if (g_variant_lookup (v_dict, "MessageProxy", "&s", &s)) {
+		_LOGI ("MessageProxy: %s", s);
+		if (s && ip_string_to_network_address (s, &address_network)) {
+			NMPlatformIP4Route mms_route;
+
+			mms_route.network = address_network;
+			mms_route.plen = 32;
+			mms_route.gateway = gateway_network;
+
+			mms_route.metric = 1;
+
+			nm_ip4_config_add_route (priv->ip4_config, &mms_route);
+		} else {
+			_LOGW ("invalid MessageProxy: %s", s);
+		}
+	}
+
+	ret = TRUE;
+
+out:
+	if (nm_modem_get_state (NM_MODEM (self)) != NM_MODEM_STATE_CONNECTED) {
+		_LOGI ("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE");
+		if (!ret)
+			reason = NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE;
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, ret, reason);
+	} else {
+		_LOGW ("MODEM_PPP_FAILED");
+		g_signal_emit_by_name (self, NM_MODEM_PPP_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
+	}
+}
+
+static NMActStageReturn
+static_stage3_ip4_config_start (NMModem *_self,
+                                NMActRequest *req,
+                                NMDeviceStateReason *reason)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (_self);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
+	GError *error = NULL;
+
+	if (priv->ip4_config) {
+		_LOGD ("IP4 config is done; setting modem_state -> CONNECTED");
+		g_signal_emit_by_name (self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error);
+
+		/* Signal listener takes ownership of the IP4Config */
+		priv->ip4_config = NULL;
+
+		nm_modem_set_state (NM_MODEM (self),
+		                    NM_MODEM_STATE_CONNECTED,
+		                    nm_modem_state_to_string (NM_MODEM_STATE_CONNECTED));
+		ret = NM_ACT_STAGE_RETURN_POSTPONE;
+	}
+
+	return ret;
+}
+
+static void
+context_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	priv->context_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (error) {
+		_LOGE ("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_BUSY);
+		return;
+	}
+
+	if (!priv->gprs_attached) {
+		g_signal_emit_by_name (self, NM_MODEM_PREPARE_RESULT, FALSE,
+		                       NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
+		return;
+	}
+
+	/* We have an old copy of the settings from a previous activation,
+	 * clear it so that we can gate getting the IP config from oFono
+	 * on whether or not we have already received them
+	 */
+	g_clear_object (&priv->ip4_config);
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->context_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (context_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->context_proxy,
+	                   "SetProperty",
+	                   g_variant_new ("(sv)",
+	                                  "Active",
+	                                   g_variant_new ("b", TRUE)),
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) stage1_prepare_done,
+	                   g_object_ref (self));
+}
+
+static void
+do_context_activate (NMModemOfono *self)
+{
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	g_return_if_fail (NM_IS_MODEM_OFONO (self));
+
+	g_clear_object (&priv->context_proxy);
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          OFONO_DBUS_SERVICE,
+	                          priv->context_path,
+	                          OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
+	                          NULL,
+	                          (GAsyncReadyCallback) context_proxy_new_cb,
+	                          g_object_ref (self));
+}
+
+static GHashTable *
+create_connect_properties (NMConnection *connection)
+{
+	NMSettingGsm *setting;
+	GHashTable *properties;
+	const char *str;
+
+	setting = nm_connection_get_setting_gsm (connection);
+	properties = g_hash_table_new (g_str_hash, g_str_equal);
+
+	str = nm_setting_gsm_get_apn (setting);
+	if (str)
+		g_hash_table_insert (properties, "AccessPointName", g_strdup (str));
+
+	str = nm_setting_gsm_get_username (setting);
+	if (str)
+		g_hash_table_insert (properties, "Username", g_strdup (str));
+
+	str = nm_setting_gsm_get_password (setting);
+	if (str)
+		g_hash_table_insert (properties, "Password", g_strdup (str));
+
+	return properties;
+}
+
+static NMActStageReturn
+act_stage1_prepare (NMModem *modem,
+                    NMConnection *connection,
+                    NMDeviceStateReason *reason)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (modem);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	const char *context_id;
+	char **id = NULL;
+
+	context_id = nm_connection_get_id (connection);
+	id = g_strsplit (context_id, "/", 0);
+	g_assert (id[2]);
+
+	_LOGD ("trying %s %s", id[1], id[2]);
+
+	g_free (priv->context_path);
+	priv->context_path = g_strdup_printf ("%s/%s",
+	                                      nm_modem_get_path (modem),
+	                                      id[2]);
+	g_strfreev (id);
+
+	if (!priv->context_path) {
+		*reason = NM_DEVICE_STATE_REASON_GSM_APN_FAILED;
+			return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	if (priv->connect_properties)
+		g_hash_table_destroy (priv->connect_properties);
+
+	priv->connect_properties = create_connect_properties (connection);
+
+	_LOGI ("activating context %s", priv->context_path);
+
+	if (nm_modem_get_state (modem) == NM_MODEM_STATE_REGISTERED) {
+		do_context_activate (self);
+	} else {
+		_LOGW ("could not activate context: modem is not registered.");
+		*reason = NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER;
+		return NM_ACT_STAGE_RETURN_FAILURE;
+	}
+
+	return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+static void
+modem_proxy_new_cb (GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
+{
+	gs_unref_object NMModemOfono *self = NM_MODEM_OFONO (user_data);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+	GError *error = NULL;
+
+	priv->modem_proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+	if (error) {
+		_LOGE ("failed to create ofono modem DBus proxy: %s", error->message);
+		return;
+	}
+
+	/* Watch for custom ofono PropertyChanged signals */
+	_nm_dbus_signal_connect (priv->modem_proxy,
+	                         "PropertyChanged",
+	                         G_VARIANT_TYPE ("(sv)"),
+	                         G_CALLBACK (modem_property_changed),
+	                         self);
+
+	g_dbus_proxy_call (priv->modem_proxy,
+	                   "GetProperties",
+	                   NULL,
+	                   G_DBUS_CALL_FLAGS_NONE,
+	                   20000,
+	                   NULL,
+	                   (GAsyncReadyCallback) modem_get_properties_done,
+	                   g_object_ref (self));
+}
+
+static void
+nm_modem_ofono_init (NMModemOfono *self)
+{
+}
+
+static void
+constructed (GObject *object)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (object);
+
+	g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+	                          G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+	                          NULL,
+	                          OFONO_DBUS_SERVICE,
+	                          nm_modem_get_path (NM_MODEM (self)),
+	                          OFONO_DBUS_INTERFACE_MODEM,
+	                          NULL,
+	                          (GAsyncReadyCallback) modem_proxy_new_cb,
+	                          g_object_ref (self));
+}
+
+static void
+dispose (GObject *object)
+{
+	NMModemOfono *self = NM_MODEM_OFONO (object);
+	NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE (self);
+
+	if (priv->connect_properties) {
+		g_hash_table_destroy (priv->connect_properties);
+		priv->connect_properties = NULL;
+	}
+
+	g_clear_object (&priv->ip4_config);
+
+	if (priv->modem_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->modem_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->modem_proxy);
+	}
+
+	g_clear_object (&priv->connman_proxy);
+	g_clear_object (&priv->context_proxy);
+
+	if (priv->sim_proxy) {
+		g_signal_handlers_disconnect_by_data (priv->sim_proxy, NM_MODEM_OFONO (self));
+		g_clear_object (&priv->sim_proxy);
+	}
+
+	g_free (priv->imsi);
+	priv->imsi = NULL;
+
+	G_OBJECT_CLASS (nm_modem_ofono_parent_class)->dispose (object);
+}
+
+static void
+nm_modem_ofono_class_init (NMModemOfonoClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	NMModemClass *modem_class = NM_MODEM_CLASS (klass);
+
+	g_type_class_add_private (object_class, sizeof (NMModemOfonoPrivate));
+
+	/* Virtual methods */
+	object_class->constructed = constructed;
+	object_class->dispose = dispose;
+
+	modem_class->get_capabilities = get_capabilities;
+	modem_class->disconnect = disconnect;
+	modem_class->disconnect_finish = disconnect_finish;
+	modem_class->deactivate_cleanup = deactivate_cleanup;
+	modem_class->check_connection_compatible = check_connection_compatible;
+
+	/* same as nm-modem-broadband */
+	modem_class->act_stage1_prepare = act_stage1_prepare;
+
+	/* same as nm-modem-broadband */
+	modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
+}
diff --git a/src/devices/wwan/nm-modem-ofono.h b/src/devices/wwan/nm-modem-ofono.h
new file mode 100644
index 00000000..5965526d
--- /dev/null
+++ b/src/devices/wwan/nm-modem-ofono.h
@@ -0,0 +1,53 @@
+/* -*- 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) 2013 - Canonical Ltd.
+ */
+
+#ifndef NM_MODEM_OFONO_H
+#define NM_MODEM_OFONO_H
+
+#include "nm-modem.h"
+
+#define NM_TYPE_MODEM_OFONO            (nm_modem_ofono_get_type ())
+#define NM_MODEM_OFONO(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM_OFONO, NMModemOfono))
+#define NM_IS_MODEM_OFONO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+#define NM_IS_MODEM_OFONO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  NM_TYPE_MODEM_OFONO))
+#define NM_MODEM_OFONO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  NM_TYPE_MODEM_OFONO, NMModemOfonoClass))
+
+#define OFONO_DBUS_SERVICE                      "org.ofono"
+#define OFONO_DBUS_PATH                         "/"
+#define OFONO_DBUS_INTERFACE                    "org.ofono.Manager"
+#define OFONO_DBUS_INTERFACE_MODEM              "org.ofono.Modem"
+#define OFONO_DBUS_INTERFACE_CONNECTION_MANAGER "org.ofono.ConnectionManager"
+#define OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT "org.ofono.ConnectionContext"
+#define OFONO_DBUS_INTERFACE_SIM_MANAGER        "org.ofono.SimManager"
+
+typedef struct {
+	NMModem parent;
+} NMModemOfono;
+
+typedef struct {
+	NMModemClass parent;
+} NMModemOfonoClass;
+
+GType nm_modem_ofono_get_type (void);
+
+NMModem *nm_modem_ofono_new (const char *path);
+
+#endif /* NM_MODEM_OFONO_H */
diff --git a/src/devices/wwan/nm-modem.c b/src/devices/wwan/nm-modem.c
index a6c77007..2e3d63bb 100644
--- a/src/devices/wwan/nm-modem.c
+++ b/src/devices/wwan/nm-modem.c
@@ -202,7 +202,9 @@ nm_modem_set_mm_enabled (NMModem *self,
 		return;
 	}
 
-	NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
+	/* Not all modem classes support set_mm_enabled */
+	if (NM_MODEM_GET_CLASS (self)->set_mm_enabled)
+		NM_MODEM_GET_CLASS (self)->set_mm_enabled (self, enabled);
 
 	/* Pre-empt the state change signal */
 	nm_modem_set_state (self,
@@ -573,6 +575,8 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 	const char *method;
 	NMActStageReturn ret;
 
+	nm_log_dbg (LOGD_MB, "ip4_config_start");
+
 	g_return_val_if_fail (NM_IS_MODEM (self), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE (device), NM_ACT_STAGE_RETURN_FAILURE);
 	g_return_val_if_fail (NM_IS_DEVICE_CLASS (device_class), NM_ACT_STAGE_RETURN_FAILURE);
@@ -602,9 +606,11 @@ nm_modem_stage3_ip4_config_start (NMModem *self,
 		ret = ppp_stage3_ip_config_start (self, req, reason);
 		break;
 	case NM_MODEM_IP_METHOD_STATIC:
+		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_STATIC");
 		ret = NM_MODEM_GET_CLASS (self)->static_stage3_ip4_config_start (self, req, reason);
 		break;
 	case NM_MODEM_IP_METHOD_AUTO:
+		nm_log_dbg (LOGD_MB, "MODEM_IP_METHOD_AUTO");
 		ret = device_class->act_stage3_ip4_config_start (device, NULL, reason);
 		break;
 	default:
diff --git a/src/devices/wwan/nm-modem.h b/src/devices/wwan/nm-modem.h
index 60e5589b..8f797d44 100644
--- a/src/devices/wwan/nm-modem.h
+++ b/src/devices/wwan/nm-modem.h
@@ -22,12 +22,9 @@
 #ifndef __NETWORKMANAGER_MODEM_H__
 #define __NETWORKMANAGER_MODEM_H__
 
-#include "nm-default.h"
 #include "ppp-manager/nm-ppp-manager.h"
 #include "nm-device.h"
 
-G_BEGIN_DECLS
-
 #define NM_TYPE_MODEM            (nm_modem_get_type ())
 #define NM_MODEM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MODEM, NMModem))
 #define NM_MODEM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  NM_TYPE_MODEM, NMModemClass))
@@ -268,6 +265,4 @@ void nm_modem_emit_ip6_config_result (NMModem *self,
 
 const gchar *nm_modem_ip_type_to_string (NMModemIPType ip_type);
 
-G_END_DECLS
-
 #endif /* __NETWORKMANAGER_MODEM_H__ */
diff --git a/src/devices/wwan/nm-wwan-factory.c b/src/devices/wwan/nm-wwan-factory.c
index 805aea69..507935fc 100644
--- a/src/devices/wwan/nm-wwan-factory.c
+++ b/src/devices/wwan/nm-wwan-factory.c
@@ -90,7 +90,7 @@ modem_added_cb (NMModemManager *manager,
 
 
 NM_DEVICE_FACTORY_DECLARE_TYPES (
-	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_WWAN_ETHERNET)
+	NM_DEVICE_FACTORY_DECLARE_LINK_TYPES    (NM_LINK_TYPE_WWAN_NET)
 	NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_GSM_SETTING_NAME, NM_SETTING_CDMA_SETTING_NAME)
 )
 
@@ -102,7 +102,7 @@ create_device (NMDeviceFactory *factory,
                gboolean *out_ignore)
 {
 	g_return_val_if_fail (plink, NULL);
-	g_return_val_if_fail (plink->type == NM_LINK_TYPE_WWAN_ETHERNET, NULL);
+	g_return_val_if_fail (plink->type == NM_LINK_TYPE_WWAN_NET, NULL);
 	*out_ignore = TRUE;
 	return NULL;
 }
diff --git a/src/devices/wwan/nm-wwan-factory.h b/src/devices/wwan/nm-wwan-factory.h
index 78031e60..61bdcb1e 100644
--- a/src/devices/wwan/nm-wwan-factory.h
+++ b/src/devices/wwan/nm-wwan-factory.h
@@ -21,8 +21,6 @@
 #ifndef __NETWORKMANAGER_WWAN_FACTORY_H__
 #define __NETWORKMANAGER_WWAN_FACTORY_H__
 
-#include "nm-default.h"
-
 #define NM_TYPE_WWAN_FACTORY (nm_wwan_factory_get_type ())
 #define NM_WWAN_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory))