summary refs log tree commit diff
path: root/src/core/devices/wwan
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/wwan')
-rw-r--r--src/core/devices/wwan/nm-device-modem.c24
-rw-r--r--src/core/devices/wwan/nm-device-modem.h2
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c13
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.h2
-rw-r--r--src/core/devices/wwan/nm-modem-manager.h2
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c740
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.h5
-rw-r--r--src/core/devices/wwan/nm-modem.c10
-rw-r--r--src/core/devices/wwan/nm-modem.h2
-rw-r--r--src/core/devices/wwan/nm-wwan-factory.c2
10 files changed, 625 insertions, 177 deletions
diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index e0209aff..b83120f9 100644
--- a/src/core/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -273,6 +273,15 @@ modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_d
         /* Called when the ModemManager modem enabled state is changed externally
          * to NetworkManager (eg something using MM's D-Bus API directly).
          */
+
+        if (!NM_MODEM_GET_CLASS(priv->modem)->set_mm_enabled) {
+            /* We cannot re-enable this modem, thus device becomes unavailable. */
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_UNAVAILABLE,
+                                    NM_DEVICE_STATE_REASON_USER_REQUESTED);
+            return;
+        }
+
         if (nm_device_is_activating(device) || dev_state == NM_DEVICE_STATE_ACTIVATED) {
             /* user-initiated action, hence DISCONNECTED not FAILED */
             nm_device_state_changed(device,
@@ -435,6 +444,13 @@ check_connection_available(NMDevice                      *device,
         return FALSE;
     }
 
+    if (!NM_MODEM_GET_CLASS(priv->modem)->set_mm_enabled && state <= NM_MODEM_STATE_DISABLING) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                   "modem is disabled and NM cannot enable it");
+        return FALSE;
+    }
+
     if (state == NM_MODEM_STATE_LOCKED) {
         if (!nm_connection_get_setting_gsm(connection)) {
             nm_utils_error_set_literal(error,
@@ -599,6 +615,11 @@ set_enabled(NMDevice *device, gboolean enabled)
 
     if (enabled == FALSE) {
         nm_device_state_changed(device, NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_NONE);
+    } else {
+        /* It's possible that the modem is enabled outside of NM. Need to recheck. */
+        nm_device_queue_recheck_available(device,
+                                          NM_DEVICE_STATE_REASON_MODEM_AVAILABLE,
+                                          NM_DEVICE_STATE_REASON_MODEM_FAILED);
     }
 }
 
@@ -617,6 +638,9 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
     if (modem_state <= NM_MODEM_STATE_INITIALIZING)
         return FALSE;
 
+    if (!NM_MODEM_GET_CLASS(priv->modem)->set_mm_enabled && modem_state <= NM_MODEM_STATE_DISABLING)
+        return FALSE;
+
     return TRUE;
 }
 
diff --git a/src/core/devices/wwan/nm-device-modem.h b/src/core/devices/wwan/nm-device-modem.h
index f171d76f..317a5bde 100644
--- a/src/core/devices/wwan/nm-device-modem.h
+++ b/src/core/devices/wwan/nm-device-modem.h
@@ -11,7 +11,7 @@
 
 #define NM_TYPE_DEVICE_MODEM (nm_device_modem_get_type())
 #define NM_DEVICE_MODEM(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModem))
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_MODEM, NMDeviceModem))
 #define NM_DEVICE_MODEM_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_MODEM, NMDeviceModemClass))
 #define NM_IS_DEVICE_MODEM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_MODEM))
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index db4fb29f..f0907c46 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -636,7 +636,7 @@ connect_context_step(NMModemBroadband *self)
         if (ctx->ip_types_i < ctx->ip_types->len) {
             NMModemIPType current;
 
-            current = g_array_index(ctx->ip_types, NMModemIPType, ctx->ip_types_i);
+            current = nm_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,
@@ -993,7 +993,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
         /* Fully fail if invalid IP address retrieved */
         address_string = mm_bearer_ip_config_get_address(self->_priv.ipv4_config);
         if (!address_string
-            || !nm_utils_parse_inaddr_bin(AF_INET, address_string, NULL, &address_network)) {
+            || !nm_inet_parse_bin(AF_INET, address_string, NULL, &address_network)) {
             g_set_error(&error,
                         NM_DEVICE_ERROR,
                         NM_DEVICE_ERROR_INVALID_CONNECTION,
@@ -1005,7 +1005,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
 
         /* Missing gateway not a hard failure */
         gw_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv4_config);
-        if (gw_string && !nm_utils_parse_inaddr_bin(AF_INET, gw_string, NULL, &gw)) {
+        if (gw_string && !nm_inet_parse_bin(AF_INET, gw_string, NULL, &gw)) {
             g_set_error(&error,
                         NM_DEVICE_ERROR,
                         NM_DEVICE_ERROR_INVALID_CONNECTION,
@@ -1057,9 +1057,8 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
 
         dns = mm_bearer_ip_config_get_dns(self->_priv.ipv4_config);
         for (i = 0; dns && dns[i]; i++) {
-            if (nm_utils_parse_inaddr_bin(AF_INET, dns[i], NULL, &address_network)
-                && address_network > 0) {
-                nm_l3_config_data_add_nameserver(l3cd, AF_INET, &address_network);
+            if (nm_inet_parse_bin(AF_INET, dns[i], NULL, &address_network) && address_network > 0) {
+                nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET, &address_network, NULL);
                 _LOGI("  DNS %s", dns[i]);
             }
         }
@@ -1174,7 +1173,7 @@ stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_metho
             struct in6_addr addr;
 
             if (inet_pton(AF_INET6, dns[i], &addr)) {
-                nm_l3_config_data_add_nameserver(l3cd, AF_INET6, &addr);
+                nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET6, &addr, NULL);
                 _LOGI("  DNS %s", dns[i]);
             }
         }
diff --git a/src/core/devices/wwan/nm-modem-broadband.h b/src/core/devices/wwan/nm-modem-broadband.h
index 627fe25a..e4467310 100644
--- a/src/core/devices/wwan/nm-modem-broadband.h
+++ b/src/core/devices/wwan/nm-modem-broadband.h
@@ -10,7 +10,7 @@
 
 #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))
+    (_NM_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))
 #define NM_IS_MODEM_BROADBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_BROADBAND))
diff --git a/src/core/devices/wwan/nm-modem-manager.h b/src/core/devices/wwan/nm-modem-manager.h
index e89a032b..a9339ffc 100644
--- a/src/core/devices/wwan/nm-modem-manager.h
+++ b/src/core/devices/wwan/nm-modem-manager.h
@@ -12,7 +12,7 @@
 
 #define NM_TYPE_MODEM_MANAGER (nm_modem_manager_get_type())
 #define NM_MODEM_MANAGER(obj) \
-    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_MANAGER, NMModemManager))
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MODEM_MANAGER, NMModemManager))
 #define NM_MODEM_MANAGER_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_MODEM_MANAGER, NMModemManagerClass))
 #define NM_IS_MODEM_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM_MANAGER))
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index fc27c3fd..b3745621 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -7,8 +7,12 @@
 
 #include "nm-modem-ofono.h"
 
