about summary refs log tree commit diff
path: root/src/core/devices/wwan
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-02-14 19:23:28 +0100
committerMichael Biebl <biebl@debian.org>2022-02-14 19:23:28 +0100
commit2f94dba7385fd0e0ef19a06eb4a2fcf6c43d7946 (patch)
treee2222f5577115985dd52044d2991253403cdd952 /src/core/devices/wwan
parent88c227d90a6b7b388c5c85d72802a0ca8f05ed5c (diff)
New upstream version 1.35.91 upstream/1.35.91
Diffstat (limited to 'src/core/devices/wwan')
-rw-r--r--src/core/devices/wwan/libnm-wwan.ver5
-rw-r--r--src/core/devices/wwan/nm-device-modem.c296
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c606
-rw-r--r--src/core/devices/wwan/nm-modem-manager.c92
-rw-r--r--src/core/devices/wwan/nm-modem-ofono.c278
-rw-r--r--src/core/devices/wwan/nm-modem.c985
-rw-r--r--src/core/devices/wwan/nm-modem.h137
-rw-r--r--src/core/devices/wwan/nm-service-providers.c82
-rw-r--r--src/core/devices/wwan/nm-service-providers.h18
-rw-r--r--src/core/devices/wwan/nm-wwan-factory.c20
-rw-r--r--src/core/devices/wwan/tests/test-service-providers.c36
11 files changed, 1195 insertions, 1360 deletions
diff --git a/src/core/devices/wwan/libnm-wwan.ver b/src/core/devices/wwan/libnm-wwan.ver
index c368a590..640df36d 100644
--- a/src/core/devices/wwan/libnm-wwan.ver
+++ b/src/core/devices/wwan/libnm-wwan.ver
@@ -14,7 +14,6 @@ global:
 	nm_modem_get_control_port;
 	nm_modem_get_device_id;
 	nm_modem_get_driver;
-	nm_modem_get_iid;
 	nm_modem_get_ip_ifindex;
 	nm_modem_get_operator_code;
 	nm_modem_get_path;
@@ -22,7 +21,6 @@ global:
 	nm_modem_get_state;
 	nm_modem_get_type;
 	nm_modem_get_uid;
-	nm_modem_ip4_pre_commit;
 	nm_modem_is_claimed;
 	nm_modem_manager_get;
 	nm_modem_manager_get_modems;
@@ -32,8 +30,7 @@ global:
 	nm_modem_manager_name_owner_unref;
 	nm_modem_owns_port;
 	nm_modem_set_mm_enabled;
-	nm_modem_stage3_ip4_config_start;
-	nm_modem_stage3_ip6_config_start;
+	nm_modem_stage3_ip_config_start;
 	nm_modem_state_to_string;
 	nm_modem_unclaim;
 local:
diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index 1b285469..ef802a44 100644
--- a/src/core/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -8,7 +8,7 @@
 #include "nm-device-modem.h"
 
 #include "nm-modem.h"
-#include "nm-ip4-config.h"
+#include "nm-l3-config-data.h"
 #include "devices/nm-device-private.h"
 #include "nm-rfkill-manager.h"
 #include "settings/nm-settings-connection.h"
@@ -30,14 +30,16 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceModem,
                              PROP_APN, );
 
 typedef struct {
-    NMModem *                 modem;
+    NMModem                  *modem;
     NMDeviceModemCapabilities caps;
     NMDeviceModemCapabilities current_caps;
-    char *                    device_id;
-    char *                    operator_code;
-    char *                    apn;
+    NMUtilsIPv6IfaceId        iid;
+    char                     *device_id;
+    char                     *operator_code;
+    char                     *apn;
     bool                      rf_enabled : 1;
     NMDeviceStageState        stage1_state : 3;
+    NMDeviceStageState        stage2_state : 3;
 } NMDeviceModemPrivate;
 
 struct _NMDeviceModem {
@@ -59,52 +61,18 @@ G_DEFINE_TYPE(NMDeviceModem, nm_device_modem, NM_TYPE_DEVICE)
 static void
 ppp_failed(NMModem *modem, guint i_reason, gpointer user_data)
 {
-    NMDevice *          device = NM_DEVICE(user_data);
-    NMDeviceModem *     self   = NM_DEVICE_MODEM(user_data);
+    NMDevice           *device = NM_DEVICE(user_data);
     NMDeviceStateReason reason = i_reason;
 
-    switch (nm_device_get_state(device)) {
-    case NM_DEVICE_STATE_PREPARE:
-    case NM_DEVICE_STATE_CONFIG:
-    case NM_DEVICE_STATE_NEED_AUTH:
-        nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason);
-        break;
-    case NM_DEVICE_STATE_IP_CONFIG:
-    case NM_DEVICE_STATE_IP_CHECK:
-    case NM_DEVICE_STATE_SECONDARIES:
-    case NM_DEVICE_STATE_ACTIVATED:
-        if (nm_device_activate_ip4_state_in_conf(device))
-            nm_device_activate_schedule_ip_config_timeout(device, AF_INET);
-        else if (nm_device_activate_ip6_state_in_conf(device))
-            nm_device_activate_schedule_ip_config_timeout(device, AF_INET6);
-        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);
-        }
-        break;
-    default:
-        break;
-    }
+    nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason);
 }
 
 static void
 modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer user_data)
 {
-    NMDeviceModem *       self   = NM_DEVICE_MODEM(user_data);
+    NMDeviceModem        *self   = NM_DEVICE_MODEM(user_data);
     NMDeviceModemPrivate *priv   = NM_DEVICE_MODEM_GET_PRIVATE(self);
-    NMDevice *            device = NM_DEVICE(self);
+    NMDevice             *device = NM_DEVICE(self);
     NMDeviceStateReason   reason = i_reason;
 
     if (nm_device_get_state(device) != NM_DEVICE_STATE_PREPARE
@@ -173,7 +141,7 @@ modem_auth_requested(NMModem *modem, gpointer user_data)
 static void
 modem_auth_result(NMModem *modem, GError *error, gpointer user_data)
 {
-    NMDevice *            device = NM_DEVICE(user_data);
+    NMDevice             *device = NM_DEVICE(user_data);
     NMDeviceModemPrivate *priv   = NM_DEVICE_MODEM_GET_PRIVATE(device);
 
     g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH);
@@ -188,102 +156,50 @@ modem_auth_result(NMModem *modem, GError *error, gpointer user_data)
 }
 
 static void
-modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpointer user_data)
-{
-    NMDeviceModem *self   = NM_DEVICE_MODEM(user_data);
-    NMDevice *     device = NM_DEVICE(self);
-
-    if (!nm_device_activate_ip4_state_in_conf(device)) {
-        _LOGD(LOGD_MB | LOGD_IP4,
-              "retrieving IPv4 configuration while no longer in state IPv4 conf");
+modem_new_config(NMModem                  *modem,
+                 int                       addr_family,
+                 const NML3ConfigData     *l3cd,
+                 gboolean                  do_auto,
+                 const NMUtilsIPv6IfaceId *iid,
+                 int                       failure_reason_i,
+                 GError                   *error,
+                 gpointer                  user_data)
+{
+    const int             IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDeviceModem        *self    = NM_DEVICE_MODEM(user_data);
+    NMDeviceModemPrivate *priv    = NM_DEVICE_MODEM_GET_PRIVATE(self);
+    NMDevice             *device  = NM_DEVICE(self);
+
+    if (nm_device_devip_get_state(device, addr_family) != NM_DEVICE_IP_STATE_PENDING) {
+        _LOGD(LOGD_MB, "retrieving IP configuration while no longer in pending state");
         return;
     }
 
     if (error) {
-        _LOGW(LOGD_MB | LOGD_IP4, "retrieving IPv4 configuration failed: %s", error->message);
-        nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        return;
-    }
-
-    nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG_CAST(config));
-    nm_device_activate_schedule_ip_config_result(device, AF_INET, NULL);
-}
-
-static void
-modem_ip6_config_result(NMModem *    modem,
-                        NMIP6Config *config,
-                        gboolean     do_slaac,
-                        GError *     error,
-                        gpointer     user_data)
-{
-    NMDeviceModem *     self   = NM_DEVICE_MODEM(user_data);
-    NMDevice *          device = NM_DEVICE(self);
-    NMActStageReturn    ret;
-    NMDeviceStateReason failure_reason      = NM_DEVICE_STATE_REASON_NONE;
-    gs_unref_object NMIP6Config *ignored    = NULL;
-    gboolean                     got_config = !!config;
-
-    if (!nm_device_activate_ip6_state_in_conf(device)) {
-        _LOGD(LOGD_MB | LOGD_IP6,
-              "retrieving IPv6 configuration while no longer in state IPv6 conf");
+        _LOGW(LOGD_MB, "retrieving IP configuration failed: %s", error->message);
+        nm_device_devip_set_failed(device, addr_family, failure_reason_i);
         return;
     }
 
-    if (error) {
-        _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;
-    }
+    if (!IS_IPv4)
+        priv->iid = iid ? *iid : ((NMUtilsIPv6IfaceId) NM_UTILS_IPV6_IFACE_ID_INIT);
 
-    /* Re-enable IPv6 on the interface */
-    nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "0");
-
-    if (config)
-        nm_device_set_dev2_ip_config(device, AF_INET6, NM_IP_CONFIG_CAST(config));
-
-    if (do_slaac == FALSE) {
-        if (got_config)
-            nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL);
-        else {
-            _LOGW(LOGD_MB | LOGD_IP6,
-                  "retrieving IPv6 configuration failed: SLAAC not requested and no addresses");
-            nm_device_ip_method_failed(device,
-                                       AF_INET6,
-                                       NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        }
-        return;
+    if (do_auto) {
+        if (IS_IPv4)
+            nm_device_ip_method_dhcp4_start(device);
+        else
+            nm_device_ip_method_autoconf6_start(device);
     }
 
-    /* Start SLAAC now that we have a link-local address from the modem */
-    ret =
-        NM_DEVICE_CLASS(nm_device_modem_parent_class)
-            ->act_stage3_ip_config_start(device, AF_INET6, (gpointer *) &ignored, &failure_reason);
-
-    nm_assert(ignored == NULL);
-
-    switch (ret) {
-    case NM_ACT_STAGE_RETURN_FAILURE:
-        nm_device_ip_method_failed(device, AF_INET6, failure_reason);
-        break;
-    case NM_ACT_STAGE_RETURN_IP_FAIL:
-        /* all done */
-        nm_device_activate_schedule_ip_config_result(device, AF_INET6, NULL);
-        break;
-    case NM_ACT_STAGE_RETURN_POSTPONE:
-        /* let SLAAC run */
-        break;
-    default:
-        /* Should never get here since we've assured that the IPv6 method
-         * will either be "auto" or "ignored" when starting IPv6 configuration.
-         */
-        nm_assert_not_reached();
-    }
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd);
 }
 
 static void
 ip_ifindex_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
-    NMDevice *device = NM_DEVICE(user_data);
+    NMDevice             *device = NM_DEVICE(user_data);
+    NMDeviceModem        *self   = NM_DEVICE_MODEM(device);
+    NMDeviceModemPrivate *priv   = NM_DEVICE_MODEM_GET_PRIVATE(self);
 
     if (!nm_device_is_activating(device))
         return;
@@ -300,14 +216,19 @@ ip_ifindex_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data)
      * RA handling code to run before NM is ready.
      */
     nm_device_sysctl_ip_conf_set(device, AF_INET6, "disable_ipv6", "1");
+
+    if (priv->stage2_state == NM_DEVICE_STAGE_STATE_PENDING) {
+        priv->stage2_state = NM_DEVICE_STAGE_STATE_COMPLETED;
+        nm_device_activate_schedule_stage2_device_config(device, FALSE);
+    }
 }
 
 static void
 operator_code_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
-    NMDeviceModem *       self          = NM_DEVICE_MODEM(user_data);
+    NMDeviceModem        *self          = NM_DEVICE_MODEM(user_data);
     NMDeviceModemPrivate *priv          = NM_DEVICE_MODEM_GET_PRIVATE(self);
-    const char *          operator_code = nm_modem_get_operator_code(modem);
+    const char           *operator_code = nm_modem_get_operator_code(modem);
 
     if (g_strcmp0(priv->operator_code, operator_code) != 0) {
         g_free(priv->operator_code);
@@ -319,9 +240,9 @@ operator_code_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data)
 static void
 apn_changed_cb(NMModem *modem, GParamSpec *pspec, gpointer user_data)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(user_data);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(user_data);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
-    const char *          apn  = nm_modem_get_apn(modem);
+    const char           *apn  = nm_modem_get_apn(modem);
 
     if (g_strcmp0(priv->apn, apn) != 0) {
         g_free(priv->apn);
@@ -341,7 +262,7 @@ modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_d
 {
     NMModemState          new_state = new_state_i;
     NMModemState          old_state = old_state_i;
-    NMDevice *            device    = NM_DEVICE(user_data);
+    NMDevice             *device    = NM_DEVICE(user_data);
     NMDeviceModemPrivate *priv      = NM_DEVICE_MODEM_GET_PRIVATE(device);
     NMDeviceState         dev_state = nm_device_get_state(device);
 
@@ -413,12 +334,12 @@ owns_iface(NMDevice *device, const char *iface)
 /*****************************************************************************/
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(device);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(device);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
 
     g_return_if_fail(priv->modem);
@@ -480,13 +401,13 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 static gboolean
-check_connection_available(NMDevice *                     device,
-                           NMConnection *                 connection,
+check_connection_available(NMDevice                      *device,
+                           NMConnection                  *connection,
                            NMDeviceCheckConAvailableFlags flags,
-                           const char *                   specific_object,
-                           GError **                      error)
+                           const char                    *specific_object,
+                           GError                       **error)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(device);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(device);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
     NMModemState          state;
 
@@ -525,11 +446,11 @@ check_connection_available(NMDevice *                     device,
 }
 
 static gboolean
-complete_connection(NMDevice *           device,
-                    NMConnection *       connection,
-                    const char *         specific_object,
+complete_connection(NMDevice            *device,
+                    NMConnection        *connection,
+                    const char          *specific_object,
                     NMConnection *const *existing_connections,
-                    GError **            error)
+                    GError             **error)
 {
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device);
 
@@ -547,6 +468,7 @@ deactivate(NMDevice *device)
 
     nm_modem_deactivate(priv->modem, device);
     priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
+    priv->stage2_state = NM_DEVICE_STAGE_STATE_INIT;
 }
 
 /*****************************************************************************/
@@ -554,7 +476,7 @@ deactivate(NMDevice *device)
 static void
 modem_deactivate_async_cb(NMModem *modem, GError *error, gpointer user_data)
 {
-    gs_unref_object NMDevice * self = NULL;
+    gs_unref_object NMDevice  *self = NULL;
     NMDeviceDeactivateCallback callback;
     gpointer                   callback_user_data;
 
@@ -563,8 +485,8 @@ modem_deactivate_async_cb(NMModem *modem, GError *error, gpointer user_data)
 }
 
 static void
-deactivate_async(NMDevice *                 self,
-                 GCancellable *             cancellable,
+deactivate_async(NMDevice                  *self,
+                 GCancellable              *cancellable,
                  NMDeviceDeactivateCallback callback,
                  gpointer                   user_data)
 {
@@ -584,7 +506,7 @@ static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device);
-    NMActRequest *        req;
+    NMActRequest         *req;
 
     req = nm_device_get_act_request(device);
     g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -606,51 +528,44 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    nm_modem_act_stage2_config(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem);
-    return NM_ACT_STAGE_RETURN_SUCCESS;
-}
-
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
-{
-    NMDeviceModemPrivate *priv    = NM_DEVICE_MODEM_GET_PRIVATE(device);
-    gboolean              autoip4 = FALSE;
-    NMActStageReturn      ret;
-
-    if (!NM_IS_IPv4(addr_family))
-        return nm_modem_stage3_ip6_config_start(priv->modem, device, out_failure_reason);
-
-    ret = nm_modem_stage3_ip4_config_start(priv->modem, device, &autoip4, out_failure_reason);
+    NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device);
 
-    if (ret != NM_ACT_STAGE_RETURN_SUCCESS || !autoip4)
-        return ret;
+    if (priv->stage2_state == NM_DEVICE_STAGE_STATE_INIT) {
+        priv->stage2_state = NM_DEVICE_STAGE_STATE_PENDING;
+        return nm_modem_act_stage2_config(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem,
+                                          device,
+                                          out_failure_reason);
+    }
+    if (priv->stage2_state == NM_DEVICE_STAGE_STATE_PENDING)
+        return NM_ACT_STAGE_RETURN_POSTPONE;
 
-    return NM_DEVICE_CLASS(nm_device_modem_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    nm_assert(priv->stage2_state == NM_DEVICE_STAGE_STATE_COMPLETED);
+    return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static void
-ip4_config_pre_commit(NMDevice *device, NMIP4Config *config)
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    nm_modem_ip4_pre_commit(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem, device, config);
+    NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device);
+
+    if (nm_modem_stage3_ip_config_start(priv->modem, addr_family, device))
+        nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_PENDING, NULL);
 }
 
 static gboolean
 get_ip_iface_identifier(NMDevice *device, NMUtilsIPv6IfaceId *out_iid)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(device);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(device);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
-    gboolean              success;
 
     g_return_val_if_fail(priv->modem, FALSE);
-    success = nm_modem_get_iid(priv->modem, out_iid);
-    if (!success)
-        success =
-            NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_ip_iface_identifier(device, out_iid);
-    return success;
+
+    if (priv->iid.id != 0) {
+        *out_iid = priv->iid;
+        return TRUE;
+    }
+
+    return NM_DEVICE_CLASS(nm_device_modem_parent_class)->get_ip_iface_identifier(device, out_iid);
 }
 
 /*****************************************************************************/
@@ -667,7 +582,7 @@ get_enabled(NMDevice *device)
 static void
 set_enabled(NMDevice *device, gboolean enabled)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(device);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(device);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
 
     /* Called only by the Manager in response to rfkill switch changes or
@@ -688,7 +603,7 @@ set_enabled(NMDevice *device, gboolean enabled)
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceModem *       self = NM_DEVICE_MODEM(device);
+    NMDeviceModem        *self = NM_DEVICE_MODEM(device);
     NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(self);
     NMModemState          modem_state;
 
@@ -703,6 +618,16 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
     return TRUE;
 }
 
+static gboolean
+ready_for_ip_config(NMDevice *device)
+{
+    /* Tell NMDevice to only run device-specific IP
+     * configuration (devip) and skip other methods
+     * (manual, dhcp, etc).
+     */
+    return FALSE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -716,8 +641,7 @@ set_modem(NMDeviceModem *self, NMModem *modem)
 
     g_signal_connect(modem, NM_MODEM_PPP_FAILED, G_CALLBACK(ppp_failed), self);
     g_signal_connect(modem, NM_MODEM_PREPARE_RESULT, G_CALLBACK(modem_prepare_result), self);
-    g_signal_connect(modem, NM_MODEM_IP4_CONFIG_RESULT, G_CALLBACK(modem_ip4_config_result), self);
-    g_signal_connect(modem, NM_MODEM_IP6_CONFIG_RESULT, G_CALLBACK(modem_ip6_config_result), self);
+    g_signal_connect(modem, NM_MODEM_NEW_CONFIG, G_CALLBACK(modem_new_config), self);
     g_signal_connect(modem, NM_MODEM_AUTH_REQUESTED, G_CALLBACK(modem_auth_requested), self);
     g_signal_connect(modem, NM_MODEM_AUTH_RESULT, G_CALLBACK(modem_auth_result), self);
     g_signal_connect(modem, NM_MODEM_STATE_CHANGED, G_CALLBACK(modem_state_cb), self);