+#include "libnm-glib-aux/nm-dbus-aux.h"
 #include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-glib-aux/nm-uuid.h"
 #include "devices/nm-device-private.h"
+#include "nm-setting-gsm.h"
+#include "settings/nm-settings.h"
 #include "nm-modem.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-l3-config-data.h"
@@ -27,27 +31,44 @@
 /*****************************************************************************/
 
 typedef struct {
+    NMModemOfono *self;
+    char         *name;
+    char         *type;
+    gboolean      preferred;
+    GDBusProxy   *proxy;
+} OfonoContextData;
+
+typedef struct {
     GHashTable *connect_properties;
+    GHashTable *connections;
+    GHashTable *contexts;
 
     GDBusProxy *modem_proxy;
     GDBusProxy *connman_proxy;
-    GDBusProxy *context_proxy;
     GDBusProxy *sim_proxy;
 
     GCancellable *modem_proxy_cancellable;
     GCancellable *connman_proxy_cancellable;
-    GCancellable *context_proxy_cancellable;
+    GCancellable *connect_cancellable;
     GCancellable *sim_proxy_cancellable;
 
     GError *property_error;
 
-    char *context_path;
     char *imsi;
 
     gboolean modem_online;
+    gboolean connman_powered;
     gboolean gprs_attached;
 
     NML3ConfigData *l3cd_4;
+    NMSettings     *settings;
+
+    guint n_context_proxy_pending;
+
+    /* unowned; reference held by 'contexts' above. */
+    OfonoContextData *current_octx;
+
+    GSource *deferred_connection_timeout_source;
 } NMModemOfonoPrivate;
 
 struct _NMModemOfono {
@@ -96,6 +117,21 @@ G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
 
 /*****************************************************************************/
 
+/*
+ * Deterministic UUID is used to pair imsi+context with exported connection
+ * (via a pair of hash tables).
+ */
+
+static char *
+_generate_uuid(const char *imsi, const char *object_path)
+{
+    return nm_uuid_generate_from_strings(
+        NM_UUID_TYPE_VERSION5,
+        &NM_UUID_INIT(b5, 6b, ad, f5, ef, 9d, 4a, 21, a8, e7, 7d, db, 69, bb, 6b, ee),
+        imsi,
+        object_path);
+}
+
 static void
 get_capabilities(NMModem                   *_self,
                  NMDeviceModemCapabilities *modem_caps,
@@ -106,6 +142,8 @@ get_capabilities(NMModem                   *_self,
     *current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
 }
 
+static void do_context_activate(NMModemOfono *self);
+
 static void
 update_modem_state(NMModemOfono *self)
 {
@@ -114,13 +152,16 @@ update_modem_state(NMModemOfono *self)
     NMModemState         new_state = NM_MODEM_STATE_DISABLED;
     const char          *reason    = NULL;
 
-    _LOGI("'Attached': %s 'Online': %s 'IMSI': %s",
+    _LOGI("'Attached': %s 'Online': %s 'Powered': %s 'IMSI': %s",
           priv->gprs_attached ? "true" : "false",
           priv->modem_online ? "true" : "false",
+          priv->connman_powered ? "true" : "false",
           priv->imsi);
 
     if (priv->modem_online == FALSE) {
         reason = "modem 'Online=false'";
+    } else if (priv->connman_powered == FALSE) {
+        reason = "ConnectionManager 'Powered=false'";
     } else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) {
         reason = "modem not ready";
     } else if (priv->gprs_attached == FALSE) {
@@ -131,8 +172,27 @@ update_modem_state(NMModemOfono *self)
         reason    = "modem ready";
     }
 
-    if (state != new_state)
+    if (state != new_state) {
+        if (new_state == NM_MODEM_STATE_DISABLED && priv->deferred_connection_timeout_source) {
+            /*
+             * Do this before set_state(), because we want the final device state
+             * to be "unavailable", not "failed" or "disconnected".
+             */
+            _LOGI("canceling deferred context activation");
+
+            nm_clear_g_source_inst(&priv->deferred_connection_timeout_source);
+            nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
+        }
+
         nm_modem_set_state(NM_MODEM(self), new_state, reason);
+
+        if (new_state == NM_MODEM_STATE_REGISTERED && priv->deferred_connection_timeout_source) {
+            _LOGI("resuming deferred context activation");
+
+            nm_clear_g_source_inst(&priv->deferred_connection_timeout_source);
+            do_context_activate(self);
+        }
+    }
 }
 
 /* Disconnect */
@@ -213,7 +273,8 @@ disconnect(NMModem                   *modem,
     ctx->callback           = callback;
     ctx->callback_user_data = user_data;
 
-    if (state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled(cancellable)) {
+    if (state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled(cancellable)
+        || priv->current_octx == NULL) {
         nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx);
         return;
     }
@@ -222,7 +283,7 @@ disconnect(NMModem                   *modem,
                        NM_MODEM_STATE_DISCONNECTING,
                        nm_modem_state_to_string(NM_MODEM_STATE_DISCONNECTING));
 
-    g_dbus_proxy_call(priv->context_proxy,
+    g_dbus_proxy_call(priv->current_octx->proxy,
                       "SetProperty",
                       g_variant_new("(sv)", "Active", g_variant_new("b", warn)),
                       G_DBUS_CALL_FLAGS_NONE,
@@ -230,6 +291,8 @@ disconnect(NMModem                   *modem,
                       ctx->cancellable,
                       disconnect_done,
                       ctx);
+
+    priv->current_octx = NULL;
 }
 
 static void
@@ -251,7 +314,7 @@ check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection,
 {
     NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    const char          *id;
+    const char          *uuid;
 
     if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL)) {
         nm_utils_error_set(error,
@@ -268,19 +331,12 @@ check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection,
         return FALSE;
     }
 
-    id = nm_connection_get_id(connection);
-
-    if (!strstr(id, "/context")) {
-        nm_utils_error_set_literal(error,
-                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-                                   "the connection ID has no context");
-        return FALSE;
-    }
+    uuid = nm_connection_get_uuid(connection);
 
-    if (!strstr(id, priv->imsi)) {
+    if (!g_hash_table_contains(priv->contexts, uuid)) {
         nm_utils_error_set_literal(error,
                                    NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
-                                   "the connection ID does not contain the IMSI");
+                                   "connection ID does not match known contexts");
         return FALSE;
     }
 
@@ -398,11 +454,11 @@ _sim_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     priv->sim_proxy = proxy;
 
     /* Watch for custom ofono PropertyChanged signals */
-    _nm_dbus_signal_connect(priv->sim_proxy,
-                            "PropertyChanged",
-                            G_VARIANT_TYPE("(sv)"),
-                            G_CALLBACK(sim_property_changed),
-                            self);
+    _nm_dbus_proxy_signal_connect(priv->sim_proxy,
+                                  "PropertyChanged",
+                                  G_VARIANT_TYPE("(sv)"),
+                                  G_CALLBACK(sim_property_changed),
+                                  self);
 
     g_dbus_proxy_call(priv->sim_proxy,
                       "GetProperties",
@@ -469,6 +525,19 @@ handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gp
 
             update_modem_state(self);
         }
+    } else if (nm_streq(property, "Powered") && VARIANT_IS_OF_TYPE_BOOLEAN(v)) {
+        gboolean powered     = g_variant_get_boolean(v);
+        gboolean old_powered = priv->connman_powered;
+
+        _LOGD("Powered: %s", powered ? "True" : "False");
+
+        if (old_powered != powered) {
+            priv->connman_powered = powered;
+
+            _LOGI("Powered %s -> %s", old_powered ? "true" : "false", powered ? "true" : "false");
+
+            update_modem_state(self);
+        }
     }
 }
 
@@ -484,7 +553,6 @@ static void
 connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     NMModemOfono              *self;
-    NMModemOfonoPrivate       *priv;
     gs_free_error GError      *error        = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
@@ -498,9 +566,6 @@ connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user
         return;
 
     self = NM_MODEM_OFONO(user_data);
-    priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-
-    g_clear_object(&priv->connman_proxy_cancellable);
 
     if (!v_properties) {
         g_dbus_error_strip_remote_error(error);
@@ -526,6 +591,366 @@ connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user
 }
 
 static void
+ofono_context_data_free(OfonoContextData *octx)
+{
+    g_free(octx->name);
+    g_free(octx->type);
+
+    if (octx->proxy) {
+        g_signal_handlers_disconnect_by_data(octx->proxy, octx);
+        g_object_unref(octx->proxy);
+    }
+
+    g_slice_free(OfonoContextData, octx);
+}
+
+static void
+add_or_update_connection(NMModemOfono *self, const char *context_name, const char *uuid)
+{
+    NMModemOfonoPrivate          *priv       = NM_MODEM_OFONO_GET_PRIVATE(self);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting                    *setting;
+    NMSettingsConnection         *sett_conn;
+    gs_free_error GError         *error = NULL;
+
+    /*
+     * See first if we have an existing connection (from previous or current
+     * run of NM) that we can update in-place.
+     */
+
+    sett_conn = nm_settings_get_connection_by_uuid(priv->settings, uuid);
+    if (sett_conn
+        && !NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn),
+                         NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+        /*
+         * Either we have a coliding connection, or our connection has been
+         * modified by user. For the latter case it's safe to leave it alone;
+         * the UUID will still let it connect. However, for the first case,
+         * we can't really do anything about it unless we re-write the way we
+         * track connection <-> context, which I (Ratchanan) don't want to do
+         * right now...
+         */
+        return;
+    }
+
+    connection = nm_simple_connection_new();
+    setting    = nm_setting_connection_new();
+    g_object_set(setting,
+                 NM_SETTING_CONNECTION_ID,
+                 context_name,
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_GSM_SETTING_NAME,
+                 NULL);
+    nm_connection_add_setting(connection, setting);
+
+    setting = nm_setting_gsm_new();
+
+    /*
+     * oFono should already know how to handle placing the call, but NM
+     * insists on having a number. Pass the usual *99#.
+     */
+    g_object_set(setting, NM_SETTING_GSM_NUMBER, "*99#", NULL);
+    nm_connection_add_setting(connection, setting);
+
+    if (!nm_connection_normalize(connection, NULL, NULL, &error)) {
+        nm_log_err(LOGD_MB,
+                   "ofono: could not not generate a connection for %s: %s",
+                   context_name,
+                   error->message);
+        return;
+    }
+
+    if (!sett_conn) {
+        nm_settings_add_connection(priv->settings,
+                                   NULL, /* plugin */
+                                   connection,
+                                   NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+                                   NM_SETTINGS_CONNECTION_ADD_REASON_NONE,
+                                   NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+                                   &sett_conn,
+                                   &error);
+    } else {
+        nm_settings_update_connection(priv->settings,
+                                      sett_conn,
+                                      NULL, /* plugin_name */
+                                      connection,
+                                      NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY,
+                                      NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED,
+                                      NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                      NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
+                                      /* log_context_name */ "ofono",
+                                      &error);
+    }
+
+    if (!sett_conn) {
+        nm_log_warn(LOGD_MB,
+                    "ofono: could not add or update new connection for '%s' (%s): %s",
+                    context_name,
+                    uuid,
+                    error->message);
+        return;
+    }
+
+    g_hash_table_insert(priv->connections, g_strdup(uuid), g_object_ref(sett_conn));
+}
+
+/*
+ * Used when we decide a connection is not needed. Rationale being that the
+ * connection could have been modified by user since it's created by us,
+ * after which we don't want to delete, but we don't want to track it either.
+ */
+
+static void
+untrack_connection_and_delete_if_generated(NMModemOfono *self, char const *uuid)
+{
+    NMModemOfonoPrivate  *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+    NMSettingsConnection *sett_conn;
+
+    sett_conn = g_hash_table_lookup(priv->connections, uuid);
+    if (!sett_conn)
+        return;
+
+    if (NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn),
+                     NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
+        nm_settings_connection_delete(sett_conn, FALSE);
+
+    g_hash_table_remove(priv->connections, uuid);
+}
+
+static void
+update_connection_list(NMModemOfono *self)
+{
+    NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+    GHashTableIter       iter;
+    char                *uuid;
+    OfonoContextData    *octx;
+    OfonoContextData    *octx_preferred = NULL;
+
+    _LOGI("(re-)checking which context needs a connection");
+
+    g_hash_table_iter_init(&iter, priv->contexts);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &uuid, (gpointer *) &octx)) {
+        if (octx->preferred) {
+            octx_preferred = octx;
+            break;
+        }
+    }
+
+    g_hash_table_iter_init(&iter, priv->contexts);
+    while (g_hash_table_iter_next(&iter, (gpointer *) &uuid, (gpointer *) &octx)) {
+        gboolean connection_should_exist =
+            (!octx_preferred || octx_preferred == octx)
+            && (nm_streq(octx->type, "internet") || nm_streq(octx->type, "internet+mms"));
+        gboolean connection_exists = g_hash_table_contains(priv->connections, uuid);
+
+        if (connection_should_exist && !connection_exists) {
+            _LOGI("creating connection for %s%s",
+                  octx_preferred ? "preferred context " : "",
+                  g_dbus_proxy_get_object_path(octx->proxy));
+            add_or_update_connection(self, octx->name, uuid);
+        } else if (!connection_should_exist && connection_exists) {
+            _LOGI("removing connection for %s", g_dbus_proxy_get_object_path(octx->proxy));
+            untrack_connection_and_delete_if_generated(self, uuid);
+
+            /* priv->current_octx is deliberately not cleared here because the
+             * disconnection chain happens in another main loop iteration.
+             */
+        }
+    }
+}
+
+static void handle_settings(NMModemOfono *self, GVariant *v_dict);
+
+static void
+context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
+{
+    OfonoContextData          *octx    = user_data;
+    NMModemOfono              *self    = octx->self;
+    NMModemOfonoPrivate       *priv    = NM_MODEM_OFONO_GET_PRIVATE(self);
+    gs_unref_variant GVariant *v_inner = g_variant_get_child_value(v, 0);
+
+    if (!v_inner) {
+        _LOGW("ofono: (%s): error handling PropertyChanged signal",
+              nm_modem_get_uid(NM_MODEM(self)));
+        return;
+    }
+
+    if (nm_streq(property, "Name")) {
+        gs_free char *uuid = NULL;
+
+        g_return_if_fail(g_variant_is_of_type(v_inner, G_VARIANT_TYPE_STRING));
+        g_free(octx->name);
+        octx->name = g_variant_dup_string(v_inner, /* &length */ NULL);
+
+        uuid = _generate_uuid(priv->imsi, g_dbus_proxy_get_object_path(proxy));
+        if (g_hash_table_contains(priv->connections, uuid))
+            add_or_update_connection(self, octx->name, uuid);
+    } else if (nm_streq(property, "Type")) {
+        g_return_if_fail(g_variant_is_of_type(v_inner, G_VARIANT_TYPE_STRING));
+        g_free(octx->type);
+        octx->type = g_variant_dup_string(v_inner, /* &length */ NULL);
+
+        update_connection_list(self);
+    } else if (nm_streq(property, "Preferred")) {
+        g_return_if_fail(g_variant_is_of_type(v_inner, G_VARIANT_TYPE_BOOLEAN));
+        octx->preferred = g_variant_get_boolean(v_inner);
+
+        update_connection_list(self);
+    } else if (nm_streq(property, "Settings") && priv->current_octx == octx) {
+        g_return_if_fail(g_variant_is_of_type(v_inner, G_VARIANT_TYPE_VARDICT));
+        handle_settings(self, v_inner);
+    }
+}
+
+static void
+_context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    OfonoContextData     *octx = user_data;
+    NMModemOfono         *self;
+    NMModemOfonoPrivate  *priv;
+    gs_free_error GError *error = NULL;
+    GDBusProxy           *proxy;
+    char                 *uuid;
+
+    proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
+    if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+        ofono_context_data_free(octx);
+        return;
+    }
+
+    self = octx->self;
+    priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+
+    if (!proxy) {
+        _LOGW("failed to create ConnectionContext proxy: %s", error->message);
+        ofono_context_data_free(octx);
+        return;
+    }
+
+    _LOGD("recieved proxy for %s", g_dbus_proxy_get_object_path(proxy));
+    octx->proxy = proxy;
+
+    _nm_dbus_proxy_signal_connect(proxy,
+                                  "PropertyChanged",
+                                  G_VARIANT_TYPE("(sv)"),
+                                  G_CALLBACK(context_property_changed),
+                                  octx);
+
+    uuid = _generate_uuid(priv->imsi, g_dbus_proxy_get_object_path(proxy));
+    g_hash_table_insert(priv->contexts, uuid, octx);
+    priv->n_context_proxy_pending--;
+
+    if (priv->n_context_proxy_pending == 0)
+        update_connection_list(self);
+}
+
+static void
+connman_context_removed(GDBusProxy *proxy, const char *object_path, gpointer user_data)
+{
+    NMModemOfono        *self = NM_MODEM_OFONO(user_data);
+    NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+    gs_free char        *uuid = NULL;
+    OfonoContextData    *octx;
+
+    /* look up the connction, and if we have connection disconnect and remove it */
+    uuid = _generate_uuid(priv->imsi, object_path);
+    untrack_connection_and_delete_if_generated(self, uuid);
+
+    octx = g_hash_table_lookup(priv->contexts, uuid);
+    if (octx) {
+        gboolean preferred = octx->preferred;
+
+        if (octx == priv->current_octx)
+            priv->current_octx = NULL;
+
+        g_hash_table_remove(priv->contexts, uuid);
+
+        if (preferred)
+            update_connection_list(self);
+    }
+}
+
+static void
+connman_context_added(GDBusProxy *proxy, const char *object_path, GVariant *v, gpointer user_data)
+{
+    NMModemOfono              *self = user_data;
+    NMModemOfonoPrivate       *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+    OfonoContextData          *octx;
+    gs_unref_variant GVariant *v_context_type      = NULL;
+    gs_unref_variant GVariant *v_context_name      = NULL;
+    gs_unref_variant GVariant *v_context_preferred = NULL;
+
+    nm_log_info(LOGD_MB, "ofono: processing context %s", object_path);
+
+    v_context_name = g_variant_lookup_value(v, "Name", G_VARIANT_TYPE_STRING);
+    v_context_type = g_variant_lookup_value(v, "Type", G_VARIANT_TYPE_STRING);
+    if (!v_context_name || !v_context_type) {
+        nm_log_err(LOGD_MB, "ofono: context dictionary is missing required key(s).");
+        return;
+    }
+
+    /* Preferred property exists in some oFono fork only (mostly Ubuntu Touch's).  */
+    v_context_preferred = g_variant_lookup_value(v, "Preferred", G_VARIANT_TYPE_BOOLEAN);
+
+    octx            = g_slice_new0(OfonoContextData);
+    octx->self      = self;
+    octx->name      = g_variant_dup_string(v_context_name, NULL);
+    octx->type      = g_variant_dup_string(v_context_type, NULL);
+    octx->preferred = v_context_preferred && g_variant_get_boolean(v_context_preferred);
+
+    priv->n_context_proxy_pending++;
+    g_dbus_proxy_new_for_bus(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,
+                             object_path,
+                             OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
+                             priv->connman_proxy_cancellable,
+                             _context_proxy_new_cb,
+                             octx);
+}
+
+static void
+connman_get_contexts_done(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    NMModemOfono              *self;
+    gs_free_error GError      *error      = NULL;
+    gs_unref_variant GVariant *v_contexts = NULL;
+    gs_unref_variant GVariant *v_objects  = NULL;
+    gs_unref_variant GVariant *v          = NULL;
+    GVariantIter               i;
+    const char                *object_path;
+
+    v_contexts = _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source),
+                                            result,
+                                            G_VARIANT_TYPE("(a(oa{sv}))"),
+                                            &error);
+    if (!v_contexts && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+    self = user_data;
+
+    if (!v_contexts) {
+        g_dbus_error_strip_remote_error(error);
+        _LOGW("Error getting list of contexts: %s", error->message);
+        return;
+    }
+
+    nm_log_info(LOGD_MB, "ofono: printing %s", g_variant_get_type_string(v_contexts));
+
+    v_objects = g_variant_get_child_value(v_contexts, 0);
+
+    g_variant_iter_init(&i, v_objects);
+    while (g_variant_iter_loop(&i, "(&o@a{sv})", &object_path, &v))
+        connman_context_added(NULL, object_path, v, self);
+}
+
+static void
 _connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     NMModemOfono         *self;
@@ -548,11 +973,23 @@ _connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     priv->connman_proxy = proxy;
 
-    _nm_dbus_signal_connect(priv->connman_proxy,
-                            "PropertyChanged",
-                            G_VARIANT_TYPE("(sv)"),
-                            G_CALLBACK(connman_property_changed),
-                            self);
+    _nm_dbus_proxy_signal_connect(priv->connman_proxy,
+                                  "PropertyChanged",
+                                  G_VARIANT_TYPE("(sv)"),
+                                  G_CALLBACK(connman_property_changed),
+                                  self);
+
+    _nm_dbus_proxy_signal_connect(priv->connman_proxy,
+                                  "ContextAdded",
+                                  G_VARIANT_TYPE("(oa{sv})"),
+                                  G_CALLBACK(connman_context_added),
+                                  self);
+
+    _nm_dbus_proxy_signal_connect(priv->connman_proxy,
+                                  "ContextRemoved",
+                                  G_VARIANT_TYPE("(o)"),
+                                  G_CALLBACK(connman_context_removed),
+                                  self);
 
     g_dbus_proxy_call(priv->connman_proxy,
                       "GetProperties",
@@ -562,6 +999,15 @@ _connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
                       priv->connman_proxy_cancellable,
                       connman_get_properties_done,
                       self);