@@ -838,9 +762,9 @@ nm_device_modem_new(NMModem *modem)
                         NM_DEVICE_MODEM_MODEM,
                         modem,
                         NM_DEVICE_MODEM_CAPABILITIES,
-                        caps,
+                        (guint) ((guint32) caps),
                         NM_DEVICE_MODEM_CURRENT_CAPABILITIES,
-                        current_caps,
+                        (guint) ((guint32) current_caps),
                         NM_DEVICE_MODEM_DEVICE_ID,
                         nm_modem_get_device_id(modem),
                         NULL);
@@ -885,9 +809,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_modem = {
 static void
 nm_device_modem_class_init(NMDeviceModemClass *klass)
 {
-    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     object_class->dispose      = dispose;
     object_class->get_property = get_property;
@@ -904,8 +828,7 @@ nm_device_modem_class_init(NMDeviceModemClass *klass)
     device_class->deactivate                  = deactivate;
     device_class->act_stage1_prepare          = act_stage1_prepare;
     device_class->act_stage2_config           = act_stage2_config;
-    device_class->act_stage3_ip_config_start  = act_stage3_ip_config_start;
-    device_class->ip4_config_pre_commit       = ip4_config_pre_commit;
+    device_class->act_stage3_ip_config        = act_stage3_ip_config;
     device_class->get_enabled                 = get_enabled;
     device_class->set_enabled                 = set_enabled;
     device_class->owns_iface                  = owns_iface;
@@ -913,6 +836,7 @@ nm_device_modem_class_init(NMDeviceModemClass *klass)
     device_class->get_ip_iface_identifier     = get_ip_iface_identifier;
     device_class->get_configured_mtu          = nm_modem_get_configured_mtu;
     device_class->get_dhcp_timeout_for_device = get_dhcp_timeout_for_device;
+    device_class->ready_for_ip_config         = ready_for_ip_config;
 
     device_class->state_changed = device_state_changed;
 
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index a5139f08..f5336d37 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -16,29 +16,20 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
+#include "nm-l3-config-data.h"
 
 #define NM_MODEM_BROADBAND_MODEM "modem"
 
-static gboolean
-MODEM_CAPS_3GPP(MMModemCapability caps)
-{
-    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
-    /* MM_MODEM_CAPABILITY_LTE_ADVANCED is marked as deprecated since ModemManager 1.14.0.
-     *
-     * The flag probably was never used, it certainly isn't used since 1.14.0.
-     *
-     * Still, just to be sure, there is no harm in checking it here. Suppress the
-     * warning, it should have no bad effect.
-     */
-    return NM_FLAGS_ANY(caps,
-                        (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE
-                         | MM_MODEM_CAPABILITY_LTE_ADVANCED));
-    G_GNUC_END_IGNORE_DEPRECATIONS
-}
+#if !MM_CHECK_VERSION(1, 14, 0)
+#define MM_MODEM_CAPABILITY_5GNR ((MMModemCapability) (1 << 6))
+#endif
+
+#define MODEM_CAPS_3GPP(caps) \
+    NM_FLAGS_ANY(             \
+        caps,                 \
+        (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR))
 
-#define MODEM_CAPS_3GPP2(caps) (caps & (MM_MODEM_CAPABILITY_CDMA_EVDO))
+#define MODEM_CAPS_3GPP2(caps) NM_FLAGS_ANY((caps), MM_MODEM_CAPABILITY_CDMA_EVDO)
 
 /* Maximum time to keep the DBus call waiting for a connection result.
  * This value is greater than the default timeout in ModemManager (180s since
@@ -62,13 +53,13 @@ typedef struct {
     ConnectStep       step;
 
     MMModemCapability          caps;
-    NMConnection *             connection;
-    GCancellable *             cancellable;
+    NMConnection              *connection;
+    GCancellable              *cancellable;
     MMSimpleConnectProperties *connect_properties;
-    GArray *                   ip_types;
+    GArray                    *ip_types;
     guint                      ip_types_i;
     guint                      ip_type_tries;
-    GError *                   first_error;
+    GError                    *first_error;
 } ConnectContext;
 
 /*****************************************************************************/
@@ -79,21 +70,18 @@ typedef struct {
     /* The modem object from dbus */
     MMObject *modem_object;
     /* Per-interface objects */
-    MMModem *      modem_iface;
-    MMModem3gpp *  modem_3gpp_iface;
+    MMModem       *modem_iface;
+    MMModem3gpp   *modem_3gpp_iface;
     MMModemSimple *simple_iface;
-    MMSim *        sim_iface;
+    MMSim         *sim_iface;
 
     /* Connection setup */
     ConnectContext *ctx;
 
-    MMBearer *        bearer;
+    MMBearer         *bearer;
     MMBearerIpConfig *ipv4_config;
     MMBearerIpConfig *ipv6_config;
 
-    guint idle_id_ip4;
-    guint idle_id_ip6;
-
     guint32 pin_tries;
 } NMModemBroadbandPrivate;
 
@@ -123,7 +111,7 @@ G_DEFINE_TYPE(NMModemBroadband, nm_modem_broadband, NM_TYPE_MODEM)
         if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) {                       \
             NMModemBroadband *const __self = (self);                             \
             char                    __prefix_name[128];                          \
-            const char *            __uid;                                       \
+            const char             *__uid;                                       \
                                                                                  \
             _nm_log(_level,                                                      \
                     (_NMLOG_DOMAIN),                                             \
@@ -199,24 +187,28 @@ translate_mm_error(NMModemBroadband *self, GError *error)
 /*****************************************************************************/
 
 static void
-get_capabilities(NMModem *                  _self,
+get_capabilities(NMModem                   *_self,
                  NMDeviceModemCapabilities *modem_caps,
                  NMDeviceModemCapabilities *current_caps)
 {
-    NMModemBroadband * self          = NM_MODEM_BROADBAND(_self);
-    MMModemCapability  all_supported = MM_MODEM_CAPABILITY_NONE;
-    MMModemCapability *supported;
-    guint              n_supported;
+    NMModemBroadband          *self          = NM_MODEM_BROADBAND(_self);
+    MMModemCapability          all_supported = MM_MODEM_CAPABILITY_NONE;
+    gs_free MMModemCapability *supported     = NULL;
+    guint                      n_supported;
+    guint                      i;
+
+    G_STATIC_ASSERT(MM_MODEM_CAPABILITY_POTS == (guint64) NM_DEVICE_MODEM_CAPABILITY_POTS);
+    G_STATIC_ASSERT(MM_MODEM_CAPABILITY_CDMA_EVDO
+                    == (guint64) NM_DEVICE_MODEM_CAPABILITY_CDMA_EVDO);
+    G_STATIC_ASSERT(MM_MODEM_CAPABILITY_GSM_UMTS == (guint64) NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS);
+    G_STATIC_ASSERT(MM_MODEM_CAPABILITY_LTE == (guint64) NM_DEVICE_MODEM_CAPABILITY_LTE);
+    G_STATIC_ASSERT(MM_MODEM_CAPABILITY_5GNR == (guint64) NM_DEVICE_MODEM_CAPABILITY_5GNR);
 
     /* For now, we don't care about the capability combinations, just merge all
      * combinations in a single mask */
     if (mm_modem_get_supported_capabilities(self->_priv.modem_iface, &supported, &n_supported)) {
-        guint i;
-
         for (i = 0; i < n_supported; i++)
             all_supported |= supported[i];
-
-        g_free(supported);
     }
 
     *modem_caps = (NMDeviceModemCapabilities) all_supported;
@@ -227,7 +219,7 @@ get_capabilities(NMModem *                  _self,
 static gboolean
 owns_port(NMModem *_self, const char *iface)
 {
-    NMModemBroadband *     self    = NM_MODEM_BROADBAND(_self);
+    NMModemBroadband      *self    = NM_MODEM_BROADBAND(_self);
     const MMModemPortInfo *ports   = NULL;
     guint                  n_ports = 0, i;
 
@@ -278,7 +270,7 @@ create_cdma_connect_properties(NMConnection *connection)
 #if !MM_CHECK_VERSION(1, 9, 1)
     {
         NMSettingCdma *setting;
-        const char *   str;
+        const char    *str;
 
         setting = nm_connection_get_setting_cdma(connection);
         str     = nm_setting_cdma_get_number(setting);
@@ -292,14 +284,14 @@ create_cdma_connect_properties(NMConnection *connection)
 
 static MMSimpleConnectProperties *
 create_gsm_connect_properties(NMConnection *connection,
-                              const char *  apn,
-                              const char *  username,
-                              const char *  password)
+                              const char   *apn,
+                              const char   *username,
+                              const char   *password)
 {
-    NMSettingGsm *             setting;
-    NMSettingPpp *             s_ppp;
+    NMSettingGsm              *setting;
+    NMSettingPpp              *s_ppp;
     MMSimpleConnectProperties *properties;
-    const char *               str;
+    const char                *str;
 
     setting = nm_connection_get_setting_gsm(connection);
 
@@ -369,11 +361,11 @@ static void connect_context_step(NMModemBroadband *self);
 static void
 connect_ready(MMModemSimple *simple_iface, GAsyncResult *res, NMModemBroadband *self)
 {
-    ConnectContext *ctx;
-    GError *        error            = NULL;
-    NMModemIPMethod ip4_method       = NM_MODEM_IP_METHOD_UNKNOWN;
-    NMModemIPMethod ip6_method       = NM_MODEM_IP_METHOD_UNKNOWN;
-    gs_unref_object MMBearer *bearer = NULL;
+    ConnectContext           *ctx;
+    GError                   *error      = NULL;
+    NMModemIPMethod           ip4_method = NM_MODEM_IP_METHOD_UNKNOWN;
+    NMModemIPMethod           ip6_method = NM_MODEM_IP_METHOD_UNKNOWN;
+    gs_unref_object MMBearer *bearer     = NULL;
 
     bearer = mm_modem_simple_connect_finish(simple_iface, res, &error);
 
@@ -480,18 +472,18 @@ send_pin_ready(MMSim *sim, GAsyncResult *result, NMModemBroadband *self)
 }
 
 static void
-find_gsm_apn_cb(const char *  apn,
-                const char *  username,
-                const char *  password,
-                const char *  gateway,
-                const char *  auth_method,
+find_gsm_apn_cb(const char   *apn,
+                const char   *username,
+                const char   *password,
+                const char   *gateway,
+                const char   *auth_method,
                 const GSList *dns,
-                GError *      error,
+                GError       *error,
                 gpointer      user_data)
 {
-    NMModemBroadband *       self = user_data;
+    NMModemBroadband        *self = user_data;
     NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self);
-    ConnectContext *         ctx  = priv->ctx;
+    ConnectContext          *ctx  = priv->ctx;
 
     if (error) {
         _LOGW("failed to connect '%s': APN not found: %s",
@@ -514,14 +506,14 @@ static gboolean
 try_create_connect_properties(NMModemBroadband *self)
 {
     NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self);
-    ConnectContext *         ctx  = priv->ctx;
+    ConnectContext          *ctx  = priv->ctx;
 
     if (MODEM_CAPS_3GPP(ctx->caps)) {
         NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection);
 
         if (!s_gsm || nm_setting_gsm_get_auto_config(s_gsm)) {
             gs_unref_object MMModem3gpp *modem_3gpp = NULL;
-            const char *                 network_id = NULL;
+            const char                  *network_id = NULL;
 
             s_gsm = nm_connection_get_setting_gsm(ctx->connection);
             if (s_gsm)
@@ -590,7 +582,7 @@ connect_context_step(NMModemBroadband *self)
         if (MODEM_CAPS_3GPP(ctx->caps)
             && mm_modem_get_unlock_required(self->_priv.modem_iface) == MM_MODEM_LOCK_SIM_PIN) {
             NMSettingGsm *s_gsm = nm_connection_get_setting_gsm(ctx->connection);
-            const char *  pin   = nm_setting_gsm_get_pin(s_gsm);
+            const char   *pin   = nm_setting_gsm_get_pin(s_gsm);
 
             /* If we have a PIN already, send it.  If we don't, get it. */
             if (pin) {
@@ -698,8 +690,8 @@ connect_context_step(NMModemBroadband *self)
 }
 
 static NMActStageReturn
-modem_act_stage1_prepare(NMModem *            _self,
-                         NMConnection *       connection,
+modem_act_stage1_prepare(NMModem             *_self,
+                         NMConnection        *connection,
                          NMDeviceStateReason *out_failure_reason)
 {
     NMModemBroadband *self = NM_MODEM_BROADBAND(_self);
@@ -772,15 +764,15 @@ check_connection_compatible_with_modem(NMModem *_self, NMConnection *connection,
 /*****************************************************************************/
 
 static gboolean
-complete_connection(NMModem *            modem,
-                    const char *         iface,
-                    NMConnection *       connection,
+complete_connection(NMModem             *modem,
+                    const char          *iface,
+                    NMConnection        *connection,
                     NMConnection *const *existing_connections,
-                    GError **            error)
+                    GError             **error)
 {
     NMModemBroadband *self = NM_MODEM_BROADBAND(modem);
     MMModemCapability modem_caps;
-    NMSettingPpp *    s_ppp;
+    NMSettingPpp     *s_ppp;
 
     modem_caps = mm_modem_get_current_capabilities(self->_priv.modem_iface);
 
@@ -860,7 +852,7 @@ complete_connection(NMModem *            modem,
 static gboolean
 get_user_pass(NMModem *modem, NMConnection *connection, const char **user, const char **pass)
 {
-    NMSettingGsm * s_gsm;
+    NMSettingGsm  *s_gsm;
     NMSettingCdma *s_cdma;
 
     s_gsm  = nm_connection_get_setting_gsm(connection);
@@ -958,259 +950,257 @@ set_mm_enabled(NMModem *_self, gboolean enabled)
 }
 
 /*****************************************************************************/
-/* IPv4 method static */
 
-static gboolean
-static_stage3_ip4_done(NMModemBroadband *self)
+static void
+stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_method)
 {
-    GError *        error               = NULL;
-    gs_unref_object NMIP4Config *config = NULL;
-    const char *                 data_port;
-    const char *                 address_string;
-    const char *                 gw_string;
-    guint32                      address_network;
-    guint32                      gw = 0;
-    NMPlatformIP4Address         address;
-    const char **                dns;
-    guint                        i;
-    guint32                      ip4_route_table, ip4_route_metric;
-    NMPlatformIP4Route *         r;
-    guint32                      mtu_n;
-
-    g_return_val_if_fail(self->_priv.ipv4_config, FALSE);
-    g_return_val_if_fail(self->_priv.bearer, FALSE);
-
-    self->_priv.idle_id_ip4 = 0;
-
-    _LOGI("IPv4 static configuration:");
-
-    /* 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)) {
-        error =
-            g_error_new(NM_DEVICE_ERROR,
+    const int                               IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMModemBroadband                       *self    = NM_MODEM_BROADBAND(modem);
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd    = NULL;
+    char                                    sbuf[sizeof(_nm_utils_to_string_buffer)];
+    gs_free_error GError                   *error = NULL;
+    const char                             *data_port;
+    const char                             *address_string;
+    const char                            **dns;
+    guint                                   i;
+    gboolean                                do_auto = FALSE;
+    int                                     ifindex;
+    NMUtilsIPv6IfaceId                      iid_data;
+    const NMUtilsIPv6IfaceId               *iid = NULL;
+
+    if (IS_IPv4) {
+        g_return_if_fail(self->_priv.ipv4_config);
+        g_return_if_fail(self->_priv.bearer);
+
+        if (ip_method == NM_MODEM_IP_METHOD_AUTO) {
+            do_auto = TRUE;
+            goto out;
+        }
+    } else {
+        g_return_if_fail(self->_priv.ipv6_config);
+    }
+
+    if (IS_IPv4) {
+        guint32              address_network;
+        guint32              gw = 0;
+        NMPlatformIP4Address address;
+        NMPlatformIP4Route   route;
+        guint32              mtu_n;
+        const char          *gw_string;
+
+        _LOGI("IPv4 static configuration:");
+
+        /* 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)) {
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
                         NM_DEVICE_ERROR_INVALID_CONNECTION,
                         "(%s) retrieving IP4 configuration failed: invalid address given %s%s%s",
                         nm_modem_get_uid(NM_MODEM(self)),
                         NM_PRINT_FMT_QUOTE_STRING(address_string));
-        goto out;
-    }
+            goto out;
+        }
 
-    /* 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)) {
-        error =
-            g_error_new(NM_DEVICE_ERROR,
+        /* 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)) {
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
                         NM_DEVICE_ERROR_INVALID_CONNECTION,
                         "(%s) retrieving IP4 configuration failed: invalid gateway address \"%s\"",
                         nm_modem_get_uid(NM_MODEM(self)),
                         gw_string);
-        goto out;
-    }
-
-    data_port = mm_bearer_get_interface(self->_priv.bearer);
-    g_return_val_if_fail(data_port, FALSE);
-    config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
-                               nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port));
-
-    memset(&address, 0, sizeof(address));
-    address.address      = address_network;
-    address.peer_address = address_network;
-    address.plen         = mm_bearer_ip_config_get_prefix(self->_priv.ipv4_config);
-    address.addr_source  = NM_IP_CONFIG_SOURCE_WWAN;
-    if (address.plen <= 32)
-        nm_ip4_config_add_address(config, &address);
-
-    _LOGI("  address %s/%d", address_string, address.plen);
-
-    nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL);
-    r = &(NMPlatformIP4Route){
-        .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
-        .gateway       = gw,
-        .table_coerced = nm_platform_route_table_coerce(ip4_route_table),
-        .metric        = ip4_route_metric,
-    };
-    nm_ip4_config_add_route(config, r, NULL);
-    _LOGI("  gateway %s", gw_string);
-
-    /* DNS servers */
-    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_ip4_config_add_nameserver(config, address_network);
-            _LOGI("  DNS %s", dns[i]);
+            goto out;
         }
-    }
 
-#if MM_CHECK_VERSION(1, 4, 0)
-    mtu_n = mm_bearer_ip_config_get_mtu(self->_priv.ipv4_config);
-    if (mtu_n) {
-        nm_ip4_config_set_mtu(config, mtu_n, NM_IP_CONFIG_SOURCE_WWAN);
-        _LOGI("  MTU %u", mtu_n);
-    }
-#endif
-
-out:
-    g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, config, error);
-    g_clear_error(&error);
-    return FALSE;
-}
+        data_port = mm_bearer_get_interface(self->_priv.bearer);
+        g_return_if_fail(data_port);
 
-static NMActStageReturn
-static_stage3_ip4_config_start(NMModem *            modem,
-                               NMActRequest *       req,
-                               NMDeviceStateReason *out_failure_reason)
-{
-    NMModemBroadband *       self = NM_MODEM_BROADBAND(modem);
-    NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self);
+        ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port);
+        if (ifindex <= 0) {
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "(%s) data port %s not found",
+                        nm_modem_get_uid(NM_MODEM(self)),
+                        data_port);
+            goto out;
+        }
 
-    /* We schedule it in an idle just to follow the same logic as in the
-     * generic modem implementation. */
-    nm_clear_g_source(&priv->idle_id_ip4);
-    priv->idle_id_ip4 = g_idle_add((GSourceFunc) static_stage3_ip4_done, self);
+        l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                     ifindex,
+                                     NM_IP_CONFIG_SOURCE_WWAN);
+
+        address = (NMPlatformIP4Address){
+            .address      = address_network,
+            .peer_address = address_network,
+            .plen         = mm_bearer_ip_config_get_prefix(self->_priv.ipv4_config),
+            .addr_source  = NM_IP_CONFIG_SOURCE_WWAN,
+        };
+        if (address.plen <= 32)
+            nm_l3_config_data_add_address_4(l3cd, &address);
+
+        _LOGI("  address %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf)));
+
+        route = (NMPlatformIP4Route){
+            .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
+            .gateway       = gw,
+            .table_any     = TRUE,
+            .table_coerced = 0,
+            .metric_any    = TRUE,
+            .metric        = 0,
+        };
+        nm_l3_config_data_add_route_4(l3cd, &route);
+        _LOGI("  gateway %s", gw_string);
+
+        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);
+                _LOGI("  DNS %s", dns[i]);
+            }
+        }
 
-    return NM_ACT_STAGE_RETURN_POSTPONE;
-}
+#if MM_CHECK_VERSION(1, 4, 0)
+        mtu_n = mm_bearer_ip_config_get_mtu(self->_priv.ipv4_config);
+        if (mtu_n) {
+            nm_l3_config_data_set_mtu(l3cd, mtu_n);
+            _LOGI("  MTU %u", mtu_n);
+        }
+#endif
+    } else {
+        NMPlatformIP6Address address;
+
+        address_string = mm_bearer_ip_config_get_address(self->_priv.ipv6_config);
+        if (!address_string) {
+            /* DHCP/SLAAC is allowed to skip addresses; other methods require it */
+            if (ip_method != NM_MODEM_IP_METHOD_AUTO) {
+                g_set_error(&error,
+                            NM_DEVICE_ERROR,
+                            NM_DEVICE_ERROR_INVALID_CONNECTION,
+                            "(%s) retrieving IPv6 configuration failed: no address given",
+                            nm_modem_get_uid(NM_MODEM(self)));
+            }
+            goto out;
+        }
 
-/*****************************************************************************/
-/* IPv6 method static */
+        address = (NMPlatformIP6Address){};
 
-static gboolean
-stage3_ip6_done(NMModemBroadband *self)
-{
-    GError *             error  = NULL;
-    NMIP6Config *        config = NULL;
-    const char *         data_port;
-    const char *         address_string;
-    NMPlatformIP6Address address;
-    NMModemIPMethod      ip_method;
-    const char **        dns;
-    guint                i;
-
-    g_return_val_if_fail(self->_priv.ipv6_config, FALSE);
-
-    self->_priv.idle_id_ip6 = 0;
-    memset(&address, 0, sizeof(address));
-
-    ip_method = get_bearer_ip_method(self->_priv.ipv6_config);
-
-    address_string = mm_bearer_ip_config_get_address(self->_priv.ipv6_config);
-    if (!address_string) {
-        /* DHCP/SLAAC is allowed to skip addresses; other methods require it */
-        if (ip_method != NM_MODEM_IP_METHOD_AUTO) {
-            error = g_error_new(NM_DEVICE_ERROR,
-                                NM_DEVICE_ERROR_INVALID_CONNECTION,
-                                "(%s) retrieving IPv6 configuration failed: no address given",
-                                nm_modem_get_uid(NM_MODEM(self)));
+        if (!inet_pton(AF_INET6, address_string, &address.address)) {
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "(%s) retrieving IPv6 configuration failed: invalid address given '%s'",
+                        nm_modem_get_uid(NM_MODEM(self)),
+                        address_string);
+            goto out;
         }
-        goto out;
-    }
 
-    /* Fail if invalid IP address retrieved */
-    if (!inet_pton(AF_INET6, address_string, (void *) &(address.address))) {
-        error = g_error_new(NM_DEVICE_ERROR,
-                            NM_DEVICE_ERROR_INVALID_CONNECTION,
-                            "(%s) retrieving IPv6 configuration failed: invalid address given '%s'",
-                            nm_modem_get_uid(NM_MODEM(self)),
-                            address_string);
-        goto out;
-    }
+        data_port = mm_bearer_get_interface(self->_priv.bearer);
+        g_return_if_fail(data_port);
 
-    _LOGI("IPv6 base configuration:");
+        ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port);
+        if (ifindex <= 0) {
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "(%s) data port %s not found",
+                        nm_modem_get_uid(NM_MODEM(self)),
+                        data_port);
+            goto out;
+        }
 
-    data_port = mm_bearer_get_interface(self->_priv.bearer);
-    g_return_val_if_fail(data_port, FALSE);
+        _LOGI("IPv6 base configuration:");
 
-    config = nm_ip6_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
-                               nm_platform_link_get_ifindex(NM_PLATFORM_GET, data_port));
+        l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                     ifindex,
+                                     NM_IP_CONFIG_SOURCE_WWAN);
 
-    address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv6_config);
-    if (address.plen <= 128)
-        nm_ip6_config_add_address(config, &address);
+        do_auto = TRUE;
 
-    _LOGI("  address %s/%d", address_string, address.plen);
+        address.plen = mm_bearer_ip_config_get_prefix(self->_priv.ipv6_config);
+        if (address.plen <= 128) {
+            if (IN6_IS_ADDR_LINKLOCAL(&address.address)) {
+                iid_data.id = ((guint64 *) (&address.address.s6_addr))[1];
+                iid         = &iid_data;
+            } else
+                do_auto = FALSE;
+            nm_l3_config_data_add_address_6(l3cd, &address);
+        }
 
-    address_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv6_config);
-    if (address_string) {
-        guint32 ip6_route_table, ip6_route_metric;
+        _LOGI("  address %s (slaac %s)",
+              nm_platform_ip6_address_to_string(&address, sbuf, sizeof(sbuf)),
+              do_auto ? "enabled" : "disabled");
 
-        if (inet_pton(AF_INET6, address_string, &address.address) != 1) {
-            error =
-                g_error_new(NM_DEVICE_ERROR,
+        address_string = mm_bearer_ip_config_get_gateway(self->_priv.ipv6_config);
+        if (address_string) {
+            if (inet_pton(AF_INET6, address_string, &address.address) != 1) {
+                g_set_error(&error,
+                            NM_DEVICE_ERROR,
                             NM_DEVICE_ERROR_INVALID_CONNECTION,
                             "(%s) retrieving IPv6 configuration failed: invalid gateway given '%s'",
                             nm_modem_get_uid(NM_MODEM(self)),
                             address_string);
-            goto out;
-        }
+                goto out;
+            }
 
-        nm_modem_get_route_parameters(NM_MODEM(self),
-                                      NULL,
-                                      NULL,
-                                      &ip6_route_table,
-                                      &ip6_route_metric);
-        {
-            const NMPlatformIP6Route r = {
-                .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
-                .gateway       = address.address,
-                .table_coerced = nm_platform_route_table_coerce(ip6_route_table),
-                .metric        = ip6_route_metric,
-            };
-
-            _LOGI("  gateway %s", address_string);
-            nm_ip6_config_add_route(config, &r, NULL);
+            {
+                const NMPlatformIP6Route r = {
+                    .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
+                    .gateway       = address.address,
+                    .table_any     = TRUE,
+                    .table_coerced = 0,
+                    .metric_any    = TRUE,
+                    .metric        = 0,
+                };
+
+                _LOGI("  gateway %s", address_string);
+                nm_l3_config_data_add_route_6(l3cd, &r);
+            }
+        } else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
+            /* Gateway required for the 'static' method */
+            g_set_error(&error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "(%s) retrieving IPv6 configuration failed: missing gateway",
+                        nm_modem_get_uid(NM_MODEM(self)));
+            goto out;
         }
-    } else if (ip_method == NM_MODEM_IP_METHOD_STATIC) {
-        /* Gateway required for the 'static' method */
-        error = g_error_new(NM_DEVICE_ERROR,
-                            NM_DEVICE_ERROR_INVALID_CONNECTION,
-                            "(%s) retrieving IPv6 configuration failed: missing gateway",
-                            nm_modem_get_uid(NM_MODEM(self)));
-        goto out;
-    }
 
-    /* DNS servers */
-    dns = mm_bearer_ip_config_get_dns(self->_priv.ipv6_config);
-    for (i = 0; dns && dns[i]; i++) {
-        struct in6_addr addr;
+        dns = mm_bearer_ip_config_get_dns(self->_priv.ipv6_config);
+        for (i = 0; dns && dns[i]; i++) {
+            struct in6_addr addr;
 
-        if (inet_pton(AF_INET6, dns[i], &addr)) {
-            nm_ip6_config_add_nameserver(config, &addr);
-            _LOGI("  DNS %s", dns[i]);
+            if (inet_pton(AF_INET6, dns[i], &addr)) {
+                nm_l3_config_data_add_nameserver(l3cd, AF_INET6, &addr);
+                _LOGI("  DNS %s", dns[i]);
+            }
         }
     }
 
 out:
-    nm_modem_emit_ip6_config_result(NM_MODEM(self), config, error);
-    g_clear_object(&config);
-    g_clear_error(&error);
-    return FALSE;
-}
-
-static NMActStageReturn
-stage3_ip6_config_request(NMModem *modem, NMDeviceStateReason *out_failure_reason)
-{
-    NMModemBroadband *       self = NM_MODEM_BROADBAND(modem);
-    NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self);
-
-    /* We schedule it in an idle just to follow the same logic as in the
-     * generic modem implementation. */
-    nm_clear_g_source(&priv->idle_id_ip6);
-    priv->idle_id_ip6 = g_idle_add((GSourceFunc) stage3_ip6_done, self);
+    if (error) {
+        nm_modem_emit_signal_new_config_failure(modem,
+                                                addr_family,
+                                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE,
+                                                error);
+        return;
+    }
 
-    return NM_ACT_STAGE_RETURN_POSTPONE;
+    nm_modem_emit_signal_new_config_success(modem, addr_family, l3cd, do_auto, iid);
 }
 
 /*****************************************************************************/
 /* Disconnect */
 
 typedef struct {
-    NMModemBroadband *         self;
+    NMModemBroadband          *self;
     _NMModemDisconnectCallback callback;
     gpointer                   callback_user_data;
-    GCancellable *             cancellable;
+    GCancellable              *cancellable;
     gboolean                   warn;
 } DisconnectContext;
 
@@ -1227,7 +1217,7 @@ disconnect_context_complete(DisconnectContext *ctx, GError *error)
 static void
 disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable)
 {
-    DisconnectContext *ctx                = user_data;
+    DisconnectContext    *ctx             = user_data;
     gs_free_error GError *cancelled_error = NULL;
 
     g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error);
@@ -1237,9 +1227,9 @@ disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellabl
 static void
 simple_disconnect_ready(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    MMModemSimple *    modem_iface = MM_MODEM_SIMPLE(source_object);
-    DisconnectContext *ctx         = user_data;
-    gs_free_error GError *error    = NULL;
+    MMModemSimple        *modem_iface = MM_MODEM_SIMPLE(source_object);
+    DisconnectContext    *ctx         = user_data;
+    gs_free_error GError *error       = NULL;
 
     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)) {
@@ -1253,13 +1243,13 @@ simple_disconnect_ready(GObject *source_object, GAsyncResult *res, gpointer user
 }
 
 static void
-disconnect(NMModem *                  modem,
+disconnect(NMModem                   *modem,
            gboolean                   warn,
-           GCancellable *             cancellable,
+           GCancellable              *cancellable,
            _NMModemDisconnectCallback callback,
            gpointer                   user_data)
 {
-    NMModemBroadband * self = NM_MODEM_BROADBAND(modem);
+    NMModemBroadband  *self = NM_MODEM_BROADBAND(modem);
     DisconnectContext *ctx;
 
     connect_context_clear(self);
@@ -1336,11 +1326,11 @@ mm_state_to_nm(MMModemState mm_state)
 }
 
 static void
-modem_state_changed(MMModem *                modem,
+modem_state_changed(MMModem                 *modem,
                     MMModemState             old_state,
                     MMModemState             new_state,
                     MMModemStateChangeReason reason,
-                    NMModemBroadband *       self)
+                    NMModemBroadband        *self)
 {
     /* After the SIM is unlocked MM1 will move the device to INITIALIZING which
      * is an unavailable state.  That makes state handling confusing here, so
@@ -1378,7 +1368,7 @@ static void
 get_sim_ready(MMModem *modem, GAsyncResult *res, NMModemBroadband *self)
 {
     GError *error = NULL;
-    MMSim * new_sim;
+    MMSim  *new_sim;
 
     new_sim = mm_modem_get_sim_finish(modem, res, &error);
     if (new_sim != self->_priv.sim_iface) {
@@ -1512,12 +1502,12 @@ nm_modem_broadband_init(NMModemBroadband *self)
 NMModem *
 nm_modem_broadband_new(GObject *object, GError **error)
 {
-    MMObject *         modem_object;
-    MMModem *          modem_iface;
-    MMModem3gpp *      modem_3gpp_iface;
+    MMObject          *modem_object;
+    MMModem           *modem_iface;
+    MMModem3gpp       *modem_3gpp_iface;
     const char *const *drivers;
-    const char *       operator_code = NULL;
-    gs_free char *     driver        = NULL;
+    const char        *operator_code = NULL;
+    gs_free char      *driver        = NULL;
 
     g_return_val_if_fail(MM_IS_OBJECT(object), NULL);
     modem_object = MM_OBJECT(object);
@@ -1561,30 +1551,27 @@ nm_modem_broadband_new(GObject *object, GError **error)
 static void
 dispose(GObject *object)
 {
-    NMModemBroadband *       self = NM_MODEM_BROADBAND(object);
+    NMModemBroadband        *self = NM_MODEM_BROADBAND(object);
     NMModemBroadbandPrivate *priv = NM_MODEM_BROADBAND_GET_PRIVATE(self);
 
-    nm_clear_g_source(&priv->idle_id_ip4);
-    nm_clear_g_source(&priv->idle_id_ip6);
-
     connect_context_clear(self);
-    g_clear_object(&self->_priv.ipv4_config);
-    g_clear_object(&self->_priv.ipv6_config);
-    g_clear_object(&self->_priv.bearer);
+    g_clear_object(&priv->ipv4_config);
+    g_clear_object(&priv->ipv6_config);
+    g_clear_object(&priv->bearer);
 
-    if (self->_priv.modem_iface) {
-        g_signal_handlers_disconnect_by_data(self->_priv.modem_iface, self);
-        g_clear_object(&self->_priv.modem_iface);
+    if (priv->modem_iface) {
+        g_signal_handlers_disconnect_by_data(priv->modem_iface, self);
+        g_clear_object(&priv->modem_iface);
     }
 
-    if (self->_priv.modem_3gpp_iface) {
-        g_signal_handlers_disconnect_by_data(self->_priv.modem_3gpp_iface, self);
-        g_clear_object(&self->_priv.modem_3gpp_iface);
+    if (priv->modem_3gpp_iface) {
+        g_signal_handlers_disconnect_by_data(priv->modem_3gpp_iface, self);
+        g_clear_object(&priv->modem_3gpp_iface);
     }
 
-    g_clear_object(&self->_priv.simple_iface);
-    g_clear_object(&self->_priv.sim_iface);
-    g_clear_object(&self->_priv.modem_object);
+    g_clear_object(&priv->simple_iface);
+    g_clear_object(&priv->sim_iface);
+    g_clear_object(&priv->modem_object);
 
     G_OBJECT_CLASS(nm_modem_broadband_parent_class)->dispose(object);
 }
@@ -1600,8 +1587,7 @@ nm_modem_broadband_class_init(NMModemBroadbandClass *klass)
     object_class->set_property = set_property;
 
     modem_class->get_capabilities                       = get_capabilities;
-    modem_class->static_stage3_ip4_config_start         = static_stage3_ip4_config_start;
-    modem_class->stage3_ip6_config_request              = stage3_ip6_config_request;
+    modem_class->stage3_ip_config_start                 = stage3_ip_config_start;
     modem_class->disconnect                             = disconnect;
     modem_class->deactivate_cleanup                     = deactivate_cleanup;
     modem_class->set_mm_enabled                         = set_mm_enabled;
diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c
index 8d087d13..4a89f38e 100644
--- a/src/core/devices/wwan/nm-modem-manager.c
+++ b/src/core/devices/wwan/nm-modem-manager.c
@@ -45,7 +45,7 @@ typedef struct {
     GCancellable *main_cancellable;
 
     struct {
-        MMManager *   manager;
+        MMManager    *manager;
         GCancellable *poke_cancellable;
         gulong        handle_name_owner_changed_id;
         gulong        handle_object_added_id;
@@ -61,15 +61,15 @@ typedef struct {
             LOG_AVAILABLE_NO,
         } log_available : 3;
 
-        GDBusProxy *  proxy;
+        GDBusProxy   *proxy;
         GCancellable *proxy_cancellable;
         guint         proxy_ref_count;
-        char *        proxy_name_owner;
+        char         *proxy_name_owner;
     } modm;
 
 #if WITH_OFONO
     struct {
-        GDBusProxy *  proxy;
+        GDBusProxy   *proxy;
         GCancellable *cancellable;
     } ofono;
 #endif
@@ -111,7 +111,7 @@ static void
 handle_new_modem(NMModemManager *self, NMModem *modem)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    const char *           path;
+    const char            *path;
 
     path = nm_modem_get_path(modem);
     if (g_hash_table_lookup(priv->modems, path)) {
@@ -163,10 +163,10 @@ static void
 modm_handle_object_added(MMManager *modem_manager, MMObject *modem_object, NMModemManager *self)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    const char *           path;
-    MMModem *              modem_iface;
-    NMModem *              modem;
-    GError *               error = NULL;
+    const char            *path;
+    MMModem               *modem_iface;
+    NMModem               *modem;
+    GError                *error = NULL;
 
     /* Ensure we don't have the same modem already */
     path = mm_object_get_path(modem_object);
@@ -201,8 +201,8 @@ static void
 modm_handle_object_removed(MMManager *manager, MMObject *modem_object, NMModemManager *self)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    NMModem *              modem;
-    const char *           path;
+    NMModem               *modem;
+    const char            *path;
 
     path  = mm_object_get_path(modem_object);
     modem = (NMModem *) g_hash_table_lookup(priv->modems, path);
@@ -217,7 +217,7 @@ static void
 modm_manager_available(NMModemManager *self)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    GList *                modems, *l;
+    GList                 *modems, *l;
 
     if (priv->modm.log_available != LOG_AVAILABLE_YES) {
         _LOGI("ModemManager %savailable", priv->modm.log_available ? "now " : "");
@@ -235,7 +235,7 @@ static void
 modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMModemManager *self)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    char *                 name_owner;
+    char                  *name_owner;
 
     /* Quit poking, if any */
     nm_clear_g_source(&priv->modm.relaunch_id);
@@ -273,9 +273,9 @@ modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMMo
 static void
 modm_manager_poke_cb(GObject *connection, GAsyncResult *res, gpointer user_data)
 {
-    NMModemManager *       self;
-    NMModemManagerPrivate *priv;
-    gs_free_error GError *error       = NULL;
+    NMModemManager            *self;
+    NMModemManagerPrivate     *priv;
+    gs_free_error GError      *error  = NULL;
     gs_unref_variant GVariant *result = NULL;
 
     result = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection), res, &error);
@@ -327,7 +327,7 @@ static void
 modm_manager_check_name_owner(NMModemManager *self)
 {
     NMModemManagerPrivate *priv       = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    gs_free char *         name_owner = NULL;
+    gs_free char          *name_owner = NULL;
 
     name_owner = g_dbus_object_manager_client_get_name_owner(
         G_DBUS_OBJECT_MANAGER_CLIENT(priv->modm.manager));
@@ -344,10 +344,10 @@ modm_manager_check_name_owner(NMModemManager *self)
 static void
 modm_manager_new_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMModemManager *       self;
+    NMModemManager        *self;
     NMModemManagerPrivate *priv;
-    gs_free_error GError *error = NULL;
-    MMManager *           modem_manager;
+    gs_free_error GError  *error = NULL;
+    MMManager             *modem_manager;
 
     modem_manager = mm_manager_new_finish(res, &error);
     if (!modem_manager && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -445,7 +445,7 @@ static void
 modm_proxy_name_owner_reset(NMModemManager *self)
 {
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    char *                 name = NULL;
+    char                  *name = NULL;
 
     if (priv->modm.proxy)
         name = g_dbus_proxy_get_name_owner(priv->modm.proxy);
@@ -469,10 +469,10 @@ modm_proxy_name_owner_changed_cb(GObject *object, GParamSpec *pspec, gpointer us
 static void
 modm_proxy_new_cb(GObject *source_object, GAsyncResult *result, gpointer user_data)
 {
-    NMModemManager *       self;
+    NMModemManager        *self;
     NMModemManagerPrivate *priv;
-    GDBusProxy *           proxy;
-    gs_free_error GError *error = NULL;
+    GDBusProxy            *proxy;
+    gs_free_error GError  *error = NULL;
 
     proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
     if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -567,7 +567,7 @@ static void
 ofono_create_modem(NMModemManager *self, const char *path)
 {
     NMModemManagerPrivate *priv  = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    NMModem *              modem = NULL;
+    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
@@ -585,15 +585,15 @@ ofono_create_modem(NMModemManager *self, const char *path)
 
 static void
 ofono_signal_cb(GDBusProxy *proxy,
-                char *      sender_name,
-                char *      signal_name,
-                GVariant *  parameters,
+                char       *sender_name,
+                char       *signal_name,
+                GVariant   *parameters,
                 gpointer    user_data)
 {
-    NMModemManager *       self = NM_MODEM_MANAGER(user_data);
+    NMModemManager        *self = NM_MODEM_MANAGER(user_data);
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    char *                 object_path;
-    NMModem *              modem;
+    char                  *object_path;
+    NMModem               *modem;
 
     if (g_strcmp0(signal_name, "ModemAdded") == 0) {
         g_variant_get(parameters, "(oa{sv})", &object_path, NULL);
@@ -619,12 +619,12 @@ ofono_signal_cb(GDBusProxy *proxy,
 static void
 ofono_enumerate_devices_done(GObject *proxy, GAsyncResult *res, gpointer user_data)
 {
-    NMModemManager *       self;
+    NMModemManager        *self;
     NMModemManagerPrivate *priv;
-    gs_free_error GError *error = NULL;
-    GVariant *            results;
-    GVariantIter *        iter;
-    const char *          path;
+    gs_free_error GError  *error = NULL;
+    GVariant              *results;
+    GVariantIter          *iter;
+    const char            *path;
 
     results = g_dbus_proxy_call_finish(G_DBUS_PROXY(proxy), res, &error);
     if (!results && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -651,7 +651,7 @@ static void
 ofono_check_name_owner(NMModemManager *self, gboolean first_invocation)
 {
     NMModemManagerPrivate *priv       = NM_MODEM_MANAGER_GET_PRIVATE(self);
-    gs_free char *         name_owner = NULL;
+    gs_free char          *name_owner = NULL;
 
     name_owner = g_dbus_proxy_get_name_owner(G_DBUS_PROXY(priv->ofono.proxy));
     if (name_owner) {
@@ -670,7 +670,7 @@ ofono_check_name_owner(NMModemManager *self, gboolean first_invocation)
                           self);
     } else {
         GHashTableIter iter;
-        NMModem *      modem;
+        NMModem       *modem;
 
         _LOGI("oFono is %savailable", first_invocation ? "not " : "no longer ");
 
@@ -694,10 +694,10 @@ ofono_name_owner_changed(GDBusProxy *ofono_proxy, GParamSpec *pspec, NMModemMana
 static void
 ofono_proxy_new_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    NMModemManager *       self;
+    NMModemManager        *self;
     NMModemManagerPrivate *priv;
-    gs_free_error GError *error = NULL;
-    GDBusProxy *          proxy;
+    gs_free_error GError  *error = NULL;
+    GDBusProxy            *proxy;
 
     proxy = g_dbus_proxy_new_finish(res, &error);
     if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -752,10 +752,10 @@ ofono_init_proxy(NMModemManager *self)
 static void
 bus_get_ready(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMModemManager *       self;
+    NMModemManager        *self;
     NMModemManagerPrivate *priv;
-    gs_free_error GError *error = NULL;
-    GDBusConnection *     connection;
+    gs_free_error GError  *error = NULL;
+    GDBusConnection       *connection;
 
     connection = g_bus_get_finish(res, &error);
     if (!connection && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -782,7 +782,7 @@ bus_get_ready(GObject *source, GAsyncResult *res, gpointer user_data)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMModemManager *       self = NM_MODEM_MANAGER(object);
+    NMModemManager        *self = NM_MODEM_MANAGER(object);
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -812,7 +812,7 @@ nm_modem_manager_init(NMModemManager *self)
 static void
 dispose(GObject *object)
 {
-    NMModemManager *       self = NM_MODEM_MANAGER(object);
+    NMModemManager        *self = NM_MODEM_MANAGER(object);
     NMModemManagerPrivate *priv = NM_MODEM_MANAGER_GET_PRIVATE(self);
 
     nm_clear_g_cancellable(&priv->main_cancellable);
diff --git a/src/core/devices/wwan/nm-modem-ofono.c b/src/core/devices/wwan/nm-modem-ofono.c
index 78ad7b68..cac90d32 100644
--- a/src/core/devices/wwan/nm-modem-ofono.c
+++ b/src/core/devices/wwan/nm-modem-ofono.c
@@ -11,7 +11,7 @@
 #include "devices/nm-device-private.h"
 #include "nm-modem.h"
 #include "libnm-platform/nm-platform.h"
-#include "nm-ip4-config.h"
+#include "nm-l3-config-data.h"
 
 #define VARIANT_IS_OF_TYPE_BOOLEAN(v) \
     ((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_BOOLEAN)))
@@ -47,7 +47,7 @@ typedef struct {
     gboolean modem_online;
     gboolean gprs_attached;
 
-    NMIP4Config *ip4_config;
+    NML3ConfigData *l3cd_4;
 } NMModemOfonoPrivate;
 
 struct _NMModemOfono {
@@ -75,7 +75,7 @@ G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
         if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) {                  \
             NMModemOfono *const __self = (self);                            \
             char                __prefix_name[128];                         \
-            const char *        __uid;                                      \
+            const char         *__uid;                                      \
                                                                             \
             _nm_log(_level,                                                 \
                     (_NMLOG_DOMAIN),                                        \
@@ -97,7 +97,7 @@ G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
 /*****************************************************************************/
 
 static void
-get_capabilities(NMModem *                  _self,
+get_capabilities(NMModem                   *_self,
                  NMDeviceModemCapabilities *modem_caps,
                  NMDeviceModemCapabilities *current_caps)
 {
@@ -112,7 +112,7 @@ 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;
+    const char          *reason    = NULL;
 
     _LOGI("'Attached': %s 'Online': %s 'IMSI': %s",
           priv->gprs_attached ? "true" : "false",
@@ -137,10 +137,10 @@ update_modem_state(NMModemOfono *self)
 
 /* Disconnect */
 typedef struct {
-    NMModemOfono *             self;
+    NMModemOfono              *self;
     _NMModemDisconnectCallback callback;
     gpointer                   callback_user_data;
-    GCancellable *             cancellable;
+    GCancellable              *cancellable;
     gboolean                   warn;
 } DisconnectContext;
 
@@ -157,7 +157,7 @@ disconnect_context_complete(DisconnectContext *ctx, GError *error)
 static void
 disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable)
 {
-    DisconnectContext *ctx      = user_data;
+    DisconnectContext    *ctx   = user_data;
     gs_free_error GError *error = NULL;
 
     if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) {
@@ -172,10 +172,10 @@ disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellabl
 static void
 disconnect_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    DisconnectContext *ctx       = user_data;
-    NMModemOfono *     self      = ctx->self;
-    gs_free_error GError *error  = NULL;
-    gs_unref_variant GVariant *v = NULL;
+    DisconnectContext         *ctx   = user_data;
+    NMModemOfono              *self  = ctx->self;
+    gs_free_error GError      *error = NULL;
+    gs_unref_variant GVariant *v     = NULL;
 
     v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error);
     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
@@ -193,15 +193,15 @@ disconnect_done(GObject *source, GAsyncResult *result, gpointer user_data)
 }
 
 static void
-disconnect(NMModem *                  modem,
+disconnect(NMModem                   *modem,
            gboolean                   warn,
-           GCancellable *             cancellable,
+           GCancellable              *cancellable,
            _NMModemDisconnectCallback callback,
            gpointer                   user_data)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(modem);
+    NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    DisconnectContext *  ctx;
+    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));
@@ -235,12 +235,12 @@ disconnect(NMModem *                  modem,
 static void
 deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(modem);
+    NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
     /* TODO: cancel SimpleConnect() if any */
 
-    g_clear_object(&priv->ip4_config);
+    nm_clear_l3cd(&priv->l3cd_4);
 
     NM_MODEM_CLASS(nm_modem_ofono_parent_class)
         ->deactivate_cleanup(modem, device, stop_ppp_manager);
@@ -249,9 +249,9 @@ deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager)
 static gboolean
 check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection, GError **error)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(modem);
+    NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    const char *         id;
+    const char          *id;
 
     if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL)) {
         nm_utils_error_set(error,
@@ -290,7 +290,7 @@ check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection,
 static void
 handle_sim_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(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)) {
@@ -324,14 +324,14 @@ sim_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpoin
 static void
 sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
-    gs_free_error GError *error             = NULL;
+    NMModemOfono              *self;
+    NMModemOfonoPrivate       *priv;
+    gs_free_error GError      *error        = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
     gs_unref_variant GVariant *v            = NULL;
     GVariantIter               i;
-    const char *               property;
+    const char                *property;
 
     v_properties =
         _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
@@ -377,10 +377,10 @@ sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_dat
 static void
 _sim_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
+    NMModemOfono         *self;
+    NMModemOfonoPrivate  *priv;
     gs_free_error GError *error = NULL;
-    GDBusProxy *          proxy;
+    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))
@@ -451,7 +451,7 @@ handle_sim_iface(NMModemOfono *self, gboolean found)
 static void
 handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(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)) {
@@ -483,14 +483,14 @@ connman_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, g
 static void
 connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
-    gs_free_error GError *error             = NULL;
+    NMModemOfono              *self;
+    NMModemOfonoPrivate       *priv;
+    gs_free_error GError      *error        = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
     gs_unref_variant GVariant *v            = NULL;
     GVariantIter               i;
-    const char *               property;
+    const char                *property;
 
     v_properties =
         _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
@@ -528,10 +528,10 @@ connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user
 static void
 _connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
+    NMModemOfono         *self;
+    NMModemOfonoPrivate  *priv;
     gs_free_error GError *error = NULL;
-    GDBusProxy *          proxy;
+    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))
@@ -606,7 +606,7 @@ handle_connman_iface(NMModemOfono *self, gboolean found)
 static void
 handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(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)) {
@@ -655,14 +655,14 @@ modem_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpo
 static void
 modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
-    gs_free_error GError *error             = NULL;
+    NMModemOfono              *self;
+    NMModemOfonoPrivate       *priv;
+    gs_free_error GError      *error        = NULL;
     gs_unref_variant GVariant *v_properties = NULL;
     gs_unref_variant GVariant *v_dict       = NULL;
-    GVariant *                 v;
+    GVariant                  *v;
     GVariantIter               i;
-    const char *               property;
+    const char                *property;
 
     v_properties =
         _nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
@@ -704,10 +704,10 @@ modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_d
 static void
 stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
-    gs_free_error GError *error  = NULL;
-    gs_unref_variant GVariant *v = NULL;
+    NMModemOfono              *self;
+    NMModemOfonoPrivate       *priv;
+    gs_free_error GError      *error = NULL;
+    gs_unref_variant GVariant *v     = NULL;
 
     v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error);
     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -732,17 +732,17 @@ stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
 static void
 handle_settings(GVariant *v_dict, gpointer user_data)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(user_data);
+    NMModemOfono        *self = NM_MODEM_OFONO(user_data);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    NMPlatformIP4Address addr;
+    char                 sbuf[sizeof(_nm_utils_to_string_buffer)];
+    NMPlatformIP4Address address;
     gboolean             ret = FALSE;
-    const char *         interface;
-    const char *         s;
-    const char **        array, **iter;
+    const char          *interface;
+    const char          *s;
+    const char         **array;
     guint32              address_network, gateway_network;
-    guint32              ip4_route_table, ip4_route_metric;
     int                  ifindex;
-    GError *             error = NULL;
+    GError              *error = NULL;
 
     //_LOGD("PropertyChanged: %s", property);
 
@@ -773,12 +773,14 @@ handle_settings(GVariant *v_dict, gpointer user_data)
     }
 
     ifindex = nm_modem_get_ip_ifindex(NM_MODEM(self));
-    nm_assert(ifindex > 0);
+    g_return_if_fail(ifindex > 0);
 
-    /* TODO: verify handling of ip4_config; check other places it's used... */
-    g_clear_object(&priv->ip4_config);
+    /* TODO: verify handling of l3cd_4; check other places it's used... */
+    nm_clear_l3cd(&priv->l3cd_4);
 
-    priv->ip4_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), ifindex);
+    priv->l3cd_4 = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
+                                         ifindex,
+                                         NM_IP_CONFIG_SOURCE_WWAN);
 
     if (!g_variant_lookup(v_dict, "Address", "&s", &s)) {
         _LOGW("Settings 'Address' missing");
@@ -788,10 +790,12 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("can't convert 'Address' %s to addr", s ?: "");
         goto out;
     }
-    memset(&addr, 0, sizeof(addr));
-    addr.ifindex     = ifindex;
-    addr.address     = address_network;
-    addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
+
+    address = (NMPlatformIP4Address){
+        .ifindex     = ifindex,
+        .address     = address_network,
+        .addr_source = NM_IP_CONFIG_SOURCE_WWAN,
+    };
 
     if (!g_variant_lookup(v_dict, "Netmask", "&s", &s)) {
         _LOGW("Settings 'Netmask' missing");
@@ -801,10 +805,10 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("invalid 'Netmask': %s", s ?: "");
         goto out;
     }
-    addr.plen = nm_utils_ip4_netmask_to_prefix(address_network);
+    address.plen = nm_utils_ip4_netmask_to_prefix(address_network);
 
-    _LOGI("Address: %s", nm_platform_ip4_address_to_string(&addr, NULL, 0));
-    nm_ip4_config_add_address(priv->ip4_config, &addr);
+    _LOGI("Address: %s", nm_platform_ip4_address_to_string(&address, sbuf, sizeof(sbuf)));
+    nm_l3_config_data_add_address_4(priv->l3cd_4, &address);
 
     if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) {
         _LOGW("Settings 'Gateway' missing");
@@ -814,17 +818,18 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         _LOGW("invalid 'Gateway': %s", s);
         goto out;
     }
-    nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL);
     {
         const NMPlatformIP4Route r = {
             .rt_source     = NM_IP_CONFIG_SOURCE_WWAN,
             .gateway       = gateway_network,
-            .table_coerced = nm_platform_route_table_coerce(ip4_route_table),
-            .metric        = ip4_route_metric,
+            .table_any     = TRUE,
+            .table_coerced = 0,
+            .metric_any    = TRUE,
+            .metric        = 0,
         };
 
         _LOGI("Gateway: %s", s);
-        nm_ip4_config_add_route(priv->ip4_config, &r, NULL);
+        nm_l3_config_data_add_route_4(priv->l3cd_4, &r);
     }
 
     if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) {
@@ -832,52 +837,48 @@ handle_settings(GVariant *v_dict, gpointer user_data)
         goto out;
     }
     if (array) {
-        for (iter = array; *iter; iter++) {
-            if (nm_utils_parse_inaddr_bin(AF_INET, *iter, NULL, &address_network)
-                && address_network) {
-                _LOGI("DNS: %s", *iter);
-                nm_ip4_config_add_nameserver(priv->ip4_config, address_network);
-            } else {
-                _LOGW("invalid NameServer: %s", *iter);
+        gboolean any_good = FALSE;
+
+        for (; array[0]; array++) {
+            if (!nm_utils_parse_inaddr_bin(AF_INET, *array, NULL, &address_network)
+                || !address_network) {
+                _LOGW("invalid NameServer: %s", *array);
+                continue;
             }
+            any_good = TRUE;
+            _LOGI("DNS: %s", *array);
+            nm_l3_config_data_add_nameserver(priv->l3cd_4, AF_INET, &address_network);
         }
-
-        if (iter == array) {
+        if (!any_good) {
             _LOGW("Settings: 'DomainNameServers': none specified");
-            g_free(array);
             goto out;
         }
-        g_free(array);
     }
 
     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)) {
-            nm_modem_get_route_parameters(NM_MODEM(self),
-                                          &ip4_route_table,
-                                          &ip4_route_metric,
-                                          NULL,
-                                          NULL);
-
-            {
-                const NMPlatformIP4Route mms_route = {
-                    .network       = address_network,
-                    .plen          = 32,
-                    .gateway       = gateway_network,
-                    .table_coerced = nm_platform_route_table_coerce(ip4_route_table),
-                    .metric        = ip4_route_metric,
-                };
-
-                nm_ip4_config_add_route(priv->ip4_config, &mms_route, NULL);
-            }
-        } else {
+            const NMPlatformIP4Route mms_route = {
+                .network       = address_network,
+                .plen          = 32,
+                .gateway       = gateway_network,
+                .table_any     = TRUE,
+                .table_coerced = 0,
+                .metric_any    = TRUE,
+                .metric        = 0,
+            };
+
+            nm_l3_config_data_add_route_4(priv->l3cd_4, &mms_route);
+        } else
             _LOGW("invalid MessageProxy: %s", s);
-        }
     }
 
     ret = TRUE;
 
 out:
+    if (priv->l3cd_4)
+        nm_l3_config_data_seal(priv->l3cd_4);
+
     if (nm_modem_get_state(NM_MODEM(self)) != NM_MODEM_STATE_CONNECTED) {
         _LOGI("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE");
         nm_modem_emit_prepare_result(NM_MODEM(self),
@@ -893,7 +894,7 @@ out:
 static void
 context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
 {
-    NMModemOfono *   self             = NM_MODEM_OFONO(user_data);
+    NMModemOfono              *self   = NM_MODEM_OFONO(user_data);
     gs_unref_variant GVariant *v_dict = NULL;
 
     _LOGD("PropertyChanged: %s", property);
@@ -912,38 +913,43 @@ context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, g
     handle_settings(v_dict, user_data);
 }
 
-static NMActStageReturn
-static_stage3_ip4_config_start(NMModem *            modem,
-                               NMActRequest *       req,
-                               NMDeviceStateReason *out_failure_reason)
+static void
+stage3_ip_config_start(NMModem *modem, int addr_family, NMModemIPMethod ip_method)
 {
-    NMModemOfono *       self  = NM_MODEM_OFONO(modem);
-    NMModemOfonoPrivate *priv  = NM_MODEM_OFONO_GET_PRIVATE(self);
-    GError *             error = NULL;
+    NMModemOfono         *self  = NM_MODEM_OFONO(modem);
+    NMModemOfonoPrivate  *priv  = NM_MODEM_OFONO_GET_PRIVATE(self);
+    gs_free_error GError *error = NULL;
 
-    if (!priv->ip4_config) {
-        _LOGD("IP4 config not ready(?)");
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    _LOGD("IP4 config is done; setting modem_state -> CONNECTED");
+
+    if (!NM_IS_IPv4(addr_family) || ip_method == NM_MODEM_IP_METHOD_AUTO) {
+        nm_modem_emit_signal_new_config_success(modem, addr_family, NULL, TRUE, NULL);
+        goto out;
     }
 
-    _LOGD("IP4 config is done; setting modem_state -> CONNECTED");
-    g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error);
+    if (!priv->l3cd_4) {
+        nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "IP config not received");
+        nm_modem_emit_signal_new_config_failure(modem,
+                                                addr_family,
+                                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE,
+                                                error);
+        goto out;
+    }
 
-    /* Signal listener takes ownership of the IP4Config */
-    priv->ip4_config = NULL;
+    nm_modem_emit_signal_new_config_success(modem, addr_family, priv->l3cd_4, FALSE, NULL);
 
+out:
     nm_modem_set_state(NM_MODEM(self),
                        NM_MODEM_STATE_CONNECTED,
                        nm_modem_state_to_string(NM_MODEM_STATE_CONNECTED));
-    return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 static void
 context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
-    gs_free_error GError *error           = NULL;
+    NMModemOfono              *self;
+    NMModemOfonoPrivate       *priv;
+    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;
@@ -1007,10 +1013,10 @@ error:
 static void
 context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
+    NMModemOfono         *self;
+    NMModemOfonoPrivate  *priv;
     gs_free_error GError *error = NULL;
-    GDBusProxy *          proxy;
+    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))
@@ -1040,7 +1046,7 @@ context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
      * 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);
+    nm_clear_l3cd(&priv->l3cd_4);
 
     /* We need to directly query ConnectionContextinteface to get the current
      * property values */
@@ -1081,8 +1087,8 @@ static GHashTable *
 create_connect_properties(NMConnection *connection)
 {
     NMSettingGsm *setting;
-    GHashTable *  properties;
-    const char *  str;
+    GHashTable   *properties;
+    const char   *str;
 
     setting    = nm_connection_get_setting_gsm(connection);
     properties = g_hash_table_new(nm_str_hash, g_str_equal);
@@ -1103,14 +1109,14 @@ create_connect_properties(NMConnection *connection)
 }
 
 static NMActStageReturn
-modem_act_stage1_prepare(NMModem *            modem,
-                         NMConnection *       connection,
+modem_act_stage1_prepare(NMModem             *modem,
+                         NMConnection        *connection,
                          NMDeviceStateReason *out_failure_reason)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(modem);
+    NMModemOfono        *self = NM_MODEM_OFONO(modem);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
-    const char *         context_id;
-    char **              id = NULL;
+    const char          *context_id;
+    char               **id = NULL;
 
     context_id = nm_connection_get_id(connection);
     id         = g_strsplit(context_id, "/", 0);
@@ -1149,10 +1155,10 @@ modem_act_stage1_prepare(NMModem *            modem,
 static void
 modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    NMModemOfono *       self;
-    NMModemOfonoPrivate *priv;
+    NMModemOfono         *self;
+    NMModemOfonoPrivate  *priv;
     gs_free_error GError *error = NULL;
-    GDBusProxy *          proxy;
+    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))
@@ -1194,7 +1200,7 @@ nm_modem_ofono_init(NMModemOfono *self)
 static void
 constructed(GObject *object)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(object);
+    NMModemOfono        *self = NM_MODEM_OFONO(object);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
     priv->modem_proxy_cancellable = g_cancellable_new();
@@ -1245,7 +1251,7 @@ nm_modem_ofono_new(const char *path)
 static void
 dispose(GObject *object)
 {
-    NMModemOfono *       self = NM_MODEM_OFONO(object);
+    NMModemOfono        *self = NM_MODEM_OFONO(object);
     NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
 
     nm_clear_g_cancellable(&priv->modem_proxy_cancellable);
@@ -1258,7 +1264,7 @@ dispose(GObject *object)
         priv->connect_properties = NULL;
     }
 
-    g_clear_object(&priv->ip4_config);
+    nm_clear_l3cd(&priv->l3cd_4);
 
     if (priv->modem_proxy) {
         g_signal_handlers_disconnect_by_data(priv->modem_proxy, self);
@@ -1300,6 +1306,6 @@ nm_modem_ofono_class_init(NMModemOfonoClass *klass)
     modem_class->deactivate_cleanup                     = deactivate_cleanup;
     modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
 
-    modem_class->modem_act_stage1_prepare       = modem_act_stage1_prepare;
-    modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
+    modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare;
+    modem_class->stage3_ip_config_start   = stage3_ip_config_start;
 }
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index ccea69db..a5ee8ce8 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -13,16 +13,16 @@
 #include <linux/if.h>
 #include <linux/rtnetlink.h>
 
-#include "libnm-core-intern/nm-core-internal.h"
-#include "libnm-platform/nm-platform.h"
-#include "nm-setting-connection.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "nm-netns.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-platform/nm-platform.h"
 #include "nm-act-request.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
+#include "nm-l3-config-data.h"
+#include "nm-netns.h"
+#include "nm-setting-connection.h"
 #include "ppp/nm-ppp-manager-call.h"
+#include "ppp/nm-ppp-mgr.h"
 #include "ppp/nm-ppp-status.h"
 
 /*****************************************************************************/
@@ -45,8 +45,7 @@ enum {
     PPP_STATS,
     PPP_FAILED,
     PREPARE_RESULT,
-    IP4_CONFIG_RESULT,
-    IP6_CONFIG_RESULT,
+    NEW_CONFIG,
     AUTH_REQUESTED,
     AUTH_RESULT,
     REMOVED,
@@ -56,6 +55,11 @@ enum {
 
 static guint signals[LAST_SIGNAL] = {0};
 
+typedef struct {
+    GSource *stage3_on_idle_source;
+    bool     stage3_started : 1;
+} IPData;
+
 typedef struct _NMModemPrivate {
     char *uid;
     char *path;
@@ -63,41 +67,38 @@ typedef struct _NMModemPrivate {
     char *control_port;
     char *data_port;
 
-    /* TODO: ip_iface is solely used for nm_modem_owns_port().
-     * We should rework the code that it's not necessary */
-    char *ip_iface;
-
-    int                ip_ifindex;
-    NMModemIPMethod    ip4_method;
-    NMModemIPMethod    ip6_method;
-    NMUtilsIPv6IfaceId iid;
-    NMModemState       state;
-    NMModemState       prev_state; /* revert to this state if enable/disable fails */
-    char *             device_id;
-    char *             sim_id;
-    NMModemIPType      ip_types;
-    char *             sim_operator_id;
-    char *             operator_code;
-    char *             apn;
+    int             ip_ifindex;
+    NMModemIPMethod ip4_method;
+    NMModemIPMethod ip6_method;
+    NMModemState    state;
+    NMModemState    prev_state; /* revert to this state if enable/disable fails */
+    char           *device_id;
+    char           *sim_id;
+    NMModemIPType   ip_types;
+    char           *sim_operator_id;
+    char           *operator_code;
+    char           *apn;
 
     NMPPPManager *ppp_manager;
+    NMPppMgr     *ppp_mgr;
 
-    NMActRequest *                act_request;
+    NMActRequest                 *act_req;
+    NMDevice                     *device;
     guint32                       secrets_tries;
     NMActRequestGetSecretsCallId *secrets_id;
 
     guint mm_ip_timeout;
 
-    guint32 ip4_route_table;
-    guint32 ip4_route_metric;
-    guint32 ip6_route_table;
-    guint32 ip6_route_metric;
+    bool claimed : 1;
 
-    /* PPP stats */
-    guint32 in_bytes;
-    guint32 out_bytes;
+    union {
+        struct {
+            IPData ip_data_6;
+            IPData ip_data_4;
+        };
+        IPData ip_data_x[2];
+    };
 
-    bool claimed : 1;
 } NMModemPrivate;
 
 G_DEFINE_TYPE(NMModem, nm_modem, G_TYPE_OBJECT)
@@ -149,7 +150,7 @@ _nmlog_prefix(char *prefix, NMModem *self)
 
 /*****************************************************************************/
 
-static void _set_ip_ifindex(NMModem *self, int ifindex, const char *ifname);
+static void _set_ip_ifindex(NMModem *self, int ifindex);
 
 /*****************************************************************************/
 /* State/enabled/connected */
@@ -180,6 +181,91 @@ nm_modem_state_to_string(NMModemState state)
 
 /*****************************************************************************/
 
+static NMPlatform *
+_get_platform(NMModem *self)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (!priv->device)
+        return NULL;
+
+    return nm_device_get_platform(priv->device);
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_emit_signal_new_config(NMModem                  *self,
+                                int                       addr_family,
+                                const NML3ConfigData     *l3cd,
+                                gboolean                  do_auto,
+                                const NMUtilsIPv6IfaceId *iid,
+                                NMDeviceStateReason       failure_reason,
+                                GError                   *error)
+{
+    nm_assert(NM_IS_MODEM(self));
+    nm_assert_addr_family(addr_family);
+    nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
+    nm_assert(!do_auto || addr_family == AF_INET6);
+    nm_assert(!iid || addr_family == AF_INET6);
+    nm_assert(!error || (!l3cd && !do_auto && !iid));
+
+    if (error) {
+        _LOGD("signal: new-config: IPv%c, failed '%s', %s",
+              nm_utils_addr_family_to_char(addr_family),
+              nm_device_state_reason_to_string_a(failure_reason),
+              error->message);
+    } else {
+        gs_free char *str_to_free = NULL;
+
+        _LOGD(
+            "signal: new-config: IPv%c%s%s%s%s",
+            nm_utils_addr_family_to_char(addr_family),
+            l3cd ? ", has-l3cd" : "",
+            do_auto ? ", do-auto" : "",
+            NM_PRINT_FMT_QUOTED2(iid,
+                                 ", iid=",
+                                 nm_utils_bin2hexstr_a(iid, sizeof(*iid), ':', FALSE, &str_to_free),
+                                 ""));
+    }
+
+    g_signal_emit(self,
+                  signals[NEW_CONFIG],
+                  0,
+                  addr_family,
+                  nm_l3_config_data_seal(l3cd),
+                  do_auto,
+                  iid,
+                  (int) failure_reason,
+                  error);
+}
+
+void
+nm_modem_emit_signal_new_config_success(NMModem                  *self,
+                                        int                       addr_family,
+                                        const NML3ConfigData     *l3cd,
+                                        gboolean                  do_auto,
+                                        const NMUtilsIPv6IfaceId *iid)
+{
+    nm_modem_emit_signal_new_config(self,
+                                    addr_family,
+                                    l3cd,
+                                    do_auto,
+                                    iid,
+                                    NM_DEVICE_STATE_REASON_NONE,
+                                    NULL);
+}
+
+void
+nm_modem_emit_signal_new_config_failure(NMModem            *self,
+                                        int                 addr_family,
+                                        NMDeviceStateReason failure_reason,
+                                        GError             *error)
+{
+    nm_assert(error);
+    nm_modem_emit_signal_new_config(self, addr_family, NULL, FALSE, NULL, failure_reason, error);
+}
+
 gboolean
 nm_modem_is_claimed(NMModem *self)
 {
@@ -240,10 +326,10 @@ nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason)
     priv->prev_state = NM_MODEM_STATE_UNKNOWN;
 
     if (new_state != old_state) {
-        _LOGI("modem state changed, '%s' --> '%s' (reason: %s)",
+        _LOGD("signal: modem state changed, '%s' --> '%s' (reason: %s%s%s)",
               nm_modem_state_to_string(old_state),
               nm_modem_state_to_string(new_state),
-              reason ?: "none");
+              NM_PRINT_FMT_QUOTE_STRING(reason));
 
         priv->state = new_state;
         _notify(self, PROP_STATE);
@@ -285,7 +371,7 @@ nm_modem_set_mm_enabled(NMModem *self, gboolean enabled)
 
         /* Try to unlock the modem if it's being enabled */
         if (enabled)
-            g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+            nm_modem_emit_auth_requested(self);
         return;
     }
 
@@ -303,14 +389,25 @@ nm_modem_set_mm_enabled(NMModem *self, gboolean enabled)
 void
 nm_modem_emit_removed(NMModem *self)
 {
+    _LOGD("signal: removed");
     g_signal_emit(self, signals[REMOVED], 0);
 }
 
 void
+nm_modem_emit_auth_requested(NMModem *self)
+{
+    _LOGD("signal: auth-requested");
+    g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+}
+
+void
 nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason)
 {
     nm_assert(NM_IS_MODEM(self));
 
+    _LOGD("signal: prepare-result: %s (%s)",
+          success ? "success" : "failure",
+          nm_device_state_reason_to_string_a(reason));
     g_signal_emit(self, signals[PREPARE_RESULT], 0, success, (guint) reason);
 }
 
@@ -319,6 +416,7 @@ nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason)
 {
     nm_assert(NM_IS_MODEM(self));
 
+    _LOGD("signal: ppp-failed (%s)", nm_device_state_reason_to_string_a(reason));
     g_signal_emit(self, signals[PPP_FAILED], 0, (guint) reason);
 }
 
@@ -363,16 +461,16 @@ build_single_ip_type_array(NMModemIPType type)
 GArray *
 nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error)
 {
-    NMModemPrivate *   priv = NM_MODEM_GET_PRIVATE(self);
+    NMModemPrivate    *priv = NM_MODEM_GET_PRIVATE(self);
     NMSettingIPConfig *s_ip4, *s_ip6;
-    const char *       method;
+    const char        *method;
     gboolean           ip4 = TRUE, ip6 = TRUE;
     gboolean           ip4_may_fail = TRUE, ip6_may_fail = TRUE;
 
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
     if (s_ip4) {
         method = nm_setting_ip_config_get_method(s_ip4);
-        if (g_strcmp0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+        if (nm_streq0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
             ip4 = FALSE;
         ip4_may_fail = nm_setting_ip_config_get_may_fail(s_ip4);
     }
@@ -413,7 +511,7 @@ nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError
 
     if (ip4 && ip6) {
         NMModemIPType type;
-        GArray *      out;
+        GArray       *out;
 
         out = g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 3);
 
@@ -487,83 +585,101 @@ nm_modem_get_apn(NMModem *self)
 }
 
 /*****************************************************************************/
-/* IP method PPP */
 
 static void
-ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+_ppp_mgr_cleanup(NMModem *self)
 {
-    switch (status) {
-    case NM_PPP_STATUS_DISCONNECT:
-        nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
-        break;
-    case NM_PPP_STATUS_DEAD:
-        nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_FAILED);
-        break;
-    default:
-        break;
-    }
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    nm_clear_pointer(&priv->ppp_mgr, nm_ppp_mgr_destroy);
 }
 
 static void
-ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data)
+_ppp_maybe_emit_new_config(NMModem *self, int addr_family)
 {
-    NMModem *self = NM_MODEM(user_data);
+    NMModemPrivate       *priv    = NM_MODEM_GET_PRIVATE(self);
+    const int             IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMPppMgrIPData *ip_data;
+    gboolean              do_auto;
 
-    nm_assert(ifindex >= 0);
-    nm_assert(NM_MODEM_GET_PRIVATE(self)->ppp_manager == ppp_manager);
+    ip_data = nm_ppp_mgr_get_ip_data(priv->ppp_mgr, addr_family);
 
-    if (ifindex <= 0 && iface) {
-        /* this might happen, if the ifname was already deleted
-         * and we failed to resolve ifindex.
-         *
-         * Forget about the name. */
-        iface = NULL;
+    if (!ip_data->ip_received)
+        return;
+
+    if (IS_IPv4)
+        do_auto = FALSE;
+    else {
+        do_auto = !ip_data->l3cd
+                  || (!nm_l3_config_data_get_first_obj(ip_data->l3cd,
+                                                       NMP_OBJECT_TYPE_IP6_ADDRESS,
+                                                       nmp_object_ip6_address_is_not_link_local));
     }
-    _set_ip_ifindex(self, ifindex, iface);
-}
 
-static void
-ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
-{
-    NMModem *self = NM_MODEM(user_data);
+    nm_assert(!IS_IPv4 || !ip_data->ipv6_iid);
 
-    g_signal_emit(self, signals[IP4_CONFIG_RESULT], 0, config, NULL);
+    nm_modem_emit_signal_new_config_success(self,
+                                            addr_family,
+                                            ip_data->l3cd,
+                                            do_auto,
+                                            ip_data->ipv6_iid);
 }
 
 static void
-ppp_ip6_config(NMPPPManager *            ppp_manager,
-               const NMUtilsIPv6IfaceId *iid,
-               NMIP6Config *             config,
-               gpointer                  user_data)
+_ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data, gpointer user_data)
 {
-    NMModem *self = NM_MODEM(user_data);
+    NMModem        *self = NM_MODEM(user_data);
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+    int             IS_IPv4;
 
-    NM_MODEM_GET_PRIVATE(self)->iid = *iid;
+    switch (callback_data->callback_type) {
+    case NM_PPP_MGR_CALLBACK_TYPE_STATE_CHANGED:
 
-    nm_modem_emit_ip6_config_result(self, config, NULL);
-}
+        if (callback_data->data.state >= _NM_PPP_MGR_STATE_FAILED_START) {
+            nm_modem_emit_ppp_failed(self, callback_data->data.reason);
+            return;
+        }
 
-static void
-ppp_stats(NMPPPManager *ppp_manager, guint i_in_bytes, guint i_out_bytes, gpointer user_data)
-{
-    NMModem *       self      = NM_MODEM(user_data);
-    NMModemPrivate *priv      = NM_MODEM_GET_PRIVATE(self);
-    guint32         in_bytes  = i_in_bytes;
-    guint32         out_bytes = i_out_bytes;
+        if (callback_data->data.state >= NM_PPP_MGR_STATE_HAVE_IFINDEX)
+            _set_ip_ifindex(self, callback_data->data.ifindex);
+
+        if (callback_data->data.state >= NM_PPP_MGR_STATE_HAVE_IP_CONFIG) {
+            for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+                if (!priv->ip_data_x[IS_IPv4].stage3_started) {
+                    /* stage3 didn't yet start. We don't emit the IP signal yet.
+                     * We will emit it together with stage3. */
+                    continue;
+                }
+                if (priv->ip_data_x[IS_IPv4].stage3_on_idle_source) {
+                    /* We scheduled already a handler. Let it handle the new configuration. */
+                    continue;
+                }
+                if (callback_data->data.ip_changed_x[IS_IPv4])
+                    _ppp_maybe_emit_new_config(self, IS_IPv4 ? AF_INET : AF_INET6);
+            }
+        }
+        return;
 
-    if (priv->in_bytes != in_bytes || priv->out_bytes != out_bytes) {
-        priv->in_bytes  = in_bytes;
-        priv->out_bytes = out_bytes;
-        g_signal_emit(self, signals[PPP_STATS], 0, (guint) in_bytes, (guint) out_bytes);
+    case NM_PPP_MGR_CALLBACK_TYPE_STATS_CHANGED:
+        g_signal_emit(self,
+                      signals[PPP_STATS],
+                      0,
+                      (guint) callback_data->data.stats_data->in_bytes,
+                      (guint) callback_data->data.stats_data->out_bytes);
+        return;
     }
+
+    nm_assert_not_reached();
 }
 
+/*****************************************************************************/
+
 static gboolean
 port_speed_is_zero(const char *port)
 {
     struct termios    options;
     nm_auto_close int fd   = -1;
-    gs_free char *    path = NULL;
+    gs_free char     *path = NULL;
 
     nm_assert(port);
 
@@ -585,291 +701,116 @@ port_speed_is_zero(const char *port)
     return cfgetospeed(&options) == B0;
 }
 
-static NMActStageReturn
-ppp_stage3_ip_config_start(NMModem *            self,
-                           NMActRequest *       req,
-                           NMDeviceStateReason *out_failure_reason)
-{
-    NMModemPrivate *priv          = NM_MODEM_GET_PRIVATE(self);
-    const char *    ppp_name      = NULL;
-    GError *        error         = NULL;
-    guint           ip_timeout    = 30;
-    guint           baud_override = 0;
-
-    g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE);
-    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE);
-
-    /* If we're already running PPP don't restart it; for example, if both
-     * IPv4 and IPv6 are requested, IPv4 gets started first, but we use the
-     * same pppd for both v4 and v6.
-     */
-    if (priv->ppp_manager)
-        return NM_ACT_STAGE_RETURN_POSTPONE;
-
-    if (NM_MODEM_GET_CLASS(self)->get_user_pass) {
-        NMConnection *connection = nm_act_request_get_applied_connection(req);
-
-        g_assert(connection);
-        if (!NM_MODEM_GET_CLASS(self)->get_user_pass(self, connection, &ppp_name, NULL))
-            return NM_ACT_STAGE_RETURN_FAILURE;
-    }
-
-    if (!priv->data_port) {
-        _LOGE("error starting PPP (no data port)");
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
-
-    /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
-     * use the default one (30s) */
-    if (priv->mm_ip_timeout > 0) {
-        _LOGI("using modem-specified IP timeout: %u seconds", priv->mm_ip_timeout);
-        ip_timeout = priv->mm_ip_timeout;
-    }
-
-    /* Some tty drivers and modems ignore port speed, but pppd requires the
-     * port speed to be > 0 or it exits. If the port speed is 0 pass an
-     * explicit speed to pppd to prevent the exit.
-     * https://bugzilla.redhat.com/show_bug.cgi?id=1281731
-     */
-    if (port_speed_is_zero(priv->data_port))
-        baud_override = 57600;
-
-    priv->ppp_manager = nm_ppp_manager_create(priv->data_port, &error);
-
-    if (priv->ppp_manager) {
-        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
-                                            priv->ip4_route_table,
-                                            priv->ip4_route_metric,
-                                            priv->ip6_route_table,
-                                            priv->ip6_route_metric);
-    }
-
-    if (!priv->ppp_manager
-        || !nm_ppp_manager_start(priv->ppp_manager,
-                                 req,
-                                 ppp_name,
-                                 ip_timeout,
-                                 baud_override,
-                                 &error)) {
-        _LOGE("error starting PPP: %s", error->message);
-        g_error_free(error);
-        g_clear_object(&priv->ppp_manager);
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
-
-    g_signal_connect(priv->ppp_manager,
-                     NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
-                     G_CALLBACK(ppp_state_changed),
-                     self);
-    g_signal_connect(priv->ppp_manager,
-                     NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
-                     G_CALLBACK(ppp_ifindex_set),
-                     self);
-    g_signal_connect(priv->ppp_manager,
-                     NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
-                     G_CALLBACK(ppp_ip4_config),
-                     self);
-    g_signal_connect(priv->ppp_manager,
-                     NM_PPP_MANAGER_SIGNAL_IP6_CONFIG,
-                     G_CALLBACK(ppp_ip6_config),
-                     self);
-    g_signal_connect(priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATS, G_CALLBACK(ppp_stats), self);
-
-    return NM_ACT_STAGE_RETURN_POSTPONE;
-}
-
 /*****************************************************************************/
 
-NMActStageReturn
-nm_modem_stage3_ip4_config_start(NMModem *            self,
-                                 NMDevice *           device,
-                                 gboolean *           out_autoip4,
-                                 NMDeviceStateReason *out_failure_reason)
+static gboolean
+_stage3_ip_config_start_on_idle(NMModem *self, int addr_family)
 {
-    NMModemPrivate * priv;
-    NMActRequest *   req;
-    NMConnection *   connection;
-    const char *     method;
-    NMActStageReturn ret;
-
-    _LOGD("ip4_config_start");
+    const int             IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMModemPrivate       *priv    = NM_MODEM_GET_PRIVATE(self);
+    NMModemIPMethod       ip_method;
+    NMConnection         *connection;
+    const char           *method;
+    gs_free_error GError *error = NULL;
+    NMDeviceStateReason   failure_reason;
 
-    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);
-    nm_assert(out_autoip4 && !*out_autoip4);
-
-    req = nm_device_get_act_request(device);
-    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
-
-    connection = nm_act_request_get_applied_connection(req);
-    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+    nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].stage3_on_idle_source);
 
-    nm_modem_set_route_parameters_from_device(self, device);
+    connection = nm_act_request_get_applied_connection(priv->act_req);
+    g_return_val_if_fail(connection, G_SOURCE_CONTINUE);
 
-    method = nm_utils_get_ip_config_method(connection, AF_INET);
+    method = nm_utils_get_ip_config_method(connection, addr_family);
 
-    /* Only Disabled and Auto methods make sense for WWAN */
-    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-        return NM_ACT_STAGE_RETURN_SUCCESS;
+    if (IS_IPv4 ? NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
+                : NM_IN_STRSET(method,
+                               NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                               NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+        nm_modem_emit_signal_new_config_success(self, addr_family, NULL, FALSE, NULL);
+        return G_SOURCE_CONTINUE;
+    }
 
-    if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-        _LOGE("unhandled WWAN IPv4 method '%s'; will fail", method);
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (!nm_streq(method,
+                  IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_AUTO
+                          : NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+        failure_reason = NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED;
+        nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "ip method unsupported by modem");
+        goto out_failure;
     }
 
-    priv = NM_MODEM_GET_PRIVATE(self);
-    switch (priv->ip4_method) {
+    ip_method = IS_IPv4 ? priv->ip4_method : priv->ip6_method;
+
+    switch (ip_method) {
     case NM_MODEM_IP_METHOD_PPP:
-        ret = ppp_stage3_ip_config_start(self, req, out_failure_reason);
-        break;
+        _ppp_maybe_emit_new_config(self, addr_family);
+        return G_SOURCE_CONTINUE;
     case NM_MODEM_IP_METHOD_STATIC:
-        _LOGD("MODEM_IP_METHOD_STATIC");
-        ret =
-            NM_MODEM_GET_CLASS(self)->static_stage3_ip4_config_start(self, req, out_failure_reason);
-        break;
     case NM_MODEM_IP_METHOD_AUTO:
-        _LOGD("MODEM_IP_METHOD_AUTO");
-        *out_autoip4 = TRUE;
-        ret          = NM_ACT_STAGE_RETURN_SUCCESS;
-        break;
+        NM_MODEM_GET_CLASS(self)->stage3_ip_config_start(self, addr_family, ip_method);
+        return G_SOURCE_CONTINUE;
     default:
-        _LOGI("IPv4 configuration disabled");
-        ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-        break;
+        failure_reason = NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED;
+        nm_utils_error_set(&error, NM_UTILS_ERROR_UNKNOWN, "modem IP method unsupported");
+        goto out_failure;
     }
 
-    return ret;
-}
-
-void
-nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config)
-{
-    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(modem);
+    nm_assert_not_reached();
 
-    /* If the modem has an ethernet-type data interface (ie, not PPP and thus
-     * not point-to-point) and IP config has a /32 prefix, then we assume that
-     * ARP will be pointless and we turn it off.
-     */
-    if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC
-        || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) {
-        const NMPlatformIP4Address *address = nm_ip4_config_get_first_address(config);
-
-        g_assert(address);
-        if (address->plen == 32)
-            nm_platform_link_change_flags(nm_device_get_platform(device),
-                                          nm_device_get_ip_ifindex(device),
-                                          IFF_NOARP,
-                                          TRUE);
-    }
+out_failure:
+    nm_modem_emit_signal_new_config_failure(self, addr_family, failure_reason, error);
+    return G_SOURCE_CONTINUE;
 }
 
-/*****************************************************************************/
-
-void
-nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error)
+static gboolean
+_stage3_ip_config_start_on_idle_4(gpointer user_data)
 {
-    NMModemPrivate *            priv = NM_MODEM_GET_PRIVATE(self);
-    NMDedupMultiIter            ipconf_iter;
-    const NMPlatformIP6Address *addr;
-    gboolean                    do_slaac = TRUE;
-
-    if (error) {
-        g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, NULL, FALSE, error);
-        return;
-    }
-
-    if (config) {
-        /* If the IPv6 configuration only included a Link-Local address, then
-         * we have to run SLAAC to get the full IPv6 configuration.
-         */
-        nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) {
-            if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) {
-                if (!priv->iid.id)
-                    priv->iid.id = ((guint64 *) (&addr->address.s6_addr))[1];
-            } else
-                do_slaac = FALSE;
-        }
-    }
-    g_assert(config || do_slaac);
-
-    g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, config, do_slaac, NULL);
+    return _stage3_ip_config_start_on_idle(user_data, AF_INET);
 }
 