+
+    g_dbus_proxy_call(priv->connman_proxy,
+                      "GetContexts",
+                      NULL,
+                      G_DBUS_CALL_FLAGS_NONE,
+                      20000,
+                      priv->connman_proxy_cancellable,
+                      connman_get_contexts_done,
+                      self);
 }
 
 static void
@@ -572,6 +1018,9 @@ handle_connman_iface(NMModemOfono *self, gboolean found)
     _LOGD("ConnectionManager interface %sfound", found ? "" : "not ");
 
     if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) {
+        GHashTableIter        iter;
+        NMSettingsConnection *conn;
+
         _LOGI("ConnectionManager interface disappeared");
         nm_clear_g_cancellable(&priv->connman_proxy_cancellable);
         if (priv->connman_proxy) {
@@ -584,6 +1033,18 @@ handle_connman_iface(NMModemOfono *self, gboolean found)
          */
         priv->gprs_attached = FALSE;
 
+        g_hash_table_iter_init(&iter, priv->connections);
+        while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &conn)) {
+            if (NM_FLAGS_HAS(nm_settings_connection_get_flags(conn),
+                             NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
+                nm_settings_connection_delete(conn, FALSE);
+            }
+        }
+
+        priv->current_octx = NULL;
+        g_hash_table_remove_all(priv->connections);
+        g_hash_table_remove_all(priv->contexts);
+
         update_modem_state(self);
     } else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) {
         _LOGI("found new ConnectionManager interface");
@@ -716,7 +1177,7 @@ stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
     self = NM_MODEM_OFONO(user_data);
     priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
-    g_clear_object(&priv->context_proxy_cancellable);
+    g_clear_object(&priv->connect_cancellable);
 
     nm_clear_pointer(&priv->connect_properties, g_hash_table_destroy);
 
@@ -730,9 +1191,8 @@ stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
 }
 
 static void