-static NMActStageReturn
-stage3_ip6_config_request(NMModem *self, NMDeviceStateReason *out_failure_reason)
+static gboolean
+_stage3_ip_config_start_on_idle_6(gpointer user_data)
 {
-    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-    return NM_ACT_STAGE_RETURN_FAILURE;
+    return _stage3_ip_config_start_on_idle(user_data, AF_INET6);
 }
 
-NMActStageReturn
-nm_modem_stage3_ip6_config_start(NMModem *            self,
-                                 NMDevice *           device,
-                                 NMDeviceStateReason *out_failure_reason)
+gboolean
+nm_modem_stage3_ip_config_start(NMModem *self, int addr_family, NMDevice *device)
 {
-    NMModemPrivate * priv;
-    NMActRequest *   req;
-    NMActStageReturn ret;
-    NMConnection *   connection;
-    const char *     method;
-
-    g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE);
-
-    req = nm_device_get_act_request(device);
-    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
-
-    connection = nm_act_request_get_applied_connection(req);
-    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+    const int       IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMModemPrivate *priv;
 
-    nm_modem_set_route_parameters_from_device(self, device);
+    g_return_val_if_fail(NM_IS_MODEM(self), FALSE);
+    g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
 
-    method = nm_utils_get_ip_config_method(connection, AF_INET6);
+    priv = NM_MODEM_GET_PRIVATE(self);
 
-    /* Only Ignore, Disabled and Auto methods make sense for WWAN */
-    if (NM_IN_STRSET(method,
-                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
-                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
-        return NM_ACT_STAGE_RETURN_IP_DONE;
+    g_return_val_if_fail(priv->device == device, FALSE);
 
-    if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
-        _LOGW("unhandled WWAN IPv6 method '%s'; will fail", method);
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (priv->ip_data_x[IS_IPv4].stage3_started) {
+        /* we already started. Nothing to do. */
+        return FALSE;
     }
 
-    priv = NM_MODEM_GET_PRIVATE(self);
-    switch (priv->ip6_method) {
-    case NM_MODEM_IP_METHOD_PPP:
-        ret = ppp_stage3_ip_config_start(self, req, out_failure_reason);
-        break;
-    case NM_MODEM_IP_METHOD_STATIC:
-    case NM_MODEM_IP_METHOD_AUTO:
-        /* Both static and DHCP/Auto retrieve a base IP config from the modem
-         * which in the static case is the full config, and the DHCP/Auto case
-         * is just the IPv6LL address to use for SLAAC.
-         */
-        ret = NM_MODEM_GET_CLASS(self)->stage3_ip6_config_request(self, out_failure_reason);
-        break;
-    default:
-        _LOGI("IPv6 configuration disabled");
-        ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-        break;
-    }
+    nm_assert(!priv->ppp_mgr
+              || nm_ppp_mgr_get_state(priv->ppp_mgr) >= NM_PPP_MGR_STATE_HAVE_IFINDEX);
+
+    priv->ip_data_x[IS_IPv4].stage3_started = TRUE;
 
-    return ret;
+    priv->ip_data_x[IS_IPv4].stage3_on_idle_source = nm_g_idle_add_source(
+        IS_IPv4 ? _stage3_ip_config_start_on_idle_4 : _stage3_ip_config_start_on_idle_6,
+        self);
+    return TRUE;
 }
 
+/*****************************************************************************/
+
 guint32
 nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force)
 {
     NMConnection *connection;
-    NMSetting *   setting;
+    NMSetting    *setting;
     gint64        mtu_default;
     guint         mtu = 0;
-    const char *  property_name;
+    const char   *property_name;
 
     nm_assert(NM_IS_DEVICE(self));
     nm_assert(out_source);
@@ -910,17 +851,17 @@ cancel_get_secrets(NMModem *self)
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
     if (priv->secrets_id)
-        nm_act_request_cancel_secrets(priv->act_request, priv->secrets_id);
+        nm_act_request_cancel_secrets(priv->act_req, priv->secrets_id);
 }
 
 static void