-handle_settings(GVariant *v_dict, gpointer user_data)
+handle_settings(NMModemOfono *self, GVariant *v_dict)
 {
-    NMModemOfono        *self = NM_MODEM_OFONO(user_data);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
     char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMPlatformIP4Address address;
@@ -744,14 +1204,20 @@ handle_settings(GVariant *v_dict, gpointer user_data)
     int                  ifindex;
     GError              *error = NULL;
 
-    //_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 (nm_modem_get_state(NM_MODEM(self)) < NM_MODEM_STATE_REGISTERED) {
+        /*
+         * Connection definitely isn't happening. Avoid trigering bogus
+         * failure which would put device in a wrong state.
+         */
+        return;
+    }
+
     _LOGI("IPv4 static Settings:");
 
     if (!g_variant_lookup(v_dict, "Interface", "&s", &interface)) {
@@ -786,7 +1252,7 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("Settings 'Address' missing");
         goto out;
     }
-    if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
+    if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) {
         _LOGW("can't convert 'Address' %s to addr", s ?: "");
         goto out;
     }
@@ -801,11 +1267,11 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("Settings 'Netmask' missing");
         goto out;
     }
-    if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
+    if (!s || !nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) {
         _LOGW("invalid 'Netmask': %s", s ?: "");
         goto out;
     }