-modem_secrets_cb(NMActRequest *                req,
+modem_secrets_cb(NMActRequest                 *req,
                  NMActRequestGetSecretsCallId *call_id,
-                 NMSettingsConnection *        connection,
-                 GError *                      error,
+                 NMSettingsConnection         *connection,
+                 GError                       *error,
                  gpointer                      user_data)
 {
-    NMModem *       self = NM_MODEM(user_data);
+    NMModem        *self = NM_MODEM(user_data);
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
     g_return_if_fail(call_id == priv->secrets_id);
@@ -934,23 +875,25 @@ modem_secrets_cb(NMActRequest *                req,
     if (error)
         _LOGW("modem-secrets: %s", error->message);
 
+    _LOGD("signal: auth-result: %s%s",
+          NM_PRINT_FMT_QUOTED2(error, "failed: ", error->message, "success"));
     g_signal_emit(self, signals[AUTH_RESULT], 0, error);
 }
 
 void
-nm_modem_get_secrets(NMModem *   self,
+nm_modem_get_secrets(NMModem    *self,
                      const char *setting_name,
                      gboolean    request_new,
                      const char *hint)
 {
-    NMModemPrivate *             priv  = NM_MODEM_GET_PRIVATE(self);
+    NMModemPrivate              *priv  = NM_MODEM_GET_PRIVATE(self);
     NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
     cancel_get_secrets(self);
 
     if (request_new)
         flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
-    priv->secrets_id = nm_act_request_get_secrets(priv->act_request,
+    priv->secrets_id = nm_act_request_get_secrets(priv->act_req,
                                                   FALSE,
                                                   setting_name,
                                                   flags,
@@ -958,14 +901,14 @@ nm_modem_get_secrets(NMModem *   self,
                                                   modem_secrets_cb,
                                                   self);
     g_return_if_fail(priv->secrets_id);
-    g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+    nm_modem_emit_auth_requested(self);
 }
 
 /*****************************************************************************/
 
 static NMActStageReturn
-modem_act_stage1_prepare(NMModem *            modem,
-                         NMConnection *       connection,
+modem_act_stage1_prepare(NMModem             *modem,
+                         NMConnection        *connection,
                          NMDeviceStateReason *out_failure_reason)
 {
     NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_UNKNOWN);
@@ -973,25 +916,28 @@ modem_act_stage1_prepare(NMModem *            modem,
 }
 
 NMActStageReturn
-nm_modem_act_stage1_prepare(NMModem *            self,
-                            NMActRequest *       req,
+nm_modem_act_stage1_prepare(NMModem             *self,
+                            NMActRequest        *req,
                             NMDeviceStateReason *out_failure_reason)
 {
-    NMModemPrivate *  priv                    = NM_MODEM_GET_PRIVATE(self);
+    NMModemPrivate              *priv         = NM_MODEM_GET_PRIVATE(self);
     gs_unref_ptrarray GPtrArray *hints        = NULL;
-    const char *                 setting_name = NULL;
+    const char                  *setting_name = NULL;
     NMSecretAgentGetSecretsFlags flags        = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
-    NMConnection *               connection;
+    NMConnection                *connection;
+    NMDevice                    *device;
 
     g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE);
 
-    if (priv->act_request)
-        g_object_unref(priv->act_request);
-    priv->act_request = g_object_ref(req);
+    nm_g_object_ref_set(&priv->act_req, req);
+    device = nm_active_connection_get_device(NM_ACTIVE_CONNECTION(priv->act_req));
+    g_return_val_if_fail(NM_IS_DEVICE(device), NM_ACT_STAGE_RETURN_FAILURE);
 
     connection = nm_act_request_get_applied_connection(req);
     g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
 
+    nm_g_object_ref_set(&priv->device, device);
+
     setting_name = nm_connection_need_secrets(connection, &hints);
     if (!setting_name) {
         nm_assert(!hints);
@@ -1015,24 +961,99 @@ nm_modem_act_stage1_prepare(NMModem *            self,
                                                   modem_secrets_cb,
                                                   self);
     g_return_val_if_fail(priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE);
-    g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+    nm_modem_emit_auth_requested(self);
     return NM_ACT_STAGE_RETURN_POSTPONE;
 }
 
 /*****************************************************************************/
 
-void
-nm_modem_act_stage2_config(NMModem *self)
+NMActStageReturn
+nm_modem_act_stage2_config(NMModem *self, NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
     NMModemPrivate *priv;
+    gboolean        needs_ppp;
 
-    g_return_if_fail(NM_IS_MODEM(self));
+    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);
 
     priv = NM_MODEM_GET_PRIVATE(self);
+
+    g_return_val_if_fail(priv->device == device, NM_ACT_STAGE_RETURN_FAILURE);
+
     /* Clear secrets tries counter since secrets were successfully used
      * already if we get here.
      */
     priv->secrets_tries = 0;
+
+    needs_ppp =
+        (priv->ip4_method == NM_MODEM_IP_METHOD_PPP || priv->ip6_method == NM_MODEM_IP_METHOD_PPP);
+
+    if (needs_ppp && !priv->ppp_mgr) {
+        const char           *ppp_name = NULL;
+        gs_free_error GError *error    = NULL;
+        guint                 ip_timeout;
+        guint                 baud_override;
+        NMActRequest         *req;
+
+        req = nm_device_get_act_request(device);
+        g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+        if (NM_MODEM_GET_CLASS(self)->get_user_pass) {
+            NMConnection *connection = nm_act_request_get_applied_connection(req);
+
+            g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+            if (!NM_MODEM_GET_CLASS(self)->get_user_pass(self, connection, &ppp_name, NULL))
+                return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        if (!priv->data_port) {
+            _LOGW("error starting PPP (no data port)");
+            NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
+         * use the default one (30s) */
+        if (priv->mm_ip_timeout > 0) {
+            _LOGI("using modem-specified IP timeout: %u seconds", priv->mm_ip_timeout);
+            ip_timeout = priv->mm_ip_timeout;
+        } else
+            ip_timeout = 30;
+
+        /* Some tty drivers and modems ignore port speed, but pppd requires the
+         * port speed to be > 0 or it exits. If the port speed is 0 pass an
+         * explicit speed to pppd to prevent the exit.
+         * https://bugzilla.redhat.com/show_bug.cgi?id=1281731
+         */
+        if (port_speed_is_zero(priv->data_port))
+            baud_override = 57600;
+        else
+            baud_override = 0;
+
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+                                             .netns         = nm_device_get_netns(device),
+                                             .parent_iface  = priv->data_port,
+                                             .callback      = _ppp_mgr_callback,
+                                             .user_data     = self,
+                                             .act_req       = req,
+                                             .ppp_username  = ppp_name,
+                                             .timeout_secs  = ip_timeout,
+                                             .baud_override = baud_override,
+                                         }),
+                                         &error);
+        if (!priv->ppp_mgr) {
+            _LOGW("PPP failed to start: %s", error->message);
+            *out_failure_reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED;
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
+
+    if (needs_ppp && nm_ppp_mgr_get_state(priv->ppp_mgr) < NM_PPP_MGR_STATE_HAVE_IFINDEX)
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+
+    return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 /*****************************************************************************/
@@ -1044,7 +1065,7 @@ nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GE
 
     if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_GSM_SETTING_NAME)) {
         NMSettingGsm *s_gsm;
-        const char *  str;
+        const char   *str;
 
         s_gsm = _nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error);
         if (!s_gsm)
@@ -1098,11 +1119,11 @@ nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GE
 /*****************************************************************************/
 
 gboolean
-nm_modem_complete_connection(NMModem *            self,
-                             const char *         iface,
-                             NMConnection *       connection,
+nm_modem_complete_connection(NMModem             *self,
+                             const char          *iface,
+                             NMConnection        *connection,
                              NMConnection *const *existing_connections,
-                             GError **            error)
+                             GError             **error)
 {
     NMModemClass *klass;
 
@@ -1126,27 +1147,26 @@ deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager)
 {
     NMModemPrivate *priv;
     int             ifindex;
+    int             IS_IPv4;
 
     g_return_if_fail(NM_IS_MODEM(self));
 
     priv = NM_MODEM_GET_PRIVATE(self);
 
+    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+        priv->ip_data_x[IS_IPv4].stage3_started = FALSE;
+        nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].stage3_on_idle_source);
+    }
+
     priv->secrets_tries = 0;
 
-    if (priv->act_request) {
+    if (priv->act_req) {
         cancel_get_secrets(self);
-        g_object_unref(priv->act_request);
-        priv->act_request = NULL;
+        g_clear_object(&priv->act_req);
     }
+    g_clear_object(&priv->device);
 
-    priv->in_bytes = priv->out_bytes = 0;
-
-    if (priv->ppp_manager) {
-        g_signal_handlers_disconnect_by_data(priv->ppp_manager, self);
-        if (stop_ppp_manager)
-            nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
-        g_clear_object(&priv->ppp_manager);
-    }
+    _ppp_mgr_cleanup(self);
 
     if (device) {
         g_return_if_fail(NM_IS_DEVICE(device));
@@ -1170,15 +1190,15 @@ deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager)
     priv->mm_ip_timeout = 0;
     priv->ip4_method    = NM_MODEM_IP_METHOD_UNKNOWN;
     priv->ip6_method    = NM_MODEM_IP_METHOD_UNKNOWN;
-    _set_ip_ifindex(self, -1, NULL);
+    _set_ip_ifindex(self, -1);
 }
 
 /*****************************************************************************/
 
 typedef struct {
-    NMModem *                 self;
-    NMDevice *                device;
-    GCancellable *            cancellable;
+    NMModem                  *self;
+    NMDevice                 *device;
+    GCancellable             *cancellable;
     NMModemDeactivateCallback callback;
     gpointer                  callback_user_data;
 } DeactivateContext;
@@ -1216,7 +1236,7 @@ _deactivate_call_disconnect(DeactivateContext *ctx)
 }
 
 static void
-_deactivate_ppp_manager_stop_cb(NMPPPManager *          ppp_manager,
+_deactivate_ppp_manager_stop_cb(NMPPPManager           *ppp_manager,
                                 NMPPPManagerStopHandle *handle,
                                 gboolean                was_cancelled,
                                 gpointer                user_data)
@@ -1239,15 +1259,15 @@ _deactivate_ppp_manager_stop_cb(NMPPPManager *          ppp_manager,
 }
 
 void
-nm_modem_deactivate_async(NMModem *                 self,
-                          NMDevice *                device,
-                          GCancellable *            cancellable,
+nm_modem_deactivate_async(NMModem                  *self,
+                          NMDevice                 *device,
+                          GCancellable             *cancellable,
                           NMModemDeactivateCallback callback,
                           gpointer                  user_data)
 {
-    NMModemPrivate *   priv = NM_MODEM_GET_PRIVATE(self);
+    NMModemPrivate    *priv = NM_MODEM_GET_PRIVATE(self);
     DeactivateContext *ctx;
-    NMPPPManager *     ppp_manager;
+    NMPPPManager      *ppp_manager;
 
     g_return_if_fail(NM_IS_MODEM(self));
     g_return_if_fail(NM_IS_DEVICE(device));
@@ -1307,11 +1327,11 @@ nm_modem_device_state_changed(NMModem *self, NMDeviceState new_state, NMDeviceSt
     case NM_DEVICE_STATE_UNAVAILABLE:
     case NM_DEVICE_STATE_FAILED:
     case NM_DEVICE_STATE_DISCONNECTED:
-        if (priv->act_request) {
+        if (priv->act_req) {
             cancel_get_secrets(self);
-            g_object_unref(priv->act_request);
-            priv->act_request = NULL;
+            g_clear_object(&priv->act_req);
         }
+        g_clear_object(&priv->device);
 
         if (was_connected) {
             /* Don't bother warning on FAILED since the modem is already gone */
@@ -1377,32 +1397,27 @@ nm_modem_get_ip_ifindex(NMModem *self)
 }
 
 static void
-_set_ip_ifindex(NMModem *self, int ifindex, const char *ifname)
+_set_ip_ifindex(NMModem *self, int ifindex)
 {
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
     nm_assert(ifindex >= -1);
-    nm_assert((ifindex > 0) == !!ifname);
-
-    if (!nm_streq0(priv->ip_iface, ifname)) {
-        g_free(priv->ip_iface);
-        priv->ip_iface = g_strdup(ifname);
-    }
 
     if (priv->ip_ifindex != ifindex) {
+        _LOGD("signal: ifindex changed: %d", ifindex);
         priv->ip_ifindex = ifindex;
         _notify(self, PROP_IP_IFINDEX);
     }
 }
 
 gboolean
-nm_modem_set_data_port(NMModem *       self,
-                       NMPlatform *    platform,
-                       const char *    data_port,
+nm_modem_set_data_port(NMModem        *self,
+                       NMPlatform     *platform,
+                       const char     *data_port,
                        NMModemIPMethod ip4_method,
                        NMModemIPMethod ip6_method,
                        guint           timeout,
-                       GError **       error)
+                       GError        **error)
 {
     NMModemPrivate *priv;
     gboolean        is_ppp;
@@ -1477,10 +1492,10 @@ nm_modem_set_data_port(NMModem *       self,
     priv->ip6_method    = ip6_method;
     if (is_ppp) {
         priv->data_port = g_strdup(data_port);
-        _set_ip_ifindex(self, -1, NULL);
+        _set_ip_ifindex(self, -1);
     } else {
         priv->data_port = NULL;
-        _set_ip_ifindex(self, ifindex, data_port);
+        _set_ip_ifindex(self, ifindex);
     }
     return TRUE;
 }
@@ -1488,96 +1503,34 @@ nm_modem_set_data_port(NMModem *       self,
 gboolean
 nm_modem_owns_port(NMModem *self, const char *iface)
 {
-    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+    NMModemPrivate       *priv = NM_MODEM_GET_PRIVATE(self);
+    NMPlatform           *platform;
+    const NMPlatformLink *plink;
 
     g_return_val_if_fail(iface != NULL, FALSE);
 
     if (NM_MODEM_GET_CLASS(self)->owns_port)
         return NM_MODEM_GET_CLASS(self)->owns_port(self, iface);
 
-    return NM_IN_STRSET(iface, priv->ip_iface, priv->data_port, priv->control_port);
-}
-
-gboolean
-nm_modem_get_iid(NMModem *self, NMUtilsIPv6IfaceId *out_iid)
-{
-    g_return_val_if_fail(NM_IS_MODEM(self), FALSE);
-
-    *out_iid = NM_MODEM_GET_PRIVATE(self)->iid;
-    return TRUE;
-}
-
-/*****************************************************************************/
-
-void
-nm_modem_get_route_parameters(NMModem *self,
-                              guint32 *out_ip4_route_table,
-                              guint32 *out_ip4_route_metric,
-                              guint32 *out_ip6_route_table,
-                              guint32 *out_ip6_route_metric)
-{
-    NMModemPrivate *priv;
-
-    g_return_if_fail(NM_IS_MODEM(self));
-
-    priv = NM_MODEM_GET_PRIVATE(self);
-    NM_SET_OUT(out_ip4_route_table, priv->ip4_route_table);
-    NM_SET_OUT(out_ip4_route_metric, priv->ip4_route_metric);
-    NM_SET_OUT(out_ip6_route_table, priv->ip6_route_table);
-    NM_SET_OUT(out_ip6_route_metric, priv->ip6_route_metric);
-}
-
-void
-nm_modem_set_route_parameters(NMModem *self,
-                              guint32  ip4_route_table,
-                              guint32  ip4_route_metric,
-                              guint32  ip6_route_table,
-                              guint32  ip6_route_metric)
-{
-    NMModemPrivate *priv;
-
-    g_return_if_fail(NM_IS_MODEM(self));
-
-    priv = NM_MODEM_GET_PRIVATE(self);
-    if (priv->ip4_route_table != ip4_route_table || priv->ip4_route_metric != ip4_route_metric
-        || priv->ip6_route_table != ip6_route_table || priv->ip6_route_metric != ip6_route_metric) {
-        priv->ip4_route_table  = ip4_route_table;
-        priv->ip4_route_metric = ip4_route_metric;
-        priv->ip6_route_table  = ip6_route_table;
-        priv->ip6_route_metric = ip6_route_metric;
-
-        _LOGT("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u",
-              priv->ip4_route_table,
-              priv->ip4_route_metric,
-              priv->ip6_route_table,
-              priv->ip6_route_metric);
-    }
-
-    if (priv->ppp_manager) {
-        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
-                                            priv->ip4_route_table,
-                                            priv->ip4_route_metric,
-                                            priv->ip6_route_table,
-                                            priv->ip6_route_metric);
-    }
-}
+    if (NM_IN_STRSET(iface, priv->data_port, priv->control_port))
+        return TRUE;
 
-void
-nm_modem_set_route_parameters_from_device(NMModem *self, NMDevice *device)
-{
-    g_return_if_fail(NM_IS_DEVICE(device));
+    /* FIXME(parent-child-relationship): the whole notion of "owns-port" is wrong.
+     * When we have a name (iface) it must be always clear what this name is (which
+     * domain). Mixing data_port, control_port and devlink names is wrong. Looking
+     * up devlinks by name is also wrong (use ifindex). */
+    if (priv->ip_ifindex > 0 && (platform = _get_platform(self))
+        && (plink = nm_platform_link_get(platform, priv->ip_ifindex))
+        && nm_streq(iface, plink->name))
+        return TRUE;
 
-    nm_modem_set_route_parameters(self,
-                                  nm_device_get_route_table(device, AF_INET),
-                                  nm_device_get_route_metric(device, AF_INET),
-                                  nm_device_get_route_table(device, AF_INET6),
-                                  nm_device_get_route_metric(device, AF_INET6));
+    return FALSE;
 }
 
 /*****************************************************************************/
 
 void
-nm_modem_get_capabilities(NMModem *                  self,
+nm_modem_get_capabilities(NMModem                   *self,
                           NMDeviceModemCapabilities *modem_caps,
                           NMDeviceModemCapabilities *current_caps)
 {
@@ -1593,9 +1546,10 @@ _nm_modem_set_operator_code(NMModem *self, const char *operator_code)
 {
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
-    if (g_strcmp0(priv->operator_code, operator_code) != 0) {
+    if (!nm_streq0(priv->operator_code, operator_code)) {
         g_free(priv->operator_code);
         priv->operator_code = g_strdup(operator_code);
+        _LOGD("signal: operator-code changed: %s%s%s", NM_PRINT_FMT_QUOTE_STRING(operator_code));
         _notify(self, PROP_OPERATOR_CODE);
     }
 }
@@ -1605,9 +1559,10 @@ _nm_modem_set_apn(NMModem *self, const char *apn)
 {
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
-    if (g_strcmp0(priv->apn, apn) != 0) {
+    if (!nm_streq0(priv->apn, apn)) {
         g_free(priv->apn);
         priv->apn = g_strdup(apn);
+        _LOGD("signal: apn changed: %s%s%s", NM_PRINT_FMT_QUOTE_STRING(apn));
         _notify(self, PROP_APN);
     }
 }
@@ -1615,7 +1570,7 @@ _nm_modem_set_apn(NMModem *self, const char *apn)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMModem *       self = NM_MODEM(object);
+    NMModem        *self = NM_MODEM(object);
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -1665,7 +1620,7 @@ static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object);
-    const char *    s;
+    const char     *s;
 
     switch (prop_id) {
     case PROP_PATH:
@@ -1726,11 +1681,7 @@ nm_modem_init(NMModem *self)
     self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_MODEM, NMModemPrivate);
     priv        = self->_priv;
 
-    priv->ip_ifindex       = -1;
-    priv->ip4_route_table  = RT_TABLE_MAIN;
-    priv->ip4_route_metric = 700;
-    priv->ip6_route_table  = RT_TABLE_MAIN;
-    priv->ip6_route_metric = 700;
+    priv->ip_ifindex = -1;
 }
 
 static void
@@ -1752,7 +1703,8 @@ dispose(GObject *object)
 {
     NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object);
 
-    g_clear_object(&priv->act_request);
+    g_clear_object(&priv->act_req);
+    g_clear_object(&priv->device);
 
     G_OBJECT_CLASS(nm_modem_parent_class)->dispose(object);
 }
@@ -1767,7 +1719,6 @@ finalize(GObject *object)
     g_free(priv->driver);
     g_free(priv->control_port);
     g_free(priv->data_port);
-    g_free(priv->ip_iface);
     g_free(priv->device_id);
     g_free(priv->sim_id);
     g_free(priv->sim_operator_id);
@@ -1790,9 +1741,8 @@ nm_modem_class_init(NMModemClass *klass)
     object_class->dispose      = dispose;
     object_class->finalize     = finalize;
 
-    klass->modem_act_stage1_prepare  = modem_act_stage1_prepare;
-    klass->stage3_ip6_config_request = stage3_ip6_config_request;
-    klass->deactivate_cleanup        = deactivate_cleanup;
+    klass->modem_act_stage1_prepare = modem_act_stage1_prepare;
+    klass->deactivate_cleanup       = deactivate_cleanup;
 
     obj_properties[PROP_UID] =
         g_param_spec_string(NM_MODEM_UID,
@@ -1904,43 +1854,28 @@ nm_modem_class_init(NMModemClass *klass)
                                        1,
                                        G_TYPE_UINT);
 
-    signals[IP4_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP4_CONFIG_RESULT,
-                                              G_OBJECT_CLASS_TYPE(object_class),
-                                              G_SIGNAL_RUN_FIRST,
-                                              0,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              G_TYPE_NONE,
-                                              2,
-                                              G_TYPE_OBJECT,
-                                              G_TYPE_POINTER);
-
-    /**
-     * NMModem::ip6-config-result:
-     * @modem: the #NMModem  on which the signal is emitted
-     * @config: the #NMIP6Config to apply to the modem's data port
-     * @do_slaac: %TRUE if IPv6 SLAAC should be started
-     * @error: a #GError if any error occurred during IP configuration
-     *
-     * This signal is emitted when IPv6 configuration has completed or failed.
-     * If @error is set the configuration failed.  If @config is set, then
+    /*
+     * This signal is emitted when IP configuration has completed or failed.
+     * If @error is set the configuration failed. If @l3cd is set, then
      * the details should be applied to the data port before any further
-     * configuration (like SLAAC) is done.  @do_slaac indicates whether SLAAC
-     * should be started after applying @config to the data port.
+     * configuration (like SLAAC) is done. @do_auto indicates whether DHCPv4/SLAAC
+     * should be started after applying @l3cd to the data port.
      */
-    signals[IP6_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP6_CONFIG_RESULT,
-                                              G_OBJECT_CLASS_TYPE(object_class),
-                                              G_SIGNAL_RUN_FIRST,
-                                              0,
-                                              NULL,
-                                              NULL,
-                                              NULL,
-                                              G_TYPE_NONE,
-                                              3,
-                                              G_TYPE_OBJECT,
-                                              G_TYPE_BOOLEAN,
-                                              G_TYPE_POINTER);
+    signals[NEW_CONFIG] = g_signal_new(NM_MODEM_NEW_CONFIG,
+                                       G_OBJECT_CLASS_TYPE(object_class),
+                                       G_SIGNAL_RUN_FIRST,
+                                       0,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       G_TYPE_NONE,
+                                       6,
+                                       G_TYPE_INT,      /* int addr_family */
+                                       G_TYPE_POINTER,  /* const NML3ConfigData *l3cd */
+                                       G_TYPE_BOOLEAN,  /* gboolean do_auto */
+                                       G_TYPE_POINTER,  /* const NMUtilsIPv6IfaceId *iid */
+                                       G_TYPE_INT,      /* NMDeviceStateReason failure_reason */
+                                       G_TYPE_POINTER); /* GError *error */
 
     signals[PREPARE_RESULT] = g_signal_new(NM_MODEM_PREPARE_RESULT,
                                            G_OBJECT_CLASS_TYPE(object_class),
diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h
index 4bc81ff8..ca541073 100644
--- a/src/core/devices/wwan/nm-modem.h
+++ b/src/core/devices/wwan/nm-modem.h
@@ -32,15 +32,14 @@
 #define NM_MODEM_APN             "apn"
 
 /* Signals */
-#define NM_MODEM_PPP_STATS         "ppp-stats"
-#define NM_MODEM_PPP_FAILED        "ppp-failed"
-#define NM_MODEM_PREPARE_RESULT    "prepare-result"
-#define NM_MODEM_IP4_CONFIG_RESULT "ip4-config-result"
-#define NM_MODEM_IP6_CONFIG_RESULT "ip6-config-result"
-#define NM_MODEM_AUTH_REQUESTED    "auth-requested"
-#define NM_MODEM_AUTH_RESULT       "auth-result"
-#define NM_MODEM_REMOVED           "removed"
-#define NM_MODEM_STATE_CHANGED     "state-changed"
+#define NM_MODEM_PPP_STATS      "ppp-stats"
+#define NM_MODEM_PPP_FAILED     "ppp-failed"
+#define NM_MODEM_PREPARE_RESULT "prepare-result"
+#define NM_MODEM_NEW_CONFIG     "new-config"
+#define NM_MODEM_AUTH_REQUESTED "auth-requested"
+#define NM_MODEM_AUTH_RESULT    "auth-result"
+#define NM_MODEM_REMOVED        "removed"
+#define NM_MODEM_STATE_CHANGED  "state-changed"
 
 typedef enum {
     NM_MODEM_IP_METHOD_UNKNOWN = 0,
@@ -101,44 +100,36 @@ typedef void (*_NMModemDisconnectCallback)(NMModem *modem, GError *error, gpoint
 typedef struct {
     GObjectClass parent;
 
-    void (*get_capabilities)(NMModem *                  self,
+    void (*get_capabilities)(NMModem                   *self,
                              NMDeviceModemCapabilities *modem_caps,
                              NMDeviceModemCapabilities *current_caps);
 
-    gboolean (*get_user_pass)(NMModem *     modem,
+    gboolean (*get_user_pass)(NMModem      *modem,
                               NMConnection *connection,
-                              const char ** user,
-                              const char ** pass);
+                              const char  **user,
+                              const char  **pass);
 
-    gboolean (*check_connection_compatible_with_modem)(NMModem *     modem,
+    gboolean (*check_connection_compatible_with_modem)(NMModem      *modem,
                                                        NMConnection *connection,
-                                                       GError **     error);
+                                                       GError      **error);
 
-    gboolean (*complete_connection)(NMModem *            modem,
-                                    const char *         iface,
-                                    NMConnection *       connection,
+    gboolean (*complete_connection)(NMModem             *modem,
+                                    const char          *iface,
+                                    NMConnection        *connection,
                                     NMConnection *const *existing_connections,
-                                    GError **            error);
+                                    GError             **error);
 
-    NMActStageReturn (*modem_act_stage1_prepare)(NMModem *            modem,
-                                                 NMConnection *       connection,
+    NMActStageReturn (*modem_act_stage1_prepare)(NMModem             *modem,
+                                                 NMConnection        *connection,
                                                  NMDeviceStateReason *out_failure_reason);
 
-    NMActStageReturn (*static_stage3_ip4_config_start)(NMModem *            self,
-                                                       NMActRequest *       req,
-                                                       NMDeviceStateReason *out_failure_reason);
-
-    /* Request the IP6 config; when the config returns the modem
-     * subclass should emit the ip6_config_result signal.
-     */
-    NMActStageReturn (*stage3_ip6_config_request)(NMModem *            self,
-                                                  NMDeviceStateReason *out_failure_reason);
+    void (*stage3_ip_config_start)(NMModem *self, int addr_family, NMModemIPMethod method);
 
     void (*set_mm_enabled)(NMModem *self, gboolean enabled);
 
-    void (*disconnect)(NMModem *                  self,
+    void (*disconnect)(NMModem                   *self,
                        gboolean                   warn,
-                       GCancellable *             cancellable,
+                       GCancellable              *cancellable,
                        _NMModemDisconnectCallback callback,
                        gpointer                   user_data);
 
@@ -161,65 +152,43 @@ const char *nm_modem_get_driver(NMModem *modem);
 const char *nm_modem_get_device_id(NMModem *modem);
 const char *nm_modem_get_sim_id(NMModem *modem);
 const char *nm_modem_get_sim_operator_id(NMModem *modem);
-gboolean    nm_modem_get_iid(NMModem *modem, NMUtilsIPv6IfaceId *out_iid);
 const char *nm_modem_get_operator_code(NMModem *modem);
 const char *nm_modem_get_apn(NMModem *modem);
 
-gboolean nm_modem_set_data_port(NMModem *       self,
-                                NMPlatform *    platform,
-                                const char *    data_port,
+gboolean nm_modem_set_data_port(NMModem        *self,
+                                NMPlatform     *platform,
+                                const char     *data_port,
                                 NMModemIPMethod ip4_method,
                                 NMModemIPMethod ip6_method,
                                 guint           timeout,
-                                GError **       error);
+                                GError        **error);
 
 gboolean nm_modem_owns_port(NMModem *modem, const char *iface);
 
-void nm_modem_get_capabilities(NMModem *                  self,
+void nm_modem_get_capabilities(NMModem                   *self,
                                NMDeviceModemCapabilities *modem_caps,
                                NMDeviceModemCapabilities *current_caps);
 
 gboolean
 nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error);
 
-gboolean nm_modem_complete_connection(NMModem *            self,
-                                      const char *         iface,
-                                      NMConnection *       connection,
+gboolean nm_modem_complete_connection(NMModem             *self,
+                                      const char          *iface,
+                                      NMConnection        *connection,
                                       NMConnection *const *existing_connections,
-                                      GError **            error);
-
-void nm_modem_get_route_parameters(NMModem *self,
-                                   guint32 *out_ip4_route_table,
-                                   guint32 *out_ip4_route_metric,
-                                   guint32 *out_ip6_route_table,
-                                   guint32 *out_ip6_route_metric);
-
-void nm_modem_set_route_parameters(NMModem *self,
-                                   guint32  ip4_route_table,
-                                   guint32  ip4_route_metric,
-                                   guint32  ip6_route_table,
-                                   guint32  ip6_route_metric);
-
-void nm_modem_set_route_parameters_from_device(NMModem *modem, NMDevice *device);
+                                      GError             **error);
 
-NMActStageReturn nm_modem_act_stage1_prepare(NMModem *            modem,
-                                             NMActRequest *       req,
+NMActStageReturn nm_modem_act_stage1_prepare(NMModem             *modem,
+                                             NMActRequest        *req,
                                              NMDeviceStateReason *out_failure_reason);
 
-void nm_modem_act_stage2_config(NMModem *modem);
+NMActStageReturn nm_modem_act_stage2_config(NMModem             *self,
+                                            NMDevice            *device,
+                                            NMDeviceStateReason *out_failure_reason);
 
-NMActStageReturn nm_modem_stage3_ip4_config_start(NMModem *            modem,
-                                                  NMDevice *           device,
-                                                  gboolean *           out_autoip4,
-                                                  NMDeviceStateReason *out_failure_reason);
+gboolean nm_modem_stage3_ip_config_start(NMModem *self, int addr_family, NMDevice *device);
 
-NMActStageReturn nm_modem_stage3_ip6_config_start(NMModem *            modem,
-                                                  NMDevice *           device,
-                                                  NMDeviceStateReason *out_failure_reason);
-
-void nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config);
-
-void nm_modem_get_secrets(NMModem *   modem,
+void nm_modem_get_secrets(NMModem    *modem,
                           const char *setting_name,
                           gboolean    request_new,
                           const char *hint);
@@ -228,9 +197,9 @@ void nm_modem_deactivate(NMModem *modem, NMDevice *device);
 
 typedef void (*NMModemDeactivateCallback)(NMModem *self, GError *error, gpointer user_data);
 
-void nm_modem_deactivate_async(NMModem *                 self,
-                               NMDevice *                device,
-                               GCancellable *            cancellable,
+void nm_modem_deactivate_async(NMModem                  *self,
+                               NMDevice                 *device,
+                               GCancellable             *cancellable,
                                NMModemDeactivateCallback callback,
                                gpointer                  user_data);
 
@@ -242,13 +211,15 @@ void nm_modem_set_mm_enabled(NMModem *self, gboolean enabled);
 NMModemState nm_modem_get_state(NMModem *self);
 void         nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason);
 void         nm_modem_set_prev_state(NMModem *self, const char *reason);
-const char * nm_modem_state_to_string(NMModemState state);
+const char  *nm_modem_state_to_string(NMModemState state);
 
 NMModemIPType nm_modem_get_supported_ip_types(NMModem *self);
 
 /* For the modem-manager only */
 void nm_modem_emit_removed(NMModem *self);
 
+void nm_modem_emit_auth_requested(NMModem *self);
+
 void nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason);
 
 void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason);
@@ -256,7 +227,23 @@ void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason);
 GArray *nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error);
 
 /* For subclasses */
-void nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error);
+
+void nm_modem_emit_signal_new_config(NMModem                  *self,
+                                     int                       addr_family,
+                                     const NML3ConfigData     *l3cd,
+                                     gboolean                  do_slaac,
+                                     const NMUtilsIPv6IfaceId *iid,
+                                     NMDeviceStateReason       failure_reason,
+                                     GError                   *error);
+void nm_modem_emit_signal_new_config_success(NMModem                  *self,
+                                             int                       addr_family,
+                                             const NML3ConfigData     *l3cd,
+                                             gboolean                  do_auto,
+                                             const NMUtilsIPv6IfaceId *iid);
+void nm_modem_emit_signal_new_config_failure(NMModem            *self,
+                                             int                 addr_family,
+                                             NMDeviceStateReason failure_reason,
+                                             GError             *error);
 
 const char *nm_modem_ip_type_to_string(NMModemIPType ip_type);
 
diff --git a/src/core/devices/wwan/nm-service-providers.c b/src/core/devices/wwan/nm-service-providers.c
index 89add6c5..83f21565 100644
--- a/src/core/devices/wwan/nm-service-providers.c
+++ b/src/core/devices/wwan/nm-service-providers.c
@@ -22,33 +22,33 @@ typedef enum {
 } ParseContextState;
 
 typedef struct {
-    char *                           mccmnc;
+    char                            *mccmnc;
     NMServiceProvidersGsmApnCallback callback;
     gpointer                         user_data;
-    GCancellable *                   cancellable;
-    GMarkupParseContext *            ctx;
+    GCancellable                    *cancellable;
+    GMarkupParseContext             *ctx;
     char                             buffer[4096];
 
-    char *            text_buffer;
+    char             *text_buffer;
     ParseContextState state;
 
     gboolean mccmnc_matched;
     gboolean found_internet_apn;
-    char *   apn;
-    char *   username;
-    char *   password;
-    char *   gateway;
-    char *   auth_method;
-    GSList * dns;
+    char    *apn;
+    char    *username;
+    char    *password;
+    char    *gateway;
+    char    *auth_method;
+    GSList  *dns;
 } ParseContext;
 
 /*****************************************************************************/
 
 static void
 parser_toplevel_start(ParseContext *parse_context,
-                      const char *  name,
-                      const char ** attribute_names,
-                      const char ** attribute_values)
+                      const char   *name,
+                      const char  **attribute_names,
+                      const char  **attribute_values)
 {
     int i;
 
@@ -72,9 +72,9 @@ parser_toplevel_start(ParseContext *parse_context,
 
 static void
 parser_country_start(ParseContext *parse_context,
-                     const char *  name,
-                     const char ** attribute_names,
-                     const char ** attribute_values)
+                     const char   *name,
+                     const char  **attribute_names,
+                     const char  **attribute_values)
 {
     if (strcmp(name, "provider") == 0)
         parse_context->state = PARSER_PROVIDER;
@@ -82,9 +82,9 @@ parser_country_start(ParseContext *parse_context,
 
 static void
 parser_provider_start(ParseContext *parse_context,
-                      const char *  name,
-                      const char ** attribute_names,
-                      const char ** attribute_values)
+                      const char   *name,
+                      const char  **attribute_names,
+                      const char  **attribute_values)
 {
     parse_context->mccmnc_matched = FALSE;
     if (strcmp(name, "gsm") == 0)
@@ -95,9 +95,9 @@ parser_provider_start(ParseContext *parse_context,
 
 static void
 parser_gsm_start(ParseContext *parse_context,
-                 const char *  name,
-                 const char ** attribute_names,
-                 const char ** attribute_values)
+                 const char   *name,
+                 const char  **attribute_names,
+                 const char  **attribute_values)
 {
     int i;
 
@@ -140,9 +140,9 @@ parser_gsm_start(ParseContext *parse_context,
 
 static void
 parser_gsm_apn_start(ParseContext *parse_context,
-                     const char *  name,
-                     const char ** attribute_names,
-                     const char ** attribute_values)
+                     const char   *name,
+                     const char  **attribute_names,
+                     const char  **attribute_values)
 {
     int i;
 
@@ -167,11 +167,11 @@ parser_gsm_apn_start(ParseContext *parse_context,
 
 static void
 parser_start_element(GMarkupParseContext *context,
-                     const char *         element_name,
-                     const char **        attribute_names,
-                     const char **        attribute_values,
+                     const char          *element_name,
+                     const char         **attribute_names,
+                     const char         **attribute_values,
                      gpointer             user_data,
-                     GError **            error)
+                     GError             **error)
 {
     ParseContext *parse_context = user_data;
 
@@ -265,9 +265,9 @@ parser_cdma_end(ParseContext *parse_context, const char *name)
 
 static void
 parser_end_element(GMarkupParseContext *context,
-                   const char *         element_name,
+                   const char          *element_name,
                    gpointer             user_data,
-                   GError **            error)
+                   GError             **error)
 {
     ParseContext *parse_context = user_data;
 
@@ -298,10 +298,10 @@ parser_end_element(GMarkupParseContext *context,
 
 static void
 parser_text(GMarkupParseContext *context,
-            const char *         text,
+            const char          *text,
             gsize                text_len,
             gpointer             user_data,
-            GError **            error)
+            GError             **error)
 {
     ParseContext *parse_context = user_data;
 
@@ -360,7 +360,7 @@ stream_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
     GInputStream *stream        = G_INPUT_STREAM(source_object);
     ParseContext *parse_context = user_data;
     gssize        len;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     len = g_input_stream_read_finish(stream, res, &error);
     if (len == -1) {
@@ -411,9 +411,9 @@ read_next_chunk(GInputStream *stream, ParseContext *parse_context)
 static void
 file_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    GFile *           file          = G_FILE(source_object);
-    ParseContext *    parse_context = user_data;
-    GFileInputStream *stream;
+    GFile                *file          = G_FILE(source_object);
+    ParseContext         *parse_context = user_data;
+    GFileInputStream     *stream;
     gs_free_error GError *error = NULL;
 
     stream = g_file_read_finish(file, res, &error);
@@ -431,13 +431,13 @@ file_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 /*****************************************************************************/
 
 void
-nm_service_providers_find_gsm_apn(const char *                     service_providers,
-                                  const char *                     mccmnc,
-                                  GCancellable *                   cancellable,
+nm_service_providers_find_gsm_apn(const char                      *service_providers,
+                                  const char                      *mccmnc,
+                                  GCancellable                    *cancellable,
                                   NMServiceProvidersGsmApnCallback callback,
                                   gpointer                         user_data)
 {
-    GFile *       file;
+    GFile        *file;
     ParseContext *parse_context;
 
     parse_context              = g_slice_new0(ParseContext);
diff --git a/src/core/devices/wwan/nm-service-providers.h b/src/core/devices/wwan/nm-service-providers.h
index 959f660a..cf90ef1e 100644
--- a/src/core/devices/wwan/nm-service-providers.h
+++ b/src/core/devices/wwan/nm-service-providers.h
@@ -6,18 +6,18 @@
 #ifndef __NETWORKMANAGER_SERVICE_PROVIDERS_H__
 #define __NETWORKMANAGER_SERVICE_PROVIDERS_H__
 
-typedef void (*NMServiceProvidersGsmApnCallback)(const char *  apn,
-                                                 const char *  username,
-                                                 const char *  password,
-                                                 const char *  gateway,
-                                                 const char *  auth_method,
+typedef void (*NMServiceProvidersGsmApnCallback)(const char   *apn,
+                                                 const char   *username,
+                                                 const char   *password,
+                                                 const char   *gateway,
+                                                 const char   *auth_method,
                                                  const GSList *dns,
-                                                 GError *      error,
+                                                 GError       *error,
                                                  gpointer      user_data);
 
-void nm_service_providers_find_gsm_apn(const char *                     service_providers,
-                                       const char *                     mccmnc,
-                                       GCancellable *                   cancellable,
+void nm_service_providers_find_gsm_apn(const char                      *service_providers,
+                                       const char                      *mccmnc,
+                                       GCancellable                    *cancellable,
                                        NMServiceProvidersGsmApnCallback callback,
                                        gpointer                         user_data);
 
diff --git a/src/core/devices/wwan/nm-wwan-factory.c b/src/core/devices/wwan/nm-wwan-factory.c
index 0d802ae1..169407c7 100644
--- a/src/core/devices/wwan/nm-wwan-factory.c
+++ b/src/core/devices/wwan/nm-wwan-factory.c
@@ -52,7 +52,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(
                                                                    NM_SETTING_CDMA_SETTING_NAME))
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     return g_object_new(NM_TYPE_WWAN_FACTORY, NULL);
 }
@@ -62,9 +62,9 @@ G_MODULE_EXPORT NMDeviceFactory *
 static void
 modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data)
 {
-    NMWwanFactory * self             = NM_WWAN_FACTORY(user_data);
+    NMWwanFactory            *self   = NM_WWAN_FACTORY(user_data);
     gs_unref_object NMDevice *device = NULL;
-    const char *              driver;
+    const char               *driver;
 
     if (nm_modem_is_claimed(modem))
         return;
@@ -89,11 +89,11 @@ modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data)
 }
 
 static NMDevice *
-create_device(NMDeviceFactory *     factory,
-              const char *          iface,
+create_device(NMDeviceFactory      *factory,
+              const char           *iface,
               const NMPlatformLink *plink,
-              NMConnection *        connection,
-              gboolean *            out_ignore)
+              NMConnection         *connection,
+              gboolean             *out_ignore)
 {
     g_return_val_if_fail(plink, NULL);
     g_return_val_if_fail(plink->type == NM_LINK_TYPE_WWAN_NET, NULL);
@@ -104,7 +104,7 @@ create_device(NMDeviceFactory *     factory,
 static void
 start(NMDeviceFactory *factory)
 {
-    NMWwanFactory *       self = NM_WWAN_FACTORY(factory);
+    NMWwanFactory        *self = NM_WWAN_FACTORY(factory);
     NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self);
 
     priv->mm = g_object_ref(nm_modem_manager_get());
@@ -121,7 +121,7 @@ nm_wwan_factory_init(NMWwanFactory *self)
 static void
 dispose(GObject *object)
 {
-    NMWwanFactory *       self = NM_WWAN_FACTORY(object);
+    NMWwanFactory        *self = NM_WWAN_FACTORY(object);
     NMWwanFactoryPrivate *priv = NM_WWAN_FACTORY_GET_PRIVATE(self);
 
     if (priv->mm)
@@ -135,7 +135,7 @@ dispose(GObject *object)
 static void
 nm_wwan_factory_class_init(NMWwanFactoryClass *klass)
 {
-    GObjectClass *        object_class  = G_OBJECT_CLASS(klass);
+    GObjectClass         *object_class  = G_OBJECT_CLASS(klass);
     NMDeviceFactoryClass *factory_class = NM_DEVICE_FACTORY_CLASS(klass);
 
     object_class->dispose = dispose;
diff --git a/src/core/devices/wwan/tests/test-service-providers.c b/src/core/devices/wwan/tests/test-service-providers.c
index f95cccf8..e808b391 100644
--- a/src/core/devices/wwan/tests/test-service-providers.c
+++ b/src/core/devices/wwan/tests/test-service-providers.c
@@ -10,13 +10,13 @@
 #include "nm-test-utils-core.h"
 
 static void
-test_positive_cb(const char *  apn,
-                 const char *  username,
-                 const char *  password,
-                 const char *  gateway,
-                 const char *  auth_method,
+test_positive_cb(const char   *apn,
+                 const char   *username,
+                 const char   *password,
+                 const char   *gateway,
+                 const char   *auth_method,
                  const GSList *dns,
-                 GError *      error,
+                 GError       *error,
                  gpointer      user_data)
 {
     GMainLoop *loop = user_data;
@@ -55,13 +55,13 @@ test_positive(void)
 /*****************************************************************************/
 
 static void
-test_negative_cb(const char *  apn,
-                 const char *  username,
-                 const char *  password,
-                 const char *  gateway,
-                 const char *  auth_method,
+test_negative_cb(const char   *apn,
+                 const char   *username,
+                 const char   *password,
+                 const char   *gateway,
+                 const char   *auth_method,
                  const GSList *dns,
-                 GError *      error,
+                 GError       *error,
                  gpointer      user_data)
 {
     GMainLoop *loop = user_data;
@@ -88,13 +88,13 @@ test_negative(void)
 /*****************************************************************************/
 
 static void
-test_nonexistent_cb(const char *  apn,
-                    const char *  username,
-                    const char *  password,
-                    const char *  gateway,
-                    const char *  auth_method,
+test_nonexistent_cb(const char   *apn,
+                    const char   *username,
+                    const char   *password,
+                    const char   *gateway,
+                    const char   *auth_method,
                     const GSList *dns,
-                    GError *      error,
+                    GError       *error,
                     gpointer      user_data)
 {
     GMainLoop *loop = user_data;