-    address.plen = _nm_utils_ip4_netmask_to_prefix(address_network);
+    address.plen = nm_ip4_addr_netmask_to_prefix(address_network);
 
     _LOGI("Address: %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf)));
     nm_l3_config_data_add_address_4(priv->l3cd_4, &address);
@@ -814,7 +1280,7 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("Settings 'Gateway' missing");
         goto out;
     }
-    if (!nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &gateway_network)) {
+    if (!nm_inet_parse_bin(AF_INET, s, NULL, &gateway_network)) {
         _LOGW("invalid 'Gateway': %s", s);
         goto out;
     }
@@ -841,14 +1307,13 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         gsize    i;
 
         for (i = 0; array[i]; i++) {
-            if (!nm_utils_parse_inaddr_bin(AF_INET, array[i], NULL, &address_network)
-                || !address_network) {
+            if (!nm_inet_parse_bin(AF_INET, array[i], NULL, &address_network) || !address_network) {
                 _LOGW("invalid NameServer: %s", array[i]);
                 continue;
             }
             any_good = TRUE;
             _LOGI("DNS: %s", array[i]);
-            nm_l3_config_data_add_nameserver(priv->l3cd_4, AF_INET, &address_network);
+            nm_l3_config_data_add_nameserver_detail(priv->l3cd_4, AF_INET, &address_network, NULL);
         }
         if (!any_good) {
             _LOGW("Settings: 'DomainNameServers': none specified");
@@ -858,7 +1323,7 @@ handle_settings(GVariant *v_dict, gpointer user_data)
 
     if (g_variant_lookup(v_dict, "MessageProxy", "&s", &s)) {
         _LOGI("MessageProxy: %s", s);
-        if (s && nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
+        if (s && nm_inet_parse_bin(AF_INET, s, NULL, &address_network)) {
             const NMPlatformIP4Route mms_route = {
                 .network       = address_network,
                 .plen          = 32,
@@ -893,28 +1358,6 @@ out:
 }
 
 static void
-context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
-{
-    NMModemOfono              *self   = NM_MODEM_OFONO(user_data);
-    gs_unref_variant GVariant *v_dict = NULL;
-
-    _LOGD("PropertyChanged: %s", property);
-
-    if (g_strcmp0(property, "Settings") != 0)
-        return;
-
-    v_dict = g_variant_get_child_value(v, 0);
-    if (!v_dict) {
-        _LOGW("ofono: (%s): error getting IPv4 Settings", nm_modem_get_uid(NM_MODEM(self)));
-        return;
-    }
-
-    g_assert(g_variant_is_of_type(v_dict, G_VARIANT_TYPE_VARDICT));
-
-    handle_settings(v_dict, user_data);
-}
-
-static void
 stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_method)
 {
     NMModemOfono         *self  = NM_MODEM_OFONO(modem);
@@ -948,17 +1391,13 @@ out:
 static void
 context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono              *self;
-    NMModemOfonoPrivate       *priv;
+    NMModemOfono              *self       = user_data;
     gs_free_error GError      *error      = NULL;
     gs_unref_variant GVariant *properties = NULL;
     gs_unref_variant GVariant *settings   = NULL;
     gs_unref_variant GVariant *v_dict     = NULL;
     gboolean                   active;
 
-    self = NM_MODEM_OFONO(user_data);
-    priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-
     properties = g_dbus_proxy_call_finish(proxy, result, &error);
 
     if (!properties) {
@@ -978,13 +1417,6 @@ context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
         goto error;
     }
 
-    /* Watch for custom ofono PropertyChanged signals */
-    _nm_dbus_signal_connect(priv->context_proxy,
-                            "PropertyChanged",
-                            G_VARIANT_TYPE("(sv)"),
-                            G_CALLBACK(context_property_changed),
-                            self);
-
     if (active) {
         _LOGD("ofono: connection is already Active");
 
@@ -994,9 +1426,9 @@ context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_dat
             goto error;
         }
 
-        handle_settings(settings, user_data);
+        handle_settings(self, settings);
     } else {
-        g_dbus_proxy_call(priv->context_proxy,
+        g_dbus_proxy_call(proxy,
                           "SetProperty",
                           g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)),
                           G_DBUS_CALL_FLAGS_NONE,
@@ -1012,36 +1444,15 @@ error:
 }
 
 static void
-context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+do_context_activate(NMModemOfono *self)
 {
-    NMModemOfono         *self;
-    NMModemOfonoPrivate  *priv;
-    gs_free_error GError *error = NULL;
-    GDBusProxy           *proxy;
-
-    proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
-    if (!proxy || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-        return;
+    NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
-    self = NM_MODEM_OFONO(user_data);
-    priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+    g_return_if_fail(NM_IS_MODEM_OFONO(self));
 
-    if (!proxy) {
-        _LOGE("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
-        g_clear_object(&priv->context_proxy_cancellable);
-        nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
-        return;
-    }
+    nm_clear_g_cancellable(&priv->connect_cancellable);
 
-    priv->context_proxy = proxy;
-
-    if (!priv->gprs_attached) {
-        g_clear_object(&priv->context_proxy_cancellable);
-        nm_modem_emit_prepare_result(NM_MODEM(self),
-                                     FALSE,
-                                     NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
-        return;
-    }
+    priv->connect_cancellable = g_cancellable_new();
 
     /* 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
@@ -1051,39 +1462,16 @@ context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     /* We need to directly query ConnectionContextinteface to get the current
      * property values */
-    g_dbus_proxy_call(priv->context_proxy,
+    g_dbus_proxy_call(priv->current_octx->proxy,
                       "GetProperties",
                       NULL,
                       G_DBUS_CALL_FLAGS_NONE,
                       20000,
-                      NULL,
+                      priv->connect_cancellable,
                       (GAsyncReadyCallback) context_properties_cb,
                       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));
-
-    nm_clear_g_cancellable(&priv->context_proxy_cancellable);
-    g_clear_object(&priv->context_proxy);
-
-    priv->context_proxy_cancellable = g_cancellable_new();
-
-    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,
-                             priv->context_proxy_cancellable,
-                             context_proxy_new_cb,
-                             self);
-}
-
 static GHashTable *
 create_connect_properties(NMConnection *connection)
 {
@@ -1109,6 +1497,20 @@ create_connect_properties(NMConnection *connection)
     return properties;
 }
 
+static gboolean
+wait_for_signal_timeout(gpointer user_data)
+{
+    NMModemOfono        *self = NM_MODEM_OFONO(user_data);
+    NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+
+    nm_modem_emit_prepare_result(NM_MODEM(self),
+                                 FALSE,
+                                 NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT);
+
+    nm_clear_g_source_inst(&priv->deferred_connection_timeout_source);
+    return G_SOURCE_REMOVE;
+}
+
 static NMActStageReturn
 modem_act_stage1_prepare(NMModem             *modem,
                          NMConnection        *connection,
@@ -1116,20 +1518,10 @@ modem_act_stage1_prepare(NMModem             *modem,
 {
     NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    const char          *context_id;
-    char               **id = NULL;
+    const char          *uuid = nm_connection_get_uuid(connection);
+    OfonoContextData    *octx = g_hash_table_lookup(priv->contexts, uuid);
 
-    context_id = nm_connection_get_id(connection);
-    id         = g_strsplit(context_id, "/", 0);
-    g_return_val_if_fail(id[2], NM_ACT_STAGE_RETURN_FAILURE);
-
-    _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) {
+    if (!octx) {
         NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_GSM_APN_FAILED);
         return NM_ACT_STAGE_RETURN_FAILURE;
     }
@@ -1139,11 +1531,19 @@ modem_act_stage1_prepare(NMModem             *modem,
 
     priv->connect_properties = create_connect_properties(connection);
 
-    _LOGI("activating context %s", priv->context_path);
+    _LOGI("activating context %s", g_dbus_proxy_get_object_path(octx->proxy));
+    priv->current_octx = octx;
 
     update_modem_state(self);
     if (nm_modem_get_state(modem) == NM_MODEM_STATE_REGISTERED) {
         do_context_activate(self);
+    } else if (nm_modem_get_state(modem) == NM_MODEM_STATE_SEARCHING) {
+        _LOGI("activation deferred while modem is searching for signal.");
+
+        nm_clear_g_source_inst(&priv->deferred_connection_timeout_source);
+        priv->deferred_connection_timeout_source =
+            nm_g_timeout_add_seconds_source(60 /* seconds */, wait_for_signal_timeout, self);
+        /* update_modem_state() will advance the next step. */
     } else {
         _LOGW("could not activate context: modem is not registered.");
         NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
@@ -1176,11 +1576,11 @@ modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
     priv->modem_proxy = proxy;
 
-    _nm_dbus_signal_connect(priv->modem_proxy,
-                            "PropertyChanged",
-                            G_VARIANT_TYPE("(sv)"),
-                            G_CALLBACK(modem_property_changed),
-                            self);
+    _nm_dbus_proxy_signal_connect(priv->modem_proxy,
+                                  "PropertyChanged",
+                                  G_VARIANT_TYPE("(sv)"),
+                                  G_CALLBACK(modem_property_changed),
+                                  self);
 
     g_dbus_proxy_call(priv->modem_proxy,
                       "GetProperties",
@@ -1196,7 +1596,17 @@ modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
 static void
 nm_modem_ofono_init(NMModemOfono *self)
-{}
+{
+    NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
+
+    priv->modem_proxy_cancellable = g_cancellable_new();
+    priv->connections = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref);
+    priv->contexts    = g_hash_table_new_full(nm_str_hash,
+                                           g_str_equal,
+                                           g_free,
+                                           (GDestroyNotify) ofono_context_data_free);
+    priv->settings    = g_object_ref(NM_SETTINGS_GET);
+}
 
 static void
 constructed(GObject *object)
@@ -1204,8 +1614,6 @@ constructed(GObject *object)
     NMModemOfono        *self = NM_MODEM_OFONO(object);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
-    priv->modem_proxy_cancellable = g_cancellable_new();
-
     g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
                              G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
                              NULL,
@@ -1257,7 +1665,7 @@ dispose(GObject *object)
 
     nm_clear_g_cancellable(&priv->modem_proxy_cancellable);
     nm_clear_g_cancellable(&priv->connman_proxy_cancellable);
-    nm_clear_g_cancellable(&priv->context_proxy_cancellable);
+    nm_clear_g_cancellable(&priv->connect_cancellable);
     nm_clear_g_cancellable(&priv->sim_proxy_cancellable);
 
     if (priv->connect_properties) {
@@ -1265,6 +1673,16 @@ dispose(GObject *object)
         priv->connect_properties = NULL;
     }
 
+    if (priv->connections) {
+        g_hash_table_destroy(priv->connections);
+        priv->connections = NULL;
+    }
+
+    if (priv->contexts) {
+        g_hash_table_destroy(priv->contexts);
+        priv->contexts = NULL;
+    }
+
     nm_clear_l3cd(&priv->l3cd_4);
 
     if (priv->modem_proxy) {
@@ -1277,19 +1695,21 @@ dispose(GObject *object)
         g_clear_object(&priv->connman_proxy);
     }
 
-    if (priv->context_proxy) {
-        g_signal_handlers_disconnect_by_data(priv->context_proxy, self);
-        g_clear_object(&priv->context_proxy);
-    }
-
     if (priv->sim_proxy) {
         g_signal_handlers_disconnect_by_data(priv->sim_proxy, self);
         g_clear_object(&priv->sim_proxy);
     }
 
+    if (priv->settings) {
+        g_signal_handlers_disconnect_by_data(priv->settings, self);
+        g_clear_object(&priv->settings);
+    }
+
     g_free(priv->imsi);
     priv->imsi = NULL;
 
+    nm_clear_g_source_inst(&priv->deferred_connection_timeout_source);
+
     G_OBJECT_CLASS(nm_modem_ofono_parent_class)->dispose(object);
 }
 
diff --git a/src/core/devices/wwan/nm-modem-ofono.h b/src/core/devices/wwan/nm-modem-ofono.h
index ebe21023..02ba49ad 100644
--- a/src/core/devices/wwan/nm-modem-ofono.h
+++ b/src/core/devices/wwan/nm-modem-ofono.h
@@ -8,8 +8,9 @@
 
 #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_TYPE_MODEM_OFONO (nm_modem_ofono_get_type())
+#define NM_MODEM_OFONO(obj) \
+    (_NM_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))
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index a5ee8ce8..0159d351 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -353,6 +353,12 @@ nm_modem_set_mm_enabled(NMModem *self, gboolean enabled)
     NMModemPrivate *priv       = NM_MODEM_GET_PRIVATE(self);
     NMModemState    prev_state = priv->state;
 
+    /* Not all modem classes support set_mm_enabled */
+    if (!NM_MODEM_GET_CLASS(self)->set_mm_enabled) {
+        _LOGD("cannot enable modem: not implemented");
+        return;
+    }
+
     if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) {
         _LOGD("cannot enable modem: already enabled");
         return;
@@ -375,9 +381,7 @@ nm_modem_set_mm_enabled(NMModem *self, gboolean enabled)
         return;
     }
 
-    /* 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);
+    NM_MODEM_GET_CLASS(self)->set_mm_enabled(self, enabled);
 
     /* Pre-empt the state change signal */
     nm_modem_set_state(self,
diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h
index ca541073..ec001102 100644
--- a/src/core/devices/wwan/nm-modem.h
+++ b/src/core/devices/wwan/nm-modem.h
@@ -11,7 +11,7 @@
 #include "devices/nm-device.h"
 
 #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(obj)            (_NM_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))
 #define NM_IS_MODEM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_MODEM))
 #define NM_IS_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_MODEM))
diff --git a/src/core/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c
index 169407c7..1c6d351f 100644
--- a/src/core/devices/wwan/nm-wwan-factory.c
+++ b/src/core/devices/wwan/nm-wwan-factory.c
@@ -18,7 +18,7 @@
 
 #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))
+    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WWAN_FACTORY, NMWwanFactory))
 #define NM_WWAN_FACTORY_CLASS(klass) \
     (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_WWAN_FACTORY, NMWwanFactoryClass))
 #define NM_IS_WWAN_FACTORY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_WWAN_FACTORY))